AJAX на практике.

Запрос на чистом JavaScript.

Во всех четырёх статьях серии "Ajax на практике", мы разбирались, как использовать эту технологию с помощью библиотеки jQuery и, в частности, её метод $.ajax(), но всегда ли нужно прибегать к помощи фреймворков? К примеру, если в своём проекте, вы больше нигде не используете библиотеку, то абсолютно не резонно нагружать страницы лишними килобайтами и особенно, если этих килобайт около девяноста. Вывод - пишем ajax-запрос на чистом JavaScript, тем более, что та же библиотека jQuery, ни что иное, как подготовленный набор методов (функций), написанный на нативном JS. Если взять для сравнения JS-код из статьи "Получение данных из формы", то переписывая код, нам нужно будет сделать следующее:

  1. Дождаться загрузки всего документа
  2. Получить в распоряжение элемент <form>
  3. Повесить на него обработчик события submit
  4. Подготовить данные формы на отправку
  5. Создать объект XMLHttpRequest
  6. С помощью XHR отправить данные
  7. Получить, обработать и вывести на экран ответ сервера

Поехали пошагово...
1) Вместо привычных $(function(){}) или $(document).ready(function(){}), устанавливаем свой обработчик события onload. Но прежде, хочу пояснить, почему мы не будем использовать конструкцию element.onevent = func;. Это способ хоть и кроссбраузерный, но главным его недостатком является то, что установив на один элемент два и более обработчика, последний "затрёт" предыдущие и они "не откатают свою обязательную программу". Поэтому, хоть кода на пару строк получится больше, но запишем так:

// функция кроссбраузерной установки обработчиков событий
function addEvent(elem, type, handler){
  if(elem.addEventListener){
    elem.addEventListener(type, handler, false);
  } else {
    elem.attachEvent('on'+type, handler);
  }
  return false;
}
// Вешаем обработчик события загрузки документа - DOM-Ready
addEvent(window, 'load', init);

После загрузки документа, будет вызвана функция init, где мы сможем получить доступ к нашей форме. Этот шаг можно и пропустить, если весь js-код, будет расположен в самом конце документа, перед закрывающим тего </body>, но случаи бывают разные...
2) В функции init, мы обращаемся к нашей форме и, в свою очередь, устанавливаем на неё обработчик события submit.

// Инициализация после загрузки документа
function init(){
  output = d.getElementById('output'); // элемент, куда мы выведем полученный в ответе результат
  myform = d.getElementById('my_form'); // форма
  addEvent(myform, 'submit', sendAjaxRequest); // устанавливаем на форму обработчик события submit
  return false;	
}

По событию submit, будет вызвана функция sendAjaxRequest. И сразу же переходим к этой функции, где сформируем данные формы для запроса и отправим ajax-запросом. Сформировать данные можно или с помощью объекта FormData, с которым вы могли познакомится (если до этого не были знакомы) в статье "Получение данных из формы", или же обращаться к каждому элементу формы, получая значения, по какому-нибудь его селектору, или пробежаться по элементам формы в цикле. Но прежде всего, нам нужно отменить обычное поведение формы на событие submit:

// Функция Ajax-запроса
function sendAjaxRequest(e){
  var evt = e || window.event;
  if(evt.preventDefault){
    evt.preventDefault(); // для нормальных браузров
  } else {
    evt.returnValue = false; // для IE старых версий
  }
  // .....
}

В этой же функции, собираем все данные формы:

// формируем данные формы
var elems = myform.elements, // все элементы формы
    url = myform.action, // путь к обработчику (берём из атрибута action нашей формы)
    params = [],
    elName,
    elType;
// проходимся в цикле по всем элементам формы
for(var i = 0; i < elems.length; i++){
  elType = elems[i].type; // тип текущего элемента (атрибут type)
  elName = elems[i].name; // имя текущего элемента (атрибут name)
  if(elName){ // если атрибут name присутствует
    // если это переключатель или чекбокс, но он не отмечен, то пропускаем
    if((elType == 'checkbox' || elType == 'radio') && !elems[i].checked) continue;
    // в остальных случаях - добавляем параметр "ключ(name)=значение(value)"
    params.push(elems[i].name + '=' + elems[i].value);
  }
}
url += '?' + params.join('&');

