Xajax driven CriteriaBuilder for Propel / php

Earlier this week, I talked about our custom datagrid for Propel and
how it was being rewritten to interact with our CriteriaBuilder (read
more
). Well, I’ll give you another glance into our coding
efforts.

Goal

We wanted to make
an object, which would manipulate the data being shown in our
grid_propel. For normal people, this means: to filter the data.
Early on, we
decided it had to ‘build a Criteria‘ (because Propel rules!), which
then could be used by our grid_propel.

Structure

The
CriteriaBuilder is in essence nothing but an array of FilterData
objects, which is indexed by the FilterColumn’s name (Propel peer
constant), so there could only be one FilterData object per
FilterColumn.
Of course, there’s more to it, about a thousand
lines of code actually (phpDoc included), but almost half of that are
xajax functions for user interaction, and then there are methods like
addAvailableColumn & qAddAvailableColumn to initialize the
CriteriaBuilder, and internal functions, which I’m not all going to
explain here. I’ll touch some of them in the next section.

Behind
the scenes

The __toString
function, will loop all FilterData objects and create a selector,
with their column name or with a more human readable alias, using our
XHTML package, which I’ll perhaps tell you more about on a
quite/rainy day. It’s an object-oriented package to quickly create
readable and XHTML valid code, I can tell you that.
CriteriaBuilder column selector
The add filter
button will trigger the xajax function addFilter, which adds a
UserFilter to the FilterData object related to the selected/passed
value and then updates the ‘criteriaform’.
Criteriabuilder UserFilter example
This criteriaform
shows for every UserFilter a ‘criteriaLine’, with a combination box
(disabled if only one line present), a filter method selector and
something to determine the search value.
Clicking ‘apply
filters’ will trigger another xajax function, which will build the
Criteria based on the current UserFilters, and pass it to all
registered CriteriaClients through the CriteriaClient interface
method processCriteria. This is an implementation of what Java
programmers probably know as the Listener pattern, meaning that an
object implementing the CriteriaClient interface registers itself as
Listener/CriteriaClient, by calling
CriteriaBuilder::addClient(CriteriaClient).

This means that the
CriteriaBuilder can serve any (and more than one) object implementing
the CriteriaClient interface, not just our grid_propel!

The ‘remove this
filter’ button will trigger a xajax fuction, which will remove the
UserFilter from the FilterColumn’s UserFilter array, reorder its
keys, and update the criteriaform.
The ‘clear all
filters’-button, naturally, deletes all UserFilters and also updates
the CriteriaForm. Because there are no filters left, the criteriaform
will disappear again.
Both functions will also immediately update
the grid by calling buildCriteria (which calls processCriteria on its
clients).
I’ve recently
added a ’save filter’-button, which makes this box appear,
CriteriaBuilder save filter combination
giving the user
the option to save his filter combination. Which is then linked to
the CriteriaBuilder’s name and saved in our database. When a
CriteriaBuilder is ‘__tostringed’, it will search for these filters,
and if one’s found, you’ll get something like this.
CriteriaBuilder load saved UserFilter
Selecting a saved
filter will once again trigger a xajax function, which will load the
filter (update criteriaform and build Criteria, and thus updating the
grid).
For now, these are personal filters, because they’re linked
to the user who’s created them. Later, we could easily extend the
code, so they can be shared, or something like that…
Note that
by linking the saved filters to the CriteriaBuilder’s name, we’re
able to use the same filters on different pages, by naming the
CriteriaBuilders the same.

Everything
configurable!

Now, you know
what’s happening behind the scenes when a user uses our
CriteriaBuilder, but that’s not even half the story. Still using our
user grid, I’ll explain how the CriteriaBuilder’s FilterData objects
are being initialized.
We
use this code on our page (FYI: our pages are also created using an
object-oriented framework)
[code]CB_pageContent[/code]
So…
We initialize our grid_propel, grid_users (see this post), and
specify its starting Criteria. You can also neglect this, then a new
(empty) Criteria will be used. Next, we use the CiteriaBuilder’s
constructor to pass the grid as CriteriaClient, set its name, and set
the original Criteria, which is used to restore the grid to its
original state.
Now,
that I come to think of it, this should be stored in the grid_propel,
not in the CriteriaBuilder, because the original Criteria can be
different per client!
You see this is code in progress… We
actually don’t use multiple CriteriaBuilders nor CriteriaClients
ourselves, yet… :)
I simply added the method getOriginalCriteria
to the interface class CriteriaClient. CriteriaBuilder::buildCriteria
now retrieves its ‘Criteria to work with’ via that method, thus from
the grid_propel, which of course has an extra variable
originalCriteria now. Grid_propel::setCriteria is now
[code]CB_setCriteria[/code]
Voila,
solved!
Ok,
to continue… $cb->qAddUserColumnSet
is a convenience method.
Admitted, these functions should better
be in an extending class, but nobody’s perfect :)
And since we’re
the only ones using it…
[code]CB_qAddUserColumnSet[/code]
As
you can see, this method adds the columns we say the grid can be
filtered on. These could also be columns which aren’t shown in the grid_propel.
[code]CB_qAddAvailableColumn[/code]
So…
In our case, we use the CriteriaBuilder’s constants, for example
CriteriaBuilder::FILTERSET_TEXT
stands for the array
[code]CB_ex_array[/code]

UserFilter
types

This
defines which ‘type’ of UserFilter will be used. For each constant,
there’s a descendant of UserFilter. They complete UserFilter by
defining the abstract __toString function. So each descendant will be
shown different.
TYPE_TEXT
simple inputfield
UserFilter type text
TYPE_BOOLEAN
simple select
UserFilter type boolean
TYPE_DATE
JavaScript calendar widget
UserFilter type date
TYPE_SPECIFIC
selector with values available in database for that column (=
dynamic)
UserFilter type specific
TYPE_SPECIFIC_SWITCH
special specific, it has extra filtermethods ‘contains’ & ‘does
not contain’. When this filter is selected the search value
determinator, will change into an inputfield.
First
UserFilter type specific_switch normal
Then
UserFilter type specific_switch for (does not) contains
Of course, you can combine as many filters as you want.
combination of multiple UserFilters

No comments:

Post a Comment