dev001.pngЗадача.
Необходимо разработать приложение/виджет – добавление заметок в виде стикера на сайте для зарегистрированных пользователей.
Условие:

  • редактирование стикера по двойному клику;
  • перетаскивание стикера (drag and drop);
  • сохранение позиции стикера на сайте;
  • удаление стикера в один клик.

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

Итак, прежде чем я разберу в деталях компоненты модуля, из чего он состоит и для чего тот или иной скрипт/файл, приведу несколько скриншотов результата его работы.

Специальная доска заметок в виде стикров на сайте

Эту доску заметок пользователи сайта могут открыть по адресу http://[youdomain.tld]/bostick/



Просмотреть доску стикеров в действии: http://demo.abricos.org/bostick/ (логин: demo, пароль: demo)

Кнопка виджет «Добавить стикер» на страницах сайта


Возможность отобразить стикеры и кнопку «Добавить стикер» на определенной странице сайта



Интернет-приложение «Стикеры»


Работа модуля в качестве интернет-приложения



Просмотреть приложение «Стикер» в действии: http://demo.abricos.org/bos/ (логин: demo, пароль: demo)

Как видно из скриншотов, возможности этого модуля обширные.
Теперь давайте заглянем внутрь самого модуля и посмотрим, как он работает.

Структура модуля


Модуль на платформе Абрикос — это самостоятельная сущность, со своим шаблоном, стилями css, картинками, серверными скриптами и прочими необходимыми для его работы компонентами. Все модули на платформе Абрикос располагаются в ее папке modules.

Главным файлом любого модуля в платформе Абрикос является скрипт module.php, который должен находиться в корневой папке модуля. Когда ядро платформы просматривает доступные модули, то оно смотрит именно этот файл.

Модуль «Стикер» имеет название bostick, и поэтому в папке модулей все его файлы будут располагаться в соответствующей папке с его названием, т.е. папка bostick.

Пример скрипта /modules/bostick/module.php модуля «Стикер».

<?php 
/**
 * Класс модуля "Стикер"
 */
class BostickModule extends CMSModule {
    
    /**
     * Конструктор
     */
    public function __construct(){
        // версия модуля
        $this->version = "0.1";
        // наименование модуля (идентификатор его)
        $this->name = "bostick";
        // имя раздела в адресной строке (http://youdomain.tld/bostick/*)
        $this->takelink = "bostick";
        // роли доступа
        $this->permission = new BostickPermission($this);
    }
    
    /**
     * Управляющий менеджер модуля. Все AJAX запросы этого модуля
     * будут переданы в этот класс. 
     * @return BostickManager
     */
    public function GetManager(){
        if (is_null($this->_manager)){
            require_once 'includes/manager.php';
            $this->_manager = new BostickManager($this);
        }
        return $this->_manager;
    }
    
    /**
    * Получить имя стартового кирпича (/modules/bostick/content/index.html)
    *
    * @return string
    */
    
    /**
     * Стартовый шаблон для сборки страницы.
     * Вызывается, когда браузер запросит страницу по адресу http://youdomain.tld/bostick/*
     * @return string
     */
    public function GetContentName(){
        // стартовые шаблоны находятся в папке модуля content
        // для этого модуля это будет папка /modules/bostick/content
        if (CMSRegistry::$instance->user->info['userid'] == 0){
            // для гостей собираем страницу из шаблона index_guest.html
            return "index_guest"; // /modules/bostick/content/index_guest.html
        }
        // для зарегистрированных пользователекй собираем страницу из index.html 
        return "index"; // /modules/bostick/content/index.html
    }
}

/**
 * Класс констант ролей модуля Стикер
 */
class BostickAction {
    /**
     * Роль на запись стикера (создание/удаление/редактирование)
     * @var integer
     */
    const WRITE	= 30;
}

/**
 * Менеджер ролей модуля Стикер
 */
class BostickPermission extends AbricosPermission {