На выходе, в переменной url, мы получим строку вида "http://site.com/handler.php?name1=val1&name2=val2&nameN=valN". Если на предыдущих этапах сложностей не возникло, то можно переходить непосредственно к ajax-запросу и это всё продолжаем писать в функции sendAjaxRequest. Но и тут нужно внести одну ремарку. Так как еще остаются пользователи, которые пользуются старыми версиями IE, то приходится учитывать этот момент при создании объекта XMLHttpRequest. Internet Explorer до десятой версии его не поддерживает, а имеет собственную реализацию - объект ActiveXObject. Поэтому напишем ещё одну функцию для создания своего объекта под определенные браузеры:

function getXhrObject(){
  if(typeof XMLHttpRequest === 'undefined'){
    XMLHttpRequest = function() {
      try { return new window.ActiveXObject( "Microsoft.XMLHTTP" ); }
        catch(e) {}
    };
  }
  return new XMLHttpRequest();
}

Вы могли видеть и более "навороченные" реализации, но уверяю вас, что на сегодняшний день этого вполнене достаточно, т.к. говоря об устаревших версиях IE, я не беру в расчет седьмой и ниже. Почему восьмой "ослик" еще требует внимания? Для себя я это определил просто: сегодня еще достаточно популярна ОС Windows 7, а с ней по умолчанию идёт в комплекте IE8. Не все имеют возможность (напр., на работе) или не умеют, или не хотят обновлять до более гуманной версии и с этим приходится мириться... надеюсь. что не долго ;)
А мы продолжаем. Создаём новый объект XHR (XMLHttpRequest), открываем соединение с помощью его метода open(), используем собятие onreadystatechange для проверки состояния выполнения запроса (readyState) и статуса ответа сервера (status) и отправляем запрос на сервер, используя метод send(). Когда readyState примет значение равное 4 (запрос завершен) и status значение 200 (OK), мы можем выводить или использовать для других целей ответ сервера, который будет нам доступен в свойстве responseText или responseXML, если мы в ответе получаем полноценный XML-документ.

var xhr = getXhrObject();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() { 
  if(xhr.readyState == 4 && xhr.status == 200){
    output.innerHTML = JSON.parse(xhr.responseText);
  }
}
xhr.send(null);

Третий параметр в методе open(), указывает как будет производиться запрос: true - асинхронно, false - синхронно. В большинстве случаев, используют асинхронный запрос, то есть такой, когда страница не блокирует свою работу, в ожидании ответа сервера. Если вы заметили, то в коде выше, я использовал http-метод передачи данных "GET". Для метода "POST", надо еще добавлять заголовки, которые будут передаваться вместе с запросом на сервер, а так же делаем ещё некоторые изменения: вторым параметром (url) в методе open(), у нас будет только путь к файлу-обработчику, а сформированную строку с данными формы, мы передаём в метод send():

var xhr = getXhrObject();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('Content-length', params.length);
xhr.setRequestHeader('Connection', 'close');
xhr.onreadystatechange = function() { 
  if(xhr.readyState == 4 && xhr.status == 200){
    output.innerHTML = JSON.parse(xhr.responseText);
  }
}
xhr.send(params.join('&'));

Вот, как бы и всё. Остаётся только собрать весь этот конструктор и привести полный листинг.

var d = document,
    myform,
    output;
