Digital Base – Blog » Programming

FusionCharts Symfony Plugin: dbFusionChartsPlugin

We're
currently writing an OO class to create FusionCharts
quickly, easily & intuitive, because the existing FusionCharts.php class is not OOP and, honestly, we thought we could do better…
Our package is based on and tested against the FusionCharts Free v2 package. It's written
symfony independent, but we provide a helper, for easy use in symfony.

The symfony plugin, dbFusionChartPlugin, is
in its alpha state now, because it only includes basic single & multi-series chart support. Although the basic code is ready, it needs to be extended, to work with all chart types, but a mediocre programmer should be able to understand its structure and adapt/extend it to his needs. We deeply appreciate all help and suggestions!

Symfony
helper

[code]FC helper ex[/code]

This helper creates the necessary JavaScript.

[code]FC javaScript[/code]

Documentation

You can find the dbFusionChart phpDoc at digitalbase.eu/dbFusionChart/doc/index.html

Logic

As you can see in the FusionCharts Documentation, you need to define your chart's data in an XML file. Our dbFusionChartXML class uses the php DOM extension to encapsulate that XML logic by providing methods to add categories and (datasets with) sets. It has (for now) the descendants dbFC_Single & dbFC_Multi. The latter will make sure there are always as many sets in each dataset as there are categories, when adding a dataset or category.

Stay tuned

We have recently added a check in the addCategory function so it
wouldn't add the same category twice, no matter how many times it's
being called, e.g. when looping Propel objects to init a dbFusionChartXML. It's checks like these that need to be added, among much much more… So stay tuned!

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

Symfony Plugin Symlinks

First a little backstage information: our programmers are developing under the Linux distribution Ubuntu, and our graphical expert uses Windows for Photoshop purposes. Because we work most of the time with multiple users on one project, we use the version control system Subversion, which offers clients for different operating systems…so far, so good.

When installing plugins in your Symfony application, the installation process sometimes places a symbolic link in the public web directory, to the plugin web directory. In Linux those symlinks work just fine, but when a collegue checks out the code in Windows, those symlinks are degrated to ordinary files without extensions. Windows does not know what to do with them, and your Symfony plugin actions and web files are not catchable by the application. Result: ajax calls are not working, extra css files are not correctly included,…

You have two options to resolve this problem:

  • first, you can install the plugin again under Windows, but that’s doing the same thing twice.
  • Second solution: copy the plugin web directory manually to your applications web dir. Clear the cache with “symfony cc” and refresh your page. The webpage should have all it’s ajax functionalities and css files.

Now the webdesigner can start to work…

change Symfony Web directory path

Because we love the Symfony Open-source PHP Web Framework, we have developed multiple applications with it. For those wo are not familiar with Symfony, take a look at their website for more information.

The scripts of the web directory are the entry points to the application. To be able to access them from the Internet or any given webpage, the web server must be correctly configured. In your development server, as well as in a professional hosting solution, you probably have access to the Apache configuration and you can set up a virtual host :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<VirtualHost *:80>
 ServerName www.domain.com
 DocumentRoot "/home/diba/myproject/web"
 DirectoryIndex index.php
 Alias /sf /$sf_symfony_data_dir/web/sf
 <Directory "/$sf_symfony_data_dir/web/sf">
   AllowOverride All
   Allow from All
 </Directory>
 <Directory "/home/diba/myproject/web">
   AllowOverride All
   Allow from All
 </Directory>
</VirtualHost>

Setting up an web application on a shared host is a little bit trickier, since
the host usually has a specific directory layout that you can't change, except when you have a webserver at your disposal of course. Let's imagine that your shared host requires that the web folder is named public_html instead of web, and that it doesn't give you access to the httpd.conf file, but only to an .htaccess file in the web folder.

In directadmin you have the option to change/update the virtualhost of a certain domain. The downside of this solution is that whenever you change data related to this domain (safe mode, open basedir, add new domain), the virtualhost will get rewritten, causing your changed to be lost. There is a solution for this, although it requires a small symfony hack.

In a symfony project, every path to a directory is configurable. You can
still rename the web directory to public_html and
have the application take it into account by changing the
configuration. These lines are to be added to
the end of the application config.php file:

1
2
3
4
5
6
$sf_root_dir = sfConfig::get('sf_root_dir');
sfConfig::add(array(
 'sf_web_dir_name' => $sf_web_dir_name = 'public_html',
 'sf_web_dir'      => $sf_root_dir.DIRECTORY_SEPARATOR.$sf_web_dir_name,
 'sf_upload_dir'   => $sf_root_dir.DIRECTORY_SEPARATOR.$sf_web_dir_name.DIRECTORY_SEPARATOR.sfConfig::get('sf_upload_dir_name'),
));

