PHP фреймворк Zippy


Что такое Zippy?

   Zippy – компонентный событийно- ориентированный фреймворк на PHP, предназначенный для быстрой разработки сайтов. Zippy легок для изучения, обеспечивает полное отделение дизайна от логики, а также отделение бизнес-логики от функций обеспечения жизненного цикла и состояния элементов страницы.

Главные особенности

  • Небольшой размер.
  • Полное отделение дизайна от бизнес-логики.
  • Использование пространства имен для разделения классов и модулей.
  • Компонентная, легко расширяемая структура.
  • Интуитивно понятная архитектура фреймворка и компонентов (особенно для разработчиков, имеющих опыт с RAD средами типа Delphi, ASP.NET).
  • Автоматическое сохранение состояния страницы при перезагрузке (включая деревья).
  • Прозрачная, не требующая дополнительного кодирования, поддержка Ajax.
  • Русская документация.
  • Библиотека компонентов ZCL.

   Главная идея – предоставить разработчику необходимый набор компонентов, который позволит сосредоточиться на разработке бизнес-логики сайта.
   Разработчику нет необходимости работать с запросами, роутерами, многочисленными API - он работает только с событиями и свойствами серверных компонентов. Обмен данными с браузером, рендеринг шаблона страницы, обработка запросов и т.д. обеспечивается компонентами, соответствующими клиентским элементам страницы. То есть если вы кликнули на странице по ссылке или кнопке, все что вам нужно - это назначить на серверной стороне обработчик данного события для данного компонента (компонент - это просто PHP класс) и написать в нем код бизнес-логики.
    Обмен данными с компонентами производится через свойства этих компонентов - если, например, вам нужно вывести выпадающий список, вы просто передаете компоненту ассоциативный массив.

   Компонентная структура Zippy позволяет легко разделять работу между разработчиками, расширять и создавать новые компоненты и модули. Наиболее эффективное применение фреймворка – интерактивные сайты, предполагающие взаимодействие с пользователями аналогично десктопным приложениям (так называемый rich-интерфейс).

   Основные архитектурные решения по разделению логики и представления заимствованы с фреймворка Wicket (в некотором смысле можно рассматривать как портинг на PHP), а также (прямо или косвенно) ряд идей с таких решений как: JSF, ASP.NET, Delphi for PHP и подобных компонентно-ориентированных систем.

Установка

Установка фреймворка производится с помощью Composer.

composer require leon-mbs/zippy

В заголовке страницы указать
<link rel="stylesheet" href="/vendor/leon-mbs/zippy/assets/css/bootstrap.css">
<link rel="stylesheet" href="/vendor/leon-mbs/zippy/assets/css/zippy-bundle.min.css">
<script src="/vendor/leon-mbs/zippy/assets/js/jquery.js" type="text/javascript">
<script src="/vendor/leon-mbs/zippy/assets/js/bootstrap.bundle.js" type="text/javascript">
<script src="/vendor/leon-mbs/zippy/assets/js/zippy-bundle.min.js" type="text/javascript">

  Эти файлы включают необходимые фронт-энд библиотеки - JQuery, Twitter Bootstrap, Awesome font и все необходимые плагины и компоненты.

Кроме того, можно скачать фреймворк в сборе со всеми необходимыми библиотеками в виде архива.

Репозиторий фреймворка на GitHub: Ссылка

Документация по классам фреймворка

Как начать

Вместе с фреймворком в папке demo идет демо приложение с примерами. Это приложение можно использовать как стартовое (заготовку). Нужно только заменить страницы с примерами на свои. Перед началом работы разумеется необходимо выполнить Сomposer.
Можно также скачать демо-приложение в сборе с фреймворком. Также можно смотреть исходники проектов, где можно найти примеры организации работы с БД, подключение авторизации и пр.

Архитектура

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

Основным элементом сайта, построенного с использованием Zippy, является страница. Страница состоит из PHP класса - наследника от класса WebPage (бэкенд) и HTML файла (фронтэнд). Для каждого элемента (HTML тэга) страницы, связанного с бизнес-логикой, создается экземпляр соответствующего класса (Zippy-компонент) в PHP коде.

Пример:

<body>
<span zippy="msg"></span>
 <a  zippy="onmsg">Клик</a>
</body>
  
  
use \Zippy\Html\Label;
use \Zippy\Html\Link\ClickLink;

