Создание динамического наполнения страницы. JavaScript-библиотеки и технология Comet
Презентацию к данной лекции Вы можете скачать здесь.
13.1. Представление объектов в виде JSON
JSON ( JavaScript Object Notation) – текстовый формат обмена данными, основанный на JavaScript и обычно используемый именно с этим языком [1]. Как и многие другие текстовые форматы, JSON легко читается людьми.
Несмотря на происхождение от JavaScript, формат считается независимым от языка и может использоваться практически с любым языком программирования. Для многих языков существует готовый код для создания и обработки данных в формате JSON.
13.1.1. Использование
Практическая польза использования JSON открывается при использовании технологии AJAX. Формат JSON является более кратким и удобочитаемым по сравнению с XML, является "родным" для JavaScript (преобразуется в структуру данных встроенной функцией eval() ). Кроме того, в JSON -код возможна вставка вполне работоспособных функций.
13.1.2. Синтаксис
Следующий пример показывает JSON -представление объекта, описывающего человека. В объекте есть строковые поля имени и фамилии, объект, описывающий адрес, и массив, содержащий список телефонов:
{ "firstName": "Иван", "lastName": "Иванов", "address": { "streetAddress": "Московское ш., 101, кв.101", "city": "Ленинград", "postalCode": 101101 }, "phoneNumbers": [ "916 123-4567" ] }
На языке XML подобная структура выглядела бы примерно так:
<person> <firstName>Иван</firstName> <lastName>Иванов</lastName> <address> <streetAddress>Московское ш., 101, кв.101</streetAddress> <city>Ленинград</city> <postalCode>101101</postalCode> </address> <phoneNumbers> <phoneNumber>916 123-4567</phoneNumber> </phoneNumbers> </person>
13.1.3. Использование JSON в Ajax
Следующий фрагмент Javascript -кода показывает, как клиент может использовать XMLHttpRequest, чтобы запрашивать с сервера объект в формате JSON:
var the_object; var http_request = new XMLHttpRequest(); http_request.open("GET", url, true); http_request.send(null); http_request.onreadystatechange = function() { if (http_request.readyState == 4) { if (http_request.status == 200) { the_object = eval("(" + http_request.responseText + ")"); } else { alert("Проблема с URL."); } http_request = null; } };
Полезность XMLHttpRequest ограничивается политикой ограничения домена: URL ответа на запрос должен проживать в том же DNS домене, что и сервер, на котором находится страница, содержащая запрос.
Браузеры могут также использовать элементы <iframe> асинхронный запрос JSON данных в кросс-браузер мода, или использовать простые <form action="url_to_cgi_script" target="name_of_hidden_iframe"> представлений. Эти подходы были распространены до появления широкой поддержки XMLHttpRequest.
13.1.4. Вопросы безопасности
Хотя JSON предназначен для передачи данных в сериализованном виде, его синтаксис соответствует синтаксису JavaScript и это создает ряд проблем безопасности. Зачастую для обработки данных, полученных от внешнего источника в формате JSON, к ним применяется функция eval() без какой-либо предварительной проверки.
13.1.4.1. JavaScript eval()
Поскольку JSON представляется синтаксически правильным фрагментом кода JavaScript, естественным способом разбора JSON -данных в JavaScript -программе является использование встроенной в JavaScript функции eval(), которая предназначена для выполнения JavaScript -выражений. При этом подходе отпадает необходимость в использовании дополнительных парсеров.
Техника использования eval() делает систему уязвимой, если источник используемых JSON -данных не относится к доверенным. В качестве таких данных может выступать вредоносный JavaScript код для атак класса "Внедрение кода". С помощью данной уязвимости можно осуществить кражу данных, подделку аутентификации. Тем не менее, уязвимость можно устранить за счет использования дополнительных средств проверки данных на корректность. Например, до выполнения eval() полученные от внешнего источника данные могут проверяться с помощью регулярных выражений. В RFC, определяющей JSON, предлагается использовать следующий код для проверки его соответствия формату JSON:
var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test( text.replace(/"(\\.|[^"\\])*"/g, ''))) && eval('(' + text + ')');
Как более безопасная альтернатива eval() была предложена новая функция parseJSON(), способная обрабатывать только JSON -данные.
13.1.4.2. Подделка кроссдоменного запроса
Непродуманное использование JSON делает сайты уязвимым к подделке межсайтовых запросов (CSRF или XSRF). Поскольку тэг <script> допускает использование источника, не принадлежащего к тому же домену, что и использующий ресурс, это позволяет выполнять код под видом данных, представленных в формате JSON, в контексте произвольной страницы, что делает возможным компрометацию паролей или другой конфиденциальной информации пользователей, прошедших авторизацию на другом сайте.
Это представляется проблемой только в случае содержания в JSON -данных конфиденциальной информации, которая может быть компрометирована третьей стороной и если сервер рассчитывает на политику одного источника, блокируя доступ к данным при обнаружении внешнего запроса. Это не является проблемой, если сервер определяет допустимость запроса, предоставляя данные только в случае его корректности. HTTP cookie нельзя использовать для определения этого. Исключительное использование HTTP cookie используется подделкой межсайтовых запросов.
13.2. Библиотека ExtJS
ExtJS – библиотека JavaScript для разработки веб-приложений и пользовательских интерфейсов, изначально задуманная как расширенная версия Yahoo! UI Library, однако, преобразовавшаяся затем в отдельный фреймворк. Использует адаптеры для доступа к библиотекам YUI, jQuery или Prototype /script.aculo.us. Поддерживает технологию AJAX, анимацию, работу с DOM, реализацию таблиц, вкладок, обработку событий и все остальные новшества "Веб 2.0" [2, 3].
С версии 2.1 библиотека ExtJS распространяется по условиям трех лицензий: Commercial License, Open Source License и OEM / Reseller License.
Начиная с версии Ext JS 3.0, библиотека разбивается на две части: Ext Core (набор JavaScript функций, позволяющий создавать динамические веб-страницы и распространяемый по MIT-лицензии) и Ext JS (набор виджетов для создания пользовательских интерфейсов, распространяемый аналогично Ext JS 2.1 по условиям трех лицензий).
13.2.1. Объектно-ориентированное программирование
Одна из задач, которую ставили себе разработчики библиотеки ExtJS при ее проектировании, была возможность сосуществования с другими JavaScript -библиотеками и фреймворками [4, 5]. Для этого были добавлены удобные методы, помогающие разработчику создавать, наследовать и сопровождать код различных классов. Несмотря на то, что все они были специально созданы для работы с компонентами ExtJS, их можно использовать с любыми объектами JavaScript, в том числе в проектах, не задействующих визуальные компоненты.
13.2.1.1. Ext.namespace
Синтаксис:
Ext.namespace( String namespace1, String namespace2, String etc ) : void
В языке JavaScript пространство имен само является объектом, содержащим список определений других объектов. Правильное использование пространств имен гарантирует, что ваш код не будет конфликтовать с другими библиотеками. Однако чем больше становится кода в библиотеке, тем глубже иерархия имен и такие классы как Ext.ux.graphing.GraphPanel перестают быть редкостью. В ExtJS существует метод Ext.namespace, который позволяет определить все пространства имен за один вызов, уменьшая не только затраты на поддержку кода, но и сокращая его размер.
Традиционный код JavaScript, создающий иерархию пространства имен, выглядит следующим образом:
var Ext = Ext || {}; Ext.ux = Ext.ux || {}; Ext.ux.graphing = Ext.ux.graphing || {}; Ext.ux.soundFx = Ext.ux.soundFx || {}; Ext.ux.maps = Ext.ux.maps || {};
Того же результата можно достичь вызовом всего одного метода:
Ext.namespace("Ext.ux.graphing", "Ext.ux.soundFx", "Ext.ux.maps");
13.2.1.2. Ext.override
Синтаксис:
Ext.override( Object origclass, Object overrides ) : void
JavaScript очень гибкий язык, и переопределение метода в нем выполняется простым переназначением его объекта другой функции:
// создаем функцию function doesStuff() { alert("Функция один!"); }; // теперь ее перекрываем doesStuff = function() { alert("Функция два!"); };
Перекрытие метода класса осуществляется путем переопределения его прототипа:
function MyClass() { // конструктор класса } MyClass.prototype.myMethod = function() { alert("привет"); }; var x = new MyClass(); x.myMethod(); // отобразит "привет" MyClass.prototype.myMethod = function() { alert("пока"); }; x.myMethod(); // отобразит "пока"
Используя тот же способ, разработчик может добавить новые методы в существующий класс.
Ext.override – это удобный способ переопределения методов класса за один присест. Следующий пример перекрывает два метода класса Ext.ux.graphing.GraphPanel. Следуя общепринятым правилам языка JavaScript, если в оригинальном классе не содержатся определений методов, то они добавляются. Первым параметром идет класс, который будет изменен, а вторым – JSON объект с методами, подлежащими переопределению:
Ext.override(Ext.ux.graphing.GraphPanel, { clearAllPoints: function() { // выполнить другие действия }, loadData: function(newData) { // загрузка новых данных } });
Этот простой метод имеет огромное значение при поддержке собственных изменений в чужом коде. Опытные JavaScript -разработчики прекрасно знают, как бывает тяжело использовать модифицированные сторонние библиотеки: каждый их новый релиз порождает необходимость проводить сверку измененных частей и внесения исправлений в собственный код. Используя метод Ext.override, разработчики могут поместить свой код в отдельный файл, связать его со сторонней библиотекой и быть уверенными, что в новом релизе ничего не сломается.
13.2.1.3. Ext.extend и соглашения о параметрах конструкторов
Синтаксис:
Ext.extend( Function superclass, Object overrides ) : Function
или
Ext.extend( Function subclass, Function superclass, [Object overrides] ) : Function
Рассмотренный ранее метод Ext.override выполняет замену реализации на новый метод целиком, а Ext.extend позволяет осуществить полноценное наследование согласно парадигме ООП. Имеются два варианта вызова этого метода, каждый из которых возвращает определение нового класса, содержащего объекты, унаследованные от родительского, определенного в параметре superclass, и перекрывает методы, заданные в параметре overrides. Также он добавляет к новому классу служебный метод override(), который дает возможность перекрывать методы экземпляра класса, и такое же служебное свойство superclass, позволяющее получить доступ к родительскому объекту.
Библиотека ExtJS имеет ряд соглашений о стиле кодирования, которые позволяют улучшить поддержку и читаемость написанного кода. Одно из них: соглашение о виде конструкторов классов. Практически все конструкторы ExtJS принимают единственный параметр, являющийся объектом нотации в формате JSON и содержащий конфигурацию того или иного компонента. Для приверженцев традиционных объектно-ориентированных языков программирования на первый взгляд это может показаться диким, так как обычно сигнатуры методов (включая конструкторы классов) ожидают явно заданные параметры с жестко определенным типом. Но из-за того, что JavaScript динамически типизированный язык, создавать таким образом объекты с помощью классов не всегда удобно.
Ниже приведен пример использования Ext.extend. Обратите внимание, что имеются два способа наследования. Первый:
var newClass = Ext.extend(Ext.form.ComboBox, { constructor: function(config) { // явное задание конструктора config.emptyText = "'5' не допускается"; newClass.superclass.constructor.call(this, config); }, setValue: function(v) { if (v == 5) { // если v равно 5, сообщить пользователю alert("'5' не допускается"); } else { // иначе вызываем метод родителя newClass.superclass.setValue.call(this, v); } } });
Второй:
function newClass(config) { // конструктор config.emptyText = "'5' не допускается"; newClass.superclass.constructor.call(this, config); }; Ext.extend(newClass, Ext.form.ComboBox, { setValue: function(v) { if (v == 5) { // если v равно 5, сообщить пользователю alert("'5' не допускается"); } else { // иначе вызываем метод родителя newClass.superclass.setValue.call(this, v); } } });
Так как все объекты ExtJS имеют одну и ту же форму указания параметров в конструкторе, это позволяет работать с ними без особых усилий: достаточно скопировать все значения объекта config. ExtJS имеет два встроенных метода для выполнения этих действий, которые далее будут рассмотрены.
13.2.1.4. Ext.apply
Синтаксис:
Ext.apply( Object obj, Object config, [Object defaults] ) : Object
Метод Ext.apply производит копирование каждого члена объекта config в объект obj. Опционально может быть задан параметр defaults, содержимое которого также копируется в объект obj, но перед выполнением копирования объекта config.
13.2.1.5. Ext.applyIf
Синтаксис:
Ext.applyIf( Object obj, Object config ) : Object
Метод Ext.applyIf производит копирование каждого члена объекта config в объект obj, но только в том случае, когда этот член еще не существует в объекте назначения.
Обычно разработчики создают дочерние классы с целью расширить функциональность элементов. Однако эта новая функциональность может потребовать дополнительной настройки, которая при помощи использования рассмотренных методов превращается в простейшую задачу:
function MyClass(config) { // конструктор, который принимает конфигурацию в параметре 'config' // присвоение некоторых значений по умолчанию, если они не заданы в конфигурации config = Ext.applyIf(config, { someValue: defaultValue }); // а здесь присваиваются значения по умолчанию, игнорирующие параметры конструктора config = Ext.apply(config, { someOtherValue: defaultValue }); // вызов конструктора класса-родителя с передачей новой конфигурации MyClass.superclass.constructor.call(this, config); } // не забудьте унаследовать родительский класс Ext.extend(MyClass, MyBase, { // ... });