Creating a datagrid in Zend Framework

Creating a datagrid isn't hard as it seems. You can make in in many ways, here is how i created my datagrid.

First i created a model named Datagrid.php. In future i will pass every parameter to this model.

Datagrid.php

<?php
class Datagrid extends Zend_Db_Table
{
    public $current_page = 1;
    public $step = 1; // Default Step
    public $order = 'id'; // Default order field
    public $order_dir = 'ASC'; // Default order direction
    
    /* Bulk Commands */
    public $enable_bulk_commands = 0;
    public $bulk_command_array = array('delete', 'enable', 'disable');
    /* Bulk Commands */
   
    /* Special Fields */
    public $special_array = array('admin_list', 'user_list');
    public $special = '';
    /* End of Special Fields */

    private $col_list = array();
    public $column_names = array();
    public $class_array = array();
   
    public $enable_status_field_image = 1;

    private $controller = '';
    private $action = '';
    private $baseUrl = '';
    private $number_of_pages = 1;
    private $item_quantity = 0;
    private $item_data = array();

    private $delete_button_enabled = 0;
    private $delete_button_controller = '';
    private $delete_button_action = '';

    private $edit_button_enabled = 0;
    private $edit_button_controller = '';
    private $edit_button_action = '';

    public function Datagrid(&$view) {
        $this->db = Zend_Registry::getInstance()->get('db');          
        $this->view = $view;

        $this->controller = $this->view->controller; // Your current controller
        $this->action = $this->view->action; // Your current action
        $this->baseUrl = $this->view->baseUrl; // Base url to your site axample ("http://localhost/mysite" or "http://www.mysite.com")
    }

    public function generateZendSql($sql){
       
        if( empty($sql) ){
            return null;
        }
       
        if( !empty($this->order) && !empty($this->order_dir) && in_array(strtoupper($this->order_dir), array('ASC', 'DESC')) ){
            $sql->order("$this->order $this->order_dir");
        }

        $sql->limitPage($this->current_page, $this->step);

        $this->item_data = $this->db->fetchAll($sql);
        $this->item_quantity = $this->db->fetchOne('select FOUND_ROWS()');
       
        $this->calculateNumberOfPages();
        $this->createTable();
        $this->setView();  
    }

    public function generateArray($array){
       
        if( empty($array) ){
            return null;
        }
       
        /* Array Order */   
        if( !empty($this->order) && !empty($this->order_dir) && in_array(strtoupper($this->order_dir), array('ASC', 'DESC')) ){
            foreach ($array as $key => $row) {
                $sort_data[$key]  = $row[$this->order];
            }

            if( $this->order_dir == "ASC" ){
                array_multisort($sort_data, SORT_ASC, $array);
            }else{
                array_multisort($sort_data, SORT_DESC, $array);
            }
        }
        /* End of Array order */

        $this->item_quantity = count($array);
       
        /* Calculating limit/page */
        $i = 0;
        foreach($array as $item){
            if( $i >= (($this->current_page * $this->step)-$this->step) && $i < (($this->step *$this->current_page)) ){
                $item_data[] = $item;
            }
            $i++;
        }
        /* End of calculating limit/page */

        $this->item_data = $item_data;      
        $this->calculateNumberOfPages();
        $this->createTable();
        $this->setView();  
    }

    private function setView(){

 
        $this->view->current_page = $this->current_page;
        $this->view->step = $this->step;
        $this->view->order = $this->order;
        $this->view->order_dir = $this->order_dir;
        $this->view->item_quantity = $this->item_quantity;
        $this->view->item_data = $this->item_data;
        $this->view->number_of_pages = $this->number_of_pages;
    }

    public function calculateNumberOfPages(){

        if( !empty($this->item_quantity) && $this->item_quantity > 0 ){
            if( ( $this->item_quantity % $this->step ) > 0 ){
                $this->number_of_pages = ceil( $this->item_quantity/$this->step );
            }else{
                $this->number_of_pages = $this->item_quantity/$this->step;
            }
        }else{
            $this->number_of_pages = 1;
        }
    }

    public function setParams($page = 0, $step = 0, $order = '', $orderdir= ''){
       
        // page - current page
        // step - table pagination step
        // order - order filed
        // orderdir - ( ASC, DESC ), default -> ASC

        if( !empty($page) && intval($page) > 0 ){
            $this->current_page = intval($page);
        }

        if( !empty($step) && intval($step) > 0 ){
            $this->step = intval($step);
        }

        if( !empty($order) ){
            $this->order = $order;
        }

        if( !empty($orderdir) && in_array(strtoupper($orderdir), array('ASC', 'DESC')) ){
            $this->order_dir = strtoupper($orderdir);
        }
    }

