Метка

JavaScript


Применение MVC на стороне клиента становится популярным, т.к. браузерные приложения становятся все более сложными. Фреймворки позволяют организовать JavaScript код используя MVC. Backbone.js является одним из самых популярных фреймворков подобного рода и наиболее часто выбираемым среди остальных.


Сегодня мы сделаем форму выбора услуг по списку на Backbone.js. Итоговая стоимость будет рассчитываться в реальном времени исходя из стоимости выбранных услуг.


Что такое Backbone.js

Это фреймворк, который вносит структурность в веб-приложения давая возможность использовать модели, коллекции и виды. Данные на сервер поступают в виде REST JSON, и могут автоматически выбирать и сохранять данные. В этом уроке мы не будем отправлять данные на сервер — все будет храниться на стороне клиента. Кстати, Backbone.js не зависит и не заменят jQuery, так что вместе они работают отлично! Не стоит забывать о том, что backbone.js — это не волшебная палочка, которая сделает все за вас. Конечный результат и структура зависит только от вас. Организация кода может быть ужасной, если вы не знакомы с MVC. Так же, не стоит использовать фреймворк для небольших проектов, где пара строк jQuery могут решить вашу задачу. В этом уроке мы познакомимся с фундаментальными возможностями backbone.js, которые есть у этого фреймворка под капотом.


Что мы будем создавать

My Services

total: $0


Шаг 1. HTML

Мы начинаем с обычного HTML5 документа. В данном примере мы не стали реализовывать работу html5 в браузере IE.

index.html

<!DOCTYPE html>
<html>

    <head>
        <meta charset="utf-8" />
        <title>Your first Backbone.js App | Tutorialzine </title>

        <!-- Google web fonts -->
        <link href="http://fonts.googleapis.com/css?family=PT+Sans:400,700" rel='stylesheet' />

        <!-- The main CSS file -->
        <link href="assets/css/style.css" rel="stylesheet" />

    </head>

    <body>

        <form id="main" method="post" action="submit.php">
            <h1>My Services</h1>

            <ul id="services">
                <!-- The services will be inserted here -->
            </ul>

            <p id="total">total: <span>$0</span></p>

            <input type="submit" id="order" value="Order" />

        </form>

        <!-- JavaScript Includes -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>

        <script src="assets/js/script.js"></script>

    </body>
</html>

Основной элемент на странице — это форма. UL внутри служит для списка услуг, а параграф #total отображает итоговую стоимость выбранных услуг.

Перед тем, как закрыть </body> я добавил jQuery, Backbone.js, и библиотеку Underscore (backbone зависит от ее функций). Последним идет файл скрипта script.js. Готовый скрипт ниже.


Шаг 2. JavaScript

  1. Сначала мы создадим модель для наших услуг. Она будет содержать свойства для имени, цены и проверки статуса (checked) — выбрана наша услуга или нет. Объект этого класса будет создан для каждой услуги, которую мы предлагаем;
  2. Затем мы создадим коллекцию Backbone. С помощью коллекций становится достаточно просто отслеживать те или иные события, производимые над объектами класса. В больших приложениях вам будет необходимо отслеживать, когда элементы добавляются или удаляются из коллекций и обновляются виды (views). В нашем же случае элементы коллекции преопределены, мы ничего добавлять не будем. Нам необходимо только отслеживать изменения и менять HTML в этой зависимости. Тут мы отслеживаем события при клике и изменении свойства модели.
  3. После этого мы определим виды (или шаблоны) для наших услуг. Каждый вид будет привязан к нашей модели и отправлять ее свойства в HTML.
  4. Наконец, мы создадим главный шаблон, который будет содержать в себе все наши услуги из коллекции и создавать виды (шаблоны) для них. Тут так же отслеживается событие на изменения в коллекции и обновляется итоговая стоимость.

Файл: script.js

