Exercise 1. Tabs, Forms, Lists, Tools

In this exercise, you will construct a simple tabbed web interface integrated into Plesk. The first tab will contain a form, the second will contain a list of items, and the third - a toolbox with two actions. The exercise demonstrates the basics of building into the Plesk GUI.

We assume that the extension ID is example, and that you start with the code inside the ex1 directory. This code is partly written by us to help you focus only on certain areas. Additionally, we have populated the directory with media content necessary for the exercise. To see the completed exercise, examine the ex1complete directory.

Step 1. Obtain the control flow from Plesk.

Each extension has a single index file that is executed first when a user navigates to the extension from Plesk (for example, when the administrator goes to Server Management > Extensions and clicks the extension name). This index file is called index.php and its path is /htdocs/index.php relatively to the extension's root directory. From this start point, you can either write your own code which renders the GUI in its own way, or use facilities for rapid extensions development that the SDK provides.

One of these facilities is support for the MVC pattern. If you are unfamiliar with the pattern, please read http://framework.zend.com/manual/1.11/en/learning.quickstart.intro.html. Applied to our example, the first thing we need to do is to select an appropriate class for further processing of the request to the index.php. Edit the file so that it looks as follows:

<?php

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

These two lines tell the system to load helpers for drawing the GUI elements, load controllers, and pass the control to the default controller for requests processing (which is in /plib/controllers/IndexController.php under the extension's root directory).

If we open IndexController.php, we will see that it extends pm_Controller_Action. It is mandatory that you use this class or its extensions for controllers.

As you can also see, this class has method stubs, for example, formAction. This method will be called when calling the following URL:

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

So the relations between requested URLs and methods can be represented this way:

URL prefix after /index.php/ Controller name Method to call

index/form

IndexController

formAction

index/tools

IndexController

toolsAction

custom/call

CustomController

callAction

In other words, the system processes request URLs, finds the <controller name>/<action name> pair and passes the control flow to the corresponding method. This mapping scheme comes from Zend, read more about it at http://framework.zend.com/manual/1.11/en/zend.controller.action.html.

Summing up, to draw the first GUI your users will see, we need to implement the indexAction method of IndexController. However, as we are going to have a form in the first tab, we need to forward requests from indexAction to formAction. To allow for the forwarding, change indexAction so that it looks as follows:

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

We used the _forward Zend method for this purpose. Now all requests to the extension's index successfully reach formAction.

Step 2. Add a tabbed view.

Currently, we are in IndexController.php and we are about to add tabs to the extension. Instead of adding the tabs definition code to each action (for the form, list, and toolbar), we can specify the tabs once in the init() method and avoid code duplication. The init() method runs before action methods. Change the method so that it looks as follows:

    public function init()
    {
        parent::init();

        // Init title for all actions
        $this->view->pageTitle = 'Example Extension';

        // Init tabs for all actions
        $this->view->tabs = array(
            array(
                'title' => 'Form',
                'action' => 'form',
            ),
            array(
                'title' => 'Tools',
                'action' => 'tools',
            ),
            array(
                'title' => 'List',
                'action' => 'list',
            ),
        );
    }

As you can see, $this->view->tabs array binds the controller actions to tabs. Loosely speaking, now when a user navigates to the List tab, the system will trigger the listAction method.

Finally, define the presentation of our default tab, or, in other words, write its view. Read more about views at http://framework.zend.com/manual/1.11/en/zend.view.introduction.html. The views must be kept the /plib/views/scripts/<controller name>/<action name>.phtml inside the extension's root directory. Since form is the default tab, we will need to edit /plib/views/scripts/index/form.phtml. Add the following line to form.phtml:

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

The renderTabs method should render the tabs. The rendered view will natively be built into the Plesk web interface.

Great, now we have the tabs, now let's add some information into them.

Step 3. Add a form.

As defined in the tabs array we created before, our form is mapped with the formAction() method, so, according to MVC, we need to create a form instance in this method and render it in the form.phtml view.

Regarding form construction, we rely on the Zend framework. If you are unfamiliar with Zend forms, read http://framework.zend.com/manual/1.11/en/zend.form.quickstart.html . The basic idea behind forms is that a form must be the pm_Form_Simple PHP class or its extension. You can add input fields and buttons to a form by calling its addElement (Zend method) and addControlButtons (the SDK method). The benefit of this approach is that form validation and HTML rendering is done for you.

The form construction code is too long to present it here, refer to IndexController.php, formAction() to view it. As you can see in formAction(), after adding form elements, the code passes the form to the view as a variable.

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

Render the form by adding the following line to /plib/views/scripts/index/form.phtml:

<?php echo $this->form; ?>
Step 4. Add a list.

Similar to forms, we need to create a list instance in listAction() and then render it in /plib/views/scripts/index/list.phtml. The list instance must be the pm_View_List_Simple class or its extension. Learn how to create a list from the code in IndexController.php, _getNumbersList(). The _getNumbersList() method generates a two-column list with some content.

Here are the most important steps of lists creation:

  1. Add the list data as an associative array.
  2. Create a list as a pm_View_List_Simple instance and specify the view and request parameters in the constructor.
  3. Add the data to the list by using the setData method.
  4. Define the list columns by the setColumns method. The column names in your data array and in setColumns must match.
  5. Specify the list action URL using setDataUrl. This URL is actually the resource which is requested when a user performs operations with list controls (for example, when a user decides to change a page or an order). In our example, this URL is list-data. When a user requests http://<plesk host name>:<port>/modules/example/index.php/index/list-data, the system transforms hyphens to camel case and actually requests the listDataAction method of IndexController.

Below is an example of the code that creates a list object:

    private function _getNumbersList()
    {
        $data = array();
        $iconPath = pm_Context::getBaseUrl() . 'images/icon_16.gif';
        for ($index = 1; $index < 150; $index++) {
            $data[] = array(
                '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(array(
            'column-1' => array(
                'title' => 'Link',
                'noEscape' => true,
                'searchable' => true,
            ),
            'column-2' => array(
                'title' => 'Description',
                'noEscape' => true,
                'sortable' => false,
            ),
        ));
        // Take into account listDataAction corresponds to the URL /list-data/
        $list->setDataUrl(array('action' => 'list-data'));

        return $list;
    }

Now when you have the list, pass it to the view by changing listAction() to look the following way:

    public function listAction()
    {
        $list = $this->_getNumbersList();

        // List object for pm_View_Helper_RenderList
        $this->view->list = $list;
    }

The next important step is to change listDataAction() to handle changes in page numbers and results order.

    public function listDataAction()
    {
        $list = $this->_getNumbersList();

        // Json data from pm_View_List_Simple
        $this->_helper->json($list->fetchData());
    }

After you add these lines, the list paging and ordering will be self-serviced by the system.

Finally, render the tabs and the list by adding the following lines to /plib/views/scripts/index/list.phtml:

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

As you can see, renderList fetches the list instance and transforms it into HTML.

In addition, it is possible to implement group operations. For example:

       $list->setColumns([
             pm_View_List_Simple::COLUMN_SELECTION,
             'column-1' => array(
                 'title' => MyTest',
             ),
         ]);
        $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'),
            ],
        ]);

 