class Example1 extends \Zippy\Html\WebPage
{
    //В конструкторе  создаем  экземпляры компонентов
    public function __construct($params = null) {
            $this->add(new Label('msg'));
            $this->add(new ClickLink('onmsg', $this, 'OnClick'));
    }
    // Обработчик  клика  по  ссылке
    public function OnClick($sender) {
            //Присваиваем  текст для вывода  в  тэге  span
            $this->msg->setText("OK");
    }
}
  

Компонент обеспечивает рендеринг в HTML, сохранение состояния (в том числе для элементов ввода), а также вызов обработчиков событий, возникающих при навигации пользователя по странице. Связь между компонентом и HTML представлением обеспечивается с помощью атрибута "zippy" в соответствующих HTML тэгах. При создании экземпляра компонента в его конструктор передается значение атрибута "zippy", которое присваивается полю id, определенному в классе HtmlComponent, от которого наследуются все компоненты.
  Таким образом HTML код не включает в себя никаких скриплетов и прочих чужеродных вставок. При рендеринге компонент, манипулируя тегом, изменяет его значение и/или значение его атрибутов, отображая таким образом свои данные в выходном HTML потоке. Важное условие - иерархия компонентов страницы строго соответствует вложенности соответствующих (с атрибутом "zippy") HTML тэгов. Если, например, ссылка или другой элемент находится в форме и для формы как и для элемента необходимо создать соответствующий серверный компонент, то компонент ссылки должен быть добавлен к форме вызовом метода Add предварительно созданного объекта формы.


<body>
<form zippy="form1">
 <input type="text" zippy="message" />
 </form>