$(function(){

    var Service = Backbone.Model.extend({

        defaults:{
            title: 'My service',
            price: 100,
            checked: false
        },

        toggle: function(){
            this.set('checked', !this.get('checked'));
        }
    });

    var ServiceList = Backbone.Collection.extend({

        model: Service,

        getChecked: function(){
            return this.where({checked:true});
        }
    });

    var services = new ServiceList([
        new Service({ title: 'web development', price: 200}),
        new Service({ title: 'web design', price: 250}),
        new Service({ title: 'photography', price: 100}),
        new Service({ title: 'coffee drinking', price: 10})
    ]);

    var ServiceView = Backbone.View.extend({
        tagName: 'li',

        events:{
            'click': 'toggleService'
        },

        initialize: function(){

            this.listenTo(this.model, 'change', this.render);
        },

        render: function(){

            this.$el.html('<input type="checkbox" name="' + this.model.get('title') + '" value="1" /> ' + this.model.get('title') + '<span>$' + this.model.get('price') + '</span>');
            this.$('input').prop('checked', this.model.get('checked'));

            return this;
        },

        toggleService: function(){
            this.model.toggle();
        }
    });

    var App = Backbone.View.extend({

        el: $('#main'),

        initialize: function(){

            this.total = $('#total span');
            this.list = $('#services');

            this.listenTo(services, 'change', this.render);

            services.each(function(service){

                var view = new ServiceView({ model: service });
                this.list.append(view.render().el);

            }, this);
        },

        render: function(){

            var total = 0;

            _.each(services.getChecked(), function(elem){
                total += elem.get('price');
            });

            this.total.text('$'+total);

            return this;
        }
    });

    new App();

});


Готово!

Наше небольшое приложение завершено. Надеюсь, что этот обзор даст вам понять, что Backbone.js очень функциональный фреймворк.




  /    /  Комментариев нет


В этом уроке мы разработаем ajax-форму для загрузки файлов с поддержкой Drag-and-drop, которая позволит посетителям загружать файлы через браузер. Мы будем использовать мощный плагин для мультизагрузки файлов jQuery File Upload совместно с jQuery Knob для отображения прогресса загрузки средствами CSS3/JS.


1. HTML и CSS

Как обычно, всё начинается с создания стандартного HTML5-документа и таблицы стилей:

<!DOCTYPE html>
<html>

	<head>
		<meta charset="utf-8"/>
		<title>Mini Ajax File Upload Form</title>

		<!-- Google web fonts -->
		<link href="http://fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700" rel='stylesheet' />

		<!-- CSS -->
		<link href="css/style.css" rel="stylesheet" />
	</head>

	<body>

		<form id="upload" method="post" action="upload.php" enctype="multipart/form-data">
			<div id="drop">
				Drop Here

				<a>Обзор</a>
				<input type="file" name="upl" multiple />
			</div>

			<ul>
				<!-- Загруженные файлы будут отображаться здесь -->
			</ul>

		</form>

		<!-- JavaScript -->
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
		<script src="js/jquery.knob.js"></script>

		<!-- jQuery File Upload Dependencies -->
		<script src="js/jquery.ui.widget.js"></script>
		<script src="js/jquery.iframe-transport.js"></script>
		<script src="js/jquery.fileupload.js"></script>

		<!-- Основной файл JS -->
		<script src="js/script.js"></script>

	</body>
</html>

В секции документа я подключил два шрифта Google Webfonts, а перед закрывающим тегом </body> вы можете увидеть несколько JavaScript-библиотек: jQuery, jQuery Knob plugin, jQuery File Upload plugin и зависимые от него библиотеки.

/*-------------------------
    Simple reset
--------------------------*/

*{
    margin:0;
    padding:0;
}

/*-------------------------
    General Styles
--------------------------*/

html{
    background-color:#ebebec;

    background-image:-webkit-radial-gradient(center, #ebebec, #b4b4b4);
    background-image:-moz-radial-gradient(center, #ebebec, #b4b4b4);
    background-image:radial-gradient(center, #ebebec, #b4b4b4);

    min-height:900px;
}

body{
    font:15px/1.3 Arial, sans-serif;
    color: #4f4f4f;
}

a, a:visited {
    outline:none;
    color:#389dc1;
}

a:hover{
    text-decoration:none;
}

section, footer, header, aside{
    display: block;
}

/*----------------------------
    The file upload form
-----------------------------*/

#upload{
    font-family:'PT Sans Narrow', sans-serif;
    background-color:#373a3d;

    background-image:-webkit-linear-gradient(top, #373a3d, #313437);
    background-image:-moz-linear-gradient(top, #373a3d, #313437);
    background-image:linear-gradient(top, #373a3d, #313437);

    width:250px;
    padding:30px;
    border-radius:3px;

    margin:200px auto 100px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}

#drop{
    background-color: #2E3134;
    padding: 40px 50px;
    margin-bottom: 30px;
    border: 20px solid rgba(0, 0, 0, 0);
    border-radius: 3px;
    border-image: url('../img/border-image.png') 25 repeat;
    text-align: center;
    text-transform: uppercase;

    font-size:16px;
    font-weight:bold;
    color:#7f858a;
}

#drop a{
    background-color:#007a96;
    padding:12px 26px;
    color:#fff;
    font-size:14px;
    border-radius:2px;
    cursor:pointer;
    display:inline-block;
    margin-top:12px;
    line-height:1;
}

#drop a:hover{
    background-color:#0986a3;
}

#drop input{
    display:none;
}

#upload ul{
    list-style:none;
    margin:0 -30px;
    border-top:1px solid #2b2e31;
    border-bottom:1px solid #3d4043;
}

