Интеграция AJAX в интерфейс

При создании компонент интерфейса часто нужен AJAX. Например - SELECT с подгружающимся списком, поле с автозаполнением, дерево с подгружающимися узлами и т.п.

Эта статья посвящена конкретному примеру удобной и универсальной реализации AJAX для компонента интерфейса. Она дает общие рамки, можно даже сказать "фреймворк" для создания таких виджетов.

Например, рассмотрим селект, загружающий данные с сервера. С точки зрения AJAX он почти ничем не отличается от подгружающегося дерева или таблицы.

Общий поток выполнения при AJAX-подгрузке данных, начиная от пустого селекта:

Список телодвижений при загрузке:

  1. Подготовить компонент для загрузки. Для нас - очистить селект
  2. Включить индикацию AJAX (loading...)
  3. Отправить асинхронный вызов
  4. Получить ответ сервера. Обработать ошибку, если она произошла.
  5. Загрузить данные в компонент
  6. Отключить индикацию AJAX

Реализация AJAX для подгрузки селекта

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

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

function ajaxSelect(id) {
    var element = document.getElementById(id)

    var onLoaded = function(data) {
        var i=0
        for(var key in data) {
            var label = data[key]
            element.options[i++] = new Option(label, key)
        }
    }
    
    var onLoadError = function(error) {
        var msg = "Ошибка "+error.errcode
        if (error.message) msg = msg + ' :'+error.message
        alert(msg)
    }
    
    var showLoading = function(on) {
        element.disabled = on
    }

    var onSuccess = function(data) {
        if (!data.errcode) {
            onLoaded(data)
            showLoading(false)        
        } else {
            showLoading(false)
            onLoadError(data)            
        }
    }
    
    
    var onAjaxError = function(xhr, status){
        showLoading(false)
        var errinfo = { errcode: status }
        if (xhr.status != 200) {
            // может быть статус 200, а ошибка
            // из-за некорректного JSON
            errinfo.message = xhr.statusText
        } else {
            errinfo.message = 'Некорректные данные с сервера'
        }
        onLoadError(errinfo)
    }

    
    return {
        load: function(url) {
            showLoading(true)

            while (element.firstChild) {
                element.removeChild(element.firstChild)
            }

            $.ajax({ // для краткости - jQuery
                url: url,
                dataType: "json",
                success: onSuccess,
                error: onAjaxError,
                cache: false
            })
        }
    }
}

Как это использовать:

  1. Создать в DOM/HTML элемент SELECT:
    <select id="ajax-select"></select>
    
  2. В javascript-коде инициализовать компонент:
    select = ajaxSelect('ajax-select')
    
  3. Например, по нажатию кнопки(onclick), вызвать загрузку:
    select.load('/ajax/ui/options.php')
    

А теперь - разберем все по порядку.

ООП

Использованный стиль описания яваскрипт-объекта описан, например, в учебнике по ООП и называется "фабрика объектов".

При стиле ООП "фабрика объектов" - приватные переменные обозначаются var, а доступные извне описываются как свойства возвращаемого return объекта.

Инициализация осуществляется непосредственно вызовом функции, без new:

select = ajaxSelect('...ID узла DOM...')

Так что функции onSuccess, onAjaxError, showLoading - приватные, а функция load - публичная.

Основные методы

Два первых метода onLoaded и onLoadError - основные. Любая AJAX-загрузка приводит к одному из них.

onLoaded(data)
Обрабатывает пришедшие данные. В нашем случае - заполняет ими опции селекта. Например, объект

{1:"Яблоко",2:"Дыня"}

превращается в опции селекта:

<option val="1">Яблоко</option>
<option val="2">Дыня</option>

Использованы стандартные методы работы с DOM (new Option).

onLoadError(error)
Обрабатывает любые ошибки. Объект error должен содержать свойство errcode - код ошибки, например "timeout" или "15541" и, дополнительно, может предоставлять более подробное описание ошибки в свойстве message.

В нашем примере - выдает форматированное сообщение об ошибке.

Вспомогательные методы

Метод индикации загрузки в примере просто включает-выключает селект.

Ничто не мешает добавить красивый анимированный значок загрузки. Желательно делать это через добавление CSS-класса к родительскому элемента select'а.

Получится что-то типа:

HTML-код для такой индикации:

<style>
.loading { 
    padding-left:20px;
    background: url(/http/javascript.ru/ajax/ui/blue-loading.gif) left no-repeat;
}
</style>
<span class="loading">
<select disabled="disabled"></select>
</span>
showLoading(on)
Включает/выключает индикацию загрузки, в зависимости от значения on. Эти действия обычно похожи, поэтому удобно объединить их в один метод.

Как правило, ставит/убирает один CSS-класс.

AJAX-коллбэки

Следующие два метода являются коллбэками для AJAX-запроса.

onSuccess(data)
Вызывается при успешном выполнении AJAX-запроса, получает прибывшие данные. Функция $.ajax автоматически интерпретирует их как JSON.

Чтобы сообщить о произошедшей ошибке, серверу достаточно передать, например, такой ответ:

{
    errcode: 500, 
    message: "Апдейт базы данных. Повторите запрос через 10 минут"
}

Обратите внимание на последовательность вызовов. Если все в порядке, то сначала вызывается обработка данных onLoaded - и только потом отключается индикация showLoading(false), чтобы посетитель увидел сразу заполненный селект.

Если произошла ошибка - удобнее сделать наоборот: сначала убрать индикацию загрузки, а затем вывести сообщение.

onAjaxError(xhr,status)
Коллбэк для ошибки при AJAX-запросе. Получает проблемный XmlHttpRequest и статус.

Вызов $.ajax автоматически интерпретирует ответ сервера как JSON - и если с этим проблемы, то хотя XmlHttpRequest выполнился успешно, но $.ajax вызывает коллбэк для ошибки и ставит status="parsererror".

Удобно то, что где бы ни произошла ошибка: во время выполнения XmlHttpRequest-запроса, или на сервере, или при разборе JSON - вызовется единый обработчик onLoadError.

Метод для загрузки данных

Ну и, наконец, единственный публичный метод:

load(url)
Загружает данные в селект. Предварительно включает индикацию загрузки showLoading и удаляет всех детей, т.е очищает элемент.

Пример использования

Работающий селект можно посмотреть в действии:


Кнопки в этом примере инициализуются так:

$(document).ready(function() {
    var select = ajaxSelect('ajax-select');

    // урл, всегда выдающий пару разных фруктов
    document.getElementById('ajax-select-load-options').onclick =
        function() { select.load('/ajax/ui/options.php') }
    
    // урл, всегда выдающий ошибку
    document.getElementById('ajax-select-load-error').onclick =
        function() { select.load('/ajax/ui/error.php') }
    
});

Разумеется, никто не мешает передавать более сложный URL, добавлять туда id и другую полезную информацию.

Резюме

Вы: