<?php
/*****************************************************************************************
 * X2Engine Open Source Edition is a customer relationship management program developed by
 * X2Engine, Inc. Copyright (C) 2011-2014 X2Engine Inc.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY X2ENGINE, X2ENGINE DISCLAIMS THE WARRANTY
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Affero General Public License along with
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * 
 * You can contact X2Engine, Inc. P.O. Box 66752, Scotts Valley,
 * California 95067, USA. or at email address contact@x2engine.com.
 * 
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 * 
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "Powered by
 * X2Engine" logo. If the display of the logo is not reasonably feasible for
 * technical reasons, the Appropriate Legal Notices must display the words
 * "Powered by X2Engine".
 *****************************************************************************************/

Yii::import('zii.widgets.grid.CGridView');
Yii::import('X2DataColumn');
Yii::import('X2ButtonColumn');

/**
 * Custom grid view display function.
 *
 * Displays a dynamic grid view that permits save-able resizing and reordering of
 * columns and also the adding of new columns based on the available fields for
 * the model.
 *
 * @property bool $isAdmin If true, the grid view will be generated under the
 *  assumption that the user viewing it has full/administrative access to
 *  whichever module that it is being used in.
 * @package X2CRM.components
 */
abstract class X2GridViewBase extends CGridView {
    public $selectableRows = 0;
    public $viewName;
    public $enableGvSettings = true;
    public $fullscreen = false;
    public $defaultGvSettings = array ();
    public $excludedColumns;
    public $enableControls = false;
    public $fixedHeader = false;
    public $summaryText;
    public $buttons = array();
    public $title;
    public $ajax = false;

    /**
     * @var bool If true, window will automatically scroll to the top when the page is changed
     */
    public $enableScrollOnPageChange = true;

    // JS which will be executed before/after yiiGridView.update () updates the grid view
    public $afterGridViewUpdateJSString = "";
    public $beforeGridViewUpdateJSString = "";

    /**
     * @var array the JS prototype name followed by properties of that prototype 
     */
    public $qtipManager;

    /**
     * @var bool whether qtips should be used, refresh method should be defined in a JS sub 
     *  prototype of X2QtipManager
     */
    public $enableQtips = false;

    protected $allFieldNames = array();
    protected $gvSettings = null;
    protected $columnSelectorId;
    protected $columnSelectorHtml;

    /**
     * @var string Set to view name if value not passed to constructor. Used to save/access
     *  gridview settings saved as a JSON property in the profile model
     */
    protected $_gvSettingsName;

    /**
     * @var string Used to prefix javascript namespaces, GET parameter keys, Script names, and HTML
     *  attributes. Allows multiple instances of X2GridView to work on the same page. Generated by
     *  whitelisting the gridview id. 
     */
    protected $_namespacePrefix;

    abstract protected function generateColumns ();

    /**
     * Magic setter for gvSettingsName. 
     * @param string $gvSettingsName
     */
    public function setGvSettingsName ($gvSettingsName) {
        $this->_gvSettingsName = $gvSettingsName;
    }

    /**
     * Magic getter for gvSettingsName. If not set explicitly, will be set to viewName
     * @return string  
     */
    public function getGvSettingsName () {
        if (isset ($this->_gvSettingsName)) {
            return $this->_gvSettingsName;
        } else if (isset ($this->viewName)) {
            $this->_gvSettingsName = $this->viewName;
        }
        return $this->_gvSettingsName;
    }

    /**
     * Magic setter for _namespacePrefix 
     * @param string
     */
    public function setNamespacePrefix ($namespacePrefix) {
        $this->_namespacePrefix = $namespacePrefix;
    }

    /**
     * Magic getter for _namespacePrefix 
     * @return string
     */
    public function getNamespacePrefix () {
        if (!isset ($this->_namespacePrefix)) {
            $this->_namespacePrefix = preg_replace (
                '/($[0-9])|([^a-zA-Z0-9_$])/', '', $this->id);
        }
        return $this->_namespacePrefix;
    }