#upload ul li{

    background-color:#333639;

    background-image:-webkit-linear-gradient(top, #333639, #303335);
    background-image:-moz-linear-gradient(top, #333639, #303335);
    background-image:linear-gradient(top, #333639, #303335);

    border-top:1px solid #3d4043;
    border-bottom:1px solid #2b2e31;
    padding:15px;
    height: 52px;

    position: relative;
}

#upload ul li input{
    display: none;
}

#upload ul li p{
    width: 144px;
    overflow: hidden;
    white-space: nowrap;
    color: #EEE;
    font-size: 16px;
    font-weight: bold;
    position: absolute;
    top: 20px;
    left: 100px;
}

#upload ul li i{
    font-weight: normal;
    font-style:normal;
    color:#7f7f7f;
    display:block;
}

#upload ul li canvas{
    top: 15px;
    left: 32px;
    position: absolute;
}

#upload ul li span{
    width: 15px;
    height: 12px;
    background: url('../img/icons.png') no-repeat;
    position: absolute;
    top: 34px;
    right: 33px;
    cursor:pointer;
}

#upload ul li.working span{
    height: 16px;
    background-position: 0 -12px;
}

#upload ul li.error p{
    color:red;
}

Основным элементом страницы является форма #upload. Внутри формы расположен div с id #drop (используется для drag-and-drop) и маркированный список. Каждый элемент этого списка будет содержать информацию о каждом загруженном файле и будет выглядеть примерно так:

<li class="working">
    <input type="text" value="0" data-width="48" data-height="48" data-fgColor="#0788a5" data-readOnly="1" data-bgColor="#3e4043" />
    <p>Sunset.jpg <i>145 KB</i></p>
    <span></span>
</li>

Элемент <input> скрыт с помощью CSS. Плагин jQuery Knob, в свою очередь, после инициализации создаст все необходимые элементы управления на основе Canvas. Так же элемент <input> будет содержать несколько атрибутов data-*, значение которых будет обновляться и использоваться Knob-ом для отображения статуса загрузки и перерисовки canvas-элементов. Элемент <span> будет содержать значок результата загрузки файла.

AJAX-форма загрузки файлов


2. jQuery

Выбор и загрузка файлов осуществляется двумя способами:

  • Простым перетаскиванием файлов на div#drop (кроме IE);
  • Нажатимем на кнопку “Обзор” (“Browse”). Нажатие имитирует клик по скрытому полю <input>, которое открывает диалог выбора файлов в системе. Заметьте, скрытое поле имеет параметр multiple, что позволяет выбрать сразу несколько файлов для загрузки, при этом каждый файл всё равно будет закачан по отдельности.

По умолчанию плагин помещает выбранные файлы в очередь для загрузки, но мы сделаем так, чтобы файлы автоматически загружались на сервер после их перетаскивания/выбора.

Файл script.js:

