# Упражнение 1. Вкладки, Формы, Списки, Инструменты

В этом упражнении вы будете конструировать простой веб-интерфейс с вкладками, интегрированный в Plesk. Первая вкладка будет содержать форму, вторая – список пунктов, а третья – панель инструментов с двумя действиями. Это упражнение демонстрирует основы построения интерфейса Plesk.

Мы предполагаем, что идентификатор расширения – `example`, и вы начинаете разработку с кода из папки `ex1`. Этот код уже частично написан нами, чтобы помочь вам сосредоточиться только на определенных областях. Кроме того, мы добавили в папку медиа-контент, необходимый для выполнения упражнения. Чтобы посмотреть завершенное упражнение, ознакомьтесь с содержимым папки `ex1complete`.

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

## Шаг 1. Получите порядок выполнения из Plesk.

У каждого расширения есть единственный файл стартовой страницы, который выполняется в первую очередь, когда пользователь переходит к расширению из Plesk (например, когда администратор переходит в раздел Управление сервером > Расширения и нажимает имя расширения). Этот файл стартовой страницы называется `index.php`, и путь к нему – `/htdocs/index.php` относительно корневой папки расширения. Начиная с этой точки отсчета, вы можете либо писать свой собственный код, который реализует графический интерфейс определенным способом, либо использовать средства быстрой разработки расширений, которые предоставляет SDK.

Одним из этих средств является поддержка шаблона MVC. Если вы не знакомы с этим шаблоном, смотрите http://framework.zend.com/manual/1.11/en/learning.quickstart.intro.html. Применительно к нашему примеру, первое, что нужно сделать – выбрать подходящий класс для дальнейшей обработки запроса к `index.php`. Отредактируйте файл, чтобы он выглядел следующим образом (https://github.com/plesk/pm-exercises/blob/master/ex1/htdocs/index.php):

    <?php

    pm_Context::init('example');

    $application = new pm_Application();
    $application->run();

Эти две строки дают указание системе загрузить помощники для отрисовки элементов интерфейса, загрузить контроллеры и передать управление контроллеру, используемому по умолчанию для обработки запросов (это `/plib/controllers/IndexController.php` в корневой папке расширения).

Если мы откроем файл `IndexController.php`, мы увидим, что он расширяет pm_Controller_Action. Этот класс или его расширения должны использоваться для создания контроллеров.

Кроме того, как вы можете видеть, в этом классе есть заглушки методов, например, `formAction`. Этот метод будет вызван во время обращения к следующему URL-адресу:

`http://<plesk-host-name>:<port>/modules/<extension id>/index.php/index/form`

Таким образом, отношения между запрашиваемыми URL-адресами и методами могут быть представлены так:

Префикс URL-адреса после /index.php/ | Имя контроллера | Вызываемый метод
--- | --- | ---
index/form | IndexController | formAction
index/tools | IndexController | toolsAction
custom/call | CustomController | callAction

Другими словами, система обрабатывает URL-адреса из запроса, находит пару `<controller name>/<action name>` и передает порядок выполнения соответствующему методу. Эта схема соответствия берет начало в Zend, читайте больше о ней в 1.11http://framework.zend.com/manual/en/zend.controller.action.html.

Таким образом, чтобы отрисовать первый графический интерфейс, который увидят пользователи, нам надо реализовать метод `indexAction()` из `IndexController`. Однако, поскольку мы собираемся поместить на первую вкладку форму, нам надо перенаправить запросы от `indexAction()` к `formAction()` . Чтобы обеспечить это перенаправление, измените `indexAction` так, чтобы он выглядел следующим образом:

    public function indexAction()
    {
        // Default action will be formAction
        $this->_forward('form');
    }

Мы использовали для этой цели метод `_forward` Zend. Теперь все запросы к стартовой странице расширения успешно достигают `formAction`.

Давайте проверим результат. Если вы откроете главную страницу расширения (Управление сервером > Расширения> Extension SDK Example), вы увидите пустую страницу. Выглядит не слишком впечатляюще, так что давайте перейдем к следующему шагу.

## Шаг 2. Добавьте форму.

Мы определили, что на главной странице расширения будет форма. Согласно MVC, нам надо создать экземпляр формы, используя метод `formAction()`, и визуализировать его с помощью представления `form.phtml`.

При построении формы мы будем опираться на Zend framework. Если вы незнакомы с формами Zend, читайте http://framework.zend.com/manual/1.11/en/zend.form.quickstart.html. Основная идея, на которой базируется построение форм, заключается в том, что форма должна моделироваться PHP-классом pm_Form_Simple или его расширением . Вы можете добавлять на форму поля ввода и кнопки, вызывая методы `addElement()` (Zend method) и `addControlButtons()` (метод SDK). Преимуществом этого подхода является то, что при этом осуществляется валидация формы и визуализация HTML.

Код построения формы слишком велик для того, чтобы приводить его здесь, смотрите его в `IndexController.php`, `formAction()`. Как вы можете видеть в `formAction()` , после добавления элементов формы код передает форму представлению как переменную.

    $this->view->form = $form;