    private function createTable(){
       
        $columns = $this->getHeaderCols();
           
        $html = "";

        if( !empty($this->item_data) && !empty($columns) ) {
           
            if( $this->delete_button_enabled == 1 ){
                $html .= "
                    <script type='text/javascript'>
                        $(document).ready(function(){
                            $('a.grid_delete_item').live('click', function(event){
                                event.preventDefault();
                                var href = $(this).attr('href');
                                var question_text = 'Are you sure to delete?';

                                apprise(question_text, {verify : true}, function(r) {
                                    if(r){
                                        window.location.href = href;
                                    }else{
                                        event.preventDefault();
                                    }
                                });
                            });
                        });
                    </script>";
            }

            $filtoptions = "/order/$this->order/orderdir/$this->order_dir/step/$this->step";

            $html .= "<table cellspacing='0' cellpaddin='0' class='grid'>";
           
           
            /* Table header */
            $html .= "<tr class='grid_head_tr'>";
           
            if($this->delete_button_enabled == 1 && $this->edit_button_enabled == 1){
                $html .= "<td class='grid_head_td' colspan='2'>&nbsp;</td>";
            }else{
                if( $this->delete_button_enabled == 1 ){
                    $html .= "<td class='grid_head_td'>&nbsp;</td>";
                }
               
                if( $this->edit_button_enabled == 1 ){
                    $html .= "<td class='grid_head_td'>&nbsp;</td>";
                }
            }

            foreach( $columns as $key=>$col ){

                $html .= "<td class='grid_head_td'>";
               
                if( !($this->order_dir == "ASC" && $this->order == $this->col_list[$key]) ){
                    $html .= "<a href='".$this->baseUrl . "/" . $this->controller."/".$this->action."/page/".$this->current_page."/order/".$this->col_list[$key]."/orderdir/ASC/step/".$this->step."'><img class='order_img' src='".$this->baseUrl."/gfx/grid/arrow-up.png' /></a>";
                }

                $html .= "$col";
               
                if( !($this->order_dir == "DESC" && $this->order == $this->col_list[$key]) ){
                    $html .= "<a href='".$this->baseUrl."/".$this->controller."/".$this->action."/page/".$this->current_page."/order/".$this->col_list[$key]."/orderdir/DESC/step/".$this->step."'><img class='order_img' src='".$this->baseUrl."/gfx/grid/arrow-down.png' /></a>";
                }
               
                $html .= "</td>";
            }
            $html .= "</tr>";
            /* End of table header */
           
            /* Datagrid items */
            $i = 0;
            foreach( $this->item_data as $item ){
               
                if($i%2 == 0) $hl = " class='alt'"; else $hl = "";

                $html .= "<tr$hl>";
                if( $this->delete_button_enabled == 1 ){
                    $html .= "<td class='content textCenter'><a class='tippsy grid_delete_item' original-title='Delete' href='".$this->baseUrl."/".$this->delete_button_controller."/".$this->delete_button_action."/id/".$item['id']."'><img src='".$this->baseUrl."/gfx/grid/delete.png' alt='delete'  /></a></td>";
                }

                if( $this->edit_button_enabled == 1 ){
                    $html .= "<td class='content textCenter'><a class='tippsy' original-title='Edit' href='".$this->baseUrl."/".$this->edit_button_controller."/".$this->edit_button_action."/id/".$item['id']."'><img src='".$this->baseUrl."/gfx/grid/edit.gif' alt='edit'  /></a></td>";
                }

                foreach( $item as $field => $data ){
                    if( in_array($field, $this->col_list) ){
                        if( $field == "enabled" && $this->enable_status_field_image ){
                            // Enabled image
                            $enabled_image = ($data == 1) ? "enabled.gif": "disabled.gif";
                            if( !empty($this->class_array[$field]) ){
                                $html .= "<td class='content ".$this->class_array[$field]."'><img src='".$this->baseUrl."/gfx/grid/".$enabled_image."' alt='status' /></td>";
                            }else{
                                $html .= "<td class='content'><img src='".$this->baseUrl."/gfx/grid/".$enabled_image."' alt='status' /></td>";
                            }
                            // End of enabled image
                        }else{
                            if( empty($data) ){
                                $data = "---";
                            }

                            /* just for fields created, modified */
                            if( in_array($field, array('created', 'modified')) ){
                                if( $data == "0000-00-00 00:00:00" ){
                                    $data = '---';
                                }
                            }
                            /* --------------------------------- */

                            if( !empty($this->class_array[$field]) ){
                                $html .= "<td class='content ".$this->class_array[$field]."'>".$data."</td>";
                            }else{
                                $html .= "<td class='content'>".$data."</td>";
                            }
                        }
                    }
                }
                $html .= "</tr>";
                $i++;
            }
            /* End of datagrid items */

            /* Page Numbers */

            $col_number = count($this->col_list);
            if( $this->delete_button_enabled == 1 ){
                $col_number++;
            }
           
            if( $this->edit_button_enabled == 1 ){
                $col_number++;
            }

            $html .= "<tr class='grid_footer_tr'>";
                $html .= "<td class='grid_footer_td' colspan='".$col_number."'>";
                    $html .= "<div style='width:100%;'>";
                   
                    /* Left side info */
                    $html .= "<div class='fl'>Quantity: ".$this->item_quantity." | Page ".$this->current_page." of ".$this->number_of_pages." | Step <form action='".$this->baseUrl."/".$this->controller."/".$this->action."' method='post' style='display:inline'>";
                    $html .= "<select name='step'>";
                        $stepArray = array(1,3,5,7,10,15,20,25,30,40,50,75,100);
                        foreach( $stepArray as $step ){
                            if( $step == $this->step ){
                                $html .= "<option value='$step' selected='selected'>$step</option>";
                            }else{
                                $html .= "<option value='$step'>$step</option>";
                            }
                        }

                    $html .= "</select><input type='hidden' name='order' value='".$this->order."' /><input type='hidden' name='orderdir' value='".$this->order_dir."' />&nbsp;&nbsp;<input class='grid_submit_btn' type='submit' value='Change' /></div>";
                    /* End of left side info */
                   
                    /* Pagination */
                    if($this->number_of_pages > 1){
                        $html .= "<div class='fr pagination'>";
                            // add order + step
                           
                            if( $this->current_page != 1 ){
                               $html .= "<a class='pagination_lnk' href='".$this->baseUrl."/".$this->controller."/".$this->action."/page/1$filtoptions'><<</a>";
                               $html .= "<a class='pagination_lnk' href='".$this->baseUrl."/".$this->controller."/".$this->action."/page/".($this->current_page - 1)."$filtoptions'><</a>";
                            }

                            for($i=1;$i<=$this->number_of_pages;$i++){
                                if( $i == $this->current_page ){
                                    $html .= "<a class='active pagination_lnk' href='".$this->baseUrl."/".$this->controller."/".$this->action."/page/".$i."$filtoptions'>".$i."</a>";
                                }else{
                                    $html .= "<a class='pagination_lnk' href='".$this->baseUrl."/".$this->controller."/".$this->action."/page/".$i."$filtoptions'>".$i."</a>";
                                }
                            }

                            if( $this->current_page != $this->number_of_pages ){
                               $html .= "<a class='pagination_lnk' href='".$this->baseUrl."/".$this->controller."/".$this->action."/page/".($this->current_page + 1)."$filtoptions'>></a>";
                               $html .= "<a class='pagination_lnk' href='".$this->baseUrl."/".$this->controller."/".$this->action."/page/".$this->number_of_pages."$filtoptions'>>></a>";
                            }

                        $html .= "<div>";
                    }
                    /* End of pagination */

                    $html .= "<div class='clear'></div>";
                    $html .= "</div>";
                $html .= "</td>";
            $html .= "</tr>";
            /* End Of Page Numbers */

            $html .= "</table>";
        }
       
        $this->view->grid = $html;
    }

