Using grids module & plugin
Goal
The purpose of this example is to show how to build data grids in OPNsense, using the various components within our framework.
If you haven’t read the HelloWorld example yet, we advise you to start there. This example assumes you already know the basics.
Our topic of choice for this module is a basic list for email addresses, for which you should be able to add, remove and change items.
Model
Our example starts with a model, which is constructed by creating a php class deriving from BaseModel
and an XML
file containing the actual model definition.
1<?php
2namespace OPNsense\GridExample;
3
4use OPNsense\Base\BaseModel;
5
6class GridExample extends BaseModel
7{
8}
1<model>
2 <mount>//OPNsense/GridExample</mount>
3 <description>
4 the OPNsense "GridExample" application
5 </description>
6 <items>
7 <addresses>
8 <address type="ArrayField">
9 <enabled type="BooleanField">
10 <default>1</default>
11 <Required>Y</Required>
12 </enabled>
13 <email type="EmailField">
14 <Required>Y</Required>
15 </email>
16 </address>
17 </addresses>
18 </items>
19</model>
Note the ArrayField
type in the XML, this is a special field type for nested items in automatically includes an internal uuid for easy referencing when written to disk.
Both other field types are also used in the HelloWorld example earlier. All
the preinstalled types can be found in our field type directory on GitHub.
API controller
The ApiMutableModelControllerBase
class supports most model manipulations, all *Base
methods embody
shared functionality to operate on either new or existing model items.
Our example below uses the base methods to link all operations we need and link them on endpoints ending at Item
:
searchItemAction, queries the items in your configuration
getItemAction, fetches an existing record (or returns a blank one with all defaults)
addItemAction, add a new record
setItemAction, update a record
delItemAction, delete a record
toggleItemAction, toggle [0|1] the “enabled” property (see the enabled
BooleanField
in the model)
1namespace OPNsense\GridExample\Api;
2
3use \OPNsense\Base\ApiMutableModelControllerBase;
4
5class SettingsController extends ApiMutableModelControllerBase
6{
7 protected static $internalModelName = 'gridexample';
8 protected static $internalModelClass = 'OPNsense\GridExample\GridExample';
9
10 public function searchItemAction()
11 {
12 return $this->searchBase("addresses.address", array('enabled', 'email'), "email");
13 }
14
15 public function setItemAction($uuid)
16 {
17 return $this->setBase("address", "addresses.address", $uuid);
18 }
19
20 public function addItemAction()
21 {
22 return $this->addBase("address", "addresses.address");
23 }
24
25 public function getItemAction($uuid = null)
26 {
27 return $this->getBase("address", "addresses.address", $uuid);
28 }
29
30 public function delItemAction($uuid)
31 {
32 return $this->delBase("addresses.address", $uuid);
33 }
34
35 public function toggleItemAction($uuid, $enabled = null)
36 {
37 return $this->toggleBase("addresses.address", $uuid, $enabled);
38 }
39}
The parameters of all methods contain at least the root of the ArrayField
type you want to operate on
and in cases the action involves form data the name of the root property used as in the container to transport data in.
For example, a getItem (/api/gridexample/settings/getItem/my-uuid-id) would return a response like this (highlighted the container):
1{
2 "address": {
3 "enabled": "1",
4 "email": "test@example.com"
5 }
6}
Define dialog items
To edit the data we define which fields should be presented to the user and how they are formatted.
Below a simple layout, the id fields reference the actual data points to map (address.enabled
for example), which is exactly
what the api endpoint returns.
<form>
<field>
<id>address.enabled</id>
<label>enabled</label>
<type>checkbox</type>
<help>Enable this address</help>
</field>
<field>
<id>address.email</id>
<label>Email</label>
<type>text</type>
</field>
</form>
Constructing the volt template
We ship a javascript wrapper to implement a slightly modified version of jquery-bootgrid, to use this in our template (view) we define three different blocks.
First of all we bind a table by id (grid-addresses) using UIBootgrid()
, then we define the table which will be
changed into a dynamic searchable grid and finally we link our dialog content using a volt partial()
.
The basic “UIBootgrid” bind connects all actions which we have defined in our API controller earlier, there are more options available, but these are not needed for this use-case.
When defining the table, we need to add all fields that should be displayed and the order in which they should appear. If
fields should not be visible by default, simply use data-visible="false"
on the <th>
tag.
Our edit dialog is being written in advance so the javascript code can open the statically defined form when needed, the last highlighted block takes care of this. The partial uses three argument, the variable connected via the controller containing all form entries, the name (id) of the form, which is referenced in the table (data-editDialog) and the caption of the dialog.
1<script>
2 $( document ).ready(function() {
3 $("#grid-addresses").UIBootgrid(
4 { search:'/api/gridexample/settings/searchItem/',
5 get:'/api/gridexample/settings/getItem/',
6 set:'/api/gridexample/settings/setItem/',
7 add:'/api/gridexample/settings/addItem/',
8 del:'/api/gridexample/settings/delItem/',
9 toggle:'/api/gridexample/settings/toggleItem/'
10 }
11 );
12 });
13</script>
14<table id="grid-addresses" class="table table-condensed table-hover table-striped" data-editDialog="DialogAddress">
15 <thead>
16 <tr>
17 <th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
18 <th data-column-id="enabled" data-width="6em" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
19 <th data-column-id="email" data-type="string">{{ lang._('Email') }}</th>
20 <th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
21 </tr>
22 </thead>
23 <tbody>
24 </tbody>
25 <tfoot>
26 <tr>
27 <td></td>
28 <td>
29 <button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
30 <button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
31 </td>
32 </tr>
33 </tfoot>
34</table>
35
36
37{{ partial("layout_partials/base_dialog",['fields':formDialogAddress,'id':'DialogAddress','label':lang._('Edit address')])}}
UI controller
The user interface controller sets the template (view) to use and collects the dialog form properties from the xml file defined earlier.
1namespace OPNsense\GridExample;
2
3class IndexController extends \OPNsense\Base\IndexController
4{
5 public function indexAction()
6 {
7 $this->view->pick('OPNsense/GridExample/index');
8 $this->view->formDialogAddress = $this->getForm("dialogAddress");
9 }
10}