</body>
  
        public function __construct($params = null) {
            $this->add(new Form('form1'));
            $this->form1->add(new TextInput('message');
    }

  Кроме компонентов сохранность состояния будет обеспечена любым членам страницы до тех пор, пока не будет выполнена переадресация на другую страницу. Создание экземпляра страницы производится только при первом обращении. Это аналогично формам в десктопных приложениях. Мы создаем экземпляр класса формы и, пока форма не будет закрыта, все данные формы сохраняются.

use \Zippy\Html\Label;
use \Zippy\Html\Link\ClickLink;

class Example1 extends \Zippy\Html\WebPage
{
public $counter=0;  
    // Обработчик  клика  по  ссылке
    public function OnClick($sender) {
            //Присваиваем  текст для вывода  в  тэге  span
             
            $this->counter++;
            $this->msg->setText("Счетчик ". $this->counter);
    }
}
Таким образом может быть сохранена между обновлениями страницы любая сложная структура. Естественно, если нужно очистить данные в компоненте, нужно делать это явно аналогично десктопным приложениям.
$this->msg->setText("");

  Связь между классом страницы и соответствующим HTML файлом шаблона задается разработчиком сайта, что позволяет избежать жесткой структуры каталогов, а также легко реализовать сменяемый дизайн и/или локализацию. Жизненный цикл страницы и навигация между страницами обеспечивается классом WebApplication в зависимости от анализа запросов браузера к серверу.

Жизненный цикл страницы

При первом открытии страницы приложение создает экземпляр класса страницы. В конструкторе страницы создаются экземпляры всех компонентов страницы, выполняется биндинг и назначение обработчиков событий. Затем экземпляр класса страницы сериализуется со всем содержимым и записывается в сессионное хранилище. После этого приложение загружает HTML шаблон с места на диске, указанного разработчиком в функции getTemplate, парсит его и вызывает метод рендеринга класса страницы. Класс страницы рендерит все дочерние компоненты. Компонент изменяет соответствующий ему (связанный через атрибут zippy) HTML тэг, корректируя его содержание и/или атрибуты. После рендеринга страницы приложение отправляет измененный HTML код браузеру.

При запросе со страницы (например, клик по ссылке) приложение десериализует экземпляр класса страницы из сессии и находит компонент - инициатор запроса (ссылка, кнопка и т.д.). Компонент активизирует связанную с ней пользовательскую функцию – обработчик события, которым обычно является метод класса страницы, поскольку это позволяет иметь доступ ко всем компонентам страницы и таким образом реализовать всю бизнес-логику, связанную с поведением страницы, обменом данных и т.д. Затем экземпляр страницы вместе с экземплярами всех компонентов (хранящих текущее состояние данных) сохраняется в сессионном хранилище, выполняется рендеринг и отправка ответа в браузер. Сессионное хранилище сохраняет историю изменения страниц, что позволяет отдавать страницу с соответствующим "историческим" состоянием при навигации браузера кнопками "вперед"/"назад".

diagram

Основные компоненты

WebApplication

Класс приложения. Выполняет разбор HTTP запроса, управляет жизненным циклом страницы и формирует ответ для клиента. Для использования создать экземпляр класса наследнинка от WebApplication и переопределить минимум одну функцию загрузки шаблонов страниц getTemplate. Входным параметром является имя класса страницы, выходным - содержание шаблона. Пример реализации можно посмотреть в демо приложеиии. Управляя загрузкой шаблона можно легко реализовать сменяемые темы сайта.
В сложных проектах функцию обработки (или несколько функций, если модульное приложение) можно установить методом setTemplate и может задаватся строковым именем или лямбдой. В этом случае можно не создавать свой экзеспляр приложения, а использовать непосредственно WebApplication.

При создании экземпляра WebApplication или собственного класса-наследника в конструктор необходимо передать имя класса начальной страницы сайта. Пример кода из index.php:

  

class Application extends \Zippy\WebApplication
{

public function getTemplate($name)
{
    //загрузка  шаблонов  для  страниц
 
    $name = str_replace("Pages\\", "", ltrim($name, '\\'));

    $path = __DIR__ . '/' ."templates/" .  strtolower($name) . ".html";


    $template = file_get_contents($path);
    if ($template == false) {
        new \Exception('Неверный путь к шаблону страницы: ' . $path);
    }

    return $template;
}

// если  требуется  роутинг
public function Route($uri){
     if($uri == '')  $uri = 'page1';
     
     $uria= explode("/",$uri);
     
     if($pages[$uria[0]]=='page1'){
        $this->LoadPage("\\Pages\\Page1");
     }
     else if ($pages[$uria[0]]=='page2'){    
        $this->LoadPage("\\Pages\\Page2",$uria[1]); //страница с параметром
     }
     else {
        $this->getResponse()->to404Page() ;   
     }
}


}

// создаем  экземпляр приложения с  параметром класса  главной страницы сайта
$app = new Application('Pages\Main');

$app->Run();


  

WebPage

Класс страницы. Для создания класса необходимо расширить абстрактный класс WebPage. В конструкторе создаются все компоненты страницы. Иерархия компонентов должна строго соответствовать вложенности тэгов с атрибутом zippy в шаблоне. Как правило, в классе страницы создаются функции обработчиков событий, а также реализуется логика работы страницы. Экземпляры компонентов страницы создаются один раз при первом обращении к странице. Отслеживать жизненный цикл страницы можно переопределив методы beforeRequestHandle(), afterRequestHandle() и пр.
Приинцип работы класса страницы напомнинает класс бекенда в ASP WebForms или десктопных Delphi/WinForms - экземпляр класса страницы (формы) содержит остальные элементы как контейнер, управляет их жизненным циклом. Обработчики событий элементов являются методами класса страницы.

HtmlComponent

Базовый класс для всех компонентов. Каждый компонент имеет уникальный в пределах страницы номер элемента (поле id) , соответствующий аттрибуту zippy из соответствующего тэга HTML шаблона. Каждый компонент, расширяющий базовый класс, должен реализовать метод RenderImpl(), который отвечает за рендеринг (прорисовку) компонента на странице путем изменения HTML тэга, связанного с данным компонентом. Свойство attributes позволяет управлять атрибутами HTML тэга. Содержимое тэга задается классами-потомками HtmlComponent в зависимости от его типа.

HtmlContainer

Базовый класс контейнера для компонентов. Контейнерами являются: класс страницы, класс HTML формы, панели (обычно тэг DIV) и прочие компоненты которые могут содержать в себе другие объекты типа HtmlComponent (то есть HTML тэги, которые могут содержать вложенные тэги). Сам HtmlContainer также является наследником от HtmlComponent. В классе перегружены методы __set() и __get(), поэтому к вложенным компонентам можно обращаться, используя динамическое свойство, совпадающее с ID компонента. Например:


<form zippy="form1">
<input  type="text" zippy="username">
</form>

$form = new Form();
$form = add(new TextInput('username'));
...
$form->username->getText();

Диаграмма иерархии основных компонентов

diagram

Label

LabelСлужит для вывода текстовых данных на странице. Как правило отображается с помощью тэга SPAN, но можно использовать TD, DIV и прочие, которые могут иметь текстовое содержимое внутри тега.

Panel

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

Фреймворк содержит несколько разновидностей компонентов для HTML ссылок. Все они привязываются к тэгу <a> шаблона. Наиболее используемые:

  • ClickLink - служит для вызова обработчика события в классе cтраницы без возможности сделать закладку в браузере (поскольку это не имеет смысла - ссылка не ведет на какой-либо ресурс, а просто вызывает обработчик).
      
    
        public function __construct($params = null) {
                
            $this->add(new ClickLink('onmsg'))->onClick( $this, 'OnClick');
        }
        // Обработчик   
        public function OnClick($sender) {
                 
        }
    }
            
  • BookmarkableLink - используется для внешнего перехода или ЧПУ с возможностью создать закладку. Для указания адреса страницы указывается имя класса страницы с прямыми слешами
    /?p=Pages/Page1
    Могут быть указаны один или несколько (через прямой слэш) параметров
    /?p=Pages/Page1&arg=1
  • RedirectLink - используется для редиректа на другую страницу. Имя класса страницы задается как параметр в конструкторе.
  • SubmitLink - отправляет форму на сервер, c возможностью вызвать обработчик события по отправке формы.

