Наддинамічний веб-інтерфейси (документація)

Одне з головних труднощів, з яким зіштовхуються розроблювачі інтерфейсів веб-додатків, полягає в тому, що після того, як сторінка опинилася в браузері клієнта, зв'язок браузера з сервером закінчується. Будь-яка дія з елементом інтерфейсу вимагає повторного звернення до сервера з повторним завантаженням нової сторінки. Через це веб-додаток втрачає свою елегантність і повільно працює. У даній статті я розповім про те, як дану проблему можна вирішити за допомогою javascript і об'єкта xmlhttprequest.


Я впевнений, що вам знайома традиційна модель інтерфейсу веб-додатків. Користувач запитує сторінку з сервера, яка на сервері створюється, а потім пересилається браузеру. У даної сторінки є html-елементи, що описують форму, в яку користувач вводить дані. Після цього користувач відсилає дані на сервер і отримує нову сторінку, засновану на введених даних, і процес повторюється. Весь цей процес визначається самою природою http-протоколу і відрізняється від того, як ми працюємо зі звичайними додатками, інтерфейс яких нерозривно пов'язаний з програмною логікою.


Візьмемо простий приклад введення серійного номера в будь-якому windows-додатку. Згідно з правилами, після того, як ви закінчите вводити хитромудрий набір цифр і літер у поля, поруч з ними з'явиться зелена "Галочка", що означає, що ви ввели правильний номер. Вона з'являється вмить, як результат логіки "вшитой" в інтерфейс. Як тільки ви закінчили набирати номер, програма перевіряє її та видає відповідь.


У веб-інтерфейсі це стандартне поведінка виглядає зовсім по-іншому. Зрозуміло, поля, в які ви вводите серійний номер виглядають точно так само, але по завершенні введення, користувачу треба, натиснувши кнопку, відправити сторінку на сервер, який перевірить введені дані. Назад користувачеві повернеться нова сторінка, де буде виведено повідомлення про правильному чи неправильному серійному номері. Користувачеві в випадку невдачі треба повернутися на попередню сторінку і знову повторити спробу. І так до нескінченності.


Зрозуміло не часто веб-додаток вимагає від користувача ввести серійний номер, але є безліч інших прикладів, де швидка реакція інтерфейсу на дії користувача дуже б знадобилася. А так як вся програмна логіка знаходиться на сервері, отримати такий результат у традиційному веб-додатку вельми складно.
На сцені з'являється javascript


Завдяки javascript певну кількість програмної логіки можна перенести в html-сторінку, що дозволить швидко реагувати на дії користувача. Однак у цього рішення є один головний недолік. Перша проблема полягає в тому, що як тільки javascript потрапляє в браузер користувача разом зі сторінкою, програмна логіка доступна для перегляду неозброєним оком. У випадку наприклад з перевіркою правильності введеної адреси e-mail це може бути і не страшно, але якщо перевірка пов'язана з серійним номером, алгоритм перевірки стає доступним всім, хто завантажив сторінку, а це неприйнятно.


Друга проблема полягає в тому, що серйозну програмну логіку в сторінку помістити неможливо, так як інтерфейс просто не призначений для цього. Вся логіка повинна перебувати на рівні додатку, а не для користувача інтерфейсу, а це значить – ми знову повертаємося на сервер. Проблема доповнюється ще тим, що не завжди з упевненістю можна очікувати наявності javascript в браузері клієнта. У той час, як більшість користувачів залишають підтримку javascript в своїх браузерах включеною, існує значна кількість користувачів, які цього не роблять, або користуються таким браузером, де javascript відсутній або взагалі не потрібен як клас. Отже всю логіку, яку робить javascript на стороні клієнта, все одно доведеться перевіряти на сервері про всяк випадок.
Об'єкт xmlhttprequest


Рішенням цієї проблеми може стати об'єкт xmlhttprequest. Цей об'єкт вперше був реалізований компанією microsoft у вигляді об'єкта activex, але зараз він доступний як вбудований об'єкт у всіх браузерах mozilla і safari. Цей об'єкт дозволяє javascript-у здійснювати http-запити до віддаленого сервера без необхідності перезавантажувати сторінку. По суті http-запити відправляються і виходять повністю за "лаштунками" сторінки, а користувач їх навіть не помічає.


Це величезний крок вперед, оскільки дозволяє розробнику веб-додатки досягти тієї самої бажаної мети – створення швидкого користувальницького інтерфейсу зі збереженням при цьому програмної логіки на сервері. За допомогою javascript-а дані, введені користувачем, відправляються на сервер, там вони обробляються і користувач практично тут же отримує відповідь на введені дані.
Почнемо з основ