    /**
     * Конструктор
     * @param BostickModule $module
     */
    public function BostickPermission(BostickModule $module){
        // объявление ролей по умолчанию
        // используется при инсталяции модуля в платформе
        $defRoles = array(
            new AbricosRole(BostickAction::WRITE, UserGroup::REGISTERED),
            new AbricosRole(BostickAction::WRITE, UserGroup::ADMIN)
        );
        parent::__construct($module, $defRoles);
    }

    /**
     * Получить роли
     */
    public function GetRoles(){
        return array(
            BostickAction::WRITE => $this->CheckAction(BostickAction::WRITE)
        );
    }
}

// создать экземляр класса модуля и зарегистрировать его в ядре 
$mod = new BostickModule();
CMSRegistry::$instance->modules->Register($mod);
?>



Просматривая каждый модуль, платформа регистрирует его в ядре. Каждый раз при регистрации происходит сравнение версии модуля с предыдущей зарегистрированной версией. Если текущая версия модуля выше зарегистрированной, то происходит инсталляция или обновление модуля, запросом скрипта includes/shema.php.

Пример скрипта /modules/bostick/includes/shema.php модуля «Стикер».

<?php
$charset = "CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'";
$updateManager = CMSRegistry::$instance->modules->updateManager; 
$db = CMSRegistry::$instance->db;
$pfx = $db->prefix;

// первое обращение к модулю, произвести его инсталляцию
if ($updateManager->isInstall()){
    // инсталлировать роли модуля
    CMSRegistry::$instance->modules->GetModule('bostick')->permission->Install();
    
    // Создать таблицу стикеров в базе 
    $db->query_write("
        CREATE TABLE IF NOT EXISTS ".$pfx."bostk_stick (
          `stickid` int(10) unsigned NOT NULL auto_increment COMMENT 'Идентификатор стикера',
          `userid` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'Идентификатор автора',
          `body` TEXT NOT NULL  COMMENT 'Текст стикера',
          `color` varchar(6) NOT NULL DEFAULT '' COMMENT 'Цвет',
          `region` varchar(36) NOT NULL DEFAULT '' COMMENT 'Цвет',
          `dateline` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'Дата/время создания',
          `deldate` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'Дата/время удаления',
          PRIMARY KEY  (`stickid`),
          KEY `userid` (`userid`)
        )".$charset
    );
}
?>



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

Запрос к модулю — отобразить страницу под его управлением


Так как в переменной takelink класса BostickModule было установлено значение «bostick», то все запросы на сайте по адресу http://[youdomain.tld]/bostick/* будут обращены к модулю «Стикер»

<?php 
...
class BostickModule extends CMSModule {
    ...
    public function __construct(){
        ...
        $this->takelink = "bostick";
        ...
    }
    ...
}
...
?>


