qcubed/framework

View on GitHub
includes/base_controls/QHtmlTableColumn.class.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

    /**
     * Represents a column for a QHtmlTable. Different subclasses (see below) allow accessing and fetching the data
     * for each cells in a variety of ways
     *
     * @property string                 $Name           name of the column
     * @property string                 $CssClass       CSS class of the column. This will be applied to every cell in the column. Use ColStyper
     *                                                     to set the class for the actual 'col' tag if using col tags.
     * @property string                 $HeaderCssClass CSS class of the column's cells when it's rendered in a table header
     * @property boolean                $HtmlEntities   if true, cell values will be converted using htmlentities()
     * @property boolean                $RenderAsHeader if true, all cells in the column will be rendered with a <<th>> tag instead of <<td>>
     * @property integer                $Id             HTML id attribute to put in the col tag
     * @property integer                $Span           HTML span attribute to put in the col tag
     * @property-read QHtmlTableBase  $ParentTable    parent table of the column
     * @property-write QHtmlTableBase $_ParentTable   Parent table of this column
     * @property-write callable $CellParamsCallback A callback to set the html parameters of a generated cell
     * @property boolean                $Visible        Whether the column will be drawn. Defaults to true.
     * @property-read QTagStyler        $CellStyler        The tag styler for the cells in the column
     * @property-read QTagStyler        $HeaderCellStyler        The tag styler for the header cells in the column
     * @property-read QTagStyler        $ColStyler        The tag styler for the col tag in the column
     */
    abstract class QAbstractHtmlTableColumn extends QBaseClass {
        /** @var string */
        protected $strName;
        /** @var string */
        protected $strCssClass = null;
        /** @var string */
        protected $strHeaderCssClass = null;
        /** @var boolean */
        protected $blnHtmlEntities = true;
        /** @var boolean */
        protected $blnRenderAsHeader = false;
        /** @var QHtmlTableBase */
        protected $objParentTable = null;
        /** @var integer */
        protected $intSpan = 1;
        /** @var string optional id for column tag rendering and datatables */
        protected $strId = null;
        /** @var bool Easy way to hide a column without removing the column. */
        protected $blnVisible = true;
        /** @var callable Callback to modify the html attributes of the generated cell. */
        protected $cellParamsCallback = null;
        /** @var QTagStyler Styles for each cell. Usually this should be done in css for efficient code generation. */
        protected $objCellStyler;
        /** @var QTagStyler Styles for each header cell. Usually this should be done in css for efficient code generation. */
        protected $objHeaderCellStyler;
        /** @var QTagStyler Styles for each col. Usually this should be done in css for efficient code generation. */
        protected $objColStyler;



        /**
         * @param string $strName Name of the column
         */
        public function __construct($strName) {
            $this->strName = $strName;
        }

        /**
         * 
         * Render the header cell including opening and closing tags. 
         * 
         * This will be called by the data table if ShowHeader is on, and will only
         * be called for the top line item.
         * 
         */
        public function RenderHeaderCell() {
            if (!$this->blnVisible) return '';

            $cellValue = $this->FetchHeaderCellValue();
            if ($this->blnHtmlEntities)
                $cellValue = QApplication::HtmlEntities($cellValue);
            if ($cellValue == '' && QApplication::IsBrowser(QBrowserType::InternetExplorer)) {
                $cellValue = '&nbsp;';
            }
            
            return QHtml::RenderTag('th', $this->GetHeaderCellParams(), $cellValue);
        }
        
        /**
         * Returns the text to print in the header cell, if one is to be drawn. Override if you want
         * something other than the default.
         */
        public function FetchHeaderCellValue() {
            return $this->strName;
        }

        /**
         * Returns an array of key/value pairs to insert as parameters in the header cell. Override and add
         * more if you need them.
         * @return array
         */
        public function GetHeaderCellParams () {
            $aParams['scope'] = 'col';
            if ($this->strHeaderCssClass) {
                $aParams['class'] = $this->strHeaderCssClass;
            }
            if ($this->objHeaderCellStyler) {
                $aParams = $this->objHeaderCellStyler->GetHtmlAttributes($aParams);
            }
            return $aParams;        
        }
        
        /**
         * Render a cell.
         * Called by data table for each cell. Override and call with $blnHeader = true if you want
         * this individual cell to render with <<th>> tags instead of <<td>>.
         *
         * @param mixed   $item
         * @param boolean $blnAsHeader
         *
         * @return string
         */
        public function RenderCell($item, $blnAsHeader = false) {
            if (!$this->blnVisible) return '';

            $cellValue = $this->FetchCellValue($item);
            if ($this->blnHtmlEntities)
                $cellValue = QApplication::HtmlEntities($cellValue);
            if ($cellValue == '' && QApplication::IsBrowser(QBrowserType::InternetExplorer)) {
                $cellValue = '&nbsp;';
            }
    
            if ($blnAsHeader || $this->blnRenderAsHeader) {
                $strTag = 'th';
            } else {
                $strTag = 'td';
            }
            
            return QHtml::RenderTag($strTag, $this->GetCellParams($item), $cellValue);
        }

        /**
         * Return a key/val array of items to insert inside the cell tag.
         * Handles class, style, and id already. Override to add additional items, like an onclick handler.
         *
         * @param mixed $item
         *
         * @return array
         */
        protected function GetCellParams ($item) {
            $aParams = array();

            if ($strClass = $this->GetCellClass ($item)) {
                $aParams['class'] = $strClass;
            }
            
            if ($strId = $this->GetCellId ($item)) {
                $aParams['id'] = $strId;
            }
            
            if ($this->blnRenderAsHeader) {
                // assume this means it is a row header
                $aParams['scope'] = 'row';
            }

            $strStyle = $this->GetCellStyle ($item);

            if ($this->objCellStyler) {
                $aStyles = null;
                if ($strStyle) {
                    $aStyles = explode (';', $strStyle);
                }
                $aParams = $this->objCellStyler->GetHtmlAttributes($aParams, $aStyles);
            } elseif ($strStyle) {
                $aParams['style'] = $strStyle;
            }

            if ($this->cellParamsCallback) {
                $a = call_user_func($this->cellParamsCallback, $item);
                $aParams = array_merge ($aParams, $a);
            }

            return $aParams;        
        }
        
        /**
         * Return the class of the cell.
         *
         * @param mixed $item
         *
         * @return string
         */
        protected function GetCellClass ($item) {
            if ($this->strCssClass) {
                return $this->strCssClass;
            }
            return '';
        }
        
        /**
         * Return the id of the cell.
         *
         * @param mixed $item
         *
         * @return string
         */
        protected function GetCellId ($item) {
            return '';
        }
        
        /**
         * Return the style string for the cell.
         *
         * @param mixed $item
         *
         * @return string
         */
        protected function GetCellStyle ($item) {
            return '';
        }
        
        /**
         * Return the raw string that represents the cell value. 
         * 
         * @param mixed $item
         */
        abstract public function FetchCellValue($item);

        /**
         * Render the column tag.
         * This special tag can control specific features of columns, but is generally optional on a table.
         *
         * @return string
         */
        public function RenderColTag() {
            return QHtml::RenderTag('col', $this->GetColParams(), null, true);
        }

        /**
         * Return a key/value array of parameters to put in the col tag.
         * Override to add parameters.
         */
        protected function GetColParams () {
            $aParams = array();
            if ($this->intSpan > 1) {
                $aParams['span'] = $this->intSpan;
            }
            if ($this->strId) {
                $aParams['id'] = $this->strId;
            }

            if ($this->objColStyler) {
                $aParams = $this->objColStyler->GetHtmlAttributes($aParams);
            }

            return $aParams;        
        }

        /**
         * Prepare to serialize references to the form.
         */
        public function Sleep() {
            $this->cellParamsCallback = QControl::SleepHelper($this->cellParamsCallback);
        }

        /**
         * The object has been unserialized, so fix up pointers to embedded objects.
         * @param QForm $objForm
         */
        public function Wakeup(QForm $objForm) {
            $this->cellParamsCallback = QControl::WakeupHelper($objForm, $this->cellParamsCallback);
        }

        /**
         * Override to check for post data in your column if needed.
         */
        public function ParsePostData() {
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         *
         * @return bool|int|mixed|QHtmlTableBase|string
         * @throws Exception
         * @throws QCallerException
         */
        public function __get($strName) {
            switch ($strName) {
                case 'Name':
                    return $this->strName;
                case 'CssClass':
                    return $this->strCssClass;
                case 'HeaderCssClass':
                    return $this->strHeaderCssClass;
                case 'HtmlEntities':
                    return $this->blnHtmlEntities;
                case 'RenderAsHeader':
                    return $this->blnRenderAsHeader;
                case 'ParentTable':
                    return $this->objParentTable;
                case 'Span':
                    return $this->intSpan;
                case 'Id':
                    return $this->strId;
                case 'Visible':
                    return $this->blnVisible;
                case 'CellStyler':
                    if (!$this->objCellStyler) {
                        $this->objCellStyler = new QTagStyler();
                    }
                    return $this->objCellStyler;
                case 'HeaderCellStyler':
                    if (!$this->objHeaderCellStyler) {
                        $this->objHeaderCellStyler = new QTagStyler();
                    }
                    return $this->objHeaderCellStyler;
                case 'ColStyler':
                    if (!$this->objColStyler) {
                        $this->objColStyler = new QTagStyler();
                    }
                    return $this->objColStyler;


                default:
                    try {
                        return parent::__get($strName);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }

        /**
         * PHP Magic method
         *
         * @param string $strName
         * @param string $mixValue
         *
         * @return mixed|void
         * @throws Exception
         * @throws QCallerException
         * @throws QInvalidCastException
         */
        public function __set($strName, $mixValue) {
            switch ($strName) {
                case "Name":
                    try {
                        $this->strName = QType::Cast($mixValue, QType::String);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "CssClass":
                    try {
                        $this->strCssClass = QType::Cast($mixValue, QType::String);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "HeaderCssClass":
                    try {
                        $this->strHeaderCssClass = QType::Cast($mixValue, QType::String);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "HtmlEntities":
                    try {
                        $this->blnHtmlEntities = QType::Cast($mixValue, QType::Boolean);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
                    
                case "RenderAsHeader":
                    try {
                        $this->blnRenderAsHeader = QType::Cast($mixValue, QType::Boolean);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
                    
                case "Span":
                    try {
                        $this->intSpan = QType::Cast($mixValue, QType::Integer);
                        if ($this->intSpan < 1) {
                            throw new Exception("Span must be 1 or greater.");
                        }
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "Id":
                    try {
                        $this->strId = QType::Cast($mixValue, QType::String);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "Visible":
                    try {
                        $this->blnVisible = QType::Cast($mixValue, QType::Boolean);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "CellParamsCallback":
                    $this->cellParamsCallback = QType::Cast($mixValue, QType::CallableType);
                    break;

                case "_ParentTable":
                    try {
                        $this->objParentTable = QType::Cast($mixValue, 'QHtmlTableBase');
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                default:
                    try {
                        parent::__set($strName, $mixValue);
                        break;
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }
    }
    
    /**
     * An abstract column designed to work with QDataGrid and other tables that require more than basic columns.
     * Supports post processing of cell contents for further formatting, and OrderBy clauses.
     *
     * @property mixed          $OrderByClause        order by info for sorting the column in ascending order. Used by subclasses.
     *    Most often this is a QQ::Clause, but can be any data needed.
     * @property mixed          $ReverseOrderByClause order by info for sorting the column in descending order.
     * @property string         $Format               the default format to use for FetchCellValueFormatted(). Used by QDataTables plugin.
     *    For date columns it should be a format accepted by QDateTime::qFormat()
     * @property-write string   $PostMethod           after the cell object is retrieved, call this method on the obtained object
     * @property-write callback $PostCallback         after the cell object is retrieved, call this callback on the obtained object.
     *    If $PostMethod is also set, this will be called after that method call.
     */
    abstract class QAbstractHtmlTableDataColumn extends QAbstractHtmlTableColumn {
        /** @var mixed Order By information. Can be a QQ::Clause, or any kind of object depending on your need */
        protected $objOrderByClause = null;
        /** @var mixed */
        protected $objReverseOrderByClause = null;
        /** @var string */
        protected $strFormat = null;
        /** @var string */
        protected $strPostMethod = null;
        /** @var callback */
        protected $objPostCallback = null;

        /**
         * Return the raw string that represents the cell value.
         * This version uses a combination of post processing strategies so that you can set
         * column options to format the raw data. If no
         * options are set, then $item will just pass through, or __toString() will be called
         * if its an object. If none of these work for you, just override FetchCellObject and
         * return your formatted string from there.
         *
         * @param mixed $item
         *
         * @return mixed|string
         */
        public function FetchCellValue($item) {
            $cellValue = $this->FetchCellObject($item);
                        
            if ($cellValue !== null && $this->strPostMethod) {
                $strPostMethod = $this->strPostMethod;
                assert ('is_callable([$cellValue, $strPostMethod])');    // Malformed post method, or the item is not an object
                $cellValue = $cellValue->$strPostMethod();
            }
            if ($this->objPostCallback) {
                $cellValue = call_user_func($this->objPostCallback, $cellValue);
            }
            if ($cellValue === null) {
                return '';
            }

            if ($cellValue instanceof QDateTime) {
                return $cellValue->qFormat($this->strFormat);
            }
            if (is_object($cellValue)) {
                $cellValue = $cellValue->__toString();
            }
            if ($this->strFormat)
                return sprintf($this->strFormat, $cellValue);

            return $cellValue;
        }

        /**
         * Return the value of the cell. FetchCellValue will process this more if needed.
         * Default returns an entire data row and relies on FetchCellValue to extract the needed data.
         * 
         * @param mixed $item
         */
        abstract public function FetchCellObject($item);

        /**
         * Fix up possible embedded reference to the form.
         */
        public function Sleep() {
            $this->objPostCallback = QControl::SleepHelper($this->objPostCallback);
            parent::Sleep();
        }

        /**
         * The object has been unserialized, so fix up pointers to embedded objects.
         * @param QForm $objForm
         */
        public function Wakeup(QForm $objForm) {
            parent::Wakeup($objForm);
            $this->objPostCallback = QControl::WakeupHelper($objForm, $this->objPostCallback);
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         *
         * @return bool|int|mixed|QHtmlTableBase|string
         * @throws Exception
         * @throws QCallerException
         */
        public function __get($strName) {
            switch ($strName) {
                case "OrderByClause":
                    return $this->objOrderByClause;
                case "ReverseOrderByClause":
                    return $this->objReverseOrderByClause;
                case "Format":
                    return $this->strFormat;

                default:
                    try {
                        return parent::__get($strName);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         * @param string $mixValue
         *
         * @return mixed|void
         * @throws Exception
         * @throws QCallerException
         * @throws QInvalidCastException
         */
        public function __set($strName, $mixValue) {
            switch ($strName) {
                case "OrderByClause":
                    try {
                        $this->objOrderByClause = $mixValue;
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "ReverseOrderByClause":
                    try {
                        $this->objReverseOrderByClause = $mixValue;
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "Format":
                    try {
                        $this->strFormat = QType::Cast($mixValue, QType::String);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "PostMethod":
                    try {
                        $this->strPostMethod = QType::Cast($mixValue, QType::String);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "PostCallback":
                    $this->objPostCallback = $mixValue;
                    break;

                default:
                    try {
                        parent::__set($strName, $mixValue);
                        break;
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }
    }

    /**
     * Displays a  property of an object, as in $object->Property
     * If your DataSource is an array of objects, use this column to display a particular property of each object.
     * Can search with depth to, as in $obj->Prop1->Prop2.
     *
     * @property string  $Property the property to use when accessing the objects in the DataSource array. Can be a s
     *  series of properties separated with '->', i.e. 'Prop1->Prop2->Prop3' will find the Prop3 item inside the Prop2 object,
     *  inside the Prop1 object, inside the current object.
     * @property boolean $NullSafe if true the value fetcher will check for nulls before accessing the properties
     */
    class QHtmlTablePropertyColumn extends QAbstractHtmlTableDataColumn {
        protected $strProperty;
        protected $strPropertiesArray;
        protected $blnNullSafe = true;

        /**
         * @param string      $strName     name of the column
         * @param string|null $strProperty the property name to use when accessing the DataSource row object.
         *                                 Can be null, in which case object will have the ->__toString() function called on it.
         * @param QQNode      $objBaseNode if not null, the OrderBy and ReverseOrderBy clauses will be created using the property path and the given database node
         */
        public function __construct($strName, $strProperty, $objBaseNode = null) {
            parent::__construct($strName);
            $this->Property = $strProperty;

            if ($objBaseNode != null) {
                foreach ($this->strPropertiesArray as $strProperty) {
                    $objBaseNode = $objBaseNode->$strProperty;
                }

                $this->OrderByClause = QQ::OrderBy($objBaseNode);
                $this->ReverseOrderByClause = QQ::OrderBy($objBaseNode, 'desc');
            }
        }

        public function FetchCellObject($item) {
            if ($this->blnNullSafe && $item == null)
                return null;
            foreach ($this->strPropertiesArray as $strProperty) {
                $item = $item->$strProperty;
                if ($this->blnNullSafe && $item == null)
                    break;
            }
            return $item;
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         *
         * @return bool|int|mixed|QHtmlTableBase|string
         * @throws Exception
         * @throws QCallerException
         */
        public function __get($strName) {
            switch ($strName) {
                case 'Property':
                    return $this->strProperty;
                case 'NullSafe':
                    return $this->blnNullSafe;
                default:
                    try {
                        return parent::__get($strName);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         * @param string $mixValue
         *
         * @return mixed|void
         * @throws Exception
         * @throws QCallerException
         * @throws QInvalidCastException
         */
        public function __set($strName, $mixValue) {
            switch ($strName) {
                case "Property":
                    try {
                        $this->strProperty = QType::Cast($mixValue, QType::String);
                        $this->strPropertiesArray = $this->strProperty ? explode('->', $this->strProperty) : array();
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "NullSafe":
                    try {
                        $this->blnNullSafe = QType::Cast($mixValue, QType::Boolean);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                default:
                    try {
                        parent::__set($strName, $mixValue);
                        break;
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }
    }

    /**
     * Class QHtmlTableNodeColumn
     *
     * A table column that displays the content of a database column represented by a QQNode object.
     * The $objNodes can be a single node, or an array of nodes. If an array of nodes, the first
     * node will be the display node, and the rest of the nodes will be used for sorting.
     */
    class QHtmlTableNodeColumn extends QHtmlTablePropertyColumn {
        public function __construct($strName, $objNodes) {
            if ($objNodes instanceof QQNode) {
                $objNodes = [$objNodes];
            }
            elseif (empty($objNodes) || !is_array($objNodes) || !$objNodes[0] instanceof QQNode) {
                throw new QCallerException('Pass either a QQNode or an array of QQNodes only');
            }

            $objNode = $objNodes[0]; // First node is the data node, the rest are for sorting.

            if (!$objNode->_ParentNode) {
                throw new QCallerException('First QQNode cannot be a Top Level Node');
            }
            if (($objNode instanceof QQReverseReferenceNode) && !$objNode->IsUnique()) {
                throw new QCallerException('Content QQNode cannot go through any "To Many" association nodes.');
            }

            $properties = array($objNode->_PropertyName);
            while ($objNode = $objNode->_ParentNode) {
                if (!($objNode instanceof QQNode))
                    throw new QCallerException('QQNode cannot go through any "To Many" association nodes.');
                if (($objNode instanceof QQReverseReferenceNode) && !$objNode->IsUnique())
                    throw new QCallerException('QQNode cannot go through any "To Many" association nodes.');
                if ($strPropName = $objNode->_PropertyName) {
                    $properties[] = $strPropName;
                }
            }
            $properties = array_reverse($properties);
            $strProp = implode ('->', $properties);
            parent::__construct($strName, $strProp, null);

            // build sort nodes
            foreach ($objNodes as $objNode) {
                if ($objNode instanceof QQReverseReferenceNode) {
                    $objNode = $objNode->_PrimaryKeyNode;
                }
                $objSortNodes[] = $objNode;
                $objReverseNodes[] = $objNode;
                $objReverseNodes[] = false;
            }

            $this->OrderByClause = QQ::OrderBy($objSortNodes);
            $this->ReverseOrderByClause = QQ::OrderBy($objReverseNodes);
        }
    }


    /**
     * A type of column that should be used when the DataSource items are arrays
     *
     * @property int|string $Index the index or key to use when accessing the arrays in the DataSource array
     *
     */
    class QHtmlTableIndexedColumn extends QAbstractHtmlTableDataColumn {
        protected $mixIndex;

        /**
         * @param string $strName name of the column
         * @param int|string $mixIndex the index or key to use when accessing the DataSource row array
         */
        public function __construct($strName, $mixIndex) {
            parent::__construct($strName);
            $this->mixIndex = $mixIndex;
        }

        public function FetchCellObject($item) {
            if (isset ($item[$this->mixIndex])) {
                return $item[$this->mixIndex];
            } else {
                return '';
            }
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         *
         * @return bool|int|mixed|QHtmlTableBase|string
         * @throws Exception
         * @throws QCallerException
         */
        public function __get($strName) {
            switch ($strName) {
                case 'Index':
                    return $this->mixIndex;
                default:
                    try {
                        return parent::__get($strName);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         * @param string $mixValue
         *
         * @return mixed|void
         * @throws Exception
         * @throws QCallerException
         */
        public function __set($strName, $mixValue) {
            switch ($strName) {
                case "Index":
                    $this->mixIndex = $mixValue;
                    break;

                default:
                    try {
                        parent::__set($strName, $mixValue);
                        break;
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }
    }

    /**
     * A type of column that lets you use a PHP 'callable'. However, you CANNOT send a PHP closure to this,
     * since closures are not serializable. You CAN do things like array($this, 'method'), or 'Class::StaticMethod'.
     *
     * @property int|string $Index the index or key to use when accessing the arrays in the DataSource array
     *
     */
    class QHtmlTableCallableColumn extends QAbstractHtmlTableDataColumn {
        /** @var callback */
        protected $callback;
        /** @var array extra parameters passed to closure */
        protected $mixParams;

        /**
         * @param string $strName name of the column
         * @param callback $objCallable a callable object. It should take a single argument, the item
         *   of the array. Do NOT pass an actual Closure object, as they are not serializable. However,
         *   you can pass a callable, like array($this, 'method'), or an object that has the __invoke method defined,
         *   as long as its serializable. You can also pass static methods as a string, as in "Class::method"
         * @param mixed $mixParams extra parameters to pass to the closure callback.
         * will be called with the row of the DataSource as that single argument.
         *
         * @throws InvalidArgumentException
         */
        public function __construct($strName, callable $objCallable, $mixParams = null) {
            parent::__construct($strName);
            if ($objCallable instanceof Closure) {
                throw new InvalidArgumentException('Cannot be a Closure.');
            }
            $this->callback = $objCallable;
            $this->mixParams = $mixParams;
        }

        public function FetchCellObject($item) {
            if ($this->mixParams) {
                return call_user_func($this->callback, $item, $this->mixParams);
            } else {
                return call_user_func($this->callback, $item);
            }
        }

        /**
         * Fix up possible embedded reference to the form.
         */
        public function Sleep() {
            $this->callback = QControl::SleepHelper($this->callback);
            parent::Sleep();
        }

        /**
         * Restore serialized references.
         * @param QForm $objForm
         */
        public function Wakeup(QForm $objForm) {
            parent::Wakeup($objForm);
            $this->callback = QControl::WakeupHelper($objForm, $this->callback);
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         *
         * @return bool|callable|int|mixed|QHtmlTableBase|string
         * @throws Exception
         * @throws QCallerException
         */
        public function __get($strName) {
            switch ($strName) {
                case 'Callable':
                    return $this->callback;
                default:
                    try {
                        return parent::__get($strName);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         * @param string $mixValue
         *
         * @return mixed|void
         * @throws Exception
         * @throws QCallerException
         * @throws QInvalidCastException
         */
        public function __set($strName, $mixValue) {
            switch ($strName) {
                case "Callable":
                    $this->callback = QType::Cast($mixValue, QType::CallableType);
                    break;

                default:
                    try {
                        parent::__set($strName, $mixValue);
                        break;
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }
    }
    
    /**
     * 
     * A column to display a virtual attribute from a database record.
     *
     * @property string $Attribute
     */
    class QVirtualAttributeColumn extends QAbstractHtmlTableDataColumn {
        protected $strAttribute;
        
        public function __construct($strName, $strAttribute = null) {
            parent::__construct($strName);
            if ($strAttribute) {
                $this->strAttribute = $strAttribute;
            }

            $this->OrderByClause = QQ::OrderBy(QQ::Virtual($strAttribute));
            $this->ReverseOrderByClause = QQ::OrderBy(QQ::Virtual($strAttribute), false);

        }
        
        public function FetchCellObject($item) {
            return $item->GetVirtualAttribute ($this->strAttribute);
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         *
         * @return bool|int|mixed|null|QHtmlTableBase|string
         * @throws Exception
         * @throws QCallerException
         */
        public function __get($strName) {
            switch ($strName) {
                case 'Attribute':
                    return $this->strAttribute;
                default:
                    try {
                        return parent::__get($strName);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         * @param string $mixValue
         *
         * @return mixed|void
         * @throws Exception
         * @throws QCallerException
         * @throws QInvalidCastException
         */
        public function __set($strName, $mixValue) {
            switch ($strName) {
                case "Attribute":
                    $this->strAttribute = QType::Cast ($mixValue, QType::String);
                    break;

                default:
                    try {
                        parent::__set($strName, $mixValue);
                        break;
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }
        
    }
    
    /**
     * 
     * A column of checkboxes. 
     * 
     * Prints checkboxes in a column, including the header. Override this class and implement whatever hooks you need. In
     * particular implement the CheckId hooks, and IsChecked hooks.
     *
     * To get the checkbox values to post values back to PHP, each checkbox must have an id of the form:
     *
     * QcontrolId_index
     *
     * This class does not detect and record changes in the checkbox list. You can detect changes from within
     * ParsePostData by calling $this->objForm->CheckableControlValue,
     * or use the QHtmlTableCheckBoxColumn_ClickEvent to detect a change to a checkbox.
     *
     * You will need to detect whether
     * the header check all box was clicked, or a regular box was clicked and respond accordingly. In response to a
     * click, you could store the array of ids of the checkboxes clicked in a session variable, the database, or
     * a cache variable. You would just give an id to each checkbox. This would cause internet traffic every time
     * a box is clicked.
     *
     * @property bool $ShowCheckAll
     *
     */
    class QHtmlTableCheckBoxColumn extends QAbstractHtmlTableDataColumn {
        protected $blnHtmlEntities = false;    // turn off html entities
        protected $checkParamCallback = null;
        protected $blnShowCheckAll = false;

        /**
         * Returns a header cell with a checkbox. This could be used as a check all box. Override this and return
         * an empty string to turn it off.
         *
         * @return string
         */
        public function FetchHeaderCellValue() {
            if ($this->blnShowCheckAll) {
                $aParams = $this->GetCheckboxParams(null);
                $aParams['type'] = 'checkbox';
                return QHtml::RenderTag('input', $aParams, null, true);
            } else {
                return $this->Name;
            }
        }

        public function FetchCellObject($item) {
            $aParams = $this->GetCheckboxParams($item);
            $aParams['type'] = 'checkbox';
            return QHtml::RenderTag('input', $aParams, null, true);
        }

        /**
         * Returns an array of parameters to attach to the checkbox tag. Includes whether the
         * checkbox should appear as checked. Will try the callback first, and if not present,
         * will try overridden functions.
         * 
         * @param mixed|null $item    Null to indicate that we want the params for the header cell.
         * @return array
         */
        public function GetCheckboxParams ($item) {
            $aParams = array();
            
            if ($strId = $this->GetCheckboxId ($item)) {
                $aParams['id'] = $strId;
            }
            
            if ($this->IsChecked ($item)) {
                $aParams['checked'] = 'checked';
            }

            if ($strName = $this->GetCheckboxName ($item)) {
                $aParams['name'] = $strName; // name is not used by QCubed
            }

            $aParams['value'] = $this->GetCheckboxValue ($item); // note that value is required for html checkboxes, but is not used by QCubed

            if ($this->checkParamCallback) {
                $a = call_user_func($this->checkParamCallback, $item);
                $aParams = array_merge ($aParams, $a);
            }
            
            return $aParams;        
        }

        /**
         * Optional callback to control the appearance of the checkboxes. You can use a callback, or subclass to do this.
         * If a callback, it should be of the form:
         *     func($item)
         *
         *     $item is either the line item, or null to indicate the header
         *
         * This should return the following values in an array to indicate what should be put as attributes for the checkbox tag:
         *     id
         *  name
         *  value
         *  checked (only return a value here if you want it checked. Otherwise, do not include in the array)
         *
         *  See below for a description of what should be returned for each item.
         *
         * @param $callable
         */
        public function SetCheckParamCallback ($callable) {
            $this->checkParamCallback = $callable;
        }
        
        /**
         * Returns the id for the checkbox itself. This is used together with the check action to send the item
         * id to the action. Your id should look like:
         *
         *
         *
         * @param mixed|null $item    Null to get the id for the header checkbox
         */
        protected function GetCheckboxId ($item) {
            return null;
        }

        /**
         * Return true if the checkbox should be drawn checked. Override this to provide the correct value.
         * @param mixed|null $item    Null to get the id for the header checkbox
         * @return bool
         */
        protected function IsChecked ($item) {
            return false;
        }

        /**
         * Return the name attribute for the checkbox. If you return null, the checkbox will not get submitted to the form.
         * If you return a name, then that will be the key for the value submitted by the form. If you return a name
         * ending with brackets [], then this checkbox will be part of an array of values posted to that name.
         *
         * @param mixed|null $item    Null to get the id for the header checkbox
         * @return null|string
         */
        protected function GetCheckboxName ($item) {
            return null;
        }

        /**
         * Return the value attribute of the checkbox tag. Checkboxes are required to have a value in html.
         * This value will be what is posted by form post.
         *
         * @param mixed|null $item    Null to get the id for the header checkbox
         * @return string
         */
        protected function GetCheckboxValue ($item) {
            return "1"; // Means that if the checkbox is checked, the POST value corresponding to the name of the checkbox will be 1.
        }

        /**
         * Fix up possible embedded reference to the form.
         */
        public function Sleep() {
            $this->checkParamCallback = QControl::SleepHelper($this->checkParamCallback);
            parent::Sleep();
        }

        /**
         * Restore embedded objects.
         * 
         * @param QForm $objForm
         */
        public function Wakeup(QForm $objForm) {
            parent::Wakeup($objForm);
            $this->checkParamCallback = QControl::WakeupHelper($objForm, $this->checkParamCallback);
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         *
         * @return bool|int|mixed|QHtmlTableBase|string
         * @throws Exception
         * @throws QCallerException
         */
        public function __get($strName) {
            switch ($strName) {
                case 'ShowCheckAll':
                    return $this->blnShowCheckAll;
                default:
                    try {
                        return parent::__get($strName);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         * @param string $mixValue
         *
         * @return mixed|void
         * @throws Exception
         * @throws QCallerException
         * @throws QInvalidCastException
         */
        public function __set($strName, $mixValue) {
            switch ($strName) {
                case "ShowCheckAll":
                    try {
                        $this->blnShowCheckAll = QType::Cast($mixValue, QType::Boolean);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                default:
                    try {
                        parent::__set($strName, $mixValue);
                        break;
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }


    }


    class QHtmlTableCheckBoxColumn_ClickEvent extends QClickEvent {
        const JsReturnParam = '{"row": $j(this).closest("tr")[0].rowIndex, "col": $j(this).closest("th,td")[0].cellIndex, "checked":this.checked, "id":this.id}'; // returns the array of cell info, and the new state of the checkbox

        public function __construct($intDelay = 0, $strCondition = null) {
            parent::__construct($intDelay, $strCondition, 'input[type="checkbox"]');
        }
    }

    /**
     * Class QHtmlTableLinkColumn
     *
     * A multi-purpose link column. This column lets you specify a column whose purpose is to show an anchor tag
     * with text, attributes and properties related to row item. It can handle row items that are objects or arrays,
     * and specify parameters or methods of objects, as well as offsets in arrays.
     *
     * You can specify the text of the link, the destination address, the html get variables, and the attributes
     * to the anchor tag in a variety of ways as follows:
     * - as a static string
     * - as a two member array callable, with the row item passed to the callable
     * - as an object property or string of properties (i.e. $item->prop1->prop2) by starting the string with "->" and
     *   separating each property with a "->". If the property ends with "()", then it will be a method call instead.
     *   The same can be accomplished by passing an array, with each item being a step in the property chain. This
     *   is provided the row item is an object.
     * - as an index into an array, or a multi-index array (i.e. $item['index1']['index2']) by passing a string of the
     *   form "[index1][index2]...". You can also pass an array that contains the indexes into the array. This is provided
     *   the row item is an array.
     *
     * Other options:
     * - Specify null for $mixDestination, and no link will be created, just text. This is helpful for turning off the
     *   link mode without having to create a completely different kind of column.
     * - Specify a QControlProxy for $mixDestination to draw it as a proxy control. In this case, $blnAsButton can be
     *   used to draw the proxy as a button rather than a link.
     *
     * Examples:
     *
     *  Create a column to edit a person, with "Edit" in the header, the name of the person as the label of each link, and give each
     *   anchor a class of "link".
     *  $objColumn = new QHtmlTableLinkColumn ("Edit", "->Name", "person_edit.php", ["intId"=>"->Id"], ["class"=>"link"]);
     *
     *
     *  Create a similar column, but use a proxy instead, with the person id as the action parameter to the proxy and
     *   drawing the proxy as a button.
     *  $objProxy = new QControlProxy($this);
     *  $objColumn = new QHtmlTableLinkColumn ("Edit", "Edit", $objProxy, "->Id", null, true);
     *
     *  Create a "zoom" column for a table that uses an array of arrays as its source. Pass the 'id' index from the item
     *   as the id to the destination link. Use the "title" index as the label for the link.
     *  $objColumn = new QHtmlTableLinkColumn ("Zoom", "[title]", "zoom.php", ["intId"=>"[id]"]);
     *
     *  Create a simple link column that just specifies a data attribute, and uses event delegation attached to the table to trap a click on the link.
     *   Return the id of the item clicked to the action as the action parameter.
     *  $objTable = new QHtmlTable ($this);
     *  $objTable->CreateLinkColumn("", "->Name", "#", null, ["data-id"=>"->Id"]);
     *  $objTable->AddAction(new QClickEvent(0, null, "a"), new QAjaxAction("myActionScript", null, null, '$j(this).data("id")'));
     *
     * @property bool $AsButton    Only used if this is drawing a QControlProxy. Will draw the proxy as a button.
     * @property-write null|string|array $Text The text to display as the label of the anchor, a callable callback to get the text,
     *   a string that represents a property chain or a multi-dimensional array, or an array that represents the same. Depends on
     *   what time of row item is passed.
     * @property-write null|string|array|QControlProxy $Destination The text representing the destination of the anchor, a callable callback to get the destination,
     *   a string that represents a property chain or a multi-dimensional array, or an array that represents the same,
     *   or a QControlProxy. Depends on what time of row item is passed.
     * @property-write null|string|array $GetVars An array of key=>value pairs to use as the GET variables in the link URL,
     *   or in the case of a QControlProxy, possibly a string to represent the action parameter. In either case, each item
     *   can be a property chain, an array index list, or a callable callback as specified above.
     * @property-write null|array $TagAttributes An array of key=>value pairs to use as additional attributes in the tag.
     *   For example, could be used to add a class or an id to each tag.
     */
    class QHtmlTableLinkColumn extends QAbstractHtmlTableDataColumn {
        /** @var bool  */
        protected $blnHtmlEntities = false;    // we are rendering a link so turn off entities

        /** @var  string|array */
        protected $mixText;
        /** @var  string|array|QControlProxy|null */
        protected $mixDestination;
        /** @var  array|string|null */
        protected $getVars;
        /** @var  array|null */
        protected $tagAttributes;
        /** @var bool  */
        protected $blnAsButton;

        /**
         * QHtmlTableLinkColumn constructor.
         *
         * @param string $strName Column name to be displayed in the table header.
         * @param null|string|array|QQNode $mixText The text to display as the label of the anchor, a callable callback to get the text,
         *   a string that represents a property chain or a multi-dimensional array, or an array that represents the same, or a QQNode representing the property.
         *   Depends on what type of row item is passed.
         * @param null|string|array|QControlProxy $mixDestination The text representing the destination of the anchor, a callable callback to get the destination,
         *   a string that represents a property chain or a multi-dimensional array, or an array that represents the same,
         *   or a QControlProxy. Depends on what type of row item is passed.
         * @param null|string|array $getVars An array of key=>value pairs to use as the GET variables in the link URL,
         *   or in the case of a QControlProxy, possibly a string to represent the action parameter. In either case, each item
         *   can be a property chain, an array index list, a QQNode, or a callable callback as specified above. If the destination is a
         *   QControlProxy, this would be what to use as the action parameter.
         * @param null|array $tagAttributes An array of key=>value pairs to use as additional attributes in the tag.
         *   For example, could be used to add a class or an id to each tag.
         * @param bool $blnAsButton Only used if this is drawing a QControlProxy. Will draw the proxy as a button.
         */
        public function __construct($strName, $mixText, $mixDestination = null, $getVars = null, $tagAttributes = null, $blnAsButton = false) {
            parent::__construct($strName);
            $this->Text = $mixText;
            $this->Destination = $mixDestination;
            $this->GetVars = $getVars;
            $this->TagAttributes = $tagAttributes;
            $this->blnAsButton = $blnAsButton;
        }

        /**
         * Utility function to pre-process a value specifier. This will take a property list chain or an array index
         * chain and split it into an array representing the parts.
         *
         * @param mixed $mixSpec
         * @return mixed
         */
        protected static function SplitSpec ($mixSpec)
        {
            if (is_array($mixSpec)) {
                return $mixSpec; // already split
            } elseif (is_string($mixSpec)) {
                if (strpos($mixSpec, '->') === 0) {
                    // It is an object property list ($item->param1->param2)
                    $parts = explode('->', substr($mixSpec, 2));
                    return $parts;
                } elseif ($mixSpec[0] == '[' && substr($mixSpec, -1) == ']') {
                    // It is a list of array dereferences
                    $parts = explode('][', $mixSpec, substr(1, strlen($mixSpec) - 2));
                    return $parts;
                }
                else {
                    return $mixSpec;
                }
            } else {
                return $mixSpec;
            }
        }


        /**
         * Utility function to post-process a value specifier. Will walk through an object property chain or an array
         * index chain and return the final value.
         *
         * @param mixed $mixSpec
         * @param mixed $item
         * @return string
         */
        protected static function GetObjectValue ($mixSpec, $item) {
            if (is_array($mixSpec)) {
                if (is_object($mixSpec[0]) && is_callable($mixSpec)) {
                    // If its a callable array, then call it
                    return call_user_func($mixSpec, $item);
                }
                elseif (is_object($item)) {
                    // It is an object property list ($item->param1->param2 or $item->method()->method2()). Can mix these too.
                    $value = $item;
                    foreach ($mixSpec as $part) {
                        // Evaluate as a function, or a param
                        if (substr($part,-2) == '()') {
                            // call as a method
                            $value = $value->$part();
                        } else {
                            $value = $value->$part;
                        }
                    }
                    return $value;
                }
                elseif (is_array($item)) {
                    $value = $item;
                    foreach ($mixSpec as $part) {
                        $value = $value[$part];
                    }
                    return $value;
                }
                else {
                    return $item; // We have no idea what this is, so return the item for possible further processing
                }
            }
            elseif ($mixSpec instanceof QQNode) {
                $properties = array($mixSpec->_PropertyName);
                $objNode = $mixSpec;
                while ($objNode = $objNode->_ParentNode) {
                    if (!($objNode instanceof QQNode))
                        throw new QCallerException('QQNode cannot go through any "To Many" association nodes.');
                    if (($objNode instanceof QQReverseReferenceNode) && !$objNode->IsUnique())
                        throw new QCallerException('QQNode cannot go through any "To Many" association nodes.');
                    if ($strPropName = $objNode->_PropertyName) {
                        $properties[] = $strPropName;
                    }
                }
                $properties = array_reverse($properties);
                $value = $item;
                foreach ($properties as $prop) {
                    $value = $value->$prop;
                }
                if (is_object($value)) {
                    return $value->__toString();
                }
                else {
                    return $value;
                }
            }
            return $mixSpec; // In this case, we return a static value
        }

        /**
         * Returns the initial text that will be the label of the link. This text can be further processed by using
         * the inherited PostCallback function and similar properties.
         *
         * @param mixed $item
         * @return string
         */
        public function FetchCellObject($item)
        {
            return static::GetObjectValue($this->mixText, $item);
        }

        /**
         * Returns the final string representing the content of the cell.
         *
         * @param mixed $item
         * @return string
         */
        public function FetchCellValue($item) {
            $strText = parent::FetchCellValue($item);    // allow post processing of cell label

            $getVars = null;
            if ($this->getVars) {
                if (is_array($this->getVars)) {
                    if (array_keys($this->getVars)[0] === 0) {
                        // assume this is not associative array. Likely we are here to extract a property list.
                        $getVars = static::GetObjectValue($this->getVars, $item);
                    }
                    else {
                        // associative array, so likely these are Get variables to be assigned individually
                        foreach ($this->getVars as $key => $val) {
                            $getVars[$key] = static::GetObjectValue($val, $item);
                        }
                    }
                }
                elseif ($this->getVars instanceof QQNode) {
                    $getVars = static::GetObjectValue($this->getVars, $item);
                }
                else {
                    $getVars = $this->getVars; // could be a simple action parameter.
                }
            }

            $tagAttributes = [];
            if ($this->tagAttributes && is_array($this->tagAttributes)) {
                foreach ($this->tagAttributes as $key=>$val) {
                    $tagAttributes[$key] = static::GetObjectValue($val, $item);
                }
            }

            if ($this->mixDestination === null) {
                return QApplication::HtmlEntities($strText);
            }
            elseif ($this->mixDestination instanceof QControlProxy) {
                if ($this->blnAsButton) {
                    return $this->mixDestination->RenderAsButton($strText, $getVars, $tagAttributes);
                } else {
                    return $this->mixDestination->RenderAsLink($strText, $getVars, $tagAttributes);
                }
            }
            else {
                $strDestination = static::GetObjectValue($this->mixDestination, $item);
                return QHtml::RenderLink(QHtml::MakeUrl($strDestination, $getVars), $strText, $tagAttributes);
            }
        }

        /**
         * Fix up possible embedded references to the form.
         */
        public function Sleep() {
            $this->mixText = QControl::SleepHelper($this->mixText);
            $this->mixDestination = QControl::SleepHelper($this->mixDestination);
            $this->getVars = QControl::SleepHelper($this->getVars);
            $this->tagAttributes = QControl::SleepHelper($this->tagAttributes);
            parent::Sleep();
        }

        /**
         * Restore embedded objects.
         *
         * @param QForm $objForm
         */
        public function Wakeup(QForm $objForm) {
            parent::Wakeup($objForm);
            $this->mixText = QControl::WakeupHelper($objForm, $this->mixText);
            $this->mixDestination = QControl::WakeupHelper($objForm, $this->mixDestination);
            $this->getVars = QControl::WakeupHelper($objForm, $this->getVars);
            $this->tagAttributes = QControl::WakeupHelper($objForm, $this->tagAttributes);
        }


        /**
         * PHP magic method
         *
         * @param string $strName
         *
         * @return bool|int|mixed|QHtmlTableBase|string
         * @throws Exception
         * @throws QCallerException
         */
        public function __get($strName) {
            switch ($strName) {
                case 'AsButton':
                    return $this->blnAsButton;
                default:
                    try {
                        return parent::__get($strName);
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }

        /**
         * PHP magic method
         *
         * @param string $strName
         * @param string $mixValue
         *
         * @return mixed|void
         * @throws Exception
         * @throws QCallerException
         * @throws QInvalidCastException
         */
        public function __set($strName, $mixValue) {
            switch ($strName) {
                case "AsButton":
                    try {
                        $this->blnAsButton = QType::Cast($mixValue, QType::Boolean);
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "Text":
                    $this->mixText = self::SplitSpec($mixValue);
                    break;

                case "Destination":
                    if ($mixValue instanceof QControlProxy) {
                        $this->mixDestination = $mixValue;
                    } else {
                        $this->mixDestination = self::SplitSpec($mixValue);
                    }
                    break;

                case "GetVars":
                    try {
                        if (is_null($mixValue)) {
                            $this->getVars = null;
                        }
                        elseif (is_string($mixValue)) {
                            $this->getVars = self::SplitSpec($mixValue); // a simple action parameter for a control proxy
                        }
                        elseif (is_array($mixValue)) {
                            $this->getVars = [];
                            foreach ($mixValue as $key=>$val) {
                                $this->getVars[$key] = self::SplitSpec($val);
                            }
                        }
                        elseif ($mixValue instanceof QQNode) {
                            $this->getVars = $mixValue;
                        }
                        else {
                            throw new Exception ("Invalid type");
                        }
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                case "TagAttributes":
                    try {
                        if (is_null($mixValue)) {
                            $this->tagAttributes = null;
                        }
                        elseif (is_array($mixValue)) {
                            $this->tagAttributes = [];
                            foreach ($mixValue as $key=>$val) {
                                $this->tagAttributes[$key] = self::SplitSpec($val);
                            }
                        }
                        else {
                            throw new Exception ("Invalid type");
                        }
                        break;
                    } catch (QInvalidCastException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }

                default:
                    try {
                        parent::__set($strName, $mixValue);
                        break;
                    } catch (QCallerException $objExc) {
                        $objExc->IncrementOffset();
                        throw $objExc;
                    }
            }
        }



    }