Компоненты формы ввода

Компоненты соответствуют тегам элементов ввода HTML формы. Для принятия данных по отправке формы компоненты реализуют метод getRequestData(), в котором считывают «свои» данные с $_POST или $_GET переменных. При рендеринге как и другие визуальные компоненты форматируют тэги в соответствии со своими значениями. Если значения не были изменены, отображаются данные, что были при вводе формы. Таким образом автоматически сохраняется состояние всех элементов формы - полей ввода, переключателей и т.д при перезагрузке страницы.
Для реакции на отправку формы, форме назначается обработчик.

  
    public function __construct($params = null) {
            
        $this->add(new Form('form1'))->onSubmit( $this, 'OnForm1');
    }
    // Обработчик   
    public function OnForm1($sender) {
             
    }
Вместо формы обработчик может быть назначен одному или нескольким компонентам типа SubmitLink или SubmitButton, которые могут отправлять форму. Компонент должен быть внутри формы.
  
         public function __construct($params = null) {
            
        $this->add(new Form('form1'))->onSubmit( $this, 'OnForm1');
        $this->form1->add(new SubmitLink('sl1'))->onClick( $this, 'OnForm1');
    }
    // Обработчик   
    public function OnForm1($sender) {
             
    }

Интерфейсы

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

  • Requestable – компонент способен обрабатывать HTTP запрос.
  • ClickListener – компонент может вызывать серверный обработчик события при клике мышкой.
  • EventReceiver – может иметь методы - обработчики события
  • SubmitDataRequest – компонент принимает данные с формы.

События

Большинство пользовательских методов страницы (в которых реализуется бизнес-логика страницы) как правило являются обработчиками событий. Например: клик по ссылке, отправка формы, навигация по странице и т.д.

Для назначения обработчика компоненту-инициатору указывается объект-получатель (обычно $this - объект класса страницы) и имя метода-обработчика в текстовом виде. Метод-обработчик содержит параметр $sender – ссылку на объект-источник (одно и тоже событие может быть назначено нескольким компонентам) и, при необходимости, дополнительные параметры.

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

Механизм событий избавляет разработчика от забот по функционированию страницы, разборе запроса и формированию ответа клиенту. Разработчик работает с методами событий аналогично приложениям типа Delphi или WinForms.

Биндинг (привязка данных)

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

Например, при биндинге компонента Label с полем страницы $msg в функциях бизнес-логики достаточно присвоить значение полю и текст будет выведен на страницу компонентом Label. Если, например, какая-либо переменная привязана к компоненту TextInput при вводе данных с поля формы, значение поля автоматически присвоится переменной. Биндинг задается ссылкой и именем привязываемого свойства.

    
<php

use  \Zippy\PropertyBinding as  Bind;