Теперь вам надо задать отображение вашей формы или, другими словами, написать ее представление. Читайте больше о представлениях в http://framework.zend.com/manual/1.11/en/zend.view.introduction.html. Представления должны храниться внутри корневой папки расширения. Так, для `formAction()` вам надо открыть на редактирование `/plib/views/scripts/index/form.phtml` и добавить в `form.phtml` следующую строку :

    <?php echo $this->form; ?>

Вы можете видеть, что форма отображается и обрабатывается.

Тщательно просмотрите свою реализацию и проверьте, как именно добавляются поля, и как их значения считываются и сохраняются в `pm_Settings` в хранилище пар «ключ-значение». Также вы можете почитать больше о реализации форм.

## Шаг 3. Добавьте представление с вкладками.

Итак, у вас уже есть одностраничное расширение с формой. Давайте теперь добавим в него навигацию с помощью вкладок. Вместо добавления кода, определяющего вкладки, к каждому действию (для формы, списка или панели инструментов), вы можете определить вкладки один раз в методе `init()`, избежав таким образом дублирования кода. Метод `init()` запускается перед методами действий. Отредактируйте метод, чтобы он выглядел следующим образом:

    public function init()
     {
            parent::init();
            // Init title for all actions
            $this->view->pageTitle = 'Example Extension';
            // Init tabs for all actions
            $this->view->tabs = [
                [
                    'title' => 'Form',
                    'action' => 'form',
                ],
                [
                    'title' => 'Tools',
                    'action' => 'tools',
                ],
                [
                    'title' => 'List',
                    'action' => 'list',
                ],
            ];
        }

Как вы можете видеть, массив `$this->view->tabs` привязывает действия контроллера к вкладкам. Проще говоря, теперь, когда пользователь переходит на вкладку List, система запускает метод `listAction`.

Вы уже знакомы с тем, как изменять представление. Давайте визуализируем вкладки прежде, чем форму. Вам надо будет снова открыть на редактирование `/plib/views/scripts/index/form.phtml` и добавить следующую строку в начале файла:

    <?php echo $this->renderTabs($this->tabs); ?>

Метод `renderTabs()` должен будет визуализировать вкладки. Представление с визуализацией будет изначально встроено в интерфейс Plesk. Читайте больше о параметрах, которые вы можете передавать в `renderTabs()`, здесь.

Отлично, теперь у вас есть вкладки:

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

## Шаг 4. Добавьте список.

Аналогично созданию форм, нам надо создать экземпляр списка в `listAction()` и визуализировать его в `/plib/views/scripts/index/list.phtml`. Экземпляр списка должен моделироваться классом pm_View_List_Simple или его расширением. Смотрите, как создать список, в коде IndexController.php, `_getListRandom()`. Метод `_getListRandom()` генерирует список из двух колонок с некоторым содержимым.

Вот наиболее важные шаги создания списков:

1. Добавьте данные списка как ассоциативный массив.

2. Создайте список как экземпляр класса `pm_View_List_Simple` и укажите его представление и параметры запроса в конструкторе.

3. Добавьте данные в список, используя метод `setData`.

4. Определите колонки списка с помощью метода `setColumns`. Имена колонок в вашем массиве данных и в методе `setColumns` должны совпадать.

5. Укажите URL действия списка с помощью `setDataUrl`. Этот URL фактически является адресом ресурса, который запрашивается, когда пользователь выполняет какие-либо действия с элементами управления списком (например, меняет страницу или порядок элементов). В нашем примере таким URL является `list-data`. Когда пользователь запрашивает `http://<plesk host name>:<port>/modules/example/index.php/index/list-data`, система преобразует дефисы к стилю СamelСase и фактически вызывает метод `listDataAction()` из `IndexController`.

Ниже приведен пример кода, создающего объект «Список»:

    private function _getListRandom()
        {
            $data = [];
            $iconPath = pm_Context::getBaseUrl() . 'images/icon_16.gif';
            for ($index = 1; $index < 150; $index++) {
                $data[] = [
                    'column-1' => '<a href="#">link #' . $index . '</a>',
                    'column-2' => '<img src="/' . $iconPath . '" /> image #' . $index,
                ];
            }
            $list = new pm_View_List_Simple($this->view, $this->_request);
            $list->setData($data);
            $list->setColumns([
                'column-1' => [
                    'title' => 'Link',
                    'noEscape' => true,
                    'searchable' => true,
                ],
                'column-2' => [
                    'title' => 'Description',
                    'noEscape' => true,
                    'sortable' => false,
                ],
            ]);
            // Take into account listDataAction corresponds to the URL /list-data/
            $list->setDataUrl(['action' => 'list-data']);
            return $list;
        }

Теперь, когда у вас есть список, передайте его в представление, изменив `listAction()` следующим образом:

    public function listAction()
        {
            $list = $this->_getListRandom();
            // List object for pm_View_Helper_RenderList
            $this->view->list = $list;
        }

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

    public function listDataAction()
        {
            $list = $this->_getListRandom();
            // Json data from pm_View_List_Simple
            $this->_helper->json($list->fetchData());
        }

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