Через свою суперечливої історії об'єкт xmlhttprequest ще не є частиною будь-якого стандарту (хоча щось подібне вже було запропоновано в специфікації w3c dom level 3 load and save). Тому існує два відмінних один від одного методу виклику цього об'єкта в коді скрипта. У internet explorer об'єкт activex викликається так:
var req = new activexobject(“microsoft.xmlhttp”);


У mozilla і safari це робиться простіше (так як там це об'єкт, вбудований в javascript):
var req = new xmlhttprequest();


Зрозуміло через таких відмінностей вам необхідно створювати в коді гілки, кожна з яких буде виконуватися залежно від того, в якому браузері завантажений скрипт. Існує кілька способів, як зробити це (включаючи різні мудровані хакі і метод "умовних коментарів"). Але я вважаю, що краще за все просто перевіряти в коді, чи підтримується браузером той чи інший об'єкт. Гарним прикладом може служити код, взятий з сайту apple, де викладена документація по цьому об'єкту. Давайте їм і будемо користуватися:
var req;


function loadxmldoc(url) {
    // branch for native xmlhttprequest object
    if (window.xmlhttprequest) {
        req = new xmlhttprequest();
        req.onreadystatechange = processreqchange;
        req.open(“get”, url, true);
        req.send(null);
    // branch for ie/windows activex version
    } else if (window.activexobject) {
        req = new activexobject(“microsoft.xmlhttp”);
        if (req) {
            req.onreadystatechange = processreqchange;
            req.open(“get”, url, true);
            req.send();
        }
    }
}


У цьому коді особливо важливо звернути увагу на властивість onreadystatechange. Подивіться, як йому присвоюється значення функції processreqchange. Це властивість – хендлер події, яка запускається всякий раз, коли змінюється стан об'єкта req. Стани позначаються номерами з 0 (об'єкт неініціалізованих) по 4 (запит виконано). Важливо це тому, що наш скрипт не буде чекати відповіді від сервера, щоб продовжити свою роботу. http-запит буде сформований і відісланий на сервер, але скрипт буде виконуватися далі. Через те, що ми вибрали такий варіант поведінки, нам не можна просто в кінці функції повернути результат запиту, оскільки нам невідомо, отримали ми його до цього часу чи ні. Для цього ми і передбачили функцію processreqchange, яка буде відслідковувати стан об'єкта req, і повідомить нам в потрібний час, що процес отримання документа закінчено, і ми можемо йти далі.


Для цього функції processreqchange потрібно перевіряти дві речі. Перша – чекати, коли стан об'єкта req зміниться на 4 (що означає, що процес отримання документа з сервера закінчений). Друге, це перевірити http-статус відповіді. Ви знаєте, що код 404 означає "файл не знайдено" і 500 – "сталася помилка на сервері". Але нам потрібен старий добрий код 200 ("все ОК"), який означає, що на сервері наш запит було успішно виконано. Якщо ми отримали і стан 4 і код 200, ми можемо продовжувати виконання нашого скрипта і обробляти результати, отримані від сервера. Зрозуміло в іншому випадку ми повинні обробити всі помилки, наприклад, якщо код відповіді відрізняється від 200.
function processreqchange()
{
    // only if req shows “complete”
    if (req.readystate == 4) {
        // only if “ok”
        if (req.status == 200) {
            // …processing statements go here…
        } else {
            alert(“there was a problem retrieving
               the xml data:
” + req.statustext);
        }
    }
}
На практиці


Я збираюся створити працюючий приклад для демонстрації вищевикладених ідей. У багатьох веб-додатках є процедура, коли новий користувач реєструється на сервері і потрібно вибрати "нік" для реєстрації. Дуже часто цей "нік" повинен бути унікальним, і тому після того, як користувач вибрав собі "нік", на сервері здійснюється перевірка по базі даних користувачів, є вже такий "нік" чи ні. Якщо ви коли-небудь реєструвалися на якому-небудь поштовому веб-сервері, ви пам'ятаєте, як утомливо підшукувати "нік", яким ще ніхто не користується. Було б дуже добре, якби перевірка здійснювалася без необхідності щоразу оновлювати сторінку.


Для рішення ми скористаємося чотирма ключовими елементами: xhtml-формою, функцією javascript, спеціально написаної для даної ситуації, двома нашими загальними функціями, про які ми говорили вище, і нарешті серверним скриптом, який буде звертатися до бази даних.
Форма


Це найлегша частина роботи – проста форма з полем для введення "ніка". До події onblur ми прив'язуємо наш скрипт перевірки. Для того, щоб вивести користувачеві повідомлення про результати перевірки, я вставив його у форму і сховав за допомогою css. Це більш елегантний і ввічливий спосіб, ніж стандартне діалогове вікно функції alert ().
<input id=”username” name=”username” type=”text”
  onblur=”checkname(this.value,””)” />


  this name is in use, please try another.


У css оголошені властивості класу hidden, а також ще одного класу error, який служить для виведення повідомлень про помилку.
span.hidden{
  display: none;
}