class Example2 extends \Zippy\Html\WebPage {

public $msg, $text;

public function __construct($params = null) {

    $this->add(new Label('outputtext', new Bind($this, 'msg')));
    $form = $this->add(new Form('form1'));
    $form->onSubmit($this, 'OnSubmit');
    $form->add(new TextInput('inputtext', new Bind($this, 'text')));
}

public function OnSubmit($sender) {
    $this->msg = $this->text;
}
}  
                

Вывод списочных данных

DataView

Основное назначение компонента DataView - вывод табличных данных.
Пример на основе демо приложения


<table >
     <tr><th>ФИО</th><th>Возраст</th><th> </th></tr>
     <tr zippy="list"><td><span zippy="fio"></span></td>
     <td><span zippy="age"></span></td>
     <td><a  zippy="edit">Редактировать></a> </td>
     </tr>
     </table> 
     
 

 
public function __construct()
{
   $this->add(new DataView('list',new DataSource(),$this,'listOnRow'))->Reload();

} 
public function listOnRow($row){
//получаем  объект данных, связанный со  строкой 
$item = $row->getDataItem();

//создаем  компоненты в  строке
$row->add(new Label('fio',$item->fio));
$row->add(new Label('age',$item->age));
$row->add(new ClickLink('edit'))->onClick($this,'editOnClick');
}    

//обработчик редактирования
public function editOnClick($sender){
//получаем  объект данных, связанный со  строкой 
$item = $sender->getOwner()->getDataItem();

//заполняем форму редактирования  и т.д.
}
 
Для инициализации вывода необходим источник данных, возвращающий массив обьектов классов, реализующих интерфейс DataItem или расширяющий класс Entity и обработчик, в котором будут создаваться компоненты вывода.
Строка данных - обычный контейнер, базирующийся на теге <tr>. Добавлять можно любые компоненты (кроме вложенных DataView). DataView сам размножит компоненты данных для каждой строки.
При необходимости можно добавить пагинатор Paginator или Pager .
<div zippy="pag></div> 

 $this->add(new DataView('list',new DataSource(),$this,'listOnRow'));
 //добавляем  пагинатор для вывода
 $this->add(new Paginator("pag",$this->list));
 //устанавливаем количество  строк  на  странице
 $this->list->setPageSize(25));
 
 $this->list->Reload();
 
DataView может использовать для вывода и другие блочные элементы, например, если надо вывести список товаров или галерею рисунков. Для примера выше (бекенд тот же)

<div class="row">
<div class="col-md-4" zippy="list">
     <span zippy="fio"></span>
     <span zippy="age"></span>
</div> 
</div> 
 

DataTable

Компонент DataTable предназначен для работы с тегом <table>. Компонент сам создает заголовки и пагинацию (если указано), но выводит данные только в виде строк. Компонент чаще всего используется для вывода различного рода репортов и прочих данных, не требующих бэкенд компонентов. При создании компонента нужно создать экземпляры столбцов с указанием необходимых параметров. Для кастомизации вывода предусмотрено н обработчик setCellDrawEvent рендеринга ячейки, которому передается имя столбца и идентификатор записи. Также можно установить обработчик setCellClickEvent клика по ячейке

<table zippy="report></div> 

  //выводим  таблицу  с  заголовком  и пагинатором
  $this->add(new \Zippy\Html\DataList\DataTable("report", new DataSource(), true, true));
   
  // создаем  столбцы с  указанием  сортировки
  $this->report->addColumn(new Column('fio', 'ФИО', true ));
  $this->report->addColumn(new Column('age', 'Возраст', true ));
 

Источники данных

Источник данных - промежуточный компонент, унифицирующий передачу данных от разных источников компонентам DataView и DataTable. Источник данных должен реализовать интерфейс DataSource. Методы интервейса предписывают реализацию источником условий выборки, сортировки и пагинации.
Существует несколько видов источников.
ArrayDataSource
ArrayDataSource применяется в случае использования массивов как набора данных. Если массив может изменятся в процессе работы, следует передавать массив не напрямую, а использовать биндинг (см. демо приложение).
EntityDataSource
EntityDataSource применяется для работы с Entity. Источнику передается как параметр имя класса сущности, унаследованной от Entity.
Пользовательский источник
Для сложных выборок разработчик может создать свой класс источника данных и, если нужно, связать его с компонентами страницы (например, фильтрацией).


    class DocDataSource implements \Zippy\Interfaces\DataSource
    {
        private function getWhere()
        {
            //возвращает  условие для  SQL запроса
            return $where;
        }        
        public function getItemCount()
        {
            //количество  для пагинатора
            return Document::findCnt($this->getWhere());
        } 

        public function getItems($start, $count, $sortfield = null, $asc = null)
        {
            //выбирает данные 
            $docs = Document::find($this->getWhere(), "document_date desc,document_id desc", $count, $start);
            return $docs;
        }
        
        public function getItem($id)
        {
            
        }            
    }
    
    используется  как  обычный источник
   $this->add(new DataView('list',new DocDataSource(),$this,'listOnRow'))->Reload();

    

