Custom Propel Grid Update

As you can read on Gijs’ Lifelog, he started developing a custom datagrid a while ago. The idea was to create some kind of module/object, which would be used for generating datagrids fast, easy and intuitive, without too much code.
Over time, it has been extended and rewritten to interact with our CriteriaBuilder (read blog).
This week, I’ve been refactoring/cleaning the code to clearly separate the code for our Propel driven datagrid from the basic html grid whose data could come from anywhere.

Underlying code

Due to several questions about releasing this code, here is some insight in how the code is structured, at this moment. Perhaps it will be released in the future, perhaps even as a symfony plugin, but we’re still indecisive about that.
So, we have an abstract grid class which contains all code needed to generate a basic html table. It has an array $columns, which holds grid_columns, and an $options array to manipulate its behaviour. One of them is the option â “pagable”, and if enabled the grid has to have a grid_pager set.
Grid_pager is an interface which defines what methods a pager (at least) should have and our grid_propel_pager implements that interface.
On __toString, the abstract function initfirstload is called. Its purpose is to set all columns and options, i.e. to define the grid. Then it calls display, which calls initdisplay. This function will do everything that has to be done before actually displaying the grid. The difference is that tostring is called once, and thus initfirstload too, while display & initdisplay are called multiple times through xajax.
The displayHeader function calls displayHeaderon each column. The displayRows method is abstract. For a very simple grid it could call displayContent on each column.A grid_column holds all data for showing an html column, like its header name, alignment, whether it’s shown, sortable, etc… It has the following display methods (among others): displayHeader & displayContent, which obviously return th and td strings.

Now that’s all pretty abstract !

Here’s how we use all of that in our grid_propel class, which uses grid_column_propels. The idea is that we could represent a table’s data fetched via Propel.
Grid_propel extends the grid_paged class (containing the code for a grid to be paged via xajax), which extends the abstract grid class. Some display methods are overridden and the abstract displayRows method is implemented. It loops all objects we retrieve from the grid_propel_pager (which implements the grid_pager interface, remember). In the loop, displayContent is called on eachcolumn and the object is passed.
Grid_column_propel extends grid_column_method which extends grid_column. In grid_column_propel::displayContent, we use call_user_func_array to retrieve the content to be shown. It also has a guessGetter method which guesses the method based on the column’s peer constant name, so you don’t have to provide it!
And voila, we have a fully Propel driven html table, which is sorted and paged via xajax.

Put it to work!

If that’s Chinese to you, don’t worry because of the great principle of encapsulation:
YOU DON’T HAVE TO UNDERSTAND IT!
Or to quote the textbook
You don’t have know how a car works to drive it!
Simply define your object class and columns that need to be shown. That’s all! Grid_propel will do the work for you!
Here’s an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

class grid_users extends grid_propel {
 
 public function __construct($name) {
   parent::__construct($name,"_User");
 }
 
 public function init_firstload() {
   $this->option_enable("sortable");
 
   $gridcontent =  page_minimal::get_grid_contentobject();
   $bid = new grid_column_propel("id",_UserPeer::ID);
   $bid->display = false;
   $this->column_add_unique($bid);
 
   $this->column_add(new grid_column_propel($gridcontent->getContent("Login"), _UserPeer::LOGIN, "", 40));
   $this->column_add(new grid_column_propel($gridcontent->getContent("First Name"), _UserPeer::NAME_FIRST, "", 40));
   $this->column_add(new grid_column_propel($gridcontent->getContent("Family Name"), _UserPeer::NAME_FAMILY, "", 40));
   $this->column_add(new grid_column_propel($gridcontent->getContent("Email"), _UserPeer::EMAIL, "", 100));
   $this->column_add(new grid_column_propel($gridcontent->getContent("Country"), _CountryPeer::COUNTRY, array("get_Country", "getCountryShort"), 25, "center"));
 }
}
When we use this in a page (and register the necessary xajax functions), this will result in:
simple custom propel grid
You can also use your own methods (not propel-generated)!
This really blows the grid wide open. You can call any method for the grid’s object class. You can even pass an array of methods. Every method is called on the result for the last call. The last method should of course return a string.
For example, adding
1
2
3
$view = new grid_column_method("", "getLink", 10, "center");
$view->setArgs(array(false, true));
$this->column_add($view);
will result in a clickable image that will pop up an information window:
custom propel grid with own methods
Note that you can now use grid_column_method, the parent class of grid_column_propel. But, TIMTOWTDI, you could also use grid_column_propel, with an empty peer constant name.
1
$view = new grid_column_propel("", "", "getLink", 10, "center");
If you provide a name for such a non-propel column, you should also disable sorting for that column, since it has no peer constant name.
Foreign key values
Our user grid also contains a special column, Country. It’s actually a foreign key for the _user table.
1
$this->column_add(new grid_column_propel($gridcontent->getContent("Country"), _CountryPeer::COUNTRY, array("get_Country", "getCountryShort"), 25, "center"));
We provide _CountryPeer::COUNTRY, the column used for sorting, so the results would be alphabetically sorted instead of being sorted by the foreign key. Our grid_propel takes care of the necessary joins.
We also provide an alternative method array, otherwise the getter is guessed by grid_column_propel, which would result in the methodarray(->get_Country->getCountry).
Other descendants of grid_column
We have also created a special grid_column, grid_action, which almost does the same as the information image. But again, it groups code.Grid_action_url extends this class and its goal is to easy generate such images.
1
$this->action_add(new grid_action_url("application_edit.jpg","","/userid/", "", 15));
Will produce an image linked to /userid/$id
1
$this->action_add(new grid_action_js_delete("_User","Delete"));
Will produce a “stop”-image which calls _User::Delete, via xajax, on the object related to the row.
That’s all pretty neat, isn’t it? But, of course, that ain’t enough !
Last week I’ve added some functionality to export the datagrid to an excel workbook using the Spreadsheet_Excel_Writer class. Grid_column_propel accepts extra arguments
1
$excelwidth=null, $excelname=null, $exportmethod=null, $exportargs=null, $export=true"));
By default all columns will be exported. But if you want the export button to be shown, you need to enable the boolean export for the grid_propel.
You could also add columns (propel or method) to the grid which will only appear in the excel file, by simply disabling display for that column. Soon, I’ll add an extra argument $format, to control the layout in excel, column per column. And some constants to represent often used formatting.
All together, our user grid looks like this
Our user grid
If that still isn’t enough for you…
There’s also a class called grid_hook, which uses a grid_column_checkbox. Without going into details, this code
1
$this->addHook(new grid_hook(page_minimal::get_grid_contentobject()->getContent("edit"),"multiEdit"));
generates the edit hook on the bottom of this grid

No comments:

Post a Comment