// кроссбраузерная установка обработчика событий
function addEvent(elem, type, handler){
  if(elem.addEventListener){
    elem.addEventListener(type, handler, false);
  } else {
    elem.attachEvent('on'+type, handler);
  }
  return false;
}
// Универсальная функция для создания нового объекта XMLHttpRequest
function getXhrObject(){
  if(typeof XMLHttpRequest === 'undefined'){
    XMLHttpRequest = function() {
      try {
        return new window.ActiveXObject( "Microsoft.XMLHTTP" );
      } catch(e) {}
    };
  }
  return new XMLHttpRequest();
}
// Функция Ajax-запроса
function sendAjaxRequest(e){
  var evt = e || window.event;
  // Отменяем стандартное действие формы по событию submit
  if(evt.preventDefault){
    evt.preventDefault(); // для нормальных браузров
  } else {
    evt.returnValue = false; // для IE старых версий
  }
  // получаем новый XMLHttpRequest-объект
  var xhr = getXhrObject();
  if(xhr){
    // формируем данные формы
    var elems = myform.elements, // все элементы формы
        url = myform.action, // путь к обработчику
        params = [],
        elName,
        elType;
    // проходимся в цикле по всем элементам формы
    for(var i = 0; i < elems.length; i++){
      elType = elems[i].type; // тип текущего элемента (атрибут type)
      elName = elems[i].name; // имя текущего элемента (атрибут name)
      if(elName){ // если атрибут name присутствует
        // если это переключатель или чекбокс, но он не отмечен, то пропускаем
        if((elType == 'checkbox' || elType == 'radio') && !elems[i].checked) continue;
        // в остальных случаях - добавляем параметр "ключ(name)=значение(value)"
        params.push(elems[i].name + '=' + elems[i].value);
      }
    }
    // Для GET-запроса 
    //url += '?' + params.join('&');
		
    xhr.open('POST', url, true); // открываем соединение
    // заголовки - для POST-запроса
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.setRequestHeader('Content-length', params.length);
    xhr.setRequestHeader('Connection', 'close');
		
    xhr.onreadystatechange = function() { 
      if(xhr.readyState == 4 && xhr.status == 200) { // проверяем стадию обработки и статус ответа сервера
        output.innerHTML = JSON.parse(xhr.responseText); // если все хорошо, то выводим полученный ответ
      }
    }
    // стартуем ajax-запрос
    xhr.send(params.join('&')); // для GET запроса - xhr.send(null);
  }
  return false;
}

// Инициализация после загрузки документа
function init(){
  output = d.getElementById('output');
  myform = d.getElementById('my_form');
  addEvent(myform, 'submit', sendAjaxRequest);
  return false;
}
// Обработчик события загрузки документа
addEvent(window, 'load', init);

Кончено, такой количество строк кода, по сравнению с записью в $.ajax(), может отпугнуть особо ленивых, но знать, что можно спокойно обойтись без всяких библиотек - будет вам плюсом. И применять его всё же нужно, если в вашем проекте, вы больше нигде не используете js-фреймворки, т.к. подключив библиотеку, вы утяжелите страницу на килобайт 90, а написав на нативном JS - всего на 4, а если еще и минифицировать код, то всего лишь на 2Кб! Разницу ощущаете? ;)


На этом я заканчиваю серию статей посвещенную Ajax. Будут вопросы - спрашивайте, по мере возможностей буду отвечать или дополнять новыми статьями. Успехов!

Incode Pro logo

17 комментариев