    private function getHeaderCols(){
       
        $cols = array();
       
       
        if( !empty($this->item_data) ){
            foreach($this->item_data as $key=>$itemd){
                foreach( $itemd as $c=>$d ){
                    if( !in_array($c, array('item_d_count')) && !in_array($c, $cols) ){
                        $cols[] = $c;
                    }
                }
            }
        }
        $this->col_list = $cols;

        if( !empty($this->column_names) && !empty($cols) ){
            foreach( $this->column_names as $colname=>$value ){
                if( in_array($colname, $cols) ){
                    foreach( $cols as $ckey=>$cdata ){
                        if( $cdata == $colname ){
                            $cols[$ckey] = $value;
                        }
                    }
                }
            }
        }
       
        return $cols;
    }

    public function setDeleteButton($status = 0, $controller = '', $action = ''){
        if( in_array(intval($status), array(1)) ){
            $this->delete_button_enabled = 1;
           
            $this->delete_button_controller = $controller;
            $this->delete_button_action = $action;
        }
    }

    public function setEditButton($status = 0, $controller = '', $action = ''){
        if( in_array(intval($status), array(1)) ){
            $this->edit_button_enabled = 1;
           
            $this->edit_button_controller = $controller;
            $this->edit_button_action = $action;
        }
    }
}
?>


After that i pass parameters to Datagrid.php in  my IndexController index action. Off course you can use any controller and any action, it only depends in you.