Дополнительные возможности

Наследование страниц

Наследование страниц предназначено для решения проблемы "единообразия" страниц. Если необходимо иметь на сайте меню, логотип и прочие неизменяемые от страницы к странице части, тогда нужно разместить их в базовой странице, а изменяемый контент вынести в дочерние. На уровне классов дочерняя страница просто наследуется от базовой, наследуя все ее компоненты и обработчики, на уровне разметки - содержимое тэга BODY дочерней страницы вкладывается внутрь базовой вместо тэга <childpage/>.

Если в шаблоне дочерней страницы в <head> есть метатеги <title>,<description> и <keyword>, при рендеринге дочерней страницы эти теги будут перенесены с заменой в базовый шаблон.

Дочерние страницы имеют доступ ко всем компонентам, объявленным в родительской и может ими управлять. Например подсвечивать текущий пункт меню.
Как пример - страница Base в демо приложении, от которой наследуются остальные.

Фрагменты страниц (виджеты)

Фрагмент страницы - это самостоятельный блок, который добавляется в страницу как обычный компонент, но при этом имеет свой шаблон (загружается аналогично шаблону страницы). Используется, если на некоторых страницах необходимо иметь один и тот же блок данных. Например: форма поиска, блок вывода рекламы и т.д. Фрагмент может содержать любые другие компоненты и обработчики событий как обычная страница. В разметку страницы компонент обычно добавляется с помощью тэга DIV. Создается класс фрагмента наследованием от класса PageFragment, который, в свою очередь, наследован от HtmlContainer.

Пользовательские компоненты

Пользовательский компонент позволяет програмно сформировать произвольное содержимое для тэга (как правило DIV). Для создания пользовательского компонента (по сути пользовательского тэга) нужно создать класс-наследник от CustomComponent и перегрузить абстрактный метод getContent(). Этот метод должен вернуть HTML код, который будет записан в тэг в шаблоне страницы, предназначенный для вывода компонента.

Ajax

Поскольку фреймворк автоматически обеспечивает сохранность состояния страницы, использование AJAX в Zippy менее востребовано по сравнению с другими решениями. Тем не менее ряд компонентов могут использовать асинхронную обработку событий. Как правило использование AJAX не требует какого-то особого кодирования и настройки. Например, для AJAX обработчика onClick() по клику на ссылке для компонента ClickLink нужно только указать третий параметр как true. Для отправки формы через AJAX используется компонент AjaxSubmitLink. Обработчики события и бизнес-логика страницы выполняются аналогично обычному синхронному.

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

Пример:
          

$this->add(new ClickLink('time'))->onClick($this,'OnClick',true);
$this->add(new Label('msg'));

// ...

public function OnClick($sender) {
    $this->msg->setText('Я обновлен  через  AJAX');
    //указываем  обновляемые  компоненты
    $this->updateAjax('msg');
}


Вызов метода страницы

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

                   
public function MyMethod($p, $post) {
    return "Привет";
}
                    
Где $p - массив параметров, если они были переданы в строке запроса, $post - данные (raw) отправленые в случае POST запроса.
Пример обращения с шаблона страницы:
                   


                  callPageMethod('MyMethod',[],null,function(answerdata){
                     
                  })            
                  
                    
где
callPageMethod - встроеная javascript функция.
callPageMethod (method,params,postdata,callback)
method - имя метода
params - массив параметров
postdata - данные тела POST запроса (может быть FormData)
callback - функция, принимающая текстовый параметр - ответ сервера

Сменяемые темы

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

ЧПУ(SEF) и роутинг

