Метка

jQuery

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


В этом уроке мы разработаем 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. Скачать демо-пример со всеми стилями и скриптами можно здесь.



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




Небольшой урок о том, как в jQuery плагине Masked Input Plugin убрать автоматическое стирание не до конца введенной информации в поле форм по маске ввода.


Стандартное поведение маски ввода в поле формы

В интернете гуляет замечательный плагин, который позволяет задавать маску ввода в поля форм на свой вкус.

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

По умолчанию, при задании маски ввода, форма работает следующим образом:

Если ввести в поле цифры, но заполнив маску не до конца, то при клике вне поля введенные данные сотрутся и пользователю придется вводить все заново.


Маска ввода без автоматического удаления информации

Я посмотрел на плагин изнутри и нашел способ, как это исправить. Становится возможным продолжать вбивать информацию в маску, после focusout.

Теперь форма работает вот таким образом:


Решение

Суть находится в файле плагина  в функции checkVal(). Найдите следующий кусок кода:

function checkVal(allow) {

Далее, в этой функции необходимо найти условие:

if (allow) {
	writeBuffer();
} else if (lastMatch + 1 < partialPosition) {
	input.val("");
	clearBuffer(0, len);
} else {
	writeBuffer();
	input.val(input.val().substring(0, lastMatch + 1));
}

Это примерно 275 строка кода.

Все, что нам остается сделать, чтобы плагин не удалял введенные данные в поле формы, при условии, что маска введена не полностью, это удалить или закомментировать строки:

input.val("");
clearBuffer(0, len);

Получается, что мы не удаляем данные, если количество знаков меньше, чем количество знаков в маске.

if (allow) {
	writeBuffer();
} else if (lastMatch + 1 < partialPosition) {
	//input.val(""); 
	//clearBuffer(0, len);
} else {
	writeBuffer();
	input.val(input.val().substring(0, lastMatch + 1));
}

Версия плагина 1.3.1. Подробнее про работу плагина Masked Input на сайте разработчика.