+});/** \r
+ * @class Ext.ux.grid.filter.ListFilter\r
+ * @extends Ext.ux.grid.filter.Filter\r
+ * <p>List filters are able to be preloaded/backed by an Ext.data.Store to load\r
+ * their options the first time they are shown. ListFilter utilizes the\r
+ * {@link Ext.ux.menu.ListMenu} component.</p>\r
+ * <p>Although not shown here, this class accepts all configuration options\r
+ * for {@link Ext.ux.menu.ListMenu}.</p>\r
+ * \r
+ * <p><b><u>Example Usage:</u></b></p>\r
+ * <pre><code> \r
+var filters = new Ext.ux.grid.GridFilters({\r
+ ...\r
+ filters: [{\r
+ type: 'list',\r
+ dataIndex: 'size',\r
+ phpMode: true,\r
+ // options will be used as data to implicitly creates an ArrayStore\r
+ options: ['extra small', 'small', 'medium', 'large', 'extra large']\r
+ }]\r
+});\r
+ * </code></pre>\r
+ * \r
+ */\r
+Ext.ux.grid.filter.ListFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
+\r
+ /**\r
+ * @cfg {Array} options\r
+ * <p><code>data</code> to be used to implicitly create a data store\r
+ * to back this list when the data source is <b>local</b>. If the\r
+ * data for the list is remote, use the <code>{@link #store}</code>\r
+ * config instead.</p>\r
+ * <br><p>Each item within the provided array may be in one of the\r
+ * following formats:</p>\r
+ * <div class="mdetail-params"><ul>\r
+ * <li><b>Array</b> :\r
+ * <pre><code>\r
+options: [\r
+ [11, 'extra small'], \r
+ [18, 'small'],\r
+ [22, 'medium'],\r
+ [35, 'large'],\r
+ [44, 'extra large']\r
+]\r
+ * </code></pre>\r
+ * </li>\r
+ * <li><b>Object</b> :\r
+ * <pre><code>\r
+labelField: 'name', // override default of 'text'\r
+options: [\r
+ {id: 11, name:'extra small'}, \r
+ {id: 18, name:'small'}, \r
+ {id: 22, name:'medium'}, \r
+ {id: 35, name:'large'}, \r
+ {id: 44, name:'extra large'} \r
+]\r
+ * </code></pre>\r
+ * </li>\r
+ * <li><b>String</b> :\r
+ * <pre><code>\r
+ * options: ['extra small', 'small', 'medium', 'large', 'extra large']\r
+ * </code></pre>\r
+ * </li>\r
+ */\r
+ /**\r
+ * @cfg {Boolean} phpMode\r
+ * <p>Adjust the format of this filter. Defaults to false.</p>\r
+ * <br><p>When GridFilters <code>@cfg encode = false</code> (default):</p>\r
+ * <pre><code>\r
+// phpMode == false (default):\r
+filter[0][data][type] list\r
+filter[0][data][value] value1\r
+filter[0][data][value] value2\r
+filter[0][field] prod \r
+\r
+// phpMode == true:\r
+filter[0][data][type] list\r
+filter[0][data][value] value1, value2\r
+filter[0][field] prod \r
+ * </code></pre>\r
+ * When GridFilters <code>@cfg encode = true</code>:\r
+ * <pre><code>\r
+// phpMode == false (default):\r
+filter : [{"type":"list","value":["small","medium"],"field":"size"}]\r
+\r
+// phpMode == true:\r
+filter : [{"type":"list","value":"small,medium","field":"size"}]\r
+ * </code></pre>\r
+ */\r
+ phpMode : false,\r
+ /**\r
+ * @cfg {Ext.data.Store} store\r
+ * The {@link Ext.data.Store} this list should use as its data source\r
+ * when the data source is <b>remote</b>. If the data for the list\r
+ * is local, use the <code>{@link #options}</code> config instead.\r
+ */\r
+\r
+ /** \r
+ * @private\r
+ * Template method that is to initialize the filter and install required menu items.\r
+ * @param {Object} config\r
+ */\r
+ init : function (config) {\r
+ this.dt = new Ext.util.DelayedTask(this.fireUpdate, this);\r
+\r
+ // if a menu already existed, do clean up first\r
+ if (this.menu){\r
+ this.menu.destroy();\r
+ }\r
+ this.menu = new Ext.ux.menu.ListMenu(config);\r
+ this.menu.on('checkchange', this.onCheckChange, this);\r
+ },\r
+ \r
+ /**\r
+ * @private\r
+ * Template method that is to get and return the value of the filter.\r
+ * @return {String} The value of this filter\r
+ */\r
+ getValue : function () {\r
+ return this.menu.getSelected();\r
+ },\r
+ /**\r
+ * @private\r
+ * Template method that is to set the value of the filter.\r
+ * @param {Object} value The value to set the filter\r
+ */ \r
+ setValue : function (value) {\r
+ this.menu.setSelected(value);\r
+ this.fireEvent('update', this);\r
+ },\r
+\r
+ /**\r
+ * @private\r
+ * Template method that is to return <tt>true</tt> if the filter\r
+ * has enough configuration information to be activated.\r
+ * @return {Boolean}\r
+ */\r
+ isActivatable : function () {\r
+ return this.getValue().length > 0;\r
+ },\r
+ \r
+ /**\r
+ * @private\r
+ * Template method that is to get and return serialized filter data for\r
+ * transmission to the server.\r
+ * @return {Object/Array} An object or collection of objects containing\r
+ * key value pairs representing the current configuration of the filter.\r
+ */\r
+ getSerialArgs : function () {\r
+ var args = {type: 'list', value: this.phpMode ? this.getValue().join(',') : this.getValue()};\r
+ return args;\r
+ },\r
+\r
+ /** @private */\r
+ onCheckChange : function(){\r
+ this.dt.delay(this.updateBuffer);\r
+ },\r
+ \r
+ \r
+ /**\r
+ * Template method that is to validate the provided Ext.data.Record\r
+ * against the filters configuration.\r
+ * @param {Ext.data.Record} record The record to validate\r
+ * @return {Boolean} true if the record is valid within the bounds\r
+ * of the filter, false otherwise.\r
+ */\r
+ validateRecord : function (record) {\r
+ return this.getValue().indexOf(record.get(this.dataIndex)) > -1;\r
+ }\r
+});/** \r
+ * @class Ext.ux.grid.filter.NumericFilter\r
+ * @extends Ext.ux.grid.filter.Filter\r
+ * Filters using an Ext.ux.menu.RangeMenu.\r
+ * <p><b><u>Example Usage:</u></b></p>\r
+ * <pre><code> \r
+var filters = new Ext.ux.grid.GridFilters({\r
+ ...\r
+ filters: [{\r
+ type: 'numeric',\r
+ dataIndex: 'price'\r
+ }]\r
+});\r
+ * </code></pre> \r
+ */\r
+Ext.ux.grid.filter.NumericFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
+\r
+ /**\r
+ * @cfg {Object} fieldCls\r
+ * The Class to use to construct each field item within this menu\r
+ * Defaults to:<pre>\r
+ * fieldCls : Ext.form.NumberField\r
+ * </pre>\r
+ */\r
+ fieldCls : Ext.form.NumberField,\r
+ /**\r
+ * @cfg {Object} fieldCfg\r
+ * The default configuration options for any field item unless superseded\r
+ * by the <code>{@link #fields}</code> configuration.\r
+ * Defaults to:<pre>\r
+ * fieldCfg : {}\r
+ * </pre>\r
+ * Example usage:\r
+ * <pre><code>\r
+fieldCfg : {\r
+ width: 150,\r
+},\r
+ * </code></pre>\r
+ */\r
+ /**\r
+ * @cfg {Object} fields\r
+ * The field items may be configured individually\r
+ * Defaults to <tt>undefined</tt>.\r
+ * Example usage:\r
+ * <pre><code>\r
+fields : {\r
+ gt: { // override fieldCfg options\r
+ width: 200,\r
+ fieldCls: Ext.ux.form.CustomNumberField // to override default {@link #fieldCls}\r
+ }\r
+},\r
+ * </code></pre>\r
+ */\r
+ /**\r
+ * @cfg {Object} iconCls\r
+ * The iconCls to be applied to each comparator field item.\r
+ * Defaults to:<pre>\r
+iconCls : {\r
+ gt : 'ux-rangemenu-gt',\r
+ lt : 'ux-rangemenu-lt',\r
+ eq : 'ux-rangemenu-eq'\r
+}\r
+ * </pre>\r
+ */\r
+ iconCls : {\r
+ gt : 'ux-rangemenu-gt',\r
+ lt : 'ux-rangemenu-lt',\r
+ eq : 'ux-rangemenu-eq'\r
+ },\r
+\r
+ /**\r
+ * @cfg {Object} menuItemCfgs\r
+ * Default configuration options for each menu item\r
+ * Defaults to:<pre>\r
+menuItemCfgs : {\r
+ emptyText: 'Enter Filter Text...',\r
+ selectOnFocus: true,\r
+ width: 125\r
+}\r
+ * </pre>\r
+ */\r
+ menuItemCfgs : {\r
+ emptyText: 'Enter Filter Text...',\r
+ selectOnFocus: true,\r
+ width: 125\r
+ },\r
+\r
+ /**\r
+ * @cfg {Array} menuItems\r
+ * The items to be shown in this menu. Items are added to the menu\r
+ * according to their position within this array. Defaults to:<pre>\r
+ * menuItems : ['lt','gt','-','eq']\r
+ * </pre>\r
+ */\r
+ menuItems : ['lt', 'gt', '-', 'eq'],\r
+\r
+ /** \r
+ * @private\r
+ * Template method that is to initialize the filter and install required menu items.\r
+ */\r
+ init : function (config) {\r
+ // if a menu already existed, do clean up first\r
+ if (this.menu){\r
+ this.menu.destroy();\r
+ } \r
+ this.menu = new Ext.ux.menu.RangeMenu(Ext.apply(config, {\r
+ // pass along filter configs to the menu\r
+ fieldCfg : this.fieldCfg || {},\r
+ fieldCls : this.fieldCls,\r
+ fields : this.fields || {},\r
+ iconCls: this.iconCls,\r
+ menuItemCfgs: this.menuItemCfgs,\r
+ menuItems: this.menuItems,\r
+ updateBuffer: this.updateBuffer\r
+ }));\r
+ // relay the event fired by the menu\r
+ this.menu.on('update', this.fireUpdate, this);\r
+ },\r
+ \r
+ /**\r
+ * @private\r
+ * Template method that is to get and return the value of the filter.\r
+ * @return {String} The value of this filter\r
+ */\r
+ getValue : function () {\r
+ return this.menu.getValue();\r
+ },\r
+\r
+ /**\r
+ * @private\r
+ * Template method that is to set the value of the filter.\r
+ * @param {Object} value The value to set the filter\r
+ */ \r
+ setValue : function (value) {\r
+ this.menu.setValue(value);\r
+ },\r
+\r
+ /**\r
+ * @private\r
+ * Template method that is to return <tt>true</tt> if the filter\r
+ * has enough configuration information to be activated.\r
+ * @return {Boolean}\r
+ */\r
+ isActivatable : function () {\r
+ var values = this.getValue();\r
+ for (key in values) {\r
+ if (values[key] !== undefined) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ },\r
+ \r
+ /**\r
+ * @private\r
+ * Template method that is to get and return serialized filter data for\r
+ * transmission to the server.\r
+ * @return {Object/Array} An object or collection of objects containing\r
+ * key value pairs representing the current configuration of the filter.\r
+ */\r
+ getSerialArgs : function () {\r
+ var key,\r
+ args = [],\r
+ values = this.menu.getValue();\r
+ for (key in values) {\r
+ args.push({\r
+ type: 'numeric',\r
+ comparison: key,\r
+ value: values[key]\r
+ });\r
+ }\r
+ return args;\r
+ },\r
+\r
+ /**\r
+ * Template method that is to validate the provided Ext.data.Record\r
+ * against the filters configuration.\r
+ * @param {Ext.data.Record} record The record to validate\r
+ * @return {Boolean} true if the record is valid within the bounds\r
+ * of the filter, false otherwise.\r
+ */\r
+ validateRecord : function (record) {\r
+ var val = record.get(this.dataIndex),\r
+ values = this.getValue();\r
+ if (values.eq !== undefined && val != values.eq) {\r
+ return false;\r
+ }\r
+ if (values.lt !== undefined && val >= values.lt) {\r
+ return false;\r
+ }\r
+ if (values.gt !== undefined && val <= values.gt) {\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+});/** \r
+ * @class Ext.ux.grid.filter.StringFilter\r
+ * @extends Ext.ux.grid.filter.Filter\r
+ * Filter by a configurable Ext.form.TextField\r
+ * <p><b><u>Example Usage:</u></b></p>\r
+ * <pre><code> \r
+var filters = new Ext.ux.grid.GridFilters({\r
+ ...\r
+ filters: [{\r
+ // required configs\r
+ type: 'string',\r
+ dataIndex: 'name',\r
+ \r
+ // optional configs\r
+ value: 'foo',\r
+ active: true, // default is false\r
+ iconCls: 'ux-gridfilter-text-icon' // default\r
+ // any Ext.form.TextField configs accepted\r
+ }]\r
+});\r
+ * </code></pre>\r
+ */\r
+Ext.ux.grid.filter.StringFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
+\r
+ /**\r
+ * @cfg {String} iconCls\r
+ * The iconCls to be applied to the menu item.\r
+ * Defaults to <tt>'ux-gridfilter-text-icon'</tt>.\r
+ */\r
+ iconCls : 'ux-gridfilter-text-icon',\r
+\r
+ emptyText: 'Enter Filter Text...',\r
+ selectOnFocus: true,\r
+ width: 125,\r
+ \r
+ /** \r
+ * @private\r
+ * Template method that is to initialize the filter and install required menu items.\r
+ */\r
+ init : function (config) {\r
+ Ext.applyIf(config, {\r
+ enableKeyEvents: true,\r
+ iconCls: this.iconCls,\r
+ listeners: {\r
+ scope: this,\r
+ keyup: this.onInputKeyUp\r
+ }\r
+ });\r
+\r
+ this.inputItem = new Ext.form.TextField(config); \r
+ this.menu.add(this.inputItem);\r
+ this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);\r
+ },\r
+ \r
+ /**\r
+ * @private\r
+ * Template method that is to get and return the value of the filter.\r
+ * @return {String} The value of this filter\r
+ */\r
+ getValue : function () {\r
+ return this.inputItem.getValue();\r
+ },\r
+ \r
+ /**\r
+ * @private\r
+ * Template method that is to set the value of the filter.\r
+ * @param {Object} value The value to set the filter\r
+ */ \r
+ setValue : function (value) {\r
+ this.inputItem.setValue(value);\r
+ this.fireEvent('update', this);\r
+ },\r
+\r
+ /**\r
+ * @private\r
+ * Template method that is to return <tt>true</tt> if the filter\r
+ * has enough configuration information to be activated.\r
+ * @return {Boolean}\r
+ */\r
+ isActivatable : function () {\r
+ return this.inputItem.getValue().length > 0;\r
+ },\r
+\r
+ /**\r
+ * @private\r
+ * Template method that is to get and return serialized filter data for\r
+ * transmission to the server.\r
+ * @return {Object/Array} An object or collection of objects containing\r
+ * key value pairs representing the current configuration of the filter.\r
+ */\r
+ getSerialArgs : function () {\r
+ return {type: 'string', value: this.getValue()};\r
+ },\r
+\r
+ /**\r
+ * Template method that is to validate the provided Ext.data.Record\r
+ * against the filters configuration.\r
+ * @param {Ext.data.Record} record The record to validate\r
+ * @return {Boolean} true if the record is valid within the bounds\r
+ * of the filter, false otherwise.\r
+ */\r
+ validateRecord : function (record) {\r
+ var val = record.get(this.dataIndex);\r
+\r
+ if(typeof val != 'string') {\r
+ return (this.getValue().length === 0);\r
+ }\r
+\r
+ return val.toLowerCase().indexOf(this.getValue().toLowerCase()) > -1;\r
+ },\r
+ \r
+ /** \r
+ * @private\r
+ * Handler method called when there is a keyup event on this.inputItem\r
+ */\r
+ onInputKeyUp : function (field, e) {\r
+ var k = e.getKey();\r
+ if (k == e.RETURN && field.isValid()) {\r
+ e.stopEvent();\r
+ this.menu.hide(true);\r
+ return;\r
+ }\r
+ // restart the timer\r
+ this.updateTask.delay(this.updateBuffer);\r
+ }\r
+});\r
+Ext.namespace('Ext.ux.menu');\r
+\r
+/** \r
+ * @class Ext.ux.menu.ListMenu\r
+ * @extends Ext.menu.Menu\r
+ * This is a supporting class for {@link Ext.ux.grid.filter.ListFilter}.\r
+ * Although not listed as configuration options for this class, this class\r
+ * also accepts all configuration options from {@link Ext.ux.grid.filter.ListFilter}.\r
+ */\r
+Ext.ux.menu.ListMenu = Ext.extend(Ext.menu.Menu, {\r
+ /**\r
+ * @cfg {String} labelField\r
+ * Defaults to 'text'.\r
+ */\r
+ labelField : 'text',\r
+ /**\r
+ * @cfg {String} paramPrefix\r
+ * Defaults to 'Loading...'.\r
+ */\r
+ loadingText : 'Loading...',\r
+ /**\r
+ * @cfg {Boolean} loadOnShow\r
+ * Defaults to true.\r
+ */\r
+ loadOnShow : true,\r
+ /**\r
+ * @cfg {Boolean} single\r
+ * Specify true to group all items in this list into a single-select\r
+ * radio button group. Defaults to false.\r
+ */\r
+ single : false,\r
+\r
+ constructor : function (cfg) {\r
+ this.selected = [];\r
+ this.addEvents(\r
+ /**\r
+ * @event checkchange\r
+ * Fires when there is a change in checked items from this list\r
+ * @param {Object} item Ext.menu.CheckItem\r
+ * @param {Object} checked The checked value that was set\r
+ */\r
+ 'checkchange'\r
+ );\r
+ \r
+ Ext.ux.menu.ListMenu.superclass.constructor.call(this, cfg = cfg || {});\r
+ \r
+ if(!cfg.store && cfg.options){\r
+ var options = [];\r
+ for(var i=0, len=cfg.options.length; i<len; i++){\r
+ var value = cfg.options[i];\r
+ switch(Ext.type(value)){\r
+ case 'array': options.push(value); break;\r
+ case 'object': options.push([value.id, value[this.labelField]]); break;\r
+ case 'string': options.push([value, value]); break;\r
+ }\r
+ }\r
+ \r
+ this.store = new Ext.data.Store({\r
+ reader: new Ext.data.ArrayReader({id: 0}, ['id', this.labelField]),\r
+ data: options,\r
+ listeners: {\r
+ 'load': this.onLoad,\r
+ scope: this\r
+ }\r
+ });\r
+ this.loaded = true;\r
+ } else {\r
+ this.add({text: this.loadingText, iconCls: 'loading-indicator'});\r
+ this.store.on('load', this.onLoad, this);\r
+ }\r
+ },\r
+\r
+ destroy : function () {\r
+ if (this.store) {\r
+ this.store.destroy(); \r
+ }\r
+ Ext.ux.menu.ListMenu.superclass.destroy.call(this);\r
+ },\r
+\r
+ /**\r
+ * Lists will initially show a 'loading' item while the data is retrieved from the store.\r
+ * In some cases the loaded data will result in a list that goes off the screen to the\r
+ * right (as placement calculations were done with the loading item). This adapter will\r
+ * allow show to be called with no arguments to show with the previous arguments and\r
+ * thus recalculate the width and potentially hang the menu from the left.\r
+ */\r
+ show : function () {\r
+ var lastArgs = null;\r
+ return function(){\r
+ if(arguments.length === 0){\r
+ Ext.ux.menu.ListMenu.superclass.show.apply(this, lastArgs);\r
+ } else {\r
+ lastArgs = arguments;\r
+ if (this.loadOnShow && !this.loaded) {\r
+ this.store.load();\r
+ }\r
+ Ext.ux.menu.ListMenu.superclass.show.apply(this, arguments);\r
+ }\r
+ };\r
+ }(),\r
+ \r
+ /** @private */\r
+ onLoad : function (store, records) {\r
+ var visible = this.isVisible();\r
+ this.hide(false);\r
+ \r
+ this.removeAll(true);\r
+ \r
+ var gid = this.single ? Ext.id() : null;\r
+ for(var i=0, len=records.length; i<len; i++){\r
+ var item = new Ext.menu.CheckItem({\r
+ text: records[i].get(this.labelField), \r
+ group: gid,\r
+ checked: this.selected.indexOf(records[i].id) > -1,\r
+ hideOnClick: false});\r
+ \r
+ item.itemId = records[i].id;\r
+ item.on('checkchange', this.checkChange, this);\r
+ \r
+ this.add(item);\r
+ }\r
+ \r
+ this.loaded = true;\r
+ \r
+ if (visible) {\r
+ this.show();\r
+ } \r
+ this.fireEvent('load', this, records);\r
+ },\r
+\r
+ /**\r
+ * Get the selected items.\r
+ * @return {Array} selected\r
+ */\r
+ getSelected : function () {\r
+ return this.selected;\r
+ },\r
+ \r
+ /** @private */\r
+ setSelected : function (value) {\r
+ value = this.selected = [].concat(value);\r
+\r
+ if (this.loaded) {\r
+ this.items.each(function(item){\r
+ item.setChecked(false, true);\r
+ for (var i = 0, len = value.length; i < len; i++) {\r
+ if (item.itemId == value[i]) {\r
+ item.setChecked(true, true);\r
+ }\r
+ }\r
+ }, this);\r
+ }\r
+ },\r
+ \r
+ /**\r
+ * Handler for the 'checkchange' event from an check item in this menu\r
+ * @param {Object} item Ext.menu.CheckItem\r
+ * @param {Object} checked The checked value that was set\r
+ */\r
+ checkChange : function (item, checked) {\r
+ var value = [];\r
+ this.items.each(function(item){\r
+ if (item.checked) {\r
+ value.push(item.itemId);\r
+ }\r
+ },this);\r
+ this.selected = value;\r
+ \r
+ this.fireEvent('checkchange', item, checked);\r
+ } \r
+});Ext.ns('Ext.ux.menu');\r
+\r
+/** \r
+ * @class Ext.ux.menu.RangeMenu\r
+ * @extends Ext.menu.Menu\r
+ * Custom implementation of Ext.menu.Menu that has preconfigured\r
+ * items for gt, lt, eq.\r
+ * <p><b><u>Example Usage:</u></b></p>\r
+ * <pre><code> \r
+\r
+ * </code></pre> \r
+ */\r
+Ext.ux.menu.RangeMenu = Ext.extend(Ext.menu.Menu, {\r
+\r
+ constructor : function (config) {\r
+\r
+ Ext.ux.menu.RangeMenu.superclass.constructor.call(this, config);\r
+\r
+ this.addEvents(\r
+ /**\r
+ * @event update\r
+ * Fires when a filter configuration has changed\r
+ * @param {Ext.ux.grid.filter.Filter} this The filter object.\r
+ */\r
+ 'update'\r
+ );\r
+ \r
+ this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);\r
+ \r
+ var i, len, item, cfg, Cls;\r
+\r
+ for (i = 0, len = this.menuItems.length; i < len; i++) {\r
+ item = this.menuItems[i];\r
+ if (item !== '-') {\r
+ // defaults\r
+ cfg = {\r
+ itemId: 'range-' + item,\r
+ enableKeyEvents: true,\r
+ iconCls: this.iconCls[item] || 'no-icon',\r
+ listeners: {\r
+ scope: this,\r
+ keyup: this.onInputKeyUp\r
+ }\r
+ };\r
+ Ext.apply(\r
+ cfg,\r
+ // custom configs\r
+ Ext.applyIf(this.fields[item] || {}, this.fieldCfg[item]),\r
+ // configurable defaults\r
+ this.menuItemCfgs\r
+ );\r
+ Cls = cfg.fieldCls || this.fieldCls;\r
+ item = this.fields[item] = new Cls(cfg);\r
+ }\r
+ this.add(item);\r
+ }\r
+ },\r
+\r
+ /**\r
+ * @private\r
+ * called by this.updateTask\r
+ */\r
+ fireUpdate : function () {\r
+ this.fireEvent('update', this);\r
+ },\r
+ \r
+ /**\r
+ * Get and return the value of the filter.\r
+ * @return {String} The value of this filter\r
+ */\r
+ getValue : function () {\r
+ var result = {}, key, field;\r
+ for (key in this.fields) {\r
+ field = this.fields[key];\r
+ if (field.isValid() && String(field.getValue()).length > 0) {\r
+ result[key] = field.getValue();\r
+ }\r
+ }\r
+ return result;\r
+ },\r
+ \r
+ /**\r
+ * Set the value of this menu and fires the 'update' event.\r
+ * @param {Object} data The data to assign to this menu\r
+ */ \r
+ setValue : function (data) {\r
+ var key;\r
+ for (key in this.fields) {\r
+ this.fields[key].setValue(data[key] !== undefined ? data[key] : '');\r
+ }\r
+ this.fireEvent('update', this);\r
+ },\r
+\r
+ /** \r
+ * @private\r
+ * Handler method called when there is a keyup event on an input\r
+ * item of this menu.\r
+ */\r
+ onInputKeyUp : function (field, e) {\r
+ var k = e.getKey();\r
+ if (k == e.RETURN && field.isValid()) {\r
+ e.stopEvent();\r
+ this.hide(true);\r
+ return;\r
+ }\r
+ \r
+ if (field == this.fields.eq) {\r
+ if (this.fields.gt) {\r
+ this.fields.gt.setValue(null);\r
+ }\r
+ if (this.fields.lt) {\r
+ this.fields.lt.setValue(null);\r
+ }\r
+ }\r
+ else {\r
+ this.fields.eq.setValue(null);\r
+ }\r
+ \r
+ // restart the timer\r
+ this.updateTask.delay(this.updateBuffer);\r
+ }\r
+});\r
+Ext.ns('Ext.ux.grid');\r
+\r
+/**\r
+ * @class Ext.ux.grid.GroupSummary\r
+ * @extends Ext.util.Observable\r
+ * A GridPanel plugin that enables dynamic column calculations and a dynamically\r
+ * updated grouped summary row.\r
+ */\r
+Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {\r
+ /**\r
+ * @cfg {Function} summaryRenderer Renderer example:<pre><code>\r
+summaryRenderer: function(v, params, data){\r
+ return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');\r
+},\r
+ * </code></pre>\r
+ */\r
+ /**\r
+ * @cfg {String} summaryType (Optional) The type of\r
+ * calculation to be used for the column. For options available see\r
+ * {@link #Calculations}.\r
+ */\r
+\r
+ constructor : function(config){\r
+ Ext.apply(this, config);\r
+ Ext.ux.grid.GroupSummary.superclass.constructor.call(this);\r
+ },\r
+ init : function(grid){\r
+ this.grid = grid;\r
+ var v = this.view = grid.getView();\r
+ v.doGroupEnd = this.doGroupEnd.createDelegate(this);\r
+\r
+ v.afterMethod('onColumnWidthUpdated', this.doWidth, this);\r
+ v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);\r
+ v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);\r
+ v.afterMethod('onUpdate', this.doUpdate, this);\r
+ v.afterMethod('onRemove', this.doRemove, this);\r
+\r
+ if(!this.rowTpl){\r
+ this.rowTpl = new Ext.Template(\r
+ '<div class="x-grid3-summary-row" style="{tstyle}">',\r
+ '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',\r
+ '<tbody><tr>{cells}</tr></tbody>',\r
+ '</table></div>'\r
+ );\r
+ this.rowTpl.disableFormats = true;\r
+ }\r
+ this.rowTpl.compile();\r
+\r
+ if(!this.cellTpl){\r
+ this.cellTpl = new Ext.Template(\r
+ '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',\r
+ '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',\r
+ "</td>"\r
+ );\r
+ this.cellTpl.disableFormats = true;\r
+ }\r
+ this.cellTpl.compile();\r
+ },\r
+\r
+ /**\r
+ * Toggle the display of the summary row on/off\r
+ * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.\r
+ */\r
+ toggleSummaries : function(visible){\r
+ var el = this.grid.getGridEl();\r
+ if(el){\r
+ if(visible === undefined){\r
+ visible = el.hasClass('x-grid-hide-summary');\r
+ }\r
+ el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');\r
+ }\r
+ },\r
+\r
+ renderSummary : function(o, cs){\r
+ cs = cs || this.view.getColumnData();\r
+ var cfg = this.grid.getColumnModel().config,\r
+ buf = [], c, p = {}, cf, last = cs.length-1;\r
+ for(var i = 0, len = cs.length; i < len; i++){\r
+ c = cs[i];\r
+ cf = cfg[i];\r
+ p.id = c.id;\r
+ p.style = c.style;\r
+ p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');\r
+ if(cf.summaryType || cf.summaryRenderer){\r
+ p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);\r
+ }else{\r
+ p.value = '';\r
+ }\r
+ if(p.value == undefined || p.value === "") p.value = " ";\r
+ buf[buf.length] = this.cellTpl.apply(p);\r
+ }\r
+\r
+ return this.rowTpl.apply({\r
+ tstyle: 'width:'+this.view.getTotalWidth()+';',\r
+ cells: buf.join('')\r
+ });\r
+ },\r
+\r
+ /**\r
+ * @private\r
+ * @param {Object} rs\r
+ * @param {Object} cs\r
+ */\r
+ calculate : function(rs, cs){\r
+ var data = {}, r, c, cfg = this.grid.getColumnModel().config, cf;\r
+ for(var j = 0, jlen = rs.length; j < jlen; j++){\r
+ r = rs[j];\r
+ for(var i = 0, len = cs.length; i < len; i++){\r
+ c = cs[i];\r
+ cf = cfg[i];\r
+ if(cf.summaryType){\r
+ data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);\r
+ }\r
+ }\r
+ }\r
+ return data;\r
+ },\r
+\r
+ doGroupEnd : function(buf, g, cs, ds, colCount){\r
+ var data = this.calculate(g.rs, cs);\r
+ buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');\r
+ },\r
+\r
+ doWidth : function(col, w, tw){\r
+ var gs = this.view.getGroups(), s;\r
+ for(var i = 0, len = gs.length; i < len; i++){\r
+ s = gs[i].childNodes[2];\r
+ s.style.width = tw;\r
+ s.firstChild.style.width = tw;\r
+ s.firstChild.rows[0].childNodes[col].style.width = w;\r
+ }\r
+ },\r
+\r
+ doAllWidths : function(ws, tw){\r
+ var gs = this.view.getGroups(), s, cells, wlen = ws.length;\r
+ for(var i = 0, len = gs.length; i < len; i++){\r
+ s = gs[i].childNodes[2];\r
+ s.style.width = tw;\r
+ s.firstChild.style.width = tw;\r
+ cells = s.firstChild.rows[0].childNodes;\r
+ for(var j = 0; j < wlen; j++){\r
+ cells[j].style.width = ws[j];\r
+ }\r
+ }\r
+ },\r
+\r
+ doHidden : function(col, hidden, tw){\r
+ var gs = this.view.getGroups(), s, display = hidden ? 'none' : '';\r
+ for(var i = 0, len = gs.length; i < len; i++){\r
+ s = gs[i].childNodes[2];\r
+ s.style.width = tw;\r
+ s.firstChild.style.width = tw;\r
+ s.firstChild.rows[0].childNodes[col].style.display = display;\r
+ }\r
+ },\r
+\r
+ // Note: requires that all (or the first) record in the\r
+ // group share the same group value. Returns false if the group\r
+ // could not be found.\r
+ refreshSummary : function(groupValue){\r
+ return this.refreshSummaryById(this.view.getGroupId(groupValue));\r
+ },\r
+\r
+ getSummaryNode : function(gid){\r
+ var g = Ext.fly(gid, '_gsummary');\r
+ if(g){\r
+ return g.down('.x-grid3-summary-row', true);\r
+ }\r
+ return null;\r
+ },\r
+\r
+ refreshSummaryById : function(gid){\r
+ var g = Ext.getDom(gid);\r
+ if(!g){\r
+ return false;\r
+ }\r
+ var rs = [];\r
+ this.grid.getStore().each(function(r){\r
+ if(r._groupId == gid){\r
+ rs[rs.length] = r;\r
+ }\r
+ });\r
+ var cs = this.view.getColumnData(),\r
+ data = this.calculate(rs, cs),\r
+ markup = this.renderSummary({data: data}, cs),\r
+ existing = this.getSummaryNode(gid);\r
+ \r
+ if(existing){\r
+ g.removeChild(existing);\r
+ }\r
+ Ext.DomHelper.append(g, markup);\r
+ return true;\r
+ },\r
+\r
+ doUpdate : function(ds, record){\r
+ this.refreshSummaryById(record._groupId);\r
+ },\r
+\r
+ doRemove : function(ds, record, index, isUpdate){\r
+ if(!isUpdate){\r
+ this.refreshSummaryById(record._groupId);\r
+ }\r
+ },\r
+\r
+ /**\r
+ * Show a message in the summary row.\r
+ * <pre><code>\r
+grid.on('afteredit', function(){\r
+ var groupValue = 'Ext Forms: Field Anchoring';\r
+ summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
+});\r
+ * </code></pre>\r
+ * @param {String} groupValue\r
+ * @param {String} msg Text to use as innerHTML for the summary row.\r
+ */\r
+ showSummaryMsg : function(groupValue, msg){\r
+ var gid = this.view.getGroupId(groupValue),\r
+ node = this.getSummaryNode(gid);\r
+ if(node){\r
+ node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';\r
+ }\r
+ }\r
+});\r
+\r
+//backwards compat\r
+Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;\r
+\r
+\r
+/**\r
+ * Calculation types for summary row:</p><div class="mdetail-params"><ul>\r
+ * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>\r
+ * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>\r
+ * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>\r
+ * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>\r
+ * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>\r
+ * </ul></div>\r
+ * <p>Custom calculations may be implemented. An example of\r
+ * custom <code>summaryType=totalCost</code>:</p><pre><code>\r
+// define a custom summary function\r
+Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){\r
+ return v + (record.data.estimate * record.data.rate);\r
+};\r
+ * </code></pre>\r
+ * @property Calculations\r
+ */\r
+\r
+Ext.ux.grid.GroupSummary.Calculations = {\r
+ 'sum' : function(v, record, field){\r
+ return v + (record.data[field]||0);\r
+ },\r
+\r
+ 'count' : function(v, record, field, data){\r
+ return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
+ },\r
+\r
+ 'max' : function(v, record, field, data){\r
+ var v = record.data[field];\r
+ var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];\r
+ return v > max ? (data[field+'max'] = v) : max;\r
+ },\r
+\r
+ 'min' : function(v, record, field, data){\r
+ var v = record.data[field];\r
+ var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];\r
+ return v < min ? (data[field+'min'] = v) : min;\r
+ },\r
+\r
+ 'average' : function(v, record, field, data){\r
+ var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
+ var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));\r
+ return t === 0 ? 0 : t / c;\r
+ }\r
+};\r
+Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;\r
+\r
+/**\r
+ * @class Ext.ux.grid.HybridSummary\r