Роутинг осуществляется перегрузкой метода Route() приложения (см. выше пример для WebApplication) . Функция принимает параметром URI и загружает страницу, соответствующую вызову. Если присутствуют параметры, они идут через прямой слеш и передаются конструктору страницы. Например, для /user/2 конструктор страницы будет public function __construct($id).

Кеширование

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

Вспомагательный шаблонизатор

Применение вспомагательного шаблонизатора (используется Mustache) не совсем согласуется с компонентной архитектурой фреймворка. Но, как показала практика, иногда использование компонентов только для вывода текстов и управления видимостью элементов страницы получается несколько громоздким, поскольку данные операции фактически не требуют бэкенд обьектов. В этих случаях можно использовать более традиционый шаблонизатор. Данные для шаблонизатора присваиваются массиву _tvars, члену класса WebPage.

Создание страниц в стиле SPA и Web API

Страница может не содержать компонентов, а только независимые методы, вызываемые с помощью javascript функции callPageMethod (см. выше). Это позволяет делать страницы в SPA стиле, которые не перезагружаются (до смены страницы) при запросе к серверу. Серверный код страницы в таком случае работает как Web Api. При этом сохраняется работа PHP сессий, в частности, не требуется отдельная авторизация. Как пример, страница АРМ кухни в складской системе сделаная на Vue.

Советы и решения типовых задач

Скрытие связанных элементов.

Если нужно скрыть, например, лейбл вместе с элементом ввода, можно использовать атрибут "data-label", например:

                    <label data-label="paynotes" for="paynotes" > Комментарий к оплате</label>
                   <input class="form-control" type="text" zippy="paynotes" >
           
                     
Если поле ввода paynotes будет скрыто функцией setVisible(false) , лейбл скроется вместе с ним.

Переход по якорю.

Для перехода в опредлеленное место страницы после обработки события можно использовать метод goAnkor() класса страницы. Параметром является id элемента или атрибут zippy (атрибуты id и name создаются в таком случае автоматически).

Перехват событий яваскриптом на элементах с серверным обработчиком.

Когда элемент имеет серверный обработчик, обычно на событие onclick фреймворк вешает соответствующий клиентский обработчик. Допустим нужно выполнить код на клиенте до того как сработает серверный обработчик. Например, спросить пользователя о подтверждении действия, или если есть ссылка <a zippy="deleteitem">Удалить итем </a> ,
для перехвата нужно написать функцию с предопределенным именем, начинающуюся с check_

                  <script>
        function check_deleteitem() {

            return confirm("Удалить  итем?")

        }

    </script>    
                     
Если функция вернет false, серверный обработчик вызван не будет.

Библиотека компонентов

Библиотека представляет собой набор компонентов, построенных на расширении стандартных компонентов фреймворка. Библиотека не входит в ядро фреймворка и располагается в отдельном пространстве имен ZCL.

Библиотека состоит из следующих компонентов:

Диаграмма Ганта

Компонент представляет собой реализацию серверной части для jquery.ganttview.

Капча

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

Для реализации собственного алгоритма достаточно отнаследоваться от компонента и переопределить метод OnCode для генерации кода и/или метод OnImage для генерации изображения. Описание методов на странице API.

Для вывода компонента на странице используется обычный тег <img>.

Компоненты для работы с БД

Нeсколько компонентов для работы с базами данных. Компоненты базируются на библиотеке ADODB, что позволяет писать код, защищенный от sql-инъекций и переносимый между разными типами БД.
(На данный момент основные компоненты работы с БД выделены в отдельный проект ZDB)

TreeEntity - класс-наследник Entity, предназначенный для работы с иерархически организованными сущностями. В дополнение к функционалу родительского класса имеет возможномть манипулировать дочерними элементами (поиск по дереву, удаление, перемещение веток), строить дерево для компонента Tree на основе набора данных из БД. Данные должны быть организованы в соответствии с алгоритмом материализованного пути.

EntityDataSource - универсальный источник данных, реализующий интерфейс DataSource, позволяющий связать страничные компоненты табличного и списочного вывода с БД. В параметрах класса задаются имя класса-сущности (Entity) и, при необходимости, условие для выборки. Это позволяет разработчику избежать создания специализированного источника для простых линейных выборок.

Twitter Bootstrap компоненты

Некоторые компоненты, базирующиеся на Twitter Bootstrap.