Sunday, October 24, 2010

Displaying AJAX Tables in PHP vs Java EE: ZFDataGrid and PrimeFaces DataTable

While developing with PHP + Zend Framework + Doctrine I missed an easy way to display/edit data using a grid/table.

A very useful component I found is ZFDataGrid.

Here's a sample code of how to use ZFDataGrid:

    function simpleAction()

    {         //Zend_Config         $config = new Zend_Config_Ini('./application/grids/grid.ini', 'production');                 //Grid Initialization         $grid = Bvb_Grid::factory('Bvb_Grid_Deploy_Table', $config, 'id');                 //Setting grid source         $grid->setSource(new Bvb_Grid_Source_Zend_Table(new Bugs()));                 //CRUD Configuration         $form = new Bvb_Grid_Form();         $form->setAdd(true)->setEdit(true)->setDelete(true);         $grid->setForm($form);                 //Pass it to the view         $this->view->pages = $grid;         $this->render('index');     }
It looks pretty good too.

Check the ZFDataGrid Live Demo here.

However, working with data grids using JSF 2.0 and PrimeFaces felt much more natural and easier.

Here's a sample code using PrimeFaces' DataTable :

<h:form>

    <p:dataTable var="car" value="#{tableBean.lazyModel}" paginator="true" rows="10" lazy="true"
                 paginatorTemplate="{RowsPerPageDropdown} {FirstPageLink} {PreviousPageLink} {CurrentPageReport} {NextPageLink} {LastPageLink}"
                 rowsPerPageTemplate="5,10,15"
                 selection="#{tableBean.selectedCar}" selectionMode="single"
                 onRowSelectComplete="carDialog.show()" onRowSelectUpdate="display">
        <f:facet name="header">
            Displaying 100,000,000 Cars
        </f:facet>
        <p:column headerText="Model">
            <h:outputText value="#{car.model}" />
        </p:column>
        <p:column headerText="Year">
            <h:outputText value="#{car.year}" />
        </p:column>
        <p:column headerText="Manufacturer">
            <h:outputText value="#{car.manufacturer}" />
        </p:column>
        <p:column headerText="Color">
            <h:outputText value="#{car.color}" />
        </p:column>
    </p:dataTable>

    <p:dialog header="Car Detail" widgetVar="carDialog" resizable="false"
              width="200" showEffect="explode" hideEffect="explode">
        <h:panelGrid id="display" columns="2" cellpadding="4">
            <f:facet name="header">
                <p:graphicImage value="/images/cars/#{tableBean.selectedCar.manufacturer}.jpg"/>
            </f:facet>
            <h:outputText value="Model:" />
            <h:outputText value="#{tableBean.selectedCar.model}"/>

            <h:outputText value="Year:" />
            <h:outputText value="#{tableBean.selectedCar.year}"/>

            <h:outputText value="Manufacturer:" />
            <h:outputText value="#{tableBean.selectedCar.manufacturer}"/>

            <h:outputText value="Color:" />
            <h:outputText value="#{tableBean.selectedCar.color}"/>
        </h:panelGrid>
    </p:dialog>

</h:form>

The above code may look verbose, but it packs a lot of functionality and it's very easy and intuitive to customize.
When you click a row it displays a nice dialog with a picture. Furthermore, it's actually lazy loading 100,000,000 rows!! (yes, ONE HUNDRED MILLION ROWS)

Here's how it looks:

You can see for real the PrimeFaces DataTable Lazy-loading Live Demo here.

It's very easy to add lazy-loading support to DataTable:

        lazyModel = new LazyDataModel<Car>() {

/**
* Dummy implementation of loading a certain segment of data.
* In a real application, this method should load data from a datasource
*/
@Override
public List<Car> load(int first, int pageSize, String sortField, boolean sortOrder, Map<String,String> filters) {
logger.log(Level.INFO, "Loading the lazy car data between {0} and {1}", new Object[]{first, (first+pageSize)});

                //Sorting and Filtering information are not used for demo purposes just random dummy data is returned

List<Car> lazyCars = new ArrayList<Car>();
populateLazyRandomCars(lazyCars, pageSize);

return lazyCars;
}
};

        /**
         * In a real application, this number should be resolved by a projection query
         */
        lazyModel.setRowCount(100000000);

Not to disrespect PHP or ZFDataGrid in anyway (I still need to use them for some of my work), but the experience with JSF 2.0 and PrimeFaces wins hands down. I think it's more because of PrimeFaces than JSF 2.0, but they're such a powerful combo (compared to if you use PrimeFaces with JSF 1.2).

I do hope that PrimeFaces provide a utility class that implements LazyDataModel for a Hibernate/HQL or JPA/JPQL query, but for now I can live with the above.