Вернуться на Пуск
О проекте
Новости
Партнеры
Форум
Разработчикам

Разработка плагинов для Pusk.ru

Скачать: CHM | PDF

Материалы с мастер-класса PHPCONF-2007

Архитектура приложения

Общие сведения

Архитектурно — система представляет собой HTML документ с разметкой, содержащей множество блоков – окон. Для каждого окна в БД существует запись, описывающая его размеры, состояние, местоположение, тип приложения и т.д. Содержимым окна управляет клиентская часть модуля или плагина (JavaScript), которая общается с серверной частью через AJAX механизм. Каждое окно живет своей собственной жизнью, не пересекаясь по пространству имен переменных и функций с другими окнами.

Визуальное представление

Окно

Окно представляет собой таблицу с контейнером в виде элемента DIV внутри. Окно может изменять размеры во всех направлениях, может сворачиваться в виде заголовка, может перезагружаться. Практически у всех окон есть диалог настроек.

Упрощенный вариант шаблона


    <table name="window" style="position:absolute;top:px;left:px;z-index:;">
     <tr>
      <td>
    
       <div class="windowContainer" style="width:px;height:px; overflow:auto;">
    
        HTML
    
       </div>
    
      </td>
     </tr>
    </table>
    

Плагины

Введение

Плагин представляет собой совокупность титульной страницы в формате xml (описан ниже) и серверных скриптов, физически находящихся или на наших серверах, или в любом другом доступном месте. Функция титульной страницы - сообщить загрузчику стартовые данные, загрузить начальный контент, подключить JavaScript и CSS библиотеки. Далее плагин начинает жить своей жизнью в общем пространстве документа. Для реализации динамического функционала плагин может обращаться к своей серверной части через AJAX интерфейс, используя функции библиотеки ajax или srvapi.invoke. В случае использования srvapi.invoke, скрипты на сервере должны формирвать ответ в виде XML документа, в случае прямого обращения через ajax ответ может быть в произвольном виде, но его обработка полностью возлагается на JS библиотеки плагина на клиенте. Для реализации кросс-доменного доступа через AJAX используется прокси сервер, все URL должны оборачиваться вызовами/

newURL = getProxy(url), где url – обычного вида http://somehost.ru/somefile.php?somequery

newURL уже можно передавать через ajax

Прокси сервер полностью передает GET запрос и POST данные, соответственно поддерживая оба метода запроса. Сервер полностью транслирует все заголовки от браузера до точки назначения, но обратные заголовки могут приходить не все. Компоненты не должны напрямую ссылаться на другие документы через теги A, FORM и др. Все запросы должны отправляться или в некий блок IFRAME или через механизм AJAX.

Общие требования

При разработке плагинов необходимо жестко следовать следующим требованиям:

Процесс загрузки

Формат файла плагина


    <?xml version="1.0" encoding="UTF-8"?>
    <plugin>
    
        <description>
            <application class="my_application_classname" title="Название приложения" version="0.1" lmtime="Tue, 17 Oct 2006 13:05:00 +0400" expires="Tue, 17 Oct 2006 13:05:00 +0400"/>
            <author name="Иванов Иван" mail="ivanov#mail.com" home="http://mypage.com"/>
            <window width="400" height="300" title="Заголовок окна" url="http://someaddr.com"/>
        </description>
    
        <config>
            <!—- Предварительный формат описан ниже -->
        </config>
    
        <content>
    
            <link rel="stylesheet" href="absolute_path_and_url/file.css"/>
    
            <style>
                <![CDATA[
                .myApp A {color: red;}
                .myApp B {color: green;}
                ]]>
            </style>
    
            <script src="someurl/somefile.js" charset="UTF-8"/>
    
            <script language="javascript">
                <![CDATA[
                if (!window.my_application_classname)
                window.my_application_classname = function(win_id, parameters)
                {
                    this.instance = win_id;
                    this.appName = 'vd_application_'+this.instance;
                    this.appWindow = document.getElementById('window_'+win_id);
                    this.appWindowContainer = document.getElementById('windowFrame_'+win_id);
                    this.parameters = parameters;
    
                    this.init = function()
                    {
                        alert('Load ok!');
                    }
    
                    this.go = function()
                    {
                        var p = document.getElementById('message_'+this.instance);
                        p.innerHTML = 'Preved, krosavcheg!';
                    }
    
                    this.unload = function()
                    {
                        alert('Unloading...');
                    }
                }
                ]]>
            </script>
    
    <body>
            <div>
                <b>Hello world!</b><br/>
                <button onclick="vd_application___VDINSTANCE__.go();">Go!</button>
                <p id="message___VDINSTANCE__"></p>
            </div>
    </body>
        </content>
    
    </plugin>
    