It is also possible to specify the default column for sorting via the options on creation of a pm_View_List_Simple class object.

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

Let's add a toolbar with two buttons to the third tab. For this, change the toolsAction() method in IndexController.php to look as follows:

    public function toolsAction()
    {
        // Tools for pm_View_Helper_RenderTools
        $this->view->tools = array(
            array(
                'icon' => pm_Context::getBaseUrl() . "img/site-aps_32.gif",
                'title' => 'Example',
                'description' => 'Example extension with UI samples',
                'link' => pm_Context::getBaseUrl(),
            ),
            array(
                'icon' => pm_Context::getBaseUrl() . "img/modules_32.gif",
                'title' => 'Extensions',
                'description' => 'Extensions installed in Plesk',
                'link' => pm_Context::getModulesListUrl(),
            ),
        );
    }

The code demonstrates that to add a toolbar item, you should specify its properties - icon, title, description, and link. Here we use the context to generate link URLs. Be sure to initialize the context before using it. We did this in the very beginning, at step 1. Another thing to note is that pm_Context::getBaseUrl() generates the path to the /htdocs directory of the extension. In our example, the first icon will be retrieved from /htdocs/img/site-aps_32.gif

Once the toolbar is ready, render it together with tabs by adding the following lines to /plib/views/scripts/index/tools.phtml:

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

The renderTools method will render the toolbar to HTML.

Congratulations! You are almost done with Exercise 1, the only thing left is to add the extension description.

Step 6. Describe the extension.

You have the meta.xml file in the extension's root directory. This file contains information about your extension which is visible to Plesk administrators after they install the extension to the system. Feel free to change it according to your needs (keep in mind that the extension ID in your code and description must match).

Step 7. Install and test the extension.

Congratulations! You have completed the exercise. To install your extension to Plesk:

  1. Add the contents of the ex1 directory (not the directory itself) to a ZIP archive using your favorite software.

    Important: Ensure that meta.xml resides in the root of the unpacked archive. This is a very important requirement, otherwise Plesk will return an error on adding your extension to Plesk.

  2. Log in to Plesk as the administrator and add the extension on the Server Management > Extensions page.

You should see your extension in the list. Click its name to see the tabs we've created in Exercise 1. You can use the completed extension available here for reference.