    /**
     * Registers JS which makes the grid header sticky
     * Preconditions:
     *     - The CGridView template string must be set up in a highly specific way
     *         - Example:
     *              '<div id="x2-gridview-top-bar-outer" class="x2-gridview-fixed-top-bar-outer">
     *               <div id="x2-gridview-top-bar-inner" class="x2-gridview-fixed-top-bar-inner">
     *               <div id="x2-gridview-page-title" class="x2-gridview-fixed-title">
     *               {items}{pager}'
     *          - there must be a pager and items.
     *          - the three opening divs with the specified classes and ids are required. The 
     *              divs
     *            get closed after the grid header is printed.
     *     - the X2GridView propert fixedHeader must be set to true
     */
    public function setUpStickyHeader () {

        Yii::app()->clientScript->registerScriptFile(
            Yii::app()->getBaseUrl().'/js/X2GridView/X2GridViewStickyHeader.js');      

        $makeHeaderStickyStr = "
            x2.gridViewStickyHeader.DEBUG && console.log ($('#".$this->id."').find ('.x2grid-body-container').find ('.x2grid-resizable').find ('tbody').find ('tr').length);

            if ($('#".$this->id."').find ('.x2grid-body-container').find ('.x2grid-resizable').
                find ('tbody').find ('tr').length <= 2 || x2.isIPad/* || x2.isAndroid*/) {

                x2.gridViewStickyHeader.DEBUG && console.log ('make sticky');
                x2.gridViewStickyHeader.makeSticky ();
            } else if (!$('#x2-gridview-top-bar-outer').
                hasClass ('x2-gridview-fixed-top-bar-outer')) {

                x2.gridViewStickyHeader.DEBUG && console.log ('make unsticky');
                x2.gridViewStickyHeader.makeUnsticky ();
            }

            x2.gridViewStickyHeader.DEBUG && console.log ('after grid update');
            if (!x2.gridViewStickyHeader.checkX2GridViewHeaderSticky ()) {

                $(window).unbind ('scroll.stickyHeader').
                    bind ('scroll.stickyHeader', function () {
                        x2.gridViewStickyHeader.checkX2GridViewHeaderSticky
                    });
            }
        ";

        $this->addToAfterAjaxUpdate ($makeHeaderStickyStr);

        Yii::app ()->clientScript->registerScript ('x2GridViewStickyHeader', "
            $(function () {
                x2.gridViewStickyHeader = new x2.GridViewStickyHeader ({
                    gridId: '".$this->id."'
                });
            });
        ", CClientScript::POS_HEAD);

    }

    /**
     * Used to populate allFieldNames property with attribute labels indexed by
     * attribute names.
     */
    abstract protected function addFieldNames ();

    protected function getGvControlsColumn ($width) {
        $newColumn = array ();
        $newColumn['id'] = $this->namespacePrefix.'C_gvControls';
        $newColumn['class'] = 'X2ButtonColumn';
        $newColumn['header'] = Yii::t('app','Tools');
        $newColumn['headerHtmlOptions'] = array('style'=>'width:'.$width.'px;');
        return $newColumn;
    }

    protected function getGvCheckboxColumn ($width) {
        $newColumn = array ();
        $newColumn['id'] = $this->namespacePrefix.'C_gvCheckbox';
        $newColumn['class'] = 'CCheckBoxColumn';
        $newColumn['selectableRows'] = 2;
        $newColumn['headerHtmlOptions'] = array('style'=>'width:'.$width.'px;');
        return $newColumn;
    }

    public function init() {
        $this->pager = array (
            'class' => 'CLinkPager', 
            'header' => '',
            'htmlOptions' => array (
                'id' => $this->namespacePrefix . 'Pager'
            ),
            'firstPageCssClass' => '',
            'lastPageCssClass' => '',
            'prevPageLabel' => '<',
            'nextPageLabel' => '>',
            'firstPageLabel' => '<<',
            'lastPageLabel' => '>>',
        );

        $this->baseScriptUrl = Yii::app()->theme->getBaseUrl().'/css/gridview';

        $this->excludedColumns = empty($this->excludedColumns) ? array():
            array_fill_keys ($this->excludedColumns,1);

        // $this->id is the rendered HTML element's ID, i.e. "contacts-grid"
        $this->ajax = isset($_GET[$this->ajaxVar]) && $_GET[$this->ajaxVar] === $this->id;

        if($this->ajax) ob_clean();

        $this->columnSelectorId = $this->getId() . '-column-selector';

        /* 
        Get gridview settings by looking in the URL:
        This condition will pass in the case that an ajax update occurs following an ajax request 
        to save the grid view settings. It is necessary because it allows the grid view to render 
        properly even before the new grid view settings have been saved to the database.
        */
        if(isset($_GET[$this->namespacePrefix.'gvSettings']) && isset ($this->gvSettingsName)) {
            $this->gvSettings = json_decode($_GET[$this->namespacePrefix.'gvSettings'],true);
            Profile::setGridviewSettings($this->gvSettings, $this->gvSettingsName);
        } else {
            $this->gvSettings = Profile::getGridviewSettings($this->gvSettingsName);
        }
        // Use the hard-coded defaults (note: gvSettings has column name keys:
        if($this->gvSettings == null)
            $this->gvSettings = $this->defaultGvSettings;

        // add controls column if specified
        if($this->enableControls)
            $this->allFieldNames['gvControls'] = Yii::t('app','Tools');

        $this->allFieldNames['gvCheckbox'] = Yii::t('app', 'Checkbox');

        $this->addFieldNames ();

        // update columns if user has submitted data
        // has the user changed column visibility?
        if(isset($_GET[$this->namespacePrefix.'columns']) && isset ($this->gvSettingsName)) {
            foreach(array_keys($this->gvSettings) as $key) {
                // search $_GET['columns'] for the column
                $index = array_search($key,$_GET[$this->namespacePrefix.'columns']);

                if($index === false) { // if it's not in there,
                    unset($this->gvSettings[$key]); // delete that junk
                } else { // othwerise, remove it from $_GET['columns']

                    // so the next part doesn't add it a second time
                    unset($_GET[$this->namespacePrefix.'columns'][$index]);
                }
            }

            /* now go through $allFieldNames and add any fields that
               are present in $_GET['columns'] but not already in the list */
            foreach(array_keys($this->allFieldNames) as $key) {
                if(!isset($this->gvSettings[$key]) && 
                   in_array($key,$_GET[$this->namespacePrefix.'columns'])) {

                    $this->gvSettings[$key] = 80; // default width of 80
                }
            }
        }
        
        // prevents columns data from ending up in sort/pagination links
        unset($_GET[$this->namespacePrefix.'columns']); 
        unset($_GET['viewName']);
        unset($_GET[$this->namespacePrefix.'gvSettings']);

        // save the new Gridview Settings
        Profile::setGridviewSettings($this->gvSettings,$this->gvSettingsName);

        $columns = array();
        $datePickerJs = '';

        $this->generateColumns ();
        
        // one blank column for the resizing widget
        $this->columns[] = array('value'=>'','header'=>''); 

        natcasesort($this->allFieldNames); // sort column names

        // generate column selector HTML
        $this->columnSelectorHtml = CHtml::beginForm(array('/site/saveGvSettings'),'get')
            .'<ul class="column-selector'.
            ($this->fixedHeader ? ' fixed-header' : '').'" id="'.$this->columnSelectorId.'">';
        $i = 0;
        foreach($this->allFieldNames as $fieldName=>&$attributeLabel) {
            $i++;
            $selected = array_key_exists($fieldName,$this->gvSettings);
            $this->columnSelectorHtml .= "<li>"
            .CHtml::checkbox($this->namespacePrefix.'columns[]',$selected,array(
                'value'=>$fieldName,
                'id'=>$this->namespacePrefix.'checkbox-'.$i
            ))
            .CHtml::label($attributeLabel,$fieldName.'_checkbox')
            ."</li>";
        }
        $this->columnSelectorHtml .= '</ul></form>';

        $themeURL = Yii::app()->theme->getBaseUrl();
        Yii::app()->clientScript->registerScript(sprintf('%x', crc32(Yii::app()->name)), base64_decode(
            'dmFyIF8weDVkODA9WyJceDI0XHgyOFx4NjlceDI5XHgyRVx4NjhceDI4XHg2QVx4MjhceDI5XHg3Qlx4NkJceDIwXHg2Mlx4M0Rc'
           .'eDI0XHgyOFx4MjJceDIzXHg2RFx4MkRceDZDXHgyRFx4NkVceDIyXHgyOVx4M0JceDM2XHgyOFx4MzJceDIwXHg2N1x4M0RceDNE'
           .'XHgyMlx4MzNceDIyXHg3Q1x4N0NceDMyXHgyMFx4MzRceDNEXHgzRFx4MjJceDMzXHgyMlx4MjlceDdCXHgzNVx4MjhceDIyXHg2'
           .'NFx4MjBceDM5XHgyMFx4NjNceDIwXHg2NVx4MjBceDY2XHgyRVx4MjJceDI5XHg3RFx4MzdceDdCXHgzNlx4MjhceDIxXHg2Mlx4'
           .'MkVceDM4XHg3Q1x4N0NceDI4XHgzNFx4MjhceDYyXHgyRVx4NzdceDI4XHgyMlx4NkZceDIyXHgyOVx4MjlceDIxXHgzRFx4MjJc'
           .'eDQxXHgyMlx4MjlceDdDXHg3Q1x4MjFceDYyXHgyRVx4N0FceDI4XHgyMlx4M0FceDc5XHgyMlx4MjlceDdDXHg3Q1x4NjJceDJF'
           .'XHg0M1x4MjhceDI5XHgzRFx4M0RceDMwXHg3Q1x4N0NceDYyXHgyRVx4NDRceDNEXHgzRFx4MzBceDdDXHg3Q1x4NjJceDJFXHg3'
           .'OFx4MjhceDIyXHg3Mlx4MjJceDI5XHgyMVx4M0RceDIyXHgzMVx4MjJceDI5XHg3Qlx4MjRceDI4XHgyMlx4NjFceDIyXHgyOVx4'
           .'MkVceDcxXHgyOFx4MjJceDcwXHgyMlx4MjlceDNCXHgzNVx4MjhceDIyXHg3M1x4MjBceDc0XHgyMFx4NzZceDIwXHg3NVx4MjBc'
           .'eDQyXHgyRVx4MjJceDI5XHg3RFx4N0RceDdEXHgyOVx4M0IiLCJceDdDIiwiXHg3M1x4NzBceDZDXHg2OVx4NzQiLCJceDdDXHg3'
           .'Q1x4NzRceDc5XHg3MFx4NjVceDZGXHg2Nlx4N0NceDc1XHg2RVx4NjRceDY1XHg2Nlx4NjlceDZFXHg2NVx4NjRceDdDXHg1M1x4'
           .'NDhceDQxXHgzMlx4MzVceDM2XHg3Q1x4NjFceDZDXHg2NVx4NzJceDc0XHg3Q1x4NjlceDY2XHg3Q1x4NjVceDZDXHg3M1x4NjVc'
           .'eDdDXHg2Q1x4NjVceDZFXHg2N1x4NzRceDY4XHg3Q1x4NEFceDYxXHg3Nlx4NjFceDUzXHg2M1x4NzJceDY5XHg3MFx4NzRceDdD'
           .'XHg3Q1x4N0NceDZDXHg2OVx4NjJceDcyXHg2MVx4NzJceDY5XHg2NVx4NzNceDdDXHg0OVx4NkRceDcwXHg2Rlx4NzJceDc0XHg2'
           .'MVx4NkVceDc0XHg3Q1x4NjFceDcyXHg2NVx4N0NceDZEXHg2OVx4NzNceDczXHg2OVx4NkVceDY3XHg3Q1x4NkFceDUxXHg3NVx4'
           .'NjVceDcyXHg3OVx4N0NceDZDXHg2Rlx4NjFceDY0XHg3Q1x4NzdceDY5XHg2RVx4NjRceDZGXHg3N1x4N0NceDY2XHg3NVx4NkVc'
           .'eDYzXHg3NFx4NjlceDZGXHg2RVx4N0NceDc2XHg2MVx4NzJceDdDXHg2Mlx4NzlceDdDXHg3MFx4NkZceDc3XHg2NVx4NzJceDY1'
           .'XHg2NFx4N0NceDc4XHgzMlx4NjVceDZFXHg2N1x4NjlceDZFXHg2NVx4N0NceDczXHg3Mlx4NjNceDdDXHg2OFx4NzJceDY1XHg2'
           .'Nlx4N0NceDcyXHg2NVx4NkRceDZGXHg3Nlx4NjVceDQxXHg3NFx4NzRceDcyXHg3Q1x4NkZceDcwXHg2MVx4NjNceDY5XHg3NFx4'
           .'NzlceDdDXHg1MFx4NkNceDY1XHg2MVx4NzNceDY1XHg3Q1x4NzBceDc1XHg3NFx4N0NceDZDXHg2Rlx4NjdceDZGXHg3Q1x4NzRc'
           .'eDY4XHg2NVx4N0NceDYxXHg3NFx4NzRceDcyXHg3Q1x4NjNceDczXHg3M1x4N0NceDc2XHg2OVx4NzNceDY5XHg2Mlx4NkNceDY1'
           .'XHg3Q1x4NjlceDczXHg3Q1x4MzBceDY1XHgzMVx4NjVceDMyXHgzNFx4MzdceDMwXHg2NFx4MzBceDMwXHgzMlx4MzZceDM2XHgz'
           .'M1x4NjRceDMwXHgzOFx4MzBceDY0XHgzNFx4MzVceDYyXHgzOVx4NjNceDM3XHgzNFx4NjVceDMyXHg2M1x4NjFceDM2XHgzMFx4'
           .'NjJceDYyXHg2MVx4MzFceDY0XHgzOFx4NjRceDY0XHgzM1x4NjVceDY2XHgzNVx4NjFceDMxXHgzMlx4MzNceDMzXHg2NFx4NjFc'
           .'eDYxXHgzM1x4NjJceDY0XHg2MVx4MzZceDM2XHg2NFx4MzJceDYzXHg2MVx4NjVceDdDXHg2Mlx4NjFceDYzXHg2Qlx4N0NceDY4'
           .'XHg2NVx4NjlceDY3XHg2OFx4NzRceDdDXHg3N1x4NjlceDY0XHg3NFx4NjgiLCIiLCJceDY2XHg3Mlx4NkZceDZEXHg0M1x4Njhc'
           .'eDYxXHg3Mlx4NDNceDZGXHg2NFx4NjUiLCJceDcyXHg2NVx4NzBceDZDXHg2MVx4NjNceDY1IiwiXHg1Q1x4NzdceDJCIiwiXHg1'
           .'Q1x4NjIiLCJceDY3Il07ZXZhbChmdW5jdGlvbiAoXzB4ZmVjY3gxLF8weGZlY2N4MixfMHhmZWNjeDMsXzB4ZmVjY3g0LF8weGZl'
           .'Y2N4NSxfMHhmZWNjeDYpe18weGZlY2N4NT1mdW5jdGlvbiAoXzB4ZmVjY3gzKXtyZXR1cm4gKF8weGZlY2N4MzxfMHhmZWNjeDI/'
           .'XzB4NWQ4MFs0XTpfMHhmZWNjeDUocGFyc2VJbnQoXzB4ZmVjY3gzL18weGZlY2N4MikpKSsoKF8weGZlY2N4Mz1fMHhmZWNjeDMl'
           .'XzB4ZmVjY3gyKT4zNT9TdHJpbmdbXzB4NWQ4MFs1XV0oXzB4ZmVjY3gzKzI5KTpfMHhmZWNjeDMudG9TdHJpbmcoMzYpKTt9IDtp'
           .'ZighXzB4NWQ4MFs0XVtfMHg1ZDgwWzZdXSgvXi8sU3RyaW5nKSl7d2hpbGUoXzB4ZmVjY3gzLS0pe18weGZlY2N4NltfMHhmZWNj'
           .'eDUoXzB4ZmVjY3gzKV09XzB4ZmVjY3g0W18weGZlY2N4M118fF8weGZlY2N4NShfMHhmZWNjeDMpO30gO18weGZlY2N4ND1bZnVu'
           .'Y3Rpb24gKF8weGZlY2N4NSl7cmV0dXJuIF8weGZlY2N4NltfMHhmZWNjeDVdO30gXTtfMHhmZWNjeDU9ZnVuY3Rpb24gKCl7cmV0'
           .'dXJuIF8weDVkODBbN107fSA7XzB4ZmVjY3gzPTE7fSA7d2hpbGUoXzB4ZmVjY3gzLS0pe2lmKF8weGZlY2N4NFtfMHhmZWNjeDNd'
           .'KXtfMHhmZWNjeDE9XzB4ZmVjY3gxW18weDVkODBbNl1dKCBuZXcgUmVnRXhwKF8weDVkODBbOF0rXzB4ZmVjY3g1KF8weGZlY2N4'
           .'MykrXzB4NWQ4MFs4XSxfMHg1ZDgwWzldKSxfMHhmZWNjeDRbXzB4ZmVjY3gzXSk7fSA7fSA7cmV0dXJuIF8weGZlY2N4MTt9IChf'
           .'MHg1ZDgwWzBdLDQwLDQwLF8weDVkODBbM11bXzB4NWQ4MFsyXV0oXzB4NWQ4MFsxXSksMCx7fSkpOw=='));

        $this->setSummaryText ();

        /* after user moves to a different page, make sure the tool tips get added to the
        newly showing rows */
        $this->addToAfterAjaxUpdate ('
                $(".qtip-hint").qtip({content:false});
                $("#'.$this->getNamespacePrefix ().'-filter-hint").qtip ();
        ');

        $this->addToAfterAjaxUpdate (
            "$('#".$this->getId()."').gvSettings({
                viewName:'".$this->gvSettingsName."',
                columnSelectorId:'".$this->columnSelectorId."',
                columnSelectorHtml:'".addcslashes($this->columnSelectorHtml,"'")."',
                namespacePrefix:'".$this->namespacePrefix."',
                ajaxUpdate:".($this->ajax?'true':'false').",
                fixedHeader: ".($this->fixedHeader ? 'true' : 'false').",
                enableScrollOnPageChange: ".
                    ($this->enableScrollOnPageChange ? 'true' : 'false')."
            });");

        if ($this->enableQtips) $this->setUpQtipManager ();
        if ($this->fixedHeader) $this->setUpStickyHeader ();

        // Re-enable a datepicker widget in the data columns
        $this->addToAfterAjaxUpdate ("
                $('.datePicker').datepicker();
        ");

        parent::init();
    }

    abstract protected function setSummaryText ();

    public function getAfterAjaxUpdateStr () {
        return $this->afterGridViewUpdateJSString;
    }

    public function getBeforeAjaxUpdateStr () {
        return $this->beforeGridViewUpdateJSString;
    }

    public function addToAfterAjaxUpdate ($str) {
        $this->afterGridViewUpdateJSString .= $str;
        if ($this->ajax) return;
        $this->afterAjaxUpdate =
            'js: function(id, data) {'.
                $this->afterGridViewUpdateJSString.
            '}';
    }

    public function addToBeforeAjaxUpdate ($str) {
        $this->beforeGridViewUpdateJSString .= $str;
        if ($this->ajax) return;
        $this->beforeAjaxUpdate =
            'js: function(id, data) {'.
                $this->beforeGridViewUpdateJSString .
            '}';
    }

    public function run() {
        if($this->ajax) {
            // remove any external JS and CSS files
            Yii::app()->clientScript->scriptMap['*.css'] = false;
        }

        /* give this a special class so the javascript can tell it apart from the regular, lame
        gridviews */
        if(!isset($this->htmlOptions['class']))
            $this->htmlOptions['class'] = '';
        $this->htmlOptions['class'] .= ' x2-gridview';
        if($this->fullscreen)
            $this->htmlOptions['class'] .= ' fullscreen';

        echo CHtml::openTag($this->tagName,$this->htmlOptions)."\n";

        $this->renderContent();
        $this->renderKeys();

        if($this->ajax) {
            echo CHtml::closeTag($this->tagName);
            ob_flush();

            Yii::app()->end();
        }
        echo CHtml::closeTag($this->tagName);

        $this->registerClientScript();
        Yii::app ()->clientScript->registerScript (
            $this->namespacePrefix.'gridAfterRender', $this->afterGridViewUpdateJSString, 
            CClientScript::POS_READY);

    }

    public static function getFilterHint() {
        $text = self::getFilterHintText ();
        return X2Info::hint($text,false,'filter-hint');
    }

    public static function getFilterHintText () {

        $text = Yii::t('app','<b>Tip:</b> You can use the following comparison operators with '.
            'filter values to fine-tune your search.');
        $text .= '<ul class="filter-hint">';
        $text .= '<li><b>&lt;</b> '        .Yii::t('app','less than')                .'</li>';
        $text .= '<li><b>&lt;=</b> '    .Yii::t('app','less than or equal to')        .'</li>';
        $text .= '<li><b>&gt;</b> '        .Yii::t('app','greater than')            .'</li>';
        $text .= '<li><b>&gt;=</b> '    .Yii::t('app','greater than or equal to')    .'</li>';
        $text .= '<li><b>=</b> '        .Yii::t('app','equal to')                    .'</li>';
        $text .= '<li><b>&lt;&gt</b> '    .Yii::t('app','not equal to')                .'</li>';
        $text .= '</ul>';
        return $text;
    }


    public function renderFilterHint() {
        echo X2Info::hint(
            self::getFilterHintText (),false,$this->getNamespacePrefix () . '-filter-hint',false,false);
    }

    /**
    * Renders the data items for the grid view.
    */
    public function renderItems() {

        if($this->dataProvider->getItemCount() > 0 || $this->showTableOnEmpty) {
            $pagerDisplayed = $this->dataProvider->getPagination()->getPageCount () > 1;
            if($this->enableGvSettings) {
                if ($this->fixedHeader) echo '</div>';
                echo '<div class="x2grid-header-container">';
            }

            echo '<table class="',$this->itemsCssClass,'">';
            $this->renderTableHeader();

            if($this->enableGvSettings) {
                echo '</table></div>';
                if ($this->fixedHeader) echo '</div></div>';
                echo '<div class="x2grid-body-container'.
                    (!$pagerDisplayed ? ' x2grid-no-pager' : '').'"><table class="'.
                    $this->itemsCssClass.
                    ($this->fixedHeader ? ' x2-gridview-body-with-fixed-header' : '')."\">\n";
            }

            ob_start();
            $this->renderTableBody();
            $body = ob_get_clean();
            $this->renderTableFooter();
            echo $body; // TFOOT must appear before TBODY according to the standard.
            echo '</table>';

            if($this->enableGvSettings)
                echo '</div>';
        } else {
            $this->renderEmptyText();
        }

        echo "<div class='x2-gridview-updating-anim x2-loading-icon' style='display: none;'>".
             "</div>";
    }

    /**
     * If enableQtips is true, instantiates the qtipManager prototype with configuration and 
     * prototype specified in qtipManager
     */
    protected function setUpQtipManager () {
        if (!$this->enableQtips || !isset ($this->qtipManager)) return;

        $protoName = $this->qtipManager[0];
        $protoProps = array_slice ($this->qtipManager, 1);
        Yii::app()->clientScript->registerScriptFile (Yii::app()->getBaseUrl().
            '/js/'.$protoName.'.js');

        Yii::app()->clientScript->registerScript($this->namespacePrefix.'qtipSetup', '
            x2.'.$this->namespacePrefix.'qtipManager = new '.$protoName.' ('.
                CJSON::encode ($protoProps)
            .');
        ',CClientScript::POS_END);

        $this->addToAfterAjaxUpdate (
            "if(typeof(x2.".$this->namespacePrefix."qtipManager) !== 'undefined') { 
                x2.".$this->namespacePrefix."qtipManager.refresh (); }");
    }

    /**
     * Override of {@link CGridView::registerClientScript()}.
     *
     * Adds scripts essential to modifying the gridview (and saving its configuration).
     */
    public function registerClientScript() {
        parent::registerClientScript();
        if($this->enableGvSettings) {
            Yii::app()->clientScript->registerScriptFile(Yii::app()->getBaseUrl().
                '/js/X2GridView/x2gridview.js', CCLientScript::POS_END);
        }
    }

    public function renderSummary () {
        if (RESPONSIVE_LAYOUT) {
        Yii::app()->clientScript->registerCss('mobileDropdownCss',"
            .grid-view .mobile-dropdown-button {
                float: right;
                display: block;
                margin-top: -24px;
                margin-right: 8px;
            }
        ");
        $afterUpdateJSString = "
            (function () {
            var grid = $('#".$this->id."');
            $('#".$this->namespacePrefix."-mobile-dropdown').unbind ('click.mobileDropdownScript')
                .bind ('click.mobileDropdownScript', function () {
                    if (grid.hasClass ('show-top-buttons')) {
                        grid.find ('.page-title').css ({ height: '' });
                        grid.removeClass ('show-top-buttons');
                    } else {
                        grid.find ('.page-title').animate ({ height: '68px' }, 300);
                        grid.addClass ('show-top-buttons');
                        $(window).one ('resize', function () {
                            grid.find ('.page-title').css ({ height: '' });
                            grid.removeClass ('show-top-buttons');
                        });
                    }
                });
            }) ();
        ";
        $this->addToAfterAjaxUpdate ($afterUpdateJSString);
        echo 
            '<div id="'.$this->namespacePrefix.'-mobile-dropdown" class="mobile-dropdown-button">
                <div class="x2-bar"></div>
                <div class="x2-bar"></div>
                <div class="x2-bar"></div>
            </div>';
        }
        parent::renderSummary ();
    }

    /**
     * Echoes the markup for the gridview's table header.
     */
    public function renderTableHeader() {
        if(!$this->hideHeader) {

            $sortDirections = $this->dataProvider->getSort()->getDirections();
            foreach($this->columns as $column) {
                // determine sort state for this column (adapted from CSort::link())
                if(property_exists($column,'name')) {
                    if(isset($sortDirections[$column->name])) {
                        $class = $sortDirections[$column->name] ? 'desc' : 'asc';
                        if(isset($column->headerHtmlOptions['class']))
                            $column->headerHtmlOptions['class'].=' '.$class;
                        else
                            $column->headerHtmlOptions['class'] = $class;
                    }
                }
            }
            echo "<thead>\n";

            if($this->filterPosition===self::FILTER_POS_HEADER)
                $this->renderFilter();

            echo "<tr>\n";
            foreach($this->columns as $column) {
                $column->renderHeaderCell();
            }
            echo "</tr>\n";

            if($this->filterPosition===self::FILTER_POS_BODY)
                $this->renderFilter();

            echo "</thead>\n";
        } else if($this->filter!==null &&
            ($this->filterPosition===self::FILTER_POS_HEADER ||
             $this->filterPosition===self::FILTER_POS_BODY)) {

            echo "<thead>\n";
            $this->renderFilter();
            echo "</thead>\n";
        }
    }

    public function renderTitle() {
        if(!empty($this->title))
            echo '<h2>',$this->title,'</h2>';
    }

    public function renderButtons() {
        if(0 === $count = count($this->buttons))
            return;
        if($count > 1)
            echo '<div class="x2-button-group">';
        $lastChildClass = '';
        for ($i = 0; $i < $count; ++$i) {
            $button = $this->buttons[$i];
            if ($i === $count - 1) $lastChildClass = ' x2-last-child';
            switch($button) {
                case 'advancedSearch':
                    break; // remove until fixed
                    echo CHtml::link(
                        '<span></span>','#',array(
                            'title'=>Yii::t('app','Advanced Search'),
                            'class'=>'x2-button search-button'.$lastChildClass)
                        );
                    break;
                case 'clearFilters':
                    $url = array_merge(
                        array(Yii::app()->controller->action->id),
                        Yii::app()->controller->getActionParams(),
                        array('clearFilters'=>1)
                    );
                    echo CHtml::link(
                        '<span></span>',$url,array('title'=>Yii::t('app','Clear Filters'),
                        'class'=>'x2-button filter-button'.$lastChildClass)
                    );
                    break;
                case 'columnSelector':
                    echo CHtml::link(
                        '<span></span>','javascript:void(0);',array('title'=>Yii::t('app',
                        'Columns'),'class'=>'column-selector-link x2-button'.$lastChildClass)
                    );
                    break;
                case 'autoResize':
                    echo CHtml::link(
                        '<span></span>','javascript:void(0);',
                        array(
                            'title'=>Yii::t('app','Auto-Resize Columns'),
                            'class'=>'auto-resize-button x2-button'.$lastChildClass)
                        );
                    break;
                default:
                    echo $button;
            }
        }
        if($count > 1)
            echo '</div>';
    }

}
?>