Предварительный пример блока конфигурации


    <tab title="Настройки RSS">
    	<fieldset>
            <legend>
                Основные параметры
            </legend>
            <description>
                Описание группы 1
            </description>
                <input type="text" label="URL:" name="url" default="" required="1" validate="url" maxlength="64" readonly="0" />
                <input type="select" label="Выводить по:" name="num" default="10" values="5,10,20" labels="5,10,20" multiple="0" />
                <input type="select" label="Открывать ссылки в:" name="target" default="_blank" values="_blank,_self" labels="Новом окне,Этом фрэйме" multiple="0" />
                <input type="select" label="Выводить описания:" name="noteformat" default="1" values="0,1,2" labels="Не выводить,Скрывать,Показывать" multiple="0" />
                <input type="text" label="Формат даты:" name="timeformat" default="d/m/Y H:i" required="1" validate="none" maxlength="16" readonly="0" />
                <input type="text" label="Частота обновления(сек):" name="rld_time" default="300" required="1" validate="none" maxlength="4" readonly="0" />
        </fieldset>
    </tab>
    

Возможные типы элементов

Кеширование плагинов

Время, на которое будет скеширован плагин нашим сервером, определяется атрибутом expires в блоке описания плагина. Обратите внимание: если expires будет в прошлом, либо невалидным, будет применено дефолтное значение времени кеширования – 1 час.

Авторизация пользователя

Для авторизации пользователя предлагается использовать следующую схему:

MD5(SecretCode + window_id + user_id) Таким образом, javascript при запросе на внешний сервер, который не имеет доступа к базе, может передать в открытом виде предполагаемый user_id, window_id и вышеупомянутый md5 ключ. На сервере скрипт знает секретный код, может воспроизвести операцию и сравнить ключи. Если ключ совпадает, значит пользователь пришел действительно от нас.

С учетом того, что проксирующий скрипт транслирует полностью все заголовки, можно реализовать полноценную авторизацию через AJAX через отправку формы с логином и паролем, а сервер может поставить cookie в домен нашей среды. В дальнейшем по этим cookies можно полноценно работать. Но этот метод не приветствуется, поскольку наша основная задача - предоставить пользователю без лишних усилий сразу же доступ к нужным сервисам. Этот метод целесообразно применять для почтового клиента, хранилища файлов и т.д.

Разработка компонента

Подготовка серверной части

  1. Разрабатывается 100% AJAX сайт в размерах PDA, а именно:
  2. Верстается с учетом того, что размер информационного блока окна может быть сжат пользователем до 200 px.
  3. Все картинки, и ссылки должны быть абсолютными или использовать переменную VDBASEURL
  4. Все внешние ссылки переписываются с target=”_blank”
  5. Все javascript функции адаптируются к условиям множества экземпляров окна
  6. Все JavaScript функции выносятся в отдельный класс

Подготовка титульной XML страницы плагина

  1. Создается XML документ, вышеописанной структуры
  2. Заполняется блок описания (description)
  3. Объявление javascript класса выносится внутрь блока content
  4. Добавляются ссылки на внешние css файлы
  5. Добавляется свёрстанный блок первой страницы. Это может быть как просто слово “загрузка”, так и полноценный интерфейс.

Разработка JavaScript части плагина

При создании окна, в случае подключения и успешной инициализации класса, создается его экземпляр, под именем vd_application_xxxx, где хххх это идентификатор окна. Для плагинов происходит попытка инициализировать класс, объявленный в блоке description. События и методы

init()
Вызывается после создания экземпляра класса. Подразумевается, что уже загружен весь HTML блок внутрь окна, поставлены на загрузку css файлы. В методе init рекомендуется произвести подстройку контента, установку таймеров, обработчиков и т.д. Как правило при инициализации задается команда на подгрузку контента. В метод init передаются в виде js-объекта параметры, заданные пользователем в настройках плагина.

unload()
Метод вызывается менеджером окон при закрытии окна. На момент вызова окно еще существует, и есть возможность произвести некие полезные действия, например снять обработчики событий, отключить таймеры, сбросить переменные и т.д. Например, для компонента «Заметка» целесообразно установить на unload обращение к серверу за удалением заметки.