And that's it! Your webapplication will now get it's public content from public_html, and the Symfony framework is smart enough to know that all the other files are one dir above this one. All hail to Sensiolabs!

P.S. If you are doing this because of the directadmin limitations, there is a post you should read.

My Favourite Design Tools

Posted by Gijs Nelissen in All, Programming

As a professional web developer i tried alot of software, this results in a great deal of expertise & experience with the most common design tools. In this post i will list the software i am currently using to create our websites.

First of all, all the developers at Digital Base work in a linux environment. All of our workstations are running an 64bit build of Ubunty Gutsy 7.10. So if you are hoping for a listing of windows software, you could stop right here, although some of the tools we are using have a windows version.

Because we all (or at least most of us, *wink* @ Jan) like "eye-candy", we are using Compiz Fusion on a Gnome Window Manager. The advantage of this package, besides having a kickass desktop environment, is you can set shortcut keys & customise almost every aspect of your user interface (maximise toggle buttons, window positioning etc..).

We develop all our websites on a LAMP platform (LAMP : Linux Apache MySQL PHP), it speaks for itself these packages are installed on all workstations for local development & debugging purposes. As for the programming we are still using Zend Studio 5.51 (the linux binary), this very nice (but never updated) IDE gives you the needed functions like :

  • code completion
  • syntax highlighting
  • debugging capabilities
  • code assist
  • source/code templates

At this moment Zend is switching their IDE to an Eclipse based IDE (codename Zend Neon ), we did try it, but back then it wasn't stable enough for us to start using it. Although i am currently testing Eclipse PDT & Zend Neon, most of us still use the Zend IDE as it is rocksteady, responsive.

Next to that we're using the popular webbrowser Firefox with a few interesting developer plugins like Web Developer Toolbar, Firebug. To test the applications in IE webbrowsers we use an RDP connection to a virtual machine or Windows Server, or use the IE4Linux binaries to check/improve how a website looks when rendered by the IE engine.

So there you have a basic insight how we do things, i'll make sure to complete this list as we're using alot more tools, but my time is limited Smile

Symfony / Propel – Subquerys

To reduce the query count in your web application, you might consider using subqueries. I'll show you howto do that in propel/symfony :

[code]propel - subqueries[/code]

It's that easy :)

Simplifying Queries: Using sum, count, etc in Propel

Simplifying Queries: Using sum, count, etc in Propel

$c-&gt;addSelectColumn('sum('.dbTimeBlockPeer::TOTAL.') as total');
$rs = dbTimeBlockPeer::doSelectRS($c);
while ($rs-&gt;next()) { $total = $rs-&gt;getInt(1); }

So, simply use addSelectColumn. You have to work with the ResultSet offcourse, since you won’t be able to populateObjects.

In this case you can simply use ‘1′ in getInt(column index), since we’re sure only one column is being fetched. Note that the index starts at 1 and not at 0.

Now, imagine a case where you are not sure what the index is (it could also change in the project’s life cycle). Then you can use, dbTimeBlockPeer::translateFieldName(dbTimeBlockPeer::TOTAL, BasePeer::TYPE_COLNAME, BasePeer::TYPE_NUM)), which will return the position of column ‘total’.

This needs no explanation, but I’ll give it anyway: translateFieldName accepts as first argument a string (source), the source type name and the destination type name. You can go and look in the basePeer which type you need.

basedbTimeBlockPeer (generated by Propel)

private static $fieldNames = array ( BasePeer::TYPE_PHPNAME => array ('Id', 'UserId', 'ProjectId', 'Ticketnr', 'Changeset', 'Description', 'TimeIn', 'TimeOut', 'Total', ), BasePeer::TYPE_COLNAME => array (dbTimeBlockPeer::ID, dbTimeBlockPeer::USER_ID, dbTimeBlockPeer::PROJECT_ID, dbTimeBlockPeer::TICKETNR, dbTimeBlockPeer::CHANGESET, dbTimeBlockPeer::DESCRIPTION, dbTimeBlockPeer::TIME_IN, dbTimeBlockPeer::TIME_OUT, dbTimeBlockPeer::TOTAL, ), BasePeer::TYPE_FIELDNAME => array ('id', 'user_id', 'project_id', 'ticketnr', 'changeset', 'description', 'time_in', 'time_out', 'total', ), BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, ) );

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
<?php
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

full option grid with hooks