$(function(){

    var ul = $('#upload ul');

    $('#drop a').click(function(){
        // Имитация клика по input
        $(this).parent().find('input').click();
    });

    // Инициализация jQuery File Upload plugin
    $('#upload').fileupload({

        // Контейнер для drag-and-drop
        dropZone: $('#drop'),

        // Функция, вызываемая после выбора файла
        add: function (e, data) {

            var tpl = $('<li class="working"><input type="text" value="0" data-width="48" data-height="48"'+
                ' data-fgColor="#0788a5" data-readOnly="1" data-bgColor="#3e4043" /><p></p><span></span></li>');

            // Добавляем имя файла и его размер
            tpl.find('p').text(data.files[0].name)
                         .append('<i>' + formatFileSize(data.files[0].size) + '</i>');

            // Добавляем описание файлв в список загрузок
            data.context = tpl.appendTo(ul);

            // Инициализируем Knob plugin
            tpl.find('input').knob();

            // Отслеживание кликов по кнопке отмены
            tpl.find('span').click(function(){

                if(tpl.hasClass('working')){
                    jqXHR.abort();
                }

                tpl.fadeOut(function(){
                    tpl.remove();
                });

            });

            // Автоматическая загрузка файла после его добавления
            var jqXHR = data.submit();
        },

        progress: function(e, data){

            // Высчитываем статус загрузки
            var progress = parseInt(data.loaded / data.total * 100, 10);

            // Обновляем скрытый input
            data.context.find('input').val(progress).change();

            if(progress == 100){
                data.context.removeClass('working');
            }
        },

        fail:function(e, data){
            // Ошибка
            data.context.addClass('error');
        }

    });

    // Отмена стандартного действия брацзера, если выполнено действие drop
    $(document).on('drop dragover', function (e) {
        e.preventDefault();
    });

    // Вспомогательная функция для переода размера файла в другие велечины
    function formatFileSize(bytes) {
        if (typeof bytes !== 'number') {
            return '';
        }

        if (bytes >= 1000000000) {
            return (bytes / 1000000000).toFixed(2) + ' GB';
        }

        if (bytes >= 1000000) {
            return (bytes / 1000000).toFixed(2) + ' MB';
        }

        return (bytes / 1000).toFixed(2) + ' KB';
    }

});

Плагин jQuery File Upload поставляется с собственным UI-интерфейсом загрузки, однако, поскольку нам необходимо сделать полностью другой интерфейс, мы будем использовать базовую версию плагина, которая не включает в себя никакого интерфейса. Чтобы заставить всё это работать, мы передадим в функцию несколько параметров:

  • dropZone — селектор, определяющий элемент, который будет реагировать на событие drag/drop. Файлы, перемещённые сюда, будут добавлены в очередь загрузки.
  • add — callback-функция, вызываемая после добавления файлов в очередь. Внутри неё мы создадим HTML-представление файла, которое будет добавляться в наш маркированный список UL, а так же вызывать метод data.submit() для немедленной загрузки файла.
  • progress — callback-функция, вызываемая каждые 100мс (настраивается). Второй аргумент (data) хранит в себе размер файла и количество переданных байт. Это позволит нам отслеживать статус загрузки файла в процентном соотношении.
  • fail — callback-функция, вызываемая при возникновении проблем на стороне сервера, например, отсутствие PHP скрипта или ошибка в нём.

Полный список возможных параметров можно посмотреть здесь.


3. PHP

Плагин jQuery File Upload поставляется с PHP скриптом, управляющим загрузкой файлов на сервер, но в этом уроке мы напишем свой собственный скрипт. Отправка файлов на сервер с помощью представленного плагина практически ничем не отличается от загрузки файлов с помощью обычной HTML-формы — вся передаваемая информация хранится в глобальном массиве $_FILES:

<?php

// Разрешённые для загрузки файлы
$allowed = array('png', 'jpg', 'gif','zip');

if(isset($_FILES['upl']) && $_FILES['upl']['error'] == 0){

    $extension = pathinfo($_FILES['upl']['name'], PATHINFO_EXTENSION);

    if(!in_array(strtolower($extension), $allowed)){
        echo '{"status":"error"}';
        exit;
    }

    if(move_uploaded_file($_FILES['upl']['tmp_name'], 'uploads/'.$_FILES['upl']['name'])){
        echo '{"status":"success"}';
        exit;
    }
}

echo '{"status":"error"}';
exit;

Как я уже говорил ранее, мы можем выбрать сразу несколько файлов — они будут загружены по отдельности один за другим, что намного упрощает наш PHP скрипт. В данном примере файлы просто помещаются в папку для загруженных файлов, однако вы можете расширить этот функционал, добавив, например, статистику загрузок в вашу базу данных.


Вот и всё!

Надеюсь, этот урок был для вас полезным. Если вы используете какой-то другой способ загрузки файлов средствами ajax, пожалуйста, поделитесь им с нами — думаю, всем будет интересно.

 

P.S. Скачать демо-пример со всеми стилями и скриптами можно здесь.



Это стоит посмотреть