public function indexAction() {
    
    /* Datagrid params */
    $page = $this->_request->getParam('page',1);
    $step = $this->_request->getParam('step',20);
    $order = $this->_request->getParam('order');
    $orderdir = $this->_request->getParam('orderdir','ASC');
    /* End of Datagrid params*/


    /* Sql for datagrid */

    /* The Zend_Db_Expr is very important, because it calculates the number of items without page/step limitations ( in a faster way ) and it need to be there in every Zend sql query */
    $select = $this->db->select()
                ->from(array("u" => "user"), array(new Zend_Db_Expr('SQL_CALC_FOUND_ROWS u.id as item_d_count'),"id", "username", "email", "created", "modified", "enabled"))
                ->join(array("ur" => "user_role"), "u.id = ur.user_id", array("role_id"))
                ->join(array("r" => "role"), "ur.role_id = r.id", array("role as role_title"))
                ->where("u.deleted='0'");
    /*  End of sql for datagrid */
   
    $datagrid = new Datagrid($this->view);
    $datagrid->setDeleteButton(1,'admin','delete'); // Status, Controller, Action
    $datagrid->setEditButton(1,'admin','editadmin'); // Status, Controller, Action
    $datagrid->setParams($page,$step, $order, $orderdir); // page, step, order, orderdir
    $datagrid->column_names = array("id" => "Field id", "role_title" => "Role"); // Field => New filed title
    $datagrid->class_array = array("id" => "textCenter", "role_title" => "textCenter", "enabled" => "textCenter"); // Field => class or class list ("textCenter textBold")
    $datagrid->generateZendSql($select);
   
    /* Example how to use datagrid with multi dimensional array's */

    $dataArray = array(
        0 => array('id' => 1,
                   'enabled' => 0,
                   'name' => 'Lorem ipsum 1',
                   'title' => 'aaaa'
        ),
        1 => array('id' => 2,
                   'enabled' => 1,
                   'name' => 'Lorem ipsum 2',
                   'title' => ''
        ),
        2 => array('id' => 3,
                   'enabled' => 1,
                   'name' => 'Lorem ipsum 3',
                   'title' => ''
        ),
        3 => array('id' => 4,
                   'enabled' => 0,
                   'name' => 'Lorem ipsum 4',
                   'title' => ''
        )
    );
   
    $datagrid->generateArray($dataArray);
    /*------------------------------------------ */
}


In your phtml file you need to add the following code ( i added to my index/index.phtml file because i used index controller and index action ):

<?php echo $this->grid; ?>

The basic css file is look like this for me:

table.grid {
    width: 100%;
    margin: 15px 0px 40px 0px;
}

table.grid img.order_img {
    margin: 0px 5px 0px 5px;
}

table.grid tr.grid_head_tr {
    background-color: #2A2A2A;
    color: #FFF;
}

table.grid td.grid_head_td {
    border-right: 1px solid #fff;
    text-align: center;
    padding: 12px 5px 12px 5px;
}

div.fl {
    float: left;
}

div.fr {
    float: right;
}

table.grid tr.grid_footer_tr {
}

table.grid tr td.grid_footer_td {
    padding-top: 20px;
    border-top: 1px solid #2A2A2A;
}

table.grid td.content {
    padding: 10px 5px 10px 5px;
}

table.grid a.pagination_lnk {
    margin: 0px 6px 0px 6px;
    text-decoration: none;
}

table.grid a.pagination_lnk:hover, table.grid a.active {
    text-decoration: underline;
}

.textCenter {
    text-align: center;
}

.textLeft {
    text-align: left;
}

.textRight {
    text-align: right;
}

table.grid input.grid_submit_btn {
    border: 1px solid #2A2A2A;
    background-color: #FFF;
    color: #2A2A2A;
}

table.grid tr.alt {
    background-color: #eee;
}

I used the following images:

Jqyery & Plugins you may want to use:


You can download the latest jquery from here : http://jquery.com/

The tipsy jquery plugin is not necessary. If you want to add than you can download the plugin here: http://onehackoranother.com/projects/jquery/tipsy/

If you added the tippsy plugin then you need to add the following jquery to your view file or into your layout.phtml:
<script type="text/javascript">
$(document).ready(function(){
    $(".tippsy").tipsy();
});
</script>


What is apprise?
An alert alternative for jQuery that looks good. Apprise is a very simple, fast, attractive, and unobtrusive way to communicate with your users.

You can download apprise plugin here: http://thrivingkings.com/apprise/


So, this is my current version of datagrid. I didn't finished everything yet, but in the future i plan to add more functionalities. But I think you can use some of functionalities in your project, or you can use the whole Datagrid system if you like it.

Functionalities i want to add in the future:
 - Order of fields in table header
 - Export to pdf with csv, tcpdf
 - Special filed values ( or replaceing field values with images )  depending on filed values ( like enable/disable fileds or created/modified etc ... )

No comments:

Post a Comment