Наконец, визуализируйте вкладки и список, добавив следующие строки в `/plib/views/scripts/index/list.phtml:`

    <?php echo $this->renderTabs($this->tabs); ?>
    <?php echo $this->renderList($this->list); ?>

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

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

    $options = [
        'defaultSortField' => 'column-1',
        'defaultSortDirection' => pm_View_List_Simple::SORT_DIR_DOWN,
    ];
    $list = new pm_View_List_Simple($this->view, $this->_request, $options);

Кроме того, у вас есть возможность реализовать операции группировки. Добавьте в параметры setColumns одну строку:

    $list->setColumns([
          pm_View_List_Simple::COLUMN_SELECTION,
          'column-1' => [
              'title' => 'MyTest',
          ],
      ]);

А затем укажите, как должна выглядеть панель инструментов списка, с помощью метода setTools:

    $list->setTools([
        [
            'title' => 'Hide',
            'description' => 'Make selected rows invisible',
            'class' => 'sb-make-invisible',
            'execGroupOperation' => [
                'submitHandler' => 'function(url, ids) {
                    $A(ids).each(function(id) {
                        $("' . $list->getId() . '")
                            .select("[name=\'listCheckbox[]\'][value=\'" + id.value + "\']")
                            .first()
                            .up("tr")
                            .hide();
                    });
                }'
            ],
        ], [
            'title' => 'Remove',
            'description' => 'Remove selected rows',
            'class' => 'sb-remove-selected',
            'execGroupOperation' => $this->_helper->url('remove'),
        ],
    ]);

Как вы видите, можно напрямую обработать действие в JavaScript, как в примере с кнопкой Hide. Или передать значения (выбранные строки) какому-либо действию, с помощью которого они будут обработаны, как в примере с кнопкой Remove. Также вы можете почитать больше о том, как реализовать список.

## Шаг 5. Добавьте панель инструментов.

Давайте добавим панель инструментов с двумя кнопками на третью вкладку, изменив метод `toolsAction()` в `IndexController.php` так, чтобы он выглядел следующим образом:

    public function toolsAction()
        {
            // Tools for pm_View_Helper_RenderTools
            $this->view->tools = [
                [
                    'icon' => pm_Context::getBaseUrl() . "images/hosting-setup.png",
                    'title' => 'Example',
                    'description' => 'Example extension with UI samples',
                    'link' => pm_Context::getBaseUrl(),
                ],
                [
                    'icon' => pm_Context::getBaseUrl() . "images/databases.png",
                    'title' => 'Extensions',
                    'description' => 'Extensions installed in Plesk',
                    'link' => pm_Context::getModulesListUrl(),
                ],
            ];
        }

Этот код показывает, что для того, чтобы добавить панель инструментов, вам надо указать ее свойства – icon, title, description и link. Здесь используется контекст (pm_Context) для создания URL-адресов ссылок. Удостоверьтесь в том, что вы инициализировали контекст прежде, чем использовать его – обычно это делается в `htdocs/index.php` (в этом примере контекст инициализируется в самом начале, на шаге 1). Другой момент, который следует отметить – это то, что pm_Context::getBaseUrl() создает путь к папке `/htdocs` расширения. В нашем примере первый значок будет получен из `/htdocs/img/site-aps_32.gif`.

Когда панель инструментов будет готова, визуализируйте ее вместе с вкладками, добавив следующие строки в `/plib/views/scripts/index/tools.phtml`:

    <?php echo $this->renderTabs($this->tabs); ?>
    <?php echo $this->renderTools($this->tools); ?>

Метод renderTools визуализирует панель инструментов в HTML.

Поздравляем! Вы почти завершили Упражнение 1; единственное, что осталось сделать – это добавить описание расширения.

## Шаг 6. Опишите расширение.

В корневой папке расширения находится файл `meta.xml`. Этот файл содержит информацию о вашем расширении, которая будет видна администраторам Plesk в каталоге расширений, после того как они установят расширение в системе. Вы можете менять ее так, как вам требуется (имейте в виду, что идентификатор расширения в вашем коде и в файле meta.xml должны совпадать). Читайте больше о возможных значениях, используемых в файле `meta.xml`, здесь.

## Шаг 7. Установите и протестируйте расширение.

Поздравляем! Вы выполнили упражнение. Чтобы установить ваше расширение в Plesk:

1. Упакуйте содержимое папки `ex1` (но не саму папку) в архив ZIP, используя вашу любимую программу-архиватор.

   Примечание

   Удостоверьтесь в том, что файл `meta.xml` находится в корне распакованного архива. Это требование очень важно, и, если оно не выполнено, при попытке добавить расширение в Plesk возникнет ошибка.

2. Войдите в Plesk как администратор, перейдите в раздел Server Management > Расширения, удалите текущую версию расширения, а затем добавьте расширение.

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