Немного вводных... В последнее время я писал достаточно мало. Особенно мало было материалов по программированию. Да и то, что я писал непосредственно про AJAX в большинстве случаев касалось ASP.Net -- ну что ж поделать, именно на нём я делаю самые большие проекты. Впрочем, это однобокий подход -- не надо забывать и мир LAMP. Итак, сегодня в блоге появляется статья, переведенная Геннадием Потаповым. Надеюсь, статья будет полезна, и Генадий достойно подхватит флаг из моей ныне сломанной руки. О статье Автор заметки Stoyan Stefanov, он известен своими статьями о PEAR (скорей всего вы сталкивались с его статьей DB to MDB2), MySQL и AJAX. Речь пойдет о раскладке AJAX под MVC (казалось бы, вещь элементарная, но на самом деле легко допустить ошибку) и о мини-фреймворке, который предлагает вам автор. Критика по перевод приветствуется. AJAX MVC Это своего рода фреймворк для создания AJAX приложений, основанных на MVC паттерне. Да, я назвал много модных словечек, признаю, но это не должно восприниматься в серьез. Недавно я работал над несколькими небольшими проектами и как-то случайно (это вообще не входило в мои планы), разработал для себя маленький фреймворк. Потом, я заметил, что этот скрипт и его организация схож с MVC. Так как должен быть построен MVC когда мы имеем смесь толстого и тонкого клиента, HTML, JavaScript, XMLHttpRequest, PHP и СSS? Как обычно работают AJAX приложения:
у нас есть HTML страничка, оформленная CSS
вы куда ни будь кликаете
JavaScript отправляет запрос на сервер (PHP скрипту)
JavaScript обновляет содержимое HTML страницы
Реализуем MVC паттерн Итак, так какая часть будет отвечать за Model (Модель), View (Вид) и Controller (Контроллер)? С Моделью все просто, это бизнес логика: запись в базу и т.п. Модель - PHP скрипт. Вид? Очевидно это HTML страница, оформленная CSS. Надо заметить, что JavaScript, который будет обновлять страницу, тоже часть Вида. Неважно как это реализовано: innerHTML в JavaScript’e или DOM, в любом случае это часть HTML. А что насчет Контроллера? У нас будет два контроллера. Один на серверной части, PHP скрипт который будет получать запросы, и запрашивать у Модели данные. Другой на стороне клиента, JavaScript, который решает, что случится по нажатию кнопки и отправляет соответствующий AJAX запрос PHP контролеру. Поэтому я буду рассматривать любые действия JavaScript, включающие отлавливание событий и отправку HTTP запросов как часть Контролера. Схема: В действии (пример) Чтобы оправдать концепцию мы рассмотрим небольшое приложение в качестве примера. Простая HTML страница с кнопкой, оформленная CSS. Она содержит два JavaScript’a отвечающих за события (Контролер) и за обновления страницы (Вид). Также страница будет содержать два вспомогательных JavaScript’а, так как я использую YUI (Yahoo User Interface) библиотеку. JavaScript’овый Контролер будет связан с кнопкой, по нажатию этой кнопки он отправит запрос PHP Контролеру. PHP Контролер (просто switch) разбирает запрос и вызывает соответствующий объект бизнес модели. В нашем случае «объект модели» это простая функция, но вы легко можете ее расширить. Модель возвращает (в JSON формате) ответ, в нашем случае это список установленных расширений PHP. Теперь используя полученные данные, JavaScript Вида обновляет содержимое страницы. Затем Вид вызывает другую функцию из JavaScript’ого Контролера, которая добавляет новые обработчики событий для нового контента. (Оп, тут небольшая ошибочка, было бы лучше, если бы ответы Модели отлавливались JavaScript’овым контролером, который бы активировал обработчики обновления JavaScript’ового вида, в любом случае это легко исправить) Структура каталогов:Код для примера Мы добрались до самого интересного – реализации. Начнем с простого .html страницы, главной части вида. Index.html: < ?xml version=”1.1″ encoding=”iso-8859-1″?> < !DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Strict//EN” “https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd”> Welcome to my app!
Это фрагмент из behaviours.js: // класс событий var behaviours = { phpcontroller: "../controller/switch.php?request=", // продолжаются behaviour.methods …. } // загрузка страницы, добавляем onload событие YAHOO.util.Event.addListener( 'thebutton', 'click', behaviours.theButtonClick); Теперь, когда пользователь кликнет по кнопке, вызовется метод behaviours.theButtonClick(), что в свою очередь отправит запрос PHP контролеру и сообщит ему тип запроса: "loadSomething": theButtonClick: function(e) { alert('Ouch! nnOK, I'll make a request for ya, buddy!'); YAHOO.util.Connect.asyncRequest ( 'GET', behaviours.phpcontroller + 'loadSomething', {success: updates.writeContent} ); }, PHP контроллер (controller/switch.php) получит запрос, пройдет switch (проверит тип запроса) и вызовет соответствующую (в нашем случае простую) функцию из бизнес модели. Полный код switch.php: < ?php // если это запрос if (empty($_GET[’request’])) { die(); } // получить бизнес-логику include_once ?../model/business.php’; // разбор запроса // и вызов объекта логики switch ($_GET[’request’]) { case ?loadSomething’: echo loadSomething(); break; case ?loadSomeMore’: // не используется, только для примера echo loadSomeMore(); break; } ?> Функция loadSomething() из PHP модели получает список установленных PHP расширений, кодируем в JSON и отправляем назад. Полный листинг ../model/business.php: < ?php function loadSomething() { $extensions = get_loaded_extensions(); return ?[” ?. implode(’”,” ?, $extensions) . ?”]’; } ?> Если вернуться назад и взглянуть на AJAX запрос, можно увидеть, что в случае успеха, я вызываю метод updates.writeContent(). В ../view/updates.js содержится код обновления исходной html страницы, который создает HTML таблицу с результатами (списком расширений PHP). Потом я хотел прикрепить листенер к новой таблице, но это уже работа JavaScript’ового Контролера. Полный листинг updates.js: var updates = { writeContent: function (xmlhttp) { if (!xmlhttp.responseText) { alert("I got nothing from the server"); } var data = eval(xmlhttp.responseText); var write_to = document.getElementById('content'); write_to.innerHTML = ''; var html2dom_root = write_to; var table = document.createElement("table"); var table_1_tbody = document.createElement("tbody"); for (var i in data) { table_1_tbody_2_tr = document.createElement("tr"); table_1_tbody_2_tr_1_td = document.createElement("td"); num = 1 + parseInt(i); table_1_tbody_2_tr_1_td_1_text = document.createTextNode(num); table_1_tbody_2_tr_1_td.appendChild(table_1_tbody_2_tr_1_td_1_text); table_1_tbody_2_tr.appendChild(table_1_tbody_2_tr_1_td); table_1_tbody_2_tr_2_td = document.createElement("td"); table_1_tbody_2_tr_2_td_1_text = document.createTextNode(data[i]); table_1_tbody_2_tr_2_td.appendChild(table_1_tbody_2_tr_2_td_1_text); table_1_tbody_2_tr.appendChild(table_1_tbody_2_tr_2_td); table_1_tbody.appendChild(table_1_tbody_2_tr); } table.appendChild(table_1_tbody); html2dom_root.appendChild(table); behaviours.updateTableBehaviour(); } } (Кстати, чтобы облегчить себе жизнь, при работе с DOM, я использовал свою небольшую утилиту html2dom) И, наконец, оставшаяся часть JavaScript’ового Контролера (behaviours.js): метод behaviours.updateTableBehaviour(), который добавляет листенер к новой таблице и метод trClick(), который отлавливает клики по этой таблице. По клику просто меняется цвет выбранной строки. trClick: function (e) { var target = (e.srcElement) ? e.srcElement.parentNode : e.target.parentNode; if (target.tagName == 'TR') { if (target.className == 'tr-on') { target.className = ''; } else { target.className = 'tr-on'; } } }, updateTableBehaviour: function () { var el = document.getElementById('content').firstChild; YAHOO.util.Event.addListener( el, 'click', behaviours.trClick); }Демо и исходники • Demo – on-line демонстрация • Zipped demo – весь исходный код этого примера • Template – исходный код примера, но часть кода закомментирована, так что вы можете использовать его в качестве шаблона для ваших будущий AJAX проектов. Единственное, что нужно сделать - кинуть YUI в папку _extras/yui.
Запись добавлена 15.01.2007 в 8:30. Автор TermiT. Категории: Библиотеки и фреймворки. Комментарии к этой записии можно отслеживать по RSS 2.0. Комментарии и пинги запрещены. теги: AJAX, JavaScript, mvc, php, Stoyan-Stefanov, перевод.
Комментарии: 4
Solid 17.01.2007 9:57
Помоему, представление вида в DOM — изврат. Лучше использовать XSLT.
Stoyan 29.01.2007 4:45
Zdrastvui Boris, bolshoe spasibo!
Solid, XSLT can often be a pain to start with, the learning curve is a bit sharp, but you’re free to use it with this little framework, won’t be a problem. It’s true that the DOM-way can be a bit verbose, but on the other hand it’s quite easy to understand. And in this case I used this sort of code-gen utility to produce it, so maybe the variable names could have been better than the “izvrat” as you put it
lex_web 22.03.2008 8:27
> С Моделью все просто, это бизнес логика
Модель – содержание системы
Вид – представление
Контроллер – содержит код бизнес логики
lex_web 22.03.2008 9:02
Беру свои слова обратно.
Просто в одной книге было написано, что: “Контроллер – содержит код бизнес логики”. Почитал статьи в инете, в которых везде пишут, что модель – бизнес логика.
И ничего не осталось, как поменять мнение.
Помоему, представление вида в DOM — изврат. Лучше использовать XSLT.
Zdrastvui Boris, bolshoe spasibo!
Solid, XSLT can often be a pain to start with, the learning curve is a bit sharp, but you’re free to use it with this little framework, won’t be a problem. It’s true that the DOM-way can be a bit verbose, but on the other hand it’s quite easy to understand. And in this case I used this sort of code-gen utility to produce it, so maybe the variable names could have been better than the “izvrat” as you put it
> С Моделью все просто, это бизнес логика
Модель – содержание системы
Вид – представление
Контроллер – содержит код бизнес логики
Беру свои слова обратно.
Просто в одной книге было написано, что: “Контроллер – содержит код бизнес логики”. Почитал статьи в инете, в которых везде пишут, что модель – бизнес логика.
И ничего не осталось, как поменять мнение.