luter1984 02.03.2015 00:16
Добрый день, а если нужно передать файл в обработчик?
Incode 02.03.2015 00:45
@luter1984, Здравствуйте. Посмотрите статью из этой же серии - Загрузка файлов. Хоть там я описываю способ загрузки с использованием jQuery, но главное - это объект FormData, с помощью которого достаточно просто добавлять файлы для отправки Ajax-запросом. Если что-то будет не получаться, то обращайтесь.
luter1984 03.03.2015 23:18
Добрый вечер, да с jQuery вроде все получается с отправкой файлов и объектом FormData. Хочется научиться правильно отправлять средствами XHR на чистом jQuery
luter1984 03.03.2015 23:19
Прошу прощения чистом javascript
Incode 04.03.2015 00:54
@luter1984, собственно, объект FormData относится именно к нативному (чистому) JavaScript. Возьмите пример из статьи и сделайте следующие изменения:
1. В форму добавляем поле файла:
<input type="file" name="myfile">
2. Изменяем только функцию "sendAjaxRequest":
function sendAjaxRequest(e){
    var evt = e || window.event;
    if(evt.preventDefault){
        evt.preventDefault();
    } else {
        evt.returnValue = false;
    }
    var xhr = getXhrObject();
    if(xhr){
        // формируем данные формы
        var url = myform.action,
        formData = new FormData(myform); // добавляем все поля формы
        formData.append('myfile', myform.myfile.files[0]); // добавляем файл		
		
        xhr.open('POST', url, true);
        xhr.onreadystatechange = function() { 
            if(xhr.readyState == 4 && xhr.status == 200) {
                output.innerHTML = JSON.parse(xhr.responseText);
            }
        }
        xhr.send(formData);
    }
    return false;
}
Вот и всё. На сервере ловим наш файл, как и при обычных запросах - $_FILES['myfile']
Гость 20.07.2015 16:11
Подскажите как выполнить отправку сразу несколько файлов из разных инпутов
name='product[$id][file]'
name='service[$id][logo]'
то есть их буквально может быть 0 шт. и может быть хоть 10 шт.
Да и как потом отловить всё это богатство.
Спасибо
детализирую
первый файл, это макет заказываемого изделия, одежды
второй лого, это логотип который нужно напечатать, на одежде
если макет одежды может быть один. то логотипы могут быть разные и иметь разные размеры и расположение.
кроме того может быть заказано много типов одежды и соответственно количество лог, тоже растёт прямо пропорционально.
В общем вот откуда, может быть так много файлов. но для нашей сферы деятельности, это необходимость.
весь функционал этой формы лежит в файле app.js, код кривой, потому как сам писал.
Incode 20.07.2015 17:38
Подскажите как выполнить отправку сразу несколько файлов из разных инпутов
Если у вас все поля файлов в одной форме, то тут заморачивать не нужно. Посмотрите эту тему. Все данные формы можно целиком "положить" в FormData:
var formData = new FormData($('form_selector').get(0));
Гость 20.07.2015 17:46
Да, то что нужно, огромное спасибо.
Гость 20.07.2015 17:59
И если правильно понимаю нужно удалить эту запись
раз уж я толкаю все фалы пачкой, тогда и присваивать им одно имя нету смысла
formData.append('myfile', myform.myfile.files[0]); // добавляем файл 
Incode 20.07.2015 18:14
И если правильно понимаю нужно удалить эту запись
Да, эта строка вам не нужна. Я просто показывал, как можно добавлять данные в FormData к уже существующим. К примеру, если вам надо к данным формы, добавить еще какие-то значения из Cookie, LocalStorage и т.д.
Гость 23.07.2015 23:19
Да спасибо, понял смысл данной строки, как выяснилось всё же она мне пригодится.
В моём случае один из инпутов используется в качестве вывода суммы ряда полей, для ввода количеств.
Этот инпут в статусе дисейбл, то есть в него можно помещать данные, и пользователь видит общую сумму, но поле не активно для изменения пользователем.
Как сам для себя выяснил, formData не передаёт такие поля, их нужно передавать принудительно.
И здесь мне явно поможет такая строка кода, естественно через переборщик, поскольку у меня может быть много таких полей.
Incode 23.07.2015 23:34
Этот инпут в статусе дисейбл
А почему бы просто не использовать "readonly"? Значения пользователем не изменяются, но в то же время данные будут передаваться.
Гость 23.07.2015 23:37
Так же сегодня мой знакомый товарищ, программист, дал пару полезных советов как проверить переданные данные.
Для проверки массива переданных файлов нужно использовать
var_dump($_FILES);

Очень упрощает понимание на примере что реально попало на сервер, из выведенных данных полезным является поле ['type']. Если не знаете на какой тип файла делать проверку и ограничение загрузки. Просо отправьте себе на сервер файлы, тип которых нужно узнать, и получите его с ответом сервера.

Для картинок все намного проще (речь про растровые изображения) их вообще чаще всего ненужно проверять на тип, достаточно узнать что это картинка, для этого можно использовать.
if(getimagesize($_FILES['file']['tmp_name']){};

У меня код выглядит так
if(getimagesize($_FILES['file']['tmp_name'][$id]['logo']){};

потому что намного больше названий переменных и может быть большой массив.
Гость 23.07.2015 23:40
А почему бы просто не использовать "readonly"?

Обязательно посмотрю, банально не знал.
Гость 06.06.2017 22:15
Круто, автор молодец, хрен где еще найдешь как реализовать это на нативе
Гость 22.10.2018 12:59
тВЕРДЫЙ ПЛАСТ ПУСТОТЫ
Гость 06.02.2023 20:11
А как сделать для нескольких форм?
Ваш комментарий:
X