Работает это следующим образом:
Платформа, обрабатывая запрос в адресе которого есть раздел (http://[youdomain.tld]/bostick/ — в этом запросе раздел bostick), ищет в списке модулей тот модуль, в свойстве takelink которого было указано имя этого раздела. Если такой модуль не найден, то платформа выдает ошибку 404. Если же такой модуль найден, то далее управление по сборке страницы берет на себя этот модуль (примечание: такой модуль мы называем — управляющий модуль).
Из какого шаблона произвести сборку страницы — определяется методом GetContentName основного класса модуля.

<?php 
...
class BostickModule extends CMSModule {
    ...
    public function GetContentName(){
        if (CMSRegistry::$instance->user->info['userid'] == 0){
            // для гостей собираем страницу из шаблона index_guest.html
            return "index_guest"; // /modules/bostick/content/index_guest.html
        }
        // для зарегистрированных пользователекй собираем страницу из index.html 
        return "index"; // /modules/bostick/content/index.html
    }
    ...
}
...
?>


Стартовые шаблоны находятся в папке модуля content. В нашем случае в этой папке есть два файла: index_guest.html и index.html.
Как видно из примера, для гостей метод GetContentName возвращает имя шаблона index_guest, для зарегистрированных пользователей имя шаблона index.

Стартовый шаблон index_guest.html (/modules/bostick/content/index_guest.html) модуля «Стикер»

<!--[*]
[tt=module]_sys[/tt]
[mod=user]userblock[/mod]
[css]/tt/default/css/main.css[/css]
[*]-->
<style>
<!--
.clear {clear: both;} 
.mod-bostick .welcome {	width: 100%;} 
.mod-bostick .welcome .blockcenter {margin: 150px auto; width: 320px;}
.mod-bostick .welcome .info {margin: 10px; font-size: 110%; }
.mod-bostick .welcome .info img { float: right; }
.mod-bostick .welcome #bostickLoading { font-size: 1.2em; }
-->
</style>
<div class="mod-bostick"><div class="welcome"><div class="blockcenter">
    <div class="info">
        <h2>Онлайн стикеры</h2>
        <p>
            <img src="/modules/bostick/images/app_icon.gif" />
            Чтобы использовать стикеры, Вам необходимо авторизоваться
            <br class="clear" />
        </p>
    </div>
    <div id="bostickLoading">
        <div>Пожалуйста, подождите, идет загрузка...</div>
        <div>Работает на платформе 
            <a href="http://abricos.org" target="_blank" 
            title="Платформа интернет приложений, WebOS">Абрикос</a></div>
    </div>
    <div class="big" id="loginblock1" style="display: none;">
        
    </div>
</div></div></div>
<script>
    Brick.ff('user', 'api', function(){
        document.getElementById('bostickLoading').style.display = 'none';
        document.getElementById('loginblock1').style.display = '';
    });
</script>


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

Шаблон в платформе Абрикос состоит из двух частей: параметры шаблона и его тело.

<!--[*]
Параметры шаблона
[*]-->
Тело шаблона



В нашем случае в шаблоне index_guest.html есть следующие параметры:
[tt=module]_sys[/tt] — обязательный параметр, указывает на то, каким шаблоном «оберткой» обернуть этот стартовый шаблон, в этом случае будет обернут системным шаблоном module (/tt/_sys/module.html);

Примечание: шаблоны «обертка» — это шаблоны отображения сайта. Все шаблоны размещаются в папке tt платформы. По умолчанию в этой папке есть только два шаблона sys (набор системных шаблонов) и default (шаблоны по умолчанию).

[css]/tt/default/css/main.css[/css] — подключить к старице стиль из файла /tt/default/css/main.css;

[mod=user]userblock[/mod] — вызвать компонент userblock из модуля user (обычно эти компоненты мы называем кирпичами), скомпилировать и результат поместить в тело шаблона по адресу :

...
    <div class="big" id="loginblock1" style="display: none;">
        
    </div>
...



В итоге, когда незарегистрированный пользователь запросит страницу по адресу http://[youdomain.tld]/bostick/, сервер ему выдаст:


Обратите внимание, в шаблоне есть вот такой вот интересный JavaScript код:

...
<script>
    Brick.ff('user', 'api', function(){
        document.getElementById('bostickLoading').style.display = 'none';
        document.getElementById('loginblock1').style.display = '';
    });
</script>
...



В двух словах он означает следующее: когда будет загружена страница, запросить загрузку JS компонента api из модуля user, по окончанию его загрузки скрыть HTML элемент bostickLoading и отобразить HTML элемент loginblock1.

Подробнее о JS компонентах этого модуля я расскажу ниже, а более подробно о самих JS компонентах и как они работают в платформе Абрикос в другой статье. Это обширная и очень интересная тема, которая заслуживает отдельного внимания.

Для зарегистрированных пользователей будет собрана страница из стартового шаблона index.html (/modules/bostick/content/index.html) модуля «Стикер»

<!--[*]
[tt=module]_sys[/tt]
[mod=user]userblock[/mod]
[css]/tt/default/css/main.css[/css]
[script]index.php[/script]
[*]-->
<style>
<!--
.clear { clear: both; }
.mod-bostick .workspace .manager { position: relative; margin: 10px 20px; }
.mod-bostick .workspace .manager h2 { float: left; }
.mod-bostick .workspace .manager #menu { margin-left: 200px; }
.mod-bostick .workspace .manager #menu a { margin-right: 5px; }
.mod-bostick .workspace .buser { position: absolute; top: 1px; right: 10px; }
.mod-bostick .workspace .buser span { font-size: 1.2em; vertical-align: top; }
.mod-bostick .blockcenter { margin: 150px auto; width: 320px; font-size: 1.2em; }
.mod-bostick .aw-bostick-icon .aligner span { display: block; }
-->
</style>
<div class="mod-bostick">
    <div class="workspace">
        <div class="manager">
            <h2>Онлайн стикеры</h2>
            <div id="menu">
                <a href="/" title="Перейти на главную страницу">Главная</a>
                <a href="/bos/" id="btnBosUI" 
                title="Перейти в расширенную версию" 
                style="display: none">Расширенная версия</a>
            </div>
            <div class="clear"></div>
            <div id="stickButton"></div>
            <div class="buser">
                <span>{v#username}</span>
                (<a href="#" onclick="Brick.mod.user.API.userLogout(); return false;" 
                title="Выйти" >выйти</a>)
            </div>
        </div>
    </div>
    <div class="blockcenter">
        <div id="bostickLoading">
            <div>Пожалуйста, подождите, идет загрузка...</div>
            <div>Работает на платформе 
            <a href="http://abricos.org" target="_blank">Абрикос</a></div>
        </div>
    </div>
</div>
<script>
    Brick.ff('user', 'api');
    Brick.ff('bostick', 'board', function(){
        var Dom = YAHOO.util.Dom,
            NS = Brick.mod.bostick;
        if (Brick.componentExists('bos', 'os')){
            Dom.get('btnBosUI').style.display = '';
        }
        NS.API.showIconWidget(Dom.get('stickButton'), function(manager){
            Dom.get('bostickLoading').style.display = 'none';
        });
    });
</script>



Обратите внимание на то, что в параметрах шаблона появился новый параметр [script]index.php[/script]. Этот параметр указывает на то, что необходимо вызвать скрипт модуля «Стикер» index.php (/modules/bostick/includes/index.php) в процессе сборки страницы.
Вот его код:

<?php
// кирпич - шаблон, который вызвал этот скрипт
$brick = Brick::$builder->brick;

// информация о текущем пользователе
$user = CMSRegistry::$instance->user->info;

$unm = $user['username'];
$lnm = $user['lastname'];
$fnm = $user['firstname'];

$username = empty($lnm) && empty($fnm) ? $unm : $fnm." ".$lnm;

// в теле шаблона есть переменные {v#userid} и {v#username}, их 
// нужно заменить на соответствующие значения
$brick->content = Brick::ReplaceVarByData($brick->content, array(
    "userid" => $user['userid'],
    "username" => $username
));
?>



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

В данном случае это делается выполнением JavaSript кода на странице:

<script>
...
    // вызвать JS компонент board модуля bostick
    Brick.ff('bostick', 'board', function(){
        // по окончанию загрузки компонента board выполнить эту функцию
        var Dom = YAHOO.util.Dom,
            NS = Brick.mod.bostick;
        if (Brick.componentExists('bos', 'os')){
            // если в платформе не установлен модуль bos, то скрыть 
            // HTML элемент btnBosUI
            Dom.get('btnBosUI').style.display = '';
        }
        // отобразить кнопку добавления стикера. В процессе вызова этой кнопки
        // происходит процесс инициализации приложения с загрузкой всех необходимых
        // данных. 
        NS.API.showIconWidget(Dom.get('stickButton'), function(manager){
            // приложение инициализировано, теперь необходимо скрыть HTML элемент bostickLoading
            Dom.get('bostickLoading').style.display = 'none';
        });
    });
...
</script>



JS компоненты в платформе Абрикос


JS компоненты — это идеология платформы Абрикос. Когда мы только проектировали ее, уже точно знали к чему она должна будет прийти. Как я и сказал выше, это обширная и очень интересная тема, которая достойна отдельного внимания.
В разрезе этой статьи я вкратце покажу, как работает система JS компонентов и как эта система применяется на практике в конкретном случае, а именно в модуле «Стикер».

Итак, JS компоненты находятся в папке модуля js. Каждый JS компонент состоит из четырех частей: исполняющий скрипт, шаблон, стиль и набор языковых фраз.

Когда в платформе происходит вызов JS компонента, ядро собирает (компилирует) все эти четыре файла в один js файл, при необходимости его пакует в zip и отдает клиенту. Причем скомпилированная версия сохраняется в кеше на сервере, а так же в кеше браузера клиента.

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

В модуле «Стикер» основной JS компонент — это board, который состоит из трех файлов board.js, board.htm и board.css.
Листинг файла board.js (/modules/bostick/js/board.js) полностью приводить не буду, а покажу лишь только ключевые моменты в нем:

// объявить компонент
var Component = new Brick.Component();

// установить зависимости и подгрузить их перед его инициализацией
Component.requires = {
    mod:[
        // из модуля bostick загрузить компонент roles
        {name: 'bostick', files: ['roles.js']}
    ]
};
// объявить точку входа (функция выполняется после того, как все зависимые компоненты подгружены)
Component.entryPoint = function(){
    var Dom = YAHOO.util.Dom,
        E = YAHOO.util.Event,
        L = YAHOO.lang;
    
    var NS = this.namespace, 
        TMG = this.template,
        API = NS.API,
        R = NS.roles;

    ...
    // функция сборки шаблона, при первом вызове применят поставляемые стили 
    var initCSS = false,
        buildTemplate = function(w, ts){
        if (!initCSS){
            Brick.util.CSS.update(Brick.util.CSS['bostick']['board']);
            delete Brick.util.CSS['bostick']['board'];
            initCSS = true;
        }
        w._TM = TMG.build(ts); w._T = w._TM.data; w._TId = w._TM.idManager;
    };
    
    // Элемент интерфейса - Стикер
    var StickWidget = function(data){
        ...
    };
    StickWidget.prototype = {
        ...
    };
    NS.StickWidget = StickWidget;
    
    // Элемент интерфейса - Кнопка
    var IconWidget = function(container){
        ...
    };
    IconWidget.prototype = {
        ...
    };
    NS.IconWidget = IconWidget;
    
    // Менеджер управления стикерами
    var StickManager = function(callback){
        ...
    };
    StickManager.prototype = {
        ...
    };
    NS.StickManager = StickManager;
    NS.stickManager = null;

    // Функция инициализации менеджера
    API.initManager = function (callback){
        ...
    };
    
    // Функция добавления стикера
    API.addStick = function(callback){
        callback = L.isFunction(callback) ? callback : function(){};
        API.initManager(function(){
            var stick = NS.stickManager.createStick();
            callback(stick);
        });
    };
    
    // Функция инициализации элемента интерфейса кнопки "Добавить стикер"
    API.showIconWidget = function(container, callback){
        API.initManager(function(){
            var widget = new IconWidget(container);
            if (L.isFunction(callback)){
                callback(widget);
            }
        });
    };
};



Шаблон board.htm (/modules/bostick/js/board.htm) JS компонента board модуля «Стикер»:

<!--{widget}--><div class="aw-bostick-w" id="{i#id}" style="display: none">
    <div id="{i#bd}" class="bd mv">
        <div id="{i#bremove}" class="bremove" title="Удалить стикер"></div>
        <div id="{i#view}" class="view"></div>
        <textarea id="{i#input}" style="display: none"></textarea>
    </div>
</div>

<!--{icon}--><a id="{i#id}" class="aw-bostick-icon label" href="#">
    <span class="aligner">
        <img src="/modules/bostick/images/app_icon.gif" title="Добавить стикер"/>
        <span title="Добавить стикер">+стикер</span>
    </span>
</a>



Стиль board.css (/modules/bostick/js/board.css) JS компонента board модуля «Стикер»:

.aw-bostick-w {
    position: absolute;
    left: 300px;
    top: 300px;
    background-color: #fefeb4;
    cursor: move;
}
...
.aw-bostick-w .bd textarea {
    border: none;
}



Когда происходит запрос JS компонента board модуля «Стикер», платформа определяет его зависимости, подгружает их (при необходимости подгружает зависимости зависимых компонентов) и выполняет функцию Component.entryPoint(). Так как клиенту поставляется скомпилированный JS компонент, то он уже содержит в себе шаблон и стиль. В функции initTemplate как раз и происходит единожды применение стилей для этого компонента, а так же генерируется шаблон для построения интерфейса.

При необходимости к JS компоненту подключаются языковые фразы из папки модуля js/langs.
На данный момент фразы только на русском языке и не полностью, так как пока платформа и ее модули работают только на русском. Эта технология заложена на будущее и после реализации менеджера по обслуживанию языковых фраз, активно начнется перевод платформы на другие языки.

AJAX запрос к модулю


Обмен данными виджетов с сервером происходит посредством AJAX. Обмен данными осуществляется через формат JSON.

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

Пример управляющего менеджера модуля «Стикер», файл manager.php (/modules/bostick/includes/manager.php)

<?php
require_once 'dbquery.php';

/**
 * Управляющий класс модуля
 */
class BostickManager extends ModuleManager {
    
    /**
     * @var BostickModule
     */
    public $module = null;
    
    /**
     * User
     * @var User
     */
    public $user = null;
    public $userid = 0;
    
    /**
     * @var BostickManager
     */
    public static $instance = null; 
    
    /**
     * Конструктор
     * @param BostickModule $module
     */
    public function BostickManager(BostickModule $module){
        parent::ModuleManager($module);
        
        $this->user = CMSRegistry::$instance->modules->GetModule('user');
        $this->userid = $this->user->info['userid'];
        BostickManager::$instance = $this;
    }
    
    /**
     * Проверка роли доступа текущего пользователя на доступ к записи стикеров
     */
    public function IsWriteRole(){
        return $this->module->permission->CheckAction(BostickAction::WRITE) > 0;
    }
    
    /**
     * Обработчик AJAX запросов
     * @param object $d данные запроса
     */
    public function AJAX($d){
        switch($d->do){
            case 'init': return $this->InitData();
            case 'sticksave': return $this->StickSave($d->stick);
            case 'stickremove': return $this->StickRemove($d->stickid);
            case 'stickordupd': return $this->StickOrderUpdate($d->order);
        }
        return null;
    }

    /**
     * Проверить доступ текущего пользователя к стикер
     * @param integer $stickid идентификатор стикера
     */
    public function StickAccess($stickid){
        if (!$this->IsWriteRole()){ return false; }
        $row = BostickQuery::Stick($this->db, $this->userid, $stickid, true);
        return !empty($row);
    }
    
    /**
     * Вернуть данные для инициализации виджета
     * @return object
     */
    public function InitData(){
        if (!$this->IsWriteRole()){
            return null;
        }
        $ret = new stdClass();
    
        $ret->sticks = array();
        $rows = BostickQuery::StickList($this->db, $this->userid);
        while (($row = $this->db->fetch_array($rows))){
            array_push($ret->sticks, $row);
        }
        
        $ord = $this->StickOrderConfigRow();
        $ret->orders = is_null($ord) ? '' : $ord['vl'];
        
        return $ret;
    }
    
    /**
     * Сохранить стикера
     * @param object $sk данные стикера отправленые виджетом
     */
    public function StickSave($sk){
        if (!$this->IsWriteRole()){ return null; }
        
        if ($sk->id == 0){
            $sk->id = BostickQuery::StickAppend($this->db, $this->userid, $sk);
        }else{
            if (!$this->StickAccess($sk->id)){
                return null;
            }
            BostickQuery::StickUpdate($this->db, $this->userid, $sk->id, $sk);
        }
        return BostickQuery::Stick($this->db,  $this->userid, $sk->id, true);
    }
    
    /**
     * Удалить стикера
     * @param integer $stickid идентификатор стикера
     */
    public function StickRemove($stickid){
        if (!$this->IsWriteRole()){ return null; }
        BostickQuery::StickRemove($this->db, $this->userid, $stickid);
    }
}
?>



Пример метода GetManager основного класса модуля «Стикер», фрагмет из файла module.php (/modules/bostick/module.php

class BostickModule extends CMSModule {
    ...
    /**
     * Управляющий менеджер модуля. Все AJAX запросы этого модуля
     * будут переданы в этот класс. 
     * @return BostickManager
     */
    public function GetManager(){
        if (is_null($this->_manager)){
            require_once 'includes/manager.php';
            $this->_manager = new BostickManager($this);
        }
        return $this->_manager;
    }
    ...
}



Пример отправки виджетом AJAX запроса на удаление стикера, фрагмент взят из файл board.js (/modules/bostick/js/board.js)

    Brick.ajax('bostick', {
        'data': {
            'do': 'stickremove',
            'stickid': stick.id
        }
    });


AJAX запрос будет отправлен модулю bostick. В теле данных запроса будет объект в свойствах которого do=«stickremove», stickid=[идентификатор стикера].

Платформа, приняв такой запрос, запросит метод AJAX управляющего класса модуля для получения ответа.

Пример обработки AJAX запроса модуля «Стикер», фрагмет взят из файла manager.php (/modules/bostick/includes/manager.php)

<?php
class BostickManager extends ModuleManager {
    ...
    /**
     * Обработчик AJAX запросов
     * @param object $d данные запроса
     */
    public function AJAX($d){
        switch($d->do){
            case 'init': return $this->InitData();
            case 'sticksave': return $this->StickSave($d->stick);
            case 'stickremove': return $this->StickRemove($d->stickid);
            case 'stickordupd': return $this->StickOrderUpdate($d->order);
        }
        return null;
    }
    ...
    /**
     * Удалить стикера
     * @param integer $stickid идентификатор стикера
     */
    public function StickRemove($stickid){
        if (!$this->IsWriteRole()){ return null; }
        BostickQuery::StickRemove($this->db, $this->userid, $stickid);
    }
}
?>



В данном случае в теле данных AJAX запроса будет передан объект в свойствах которого do=«stickremove» и stickid=[идентификатор стикера]. Метод AJAX приняв этот запрос, по свойству объекта do определит команду на удаление стикера и вызовет метод StickRemove, который в свою очередь проверит все необходимые роли и в случае доступа к этим действия, вызовет запрос к БД на удаление стикера.

Пример описания запросов к базе данных модуля «Стикер», файл dbquery.php (/modules/bostick/includes/dbquery.php)

<?php
/**
 * Все запросы к базе данных модуля Стикер
 */
class BostickQuery {
    
    /**
     * Получить данные стикера из базы
     * @param CMSDatabase $db
     * @param integer $userid идентификатор пользователя
     * @param integer $stickid идентификатор стикера
     * @param boolean $retarray вернуть массив если true, иначе указатель на результат запроса
     * @return mixed возвращает значение исходя из параметра $retarray
     */
    public static function Stick(CMSDatabase $db, $userid, $stickid, $retarray = false){
        $sql = "
            SELECT
                stickid as id,
                body as bd,
                region as rg
            FROM ".$db->prefix."bostk_stick 
            WHERE userid=".bkint($userid)." AND stickid=".bkint($stickid)."
        ";
        return ($retarray ? $db->query_first($sql) : $db->query_read($sql));
    }

    /**
     * Получить список стикеров из базы
     * @param CMSDatabase $db
     * @param integer $userid идентификатор пользователя
     * @return integer возвращает указать на результат запроса
     */
    public static function StickList(CMSDatabase $db, $userid){
        $sql = "
            SELECT
                stickid as id,
                body as bd,
                region as rg
            FROM ".$db->prefix."bostk_stick 
            WHERE userid=".bkint($userid)." AND deldate=0
            ORDER BY dateline
        ";
        return $db->query_read($sql);
    }
    
    /**
     * Добавить стикер в базу
     * @param CMSDatabase $db
     * @param integer $userid идентификатор пользователя
     * @param object $sk данные стикера
     */
    public static function StickAppend(CMSDatabase $db, $userid, $sk){
        $sql = "
            INSERT INTO ".$db->prefix."bostk_stick 
            (userid, body, color, region, dateline) VALUES (
                ".bkint($userid).",
                '".bkstr($sk->bd)."',
                'fefeb4',
                '".bkstr($sk->rg)."',
                ".TIMENOW."
            )
        ";
        $db->query_write($sql);
        return $db->insert_id();
    }

    /**
     * Обновить стикер а базе
     * @param CMSDatabase $db
     * @param integer $userid идентификатор пользователя
     * @param integer $stickid идентификатор стикера
     * @param object $sk данные стикера
     */
    public static function StickUpdate(CMSDatabase $db, $userid, $stickid, $sk){
        $sql = "
            UPDATE ".$db->prefix."bostk_stick
            SET 
                body='".bkstr($sk->bd)."',
                region='".bkstr($sk->rg)."' 
            WHERE userid=".bkint($userid)." AND stickid=".bkint($stickid)."
        ";
        $db->query_write($sql);
    }
    
    /**
     * Удалить стикер из базы
     * @param CMSDatabase $db
     * @param integer $userid идентификатор пользователя
     * @param integer $stickid идентификатор стикера
     */
    public static function StickRemove(CMSDatabase $db, $userid, $stickid){
        $sql = "
            UPDATE ".$db->prefix."bostk_stick
            SET 
                deldate=".TIMENOW." 
            WHERE userid=".bkint($userid)." AND stickid=".bkint($stickid)."
        ";
        $db->query_write($sql);
    }
}
?>



В модуле платформы Абрикос все запросы к базе данных описываются в статичных методах отдельного класса, который как правило располагается в файле includes/dbquery.php.
Это не является обязательным, но рекомендуется для разработчиков, так как на практике очень удобно видеть все запросы к базе в одном месте, а не разбросанных по коду где попало.

В заключении


Итак, в этой статье я показал каким образом работает модуль в платформе Абрикос на примере модуля «Стикер».

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

Конечно же, платформа Абрикос не идеальна в своем исполнении, и в ней присутствуют моменты, которые еще очень много и долго нужно дорабатывать и дорабатывать. Но, вектор изначально был задан в правильном направлении и уже сейчас этому есть подтверждение.

Эта первая статья из цикла запланированных статей: «Шаблоны в платформе Абрикос», «JS компоненты в платформе Абрикос» и прочих, темы которых я думаю будут выявлены в комментария.

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

Живой пример доски стикеров: http://demo.abricos.org/bostick/ (логин: demo, пароль: demo)
Скачать модуль «Стикер»: abricos-mod-bostick-r1134.zip
Просмотреть исходный код модуля в браузере: http://trac.abricos.org/svn/modules/bostick/

0
05 Октября 2011, 15:01