span.error{
  display: inline;
  color: black;
  background-color: pink; 
}
Обробка введених даних


Функція checkname служить для перевірки даних, введених користувачем форму. Завдання функції – взяти дані, вирішити, якому серверному скрипту ці дані віддати, викликати іншу функцію, яка зробить всю "Брудну" роботу з http-запитами і відповідями, і потім обробити відповідь. Ця наша функція буде складатися з двох частин. Одна частина – бере дані з форми, а інша – обробляє відповідь від сервера. Я поясню, навіщо це зроблено, трохи пізніше.
function checkname(input, response)
{
  if (response != “”){
    // response mode
    message   = document.getelementbyid(“namecheckfailed”);
    if (response == “1”){
      message.classname = “error”;
    }else{
      message.classname = “hidden”;
    }
  }else{
    // input mode
    url  = “http://localhost/xml/checkusername.php?q=”
    + input;
    loadxmldoc(url);
  }


}


Відповідь обробляється просто – відповідь, яку ми отримаємо від серверного скрипта, буде або 1, або 0. 1 означає, що такий "нік" вже ким-то використовується. Залежно від відповіді наша функція змінює назву класу повідомлення про помилку – воно або показується, або ховається. Як зрозуміло з коду, роботу на сервері виконує скрипт checkusername.php.
Формування http-запиту і відповіді


Як ви бачили вище, робота з http виконується двома функціями: loadxmldoc і processreqchange. У першому практично нічого змінювати не треба, а у другому потрібні дещо поміняти для роботи з dom-му.


Як ви пам'ятаєте, поки успішний результат виконання запиту не буде переданий функції processreqchange, ми не можемо з цієї функції повернути будь-яке значення. Через це нам потрібно виконувати явний виклик функції з іншого місця коду, щоб скористатися результатом. Ось чому функція checkname розбита на дві частини. Таким чином, головне завдання функції processreqchange полягає в тому, щоб обробити xml-відповідь, отриману від сервера, і передати певне значення з цієї відповіді функції checkname.


Ми не хочемо вносити в ці функції якої-небудь специфічний код, так як ці функції можуть використовуватися і іншими елементами сторінки для звернення до сервера. Тому ми і не вписували у функцію processreqchange ім'я функції checkname. Замість цього ми вирішили, що сервер сам буде в своїй відповіді давати назву функції, яка до нього зверталася.


  checkname
  1


Розбір цього простої відповіді виконується без будь-яких проблем.
function processreqchange()
{
    // only if req shows “complete”
    if (req.readystate == 4) {
        // only if “ok”
        if (req.status == 200) {
            // …processing statements go here…
      response = req.responsexml.documentelement;


      method = response.getelementsbytagname(“method”)
            [0].firstchild.data;


      result = response.getelementsbytagname(“result”)
            [0].firstchild.data;


      eval(method + “(“”, result)”);
        } else {
            alert(“there was a problem retrieving
            the xml data:
” + req.statustext);
        }
    }
}


Звернувшись до властивості responsexml об'єкта xmlhttprequest, ми отримуємо xml-відповідь, що прийшов від сервера, який ми потім розбираємо за допомогою dom. Взявши з відповіді назву функції, яка вимагала цей відповідь, ми знаємо, який функції треба передати отримане значення. Після того, як ви закінчите тестування, приберіть умова else, щоб скрипт не турбував користувачів зайвим повідомленням про помилку.
Серверний скрипт


Останній елемент нашої мозаїки – це серверний скрипт, який буде отримувати запит, обробляти його і повертати відповідь у вигляді xml. У нашому прикладі скрипт буде звертатися до бази даних користувачів, щоб визначити, чи використовується вже даний "нік" чи ні. Для стислості в моєму прикладі скрипт не використовує базу даних, а просто перевіряє два імені "drew" і "fred".
function nameinuse($q)

  if (isset($q)){
    switch(strtolower($q))
    {
      case  “drew” :
          return “1”;
          break;
      case  “fred” :
          return “1”;
          break;
      default:
          return “0”;
    }
  }else{
    return “0”;
  }
 
}
?>


“; ?>


  checkname
 
 


Зрозуміло, ту ж процедуру перевірки треба передбачити і в тому серверному скрипті, який буде виконуватися, коли користувач натисне кнопку "submit" на формі реєстрації. Це на той випадок, якщо у нього в браузері був відключений javascript, або користувач навмисно ввів "нік", який вже використовується. Крім того на завантажених сайтах може так статися, що під час вибору "ніка" він був вільний, а на момент подачі його вже хтось зайняв.


В якості наступного кроку розширте самі функціональність скрипта. Наприклад сервер може повертати варіанти альтернативних "ників" на основі "ніка", вибраного користувачем, але вже зайнятого.


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*