onresize()
Метод вызывается во время ручного или автоматического изменения размеров окна. Отлавливая изменение размера можно динамически менять содержимое окна, масштабировать картинки и т.д. Основное применение этого события сводится к автоподстройке контейнера (DIV), который должен содержать скроллбар и должен подгоняться автоматически под размер окна.

onbeforeresize(), onafterresize()
Методы, вызываемые соответственно до начала изменения размеров окна и после изменения. В отличие от метода onresize(), который срабатывает при каждом движении мыши в процессе ресайза, данные методы вызываются по одному разу.

Всплывающие окна

Общие сведения

Всплывающие окна используются для вывода большого объема информации, которая не уместится в окно или не вписывается в его дизайн. Всплывающее окно внешне не отличается от обычного окна, но оно не имеет функций изменения размера и всегда занимает столько пространства, сколько занимает его блок контента.

Использование

После закрытия окна, все его дочерние окна автоматически уничтожаются.

Отладчик

Общие положения

Отладчик предназначен для вывода сообщений об ошибках, текстовой информации и пр. Рекомендуется для использования вместо функции alert().

Использование

При добавлении на рабочий стол компонента Отладчик инициализируется вывод сообщений, путем вызова функции debugInit(). Все сообщения, которые отправлялись несуществующему отладчику будут переданы сразу же после инициализации автоматически. Чтобы вывести отладочную информацию необходимо вызвать одну из функций: debugError, debugResult, debugNotice в главном окне (на десктопе)

Полезные функции и переменные

Глобальные переменные

Глобальные переменные хранятся как атрибуты window

Внутренние служебные функции

Предназначены в основном для получения различных параметров.


    var mHash = {
        	 'winid': this.plugin.instance,
           	 'login': this.login,
           	 'passwd':this.passwd
       	}
      	srvapi.invoke( this.urlAuth + pf.hash.serialize(mHash) );
    

Пример кода класса

В случае необходимости написания полноценного JavaScript функционала рекомендуется использовать следующий образец:


     if (!window.DesktopBrowser_application)
     window.DesktopBrowser_application = function(win_id)
     {
        this.instance = win_id;
        this.appName = 'vd_application_'+this.instance;
        this.appWindow = document.getElementById('window_'+win_id);
        this.appWindowContainer = document.getElementById('windowFrame_'+win_id);
        this.timer = null;
        this.rld = 300;
        this.url = '';
    
        this.getContent = function()
        {
            window.clearTimeout(this.timer);
            if (this.rld > 0)
            this.timer = window.setTimeout(this.appName+".getContent()", this.rld*1000);
            document.getElementById('contentFrame_'+this.instance).src = document.getElementById('contentFrame_'+this.instance).src;
        }
    
        this.goHome = function()
        {
            window.clearTimeout(this.timer);
            if (this.rld > 0)
            this.timer = window.setTimeout(this.appName+".getContent()", this.rld*1000);
            document.getElementById('contentFrame_'+this.instance).src = this.url;
        }
    
        this.init = function(params)
        {
    	this.parameters = params;
    	this.url = params.url;
            window.clearTimeout(this.timer);
            if (this.rld > 0)
            this.timer = window.setTimeout(this.appName+".getContent()", this.rld*1000);
            if (!document.getElementById('reload_menu_'+this.instance))
            {
     	      windowMenuAddDivider(this.instance);
                windowMenuAddItem(this.instance, 'reload_menu_'+this.instance, 'обновить страницу',
                "window.vd_application_"+this.instance+".getContent()", null, true);
                windowMenuAddItem(this.instance, 'home_menu_'+this.instance, 'домой', "window.vd_application_"+
                this.instance+".goHome()", null, true);
            }
            debugResult('Application '+this.appName+' init OK!');
        }
    
    
        this.unload = function()
        {
            debugResult('Unloading application '+this.appName );
            window.clearTimeout(this.timer);
        }
     }
    

Разрешение конфликтов имен

Для того, чтобы свободно использовать в своих скриптах обращения к элементам через document.getElementById() в HTML коде используйте зарезервированную константу VDINSTANCE, она будет заменена на ID окна во время парсинга на клиентской стороне. А в скриптах используйте обращение вида $(’name_’+this.instance). Настоятельно не рекомендуется объявлять глобальные переменные и функции, поскольку они не смогут быть автоматически выгружены в случае закрытия окна.

Макропеременная __VDINSTANCE__

Переменная VDINSTANCE предназначена для автоматического разделения экземпляров одного и того же модуля или плагина на стороне клиента. В случае использования для передачи данных от сервера к клиенту нашего xml транспорта, в блоке html достаточно указать атрибут instance=”xxx” и в html коде автоматически будет произведена замена всех встречающихся переменных VDINSTANCE на реальный числовой идентификатор окна.

Пример

Нужно вписать текст, пришедший от сервера в некоторый контейнер (DIV). При этом на рабочем столе присутствует 2 или более идентичных окна с плагином.


    <a href="#"
    onclick="document.getElementById('targetDiv').innerHTML = prompt('Введите что-нибудь'); return false;">
    Изменить текст</a>
    <div id="targetDiv">Начальный текст тут</div>
    

В одном экземпляре этот код бы сработал прекрасно, но если открыто 2 одинаковых окна с абсолютно одинаковым HTML кодом внутри или просто кто-то другой написал плагин и использовал в нем элемент с ID targetDiv, то скорее всего текст попадет не туда куда требовалось. Правильнее было бы, при передаче контента, использовать VDINSTANCE и избежать конфликта имен.


    <a href="#" onclick="document.getElementById('targetDiv___VDINSTANCE__').innerHTML = prompt('Введите что-нибудь'); return false;">
    Изменить текст</a>
    <div id="targetDiv___VDINSTANCE__">Начальный текст тут</div>
    

Заметим, указанный пример будет корректно работать в одном экземпляре в любом случае, даже если его загрузить локально. Для одновременной работы нескольких экземпляров, контент должен приходить через xml транспорт или на сервере необходимо самостоятельно реализовать подстановку значений.

Макропеременная __VDBASEURL__

Переменная VDBASEURL предназначена для удобства адресации в клиентской части плагина. Вычисляется из базового URL плагина. Пример использования:


    <img src="__VDBASEURL__/images/logo.gif">
    

Написание CSS к плагину

Классы и идентификаторы, относящиеся к одному объекту нужно объединять в неймспейсы по имени объекта, который явялется первой лексемой в названии.


      .componentName .navigation
      .componentName .menu
    
      .navigation   <- неправильно
      .menu         <- неправильно
    

      TABLE.window
      TABLE.window TD
      TABLE.window .windowControlButton
    
      TABLE.window B.button_red    <- неправильно, подчеркивание запрещено
      DIV.Window                   <- неправильно, имя класса начинается с большой буквы
    

Формат ответа сервера в XML

Ответ сервера приходит в виде xml, парсится контент, содержащийся в тэге response. Парсер распознает следующие элементы:

Все тэги кроме result могут присутствовать в документе любое число раз, все информационные блоки должны содержать только текстовые ноды или CDATA контейнеры. Порядок выполнения кода следующий:

Образец ответа от сервера


    <?xml version="1.0" encoding="windows-1251"?>
     <!-- Sample server response file -->
     <response>
        <result code="1" />
        <execute>
            <![CDATA[
     parent.debugNotice("Пользователь:36");
     parent.debugNotice("Работаю с окном");
            ]]>
        </execute>
        <html id="code123" instance="123">
            <![CDATA[
            некий html код
            ]]>
        </html>
        <execute>
            <![CDATA[
            document.getElementById('someID').innerHTML = html.code123;
            serverSetStatus(1);
            ]]>
        </execute>
        <execute>
            alert('Пришел ответ от сервера');
        </execute>
        <jscript src="myscript.js" />
    <style id="someid">
    <![CDATA[
    b {color: red}
    ]]>
    </style>
    <stylesheet id="someOtherID" href="someurl/somefile.css"/>
    </response>
    

Полезные утилиты

Для упрощения и ускорения процесса разработки плагинов, нами был написан ряд полезных утилит, вызываемых из модуля DevTools:

Загрузчик плагинов предназначен для ручного запуска плагинов, расположенных на внешних серверах. Введите в поле ввода путь к плагину и нажмите «Поехали».

Дебаггер позволяет, вместо выдачи сообщений при помощи команды alert(), выводить их в специальное окно для сообщений. Поддерживаются следующие команды:

В дебаггер также выводится информация об ошибках, отловленных блоком try-catch при попытке исполнения кода плагина.

При просмотре текста в дебаггере, можно выделить его мышью и отправить в VarDump.

VarDump предназначен для просмотра свойств и методов объектов. Позволяет рекурсивно ходить по свойствам, загружая все дочерние объекты. Незаменим при отладке ООП на JavaScript.

Пользовательское соглашение
Все права защищены. © 2006—2008 Pusk.ru