=== modified file 'dhis-2/dhis-web/dhis-web-caseentry/src/main/resources/org/hisp/dhis/caseentry/i18n_module.properties' --- dhis-2/dhis-web/dhis-web-caseentry/src/main/resources/org/hisp/dhis/caseentry/i18n_module.properties 2012-05-31 04:40:52 +0000 +++ dhis-2/dhis-web/dhis-web-caseentry/src/main/resources/org/hisp/dhis/caseentry/i18n_module.properties 2012-06-01 02:04:54 +0000 @@ -347,4 +347,6 @@ confirmation = Confirmation data_entry_form = Data entry form view = View -start_date_must_be_less_then_or_equals_to_end_date = Start date must be less then or equals to end date \ No newline at end of file +start_date_must_be_less_then_or_equals_to_end_date = Start date must be less then or equals to end date +is_not_valid = is not valid +the_date_is_not_valid = The date is not valid \ No newline at end of file === added directory 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid' === added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/FiltersFeature.js' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/FiltersFeature.js 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/FiltersFeature.js 2012-06-01 02:04:54 +0000 @@ -0,0 +1,751 @@ +/* + +This file is part of Ext JS 4 + +Copyright (c) 2011 Sencha Inc + +Contact: http://www.sencha.com/contact + +GNU General Public License Usage +This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + +If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact. + +*/ +/** + * @class Ext.ux.grid.FiltersFeature + * @extends Ext.grid.Feature + +FiltersFeature is a grid {@link Ext.grid.Feature feature} that allows for a slightly more +robust representation of filtering than what is provided by the default store. + +Filtering is adjusted by the user using the grid's column header menu (this menu can be +disabled through configuration). Through this menu users can configure, enable, and +disable filters for each column. + +#Features# + +##Filtering implementations:## + +Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can be backed by a +{@link Ext.data.Store}), and Boolean. Additional custom filter types and menus are easily +created by extending {@link Ext.ux.grid.filter.Filter}. + +##Graphical Indicators:## + +Columns that are filtered have {@link #filterCls a configurable css class} applied to the column headers. + +##Automatic Reconfiguration:## + +Filters automatically reconfigure when the grid 'reconfigure' event fires. + +##Stateful:## + +Filter information will be persisted across page loads by specifying a `stateId` +in the Grid configuration. + +The filter collection binds to the {@link Ext.grid.Panel#beforestaterestore beforestaterestore} +and {@link Ext.grid.Panel#beforestatesave beforestatesave} events in order to be stateful. + +##GridPanel Changes:## + +- A `filters` property is added to the GridPanel using this feature. +- A `filterupdate` event is added to the GridPanel and is fired upon onStateChange completion. + +##Server side code examples:## + +- [PHP](http://www.vinylfox.com/extjs/grid-filter-php-backend-code.php) - (Thanks VinylFox) +- [Ruby on Rails](http://extjs.com/forum/showthread.php?p=77326#post77326) - (Thanks Zyclops) +- [Ruby on Rails](http://extjs.com/forum/showthread.php?p=176596#post176596) - (Thanks Rotomaul) +- [Python](http://www.debatablybeta.com/posts/using-extjss-grid-filtering-with-django/) - (Thanks Matt) +- [Grails](http://mcantrell.wordpress.com/2008/08/22/extjs-grids-and-grails/) - (Thanks Mike) + +#Example usage:# + + var store = Ext.create('Ext.data.Store', { + pageSize: 15 + ... + }); + + var filtersCfg = { + ftype: 'filters', + autoReload: false, //don't reload automatically + local: true, //only filter locally + // filters may be configured through the plugin, + // or in the column definition within the headers configuration + filters: [{ + type: 'numeric', + dataIndex: 'id' + }, { + type: 'string', + dataIndex: 'name' + }, { + type: 'numeric', + dataIndex: 'price' + }, { + type: 'date', + dataIndex: 'dateAdded' + }, { + type: 'list', + dataIndex: 'size', + options: ['extra small', 'small', 'medium', 'large', 'extra large'], + phpMode: true + }, { + type: 'boolean', + dataIndex: 'visible' + }] + }; + + var grid = Ext.create('Ext.grid.Panel', { + store: store, + columns: ..., + filters: [filtersCfg], + height: 400, + width: 700, + bbar: Ext.create('Ext.PagingToolbar', { + store: store + }) + }); + + // a filters property is added to the GridPanel + grid.filters + + * @markdown + */ +Ext.define('Ext.ux.grid.FiltersFeature', { + extend: 'Ext.grid.feature.Feature', + alias: 'feature.filters', + uses: [ + 'Ext.ux.grid.menu.ListMenu', + 'Ext.ux.grid.menu.RangeMenu', + 'Ext.ux.grid.filter.BooleanFilter', + 'Ext.ux.grid.filter.DateFilter', + 'Ext.ux.grid.filter.ListFilter', + 'Ext.ux.grid.filter.NumericFilter', + 'Ext.ux.grid.filter.StringFilter' + ], + + /** + * @cfg {Boolean} autoReload + * Defaults to true, reloading the datasource when a filter change happens. + * Set this to false to prevent the datastore from being reloaded if there + * are changes to the filters. See {@link updateBuffer}. + */ + autoReload : true, + /** + * @cfg {Boolean} encode + * Specify true for {@link #buildQuery} to use Ext.util.JSON.encode to + * encode the filter query parameter sent with a remote request. + * Defaults to false. + */ + /** + * @cfg {Array} filters + * An Array of filters config objects. Refer to each filter type class for + * configuration details specific to each filter type. Filters for Strings, + * Numeric Ranges, Date Ranges, Lists, and Boolean are the standard filters + * available. + */ + /** + * @cfg {String} filterCls + * The css class to be applied to column headers with active filters. + * Defaults to 'ux-filterd-column'. + */ + filterCls : 'ux-filtered-column', + /** + * @cfg {Boolean} local + * true to use Ext.data.Store filter functions (local filtering) + * instead of the default (false) server side filtering. + */ + local : false, + /** + * @cfg {String} menuFilterText + * defaults to 'Filters'. + */ + menuFilterText : 'Filters', + /** + * @cfg {String} paramPrefix + * The url parameter prefix for the filters. + * Defaults to 'filter'. + */ + paramPrefix : 'filter', + /** + * @cfg {Boolean} showMenu + * Defaults to true, including a filter submenu in the default header menu. + */ + showMenu : true, + /** + * @cfg {String} stateId + * Name of the value to be used to store state information. + */ + stateId : undefined, + /** + * @cfg {Integer} updateBuffer + * Number of milliseconds to defer store updates since the last filter change. + */ + updateBuffer : 500, + + // doesn't handle grid body events + hasFeatureEvent: false, + + + /** @private */ + constructor : function (config) { + var me = this; + + config = config || {}; + Ext.apply(me, config); + + me.deferredUpdate = Ext.create('Ext.util.DelayedTask', me.reload, me); + + // Init filters + me.filters = me.createFiltersCollection(); + me.filterConfigs = config.filters; + }, + + attachEvents: function() { + var me = this, + view = me.view, + headerCt = view.headerCt, + grid = me.getGridPanel(); + + me.bindStore(view.getStore(), true); + + // Listen for header menu being created + headerCt.on('menucreate', me.onMenuCreate, me); + + view.on('refresh', me.onRefresh, me); + grid.on({ + scope: me, + beforestaterestore: me.applyState, + beforestatesave: me.saveState, + beforedestroy: me.destroy + }); + + // Add event and filters shortcut on grid panel + grid.filters = me; + grid.addEvents('filterupdate'); + }, + + createFiltersCollection: function () { + return Ext.create('Ext.util.MixedCollection', false, function (o) { + return o ? o.dataIndex : null; + }); + }, + + /** + * @private Create the Filter objects for the current configuration, destroying any existing ones first. + */ + createFilters: function() { + var me = this, + hadFilters = me.filters.getCount(), + grid = me.getGridPanel(), + filters = me.createFiltersCollection(), + model = grid.store.model, + fields = model.prototype.fields, + field, + filter, + state; + + if (hadFilters) { + state = {}; + me.saveState(null, state); + } + + function add (dataIndex, config, filterable) { + if (dataIndex && (filterable || config)) { + field = fields.get(dataIndex); + filter = { + dataIndex: dataIndex, + type: (field && field.type && field.type.type) || 'auto' + }; + + if (Ext.isObject(config)) { + Ext.apply(filter, config); + } + + filters.replace(filter); + } + } + + // We start with filters from our config and then merge on filters from the columns + // in the grid. The Grid columns take precedence. + Ext.Array.each(me.filterConfigs, function (fc) { + add(fc.dataIndex, fc); + }); + + Ext.Array.each(grid.columns, function (column) { + if (column.filterable === false) { + filters.removeAtKey(column.dataIndex); + } else { + add(column.dataIndex, column.filter, column.filterable); + } + }); + + me.removeAll(); + me.addFilters(filters.items); + + if (hadFilters) { + me.applyState(null, state); + } + }, + + /** + * @private Handle creation of the grid's header menu. Initializes the filters and listens + * for the menu being shown. + */ + onMenuCreate: function(headerCt, menu) { + var me = this; + me.createFilters(); + menu.on('beforeshow', me.onMenuBeforeShow, me); + }, + + /** + * @private Handle showing of the grid's header menu. Sets up the filter item and menu + * appropriate for the target column. + */ + onMenuBeforeShow: function(menu) { + var me = this, + menuItem, filter; + + if (me.showMenu) { + menuItem = me.menuItem; + if (!menuItem || menuItem.isDestroyed) { + me.createMenuItem(menu); + menuItem = me.menuItem; + } + + filter = me.getMenuFilter(); + + if (filter) { + menuItem.menu = filter.menu; + menuItem.setChecked(filter.active); + // disable the menu if filter.disabled explicitly set to true + menuItem.setDisabled(filter.disabled === true); + } + menuItem.setVisible(!!filter); + this.sep.setVisible(!!filter); + } + }, + + + createMenuItem: function(menu) { + var me = this; + me.sep = menu.add('-'); + me.menuItem = menu.add({ + checked: false, + itemId: 'filters', + text: me.menuFilterText, + listeners: { + scope: me, + checkchange: me.onCheckChange, + beforecheckchange: me.onBeforeCheck + } + }); + }, + + getGridPanel: function() { + return this.view.up('gridpanel'); + }, + + /** + * @private + * Handler for the grid's beforestaterestore event (fires before the state of the + * grid is restored). + * @param {Object} grid The grid object + * @param {Object} state The hash of state values returned from the StateProvider. + */ + applyState : function (grid, state) { + var key, filter; + this.applyingState = true; + this.clearFilters(); + if (state.filters) { + for (key in state.filters) { + filter = this.filters.get(key); + if (filter) { + filter.setValue(state.filters[key]); + filter.setActive(true); + } + } + } + this.deferredUpdate.cancel(); + if (this.local) { + this.reload(); + } + delete this.applyingState; + delete state.filters; + }, + + /** + * Saves the state of all active filters + * @param {Object} grid + * @param {Object} state + * @return {Boolean} + */ + saveState : function (grid, state) { + var filters = {}; + this.filters.each(function (filter) { + if (filter.active) { + filters[filter.dataIndex] = filter.getValue(); + } + }); + return (state.filters = filters); + }, + + /** + * @private + * Handler called by the grid 'beforedestroy' event + */ + destroy : function () { + var me = this; + Ext.destroyMembers(me, 'menuItem', 'sep'); + me.removeAll(); + me.clearListeners(); + }, + + /** + * Remove all filters, permanently destroying them. + */ + removeAll : function () { + if(this.filters){ + Ext.destroy.apply(Ext, this.filters.items); + // remove all items from the collection + this.filters.clear(); + } + }, + + + /** + * Changes the data store bound to this view and refreshes it. + * @param {Store} store The store to bind to this view + */ + bindStore : function(store, initial){ + if(!initial && this.store){ + if (this.local) { + store.un('load', this.onLoad, this); + } else { + store.un('beforeload', this.onBeforeLoad, this); + } + } + if(store){ + if (this.local) { + store.on('load', this.onLoad, this); + } else { + store.on('beforeload', this.onBeforeLoad, this); + } + } + this.store = store; + }, + + + /** + * @private + * Get the filter menu from the filters MixedCollection based on the clicked header + */ + getMenuFilter : function () { + var header = this.view.headerCt.getMenu().activeHeader; + return header ? this.filters.get(header.dataIndex) : null; + }, + + /** @private */ + onCheckChange : function (item, value) { + this.getMenuFilter().setActive(value); + }, + + /** @private */ + onBeforeCheck : function (check, value) { + return !value || this.getMenuFilter().isActivatable(); + }, + + /** + * @private + * Handler for all events on filters. + * @param {String} event Event name + * @param {Object} filter Standard signature of the event before the event is fired + */ + onStateChange : function (event, filter) { + if (event !== 'serialize') { + var me = this, + grid = me.getGridPanel(); + + if (filter == me.getMenuFilter()) { + me.menuItem.setChecked(filter.active, false); + } + + if ((me.autoReload || me.local) && !me.applyingState) { + me.deferredUpdate.delay(me.updateBuffer); + } + me.updateColumnHeadings(); + + if (!me.applyingState) { + grid.saveState(); + } + grid.fireEvent('filterupdate', me, filter); + } + }, + + /** + * @private + * Handler for store's beforeload event when configured for remote filtering + * @param {Object} store + * @param {Object} options + */ + onBeforeLoad : function (store, options) { + options.params = options.params || {}; + this.cleanParams(options.params); + var params = this.buildQuery(this.getFilterData()); + Ext.apply(options.params, params); + }, + + /** + * @private + * Handler for store's load event when configured for local filtering + * @param {Object} store + * @param {Object} options + */ + onLoad : function (store, options) { + store.filterBy(this.getRecordFilter()); + }, + + /** + * @private + * Handler called when the grid's view is refreshed + */ + onRefresh : function () { + this.updateColumnHeadings(); + }, + + /** + * Update the styles for the header row based on the active filters + */ + updateColumnHeadings : function () { + var me = this, + headerCt = me.view.headerCt; + if (headerCt) { + headerCt.items.each(function(header) { + var filter = me.getFilter(header.dataIndex); + header[filter && filter.active ? 'addCls' : 'removeCls'](me.filterCls); + }); + } + }, + + /** @private */ + reload : function () { + var me = this, + store = me.view.getStore(), + start; + + if (me.local) { + store.clearFilter(true); + store.filterBy(me.getRecordFilter()); + } else { + me.deferredUpdate.cancel(); + store.loadPage(1); + } + }, + + /** + * Method factory that generates a record validator for the filters active at the time + * of invokation. + * @private + */ + getRecordFilter : function () { + var f = [], len, i; + this.filters.each(function (filter) { + if (filter.active) { + f.push(filter); + } + }); + + len = f.length; + return function (record) { + for (i = 0; i < len; i++) { + if (!f[i].validateRecord(record)) { + return false; + } + } + return true; + }; + }, + + /** + * Adds a filter to the collection and observes it for state change. + * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object. + * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object. + */ + addFilter : function (config) { + var Cls = this.getFilterClass(config.type), + filter = config.menu ? config : (new Cls(config)); + this.filters.add(filter); + + Ext.util.Observable.capture(filter, this.onStateChange, this); + return filter; + }, + + /** + * Adds filters to the collection. + * @param {Array} filters An Array of filter configuration objects. + */ + addFilters : function (filters) { + if (filters) { + var i, len, filter; + for (i = 0, len = filters.length; i < len; i++) { + filter = filters[i]; + // if filter config found add filter for the column + if (filter) { + this.addFilter(filter); + } + } + } + }, + + /** + * Returns a filter for the given dataIndex, if one exists. + * @param {String} dataIndex The dataIndex of the desired filter object. + * @return {Ext.ux.grid.filter.Filter} + */ + getFilter : function (dataIndex) { + return this.filters.get(dataIndex); + }, + + /** + * Turns all filters off. This does not clear the configuration information + * (see {@link #removeAll}). + */ + clearFilters : function () { + this.filters.each(function (filter) { + filter.setActive(false); + }); + }, + + /** + * Returns an Array of the currently active filters. + * @return {Array} filters Array of the currently active filters. + */ + getFilterData : function () { + var filters = [], i, len; + + this.filters.each(function (f) { + if (f.active) { + var d = [].concat(f.serialize()); + for (i = 0, len = d.length; i < len; i++) { + filters.push({ + field: f.dataIndex, + data: d[i] + }); + } + } + }); + return filters; + }, + + /** + * Function to take the active filters data and build it into a query. + * The format of the query depends on the {@link #encode} + * configuration: + *
+ * Override this method to customize the format of the filter query for remote requests. + * @param {Array} filters A collection of objects representing active filters and their configuration. + * Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured + * to be unique as any one filter may be a composite of more basic filters for the same dataIndex. + * @return {Object} Query keys and values + */ + buildQuery : function (filters) { + var p = {}, i, f, root, dataPrefix, key, tmp, + len = filters.length; + + if (!this.encode){ + for (i = 0; i < len; i++) { + f = filters[i]; + root = [this.paramPrefix, '[', i, ']'].join(''); + p[root + '[field]'] = f.field; + + dataPrefix = root + '[data]'; + for (key in f.data) { + p[[dataPrefix, '[', key, ']'].join('')] = f.data[key]; + } + } + } else { + tmp = []; + for (i = 0; i < len; i++) { + f = filters[i]; + tmp.push(Ext.apply( + {}, + {field: f.field}, + f.data + )); + } + // only build if there is active filter + if (tmp.length > 0){ + p[this.paramPrefix] = Ext.JSON.encode(tmp); + } + } + return p; + }, + + /** + * Removes filter related query parameters from the provided object. + * @param {Object} p Query parameters that may contain filter related fields. + */ + cleanParams : function (p) { + // if encoding just delete the property + if (this.encode) { + delete p[this.paramPrefix]; + // otherwise scrub the object of filter data + } else { + var regex, key; + regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]'); + for (key in p) { + if (regex.test(key)) { + delete p[key]; + } + } + } + }, + + /** + * Function for locating filter classes, overwrite this with your favorite + * loader to provide dynamic filter loading. + * @param {String} type The type of filter to load ('Filter' is automatically + * appended to the passed type; eg, 'string' becomes 'StringFilter'). + * @return {Class} The Ext.ux.grid.filter.Class + */ + getFilterClass : function (type) { + // map the supported Ext.data.Field type values into a supported filter + switch(type) { + case 'auto': + type = 'string'; + break; + case 'int': + case 'float': + type = 'numeric'; + break; + case 'bool': + type = 'boolean'; + break; + } + return Ext.ClassManager.getByAlias('gridfilter.' + type); + } +}); + === added directory 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/filter' === added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/filter/BooleanFilter.js' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/filter/BooleanFilter.js 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/filter/BooleanFilter.js 2012-06-01 02:04:54 +0000 @@ -0,0 +1,115 @@ +/* + +This file is part of Ext JS 4 + +Copyright (c) 2011 Sencha Inc + +Contact: http://www.sencha.com/contact + +GNU General Public License Usage +This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + +If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact. + +*/ +/** + * @class Ext.ux.grid.filter.BooleanFilter + * @extends Ext.ux.grid.filter.Filter + * Boolean filters use unique radio group IDs (so you can have more than one!) + *

Example Usage:

+ *

+var filters = Ext.create('Ext.ux.grid.GridFilters', {
+    ...
+    filters: [{
+        // required configs
+        type: 'boolean',
+        dataIndex: 'visible'
+
+        // optional configs
+        defaultValue: null, // leave unselected (false selected by default)
+        yesText: 'Yes',     // default
+        noText: 'No'        // default
+    }]
+});
+ * 
+ */ +Ext.define('Ext.ux.grid.filter.BooleanFilter', { + extend: 'Ext.ux.grid.filter.Filter', + alias: 'gridfilter.boolean', + + /** + * @cfg {Boolean} defaultValue + * Set this to null if you do not want either option to be checked by default. Defaults to false. + */ + defaultValue : false, + /** + * @cfg {String} yesText + * Defaults to 'Yes'. + */ + yesText : 'Yes', + /** + * @cfg {String} noText + * Defaults to 'No'. + */ + noText : 'No', + + /** + * @private + * Template method that is to initialize the filter and install required menu items. + */ + init : function (config) { + var gId = Ext.id(); + this.options = [ + Ext.create('Ext.menu.CheckItem', {text: this.yesText, group: gId, checked: this.defaultValue === true}), + Ext.create('Ext.menu.CheckItem', {text: this.noText, group: gId, checked: this.defaultValue === false})]; + + this.menu.add(this.options[0], this.options[1]); + + for(var i=0; iExample Usage:

+ *

+var filters = Ext.create('Ext.ux.grid.GridFilters', {
+    ...
+    filters: [{
+        // required configs
+        type: 'date',
+        dataIndex: 'dateAdded',
+
+        // optional configs
+        dateFormat: 'm/d/Y',  // default
+        beforeText: 'Before', // default
+        afterText: 'After',   // default
+        onText: 'On',         // default
+        pickerOpts: {
+            // any DatePicker configs
+        },
+
+        active: true // default is false
+    }]
+});
+ * 
+ */ +Ext.define('Ext.ux.grid.filter.DateFilter', { + extend: 'Ext.ux.grid.filter.Filter', + alias: 'gridfilter.date', + uses: ['Ext.picker.Date', 'Ext.menu.Menu'], + + /** + * @cfg {String} afterText + * Defaults to 'After'. + */ + afterText : 'After', + /** + * @cfg {String} beforeText + * Defaults to 'Before'. + */ + beforeText : 'Before', + /** + * @cfg {Object} compareMap + * Map for assigning the comparison values used in serialization. + */ + compareMap : { + before: 'lt', + after: 'gt', + on: 'eq' + }, + /** + * @cfg {String} dateFormat + * The date format to return when using getValue. + * Defaults to 'm/d/Y'. + */ + dateFormat : 'm/d/Y', + + /** + * @cfg {Date} maxDate + * Allowable date as passed to the Ext.DatePicker + * Defaults to undefined. + */ + /** + * @cfg {Date} minDate + * Allowable date as passed to the Ext.DatePicker + * Defaults to undefined. + */ + /** + * @cfg {Array} menuItems + * The items to be shown in this menu + * Defaults to:
+     * menuItems : ['before', 'after', '-', 'on'],
+     * 
+ */ + menuItems : ['before', 'after', '-', 'on'], + + /** + * @cfg {Object} menuItemCfgs + * Default configuration options for each menu item + */ + menuItemCfgs : { + selectOnFocus: true, + width: 125 + }, + + /** + * @cfg {String} onText + * Defaults to 'On'. + */ + onText : 'On', + + /** + * @cfg {Object} pickerOpts + * Configuration options for the date picker associated with each field. + */ + pickerOpts : {}, + + /** + * @private + * Template method that is to initialize the filter and install required menu items. + */ + init : function (config) { + var me = this, + pickerCfg, i, len, item, cfg; + + pickerCfg = Ext.apply(me.pickerOpts, { + xtype: 'datepicker', + minDate: me.minDate, + maxDate: me.maxDate, + format: me.dateFormat, + listeners: { + scope: me, + select: me.onMenuSelect + } + }); + + me.fields = {}; + for (i = 0, len = me.menuItems.length; i < len; i++) { + item = me.menuItems[i]; + if (item !== '-') { + cfg = { + itemId: 'range-' + item, + text: me[item + 'Text'], + menu: Ext.create('Ext.menu.Menu', { + items: [ + Ext.apply(pickerCfg, { + itemId: item + }) + ] + }), + listeners: { + scope: me, + checkchange: me.onCheckChange + } + }; + item = me.fields[item] = Ext.create('Ext.menu.CheckItem', cfg); + } + //me.add(item); + me.menu.add(item); + } + }, + + onCheckChange : function () { + this.setActive(this.isActivatable()); + this.fireEvent('update', this); + }, + + /** + * @private + * Handler method called when there is a keyup event on an input + * item of this menu. + */ + onInputKeyUp : function (field, e) { + var k = e.getKey(); + if (k == e.RETURN && field.isValid()) { + e.stopEvent(); + this.menu.hide(); + } + }, + + /** + * Handler for when the DatePicker for a field fires the 'select' event + * @param {Ext.picker.Date} picker + * @param {Object} picker + * @param {Object} date + */ + onMenuSelect : function (picker, date) { + var fields = this.fields, + field = this.fields[picker.itemId]; + + field.setChecked(true); + + if (field == fields.on) { + fields.before.setChecked(false, true); + fields.after.setChecked(false, true); + } else { + fields.on.setChecked(false, true); + if (field == fields.after && this.getFieldValue('before') < date) { + fields.before.setChecked(false, true); + } else if (field == fields.before && this.getFieldValue('after') > date) { + fields.after.setChecked(false, true); + } + } + this.fireEvent('update', this); + + picker.up('menu').hide(); + }, + + /** + * @private + * Template method that is to get and return the value of the filter. + * @return {String} The value of this filter + */ + getValue : function () { + var key, result = {}; + for (key in this.fields) { + if (this.fields[key].checked) { + result[key] = this.getFieldValue(key); + } + } + return result; + }, + + /** + * @private + * Template method that is to set the value of the filter. + * @param {Object} value The value to set the filter + * @param {Boolean} preserve true to preserve the checked status + * of the other fields. Defaults to false, unchecking the + * other fields + */ + setValue : function (value, preserve) { + var key; + for (key in this.fields) { + if(value[key]){ + this.getPicker(key).setValue(value[key]); + this.fields[key].setChecked(true); + } else if (!preserve) { + this.fields[key].setChecked(false); + } + } + this.fireEvent('update', this); + }, + + /** + * @private + * Template method that is to return true if the filter + * has enough configuration information to be activated. + * @return {Boolean} + */ + isActivatable : function () { + var key; + for (key in this.fields) { + if (this.fields[key].checked) { + return true; + } + } + return false; + }, + + /** + * @private + * Template method that is to get and return serialized filter data for + * transmission to the server. + * @return {Object/Array} An object or collection of objects containing + * key value pairs representing the current configuration of the filter. + */ + getSerialArgs : function () { + var args = []; + for (var key in this.fields) { + if(this.fields[key].checked){ + args.push({ + type: 'date', + comparison: this.compareMap[key], + value: Ext.Date.format(this.getFieldValue(key), this.dateFormat) + }); + } + } + return args; + }, + + /** + * Get and return the date menu picker value + * @param {String} item The field identifier ('before', 'after', 'on') + * @return {Date} Gets the current selected value of the date field + */ + getFieldValue : function(item){ + return this.getPicker(item).getValue(); + }, + + /** + * Gets the menu picker associated with the passed field + * @param {String} item The field identifier ('before', 'after', 'on') + * @return {Object} The menu picker + */ + getPicker : function(item){ + return this.fields[item].menu.items.first(); + }, + + /** + * Template method that is to validate the provided Ext.data.Record + * against the filters configuration. + * @param {Ext.data.Record} record The record to validate + * @return {Boolean} true if the record is valid within the bounds + * of the filter, false otherwise. + */ + validateRecord : function (record) { + var key, + pickerValue, + val = record.get(this.dataIndex), + clearTime = Ext.Date.clearTime; + + if(!Ext.isDate(val)){ + return false; + } + val = clearTime(val, true).getTime(); + + for (key in this.fields) { + if (this.fields[key].checked) { + pickerValue = clearTime(this.getFieldValue(key), true).getTime(); + if (key == 'before' && pickerValue <= val) { + return false; + } + if (key == 'after' && pickerValue >= val) { + return false; + } + if (key == 'on' && pickerValue != val) { + return false; + } + } + } + return true; + } +}); + === added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/filter/Filter.js' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/filter/Filter.js 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/filter/Filter.js 2012-06-01 02:04:54 +0000 @@ -0,0 +1,205 @@ +/* + +This file is part of Ext JS 4 + +Copyright (c) 2011 Sencha Inc + +Contact: http://www.sencha.com/contact + +GNU General Public License Usage +This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + +If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact. + +*/ +/** + * @class Ext.ux.grid.filter.Filter + * @extends Ext.util.Observable + * Abstract base class for filter implementations. + */ +Ext.define('Ext.ux.grid.filter.Filter', { + extend: 'Ext.util.Observable', + + /** + * @cfg {Boolean} active + * Indicates the initial status of the filter (defaults to false). + */ + active : false, + /** + * True if this filter is active. Use setActive() to alter after configuration. + * @type Boolean + * @property active + */ + /** + * @cfg {String} dataIndex + * The {@link Ext.data.Store} dataIndex of the field this filter represents. + * The dataIndex does not actually have to exist in the store. + */ + dataIndex : null, + /** + * The filter configuration menu that will be installed into the filter submenu of a column menu. + * @type Ext.menu.Menu + * @property + */ + menu : null, + /** + * @cfg {Number} updateBuffer + * Number of milliseconds to wait after user interaction to fire an update. Only supported + * by filters: 'list', 'numeric', and 'string'. Defaults to 500. + */ + updateBuffer : 500, + + constructor : function (config) { + Ext.apply(this, config); + + this.addEvents( + /** + * @event activate + * Fires when an inactive filter becomes active + * @param {Ext.ux.grid.filter.Filter} this + */ + 'activate', + /** + * @event deactivate + * Fires when an active filter becomes inactive + * @param {Ext.ux.grid.filter.Filter} this + */ + 'deactivate', + /** + * @event serialize + * Fires after the serialization process. Use this to attach additional parameters to serialization + * data before it is encoded and sent to the server. + * @param {Array/Object} data A map or collection of maps representing the current filter configuration. + * @param {Ext.ux.grid.filter.Filter} filter The filter being serialized. + */ + 'serialize', + /** + * @event update + * Fires when a filter configuration has changed + * @param {Ext.ux.grid.filter.Filter} this The filter object. + */ + 'update' + ); + Ext.ux.grid.filter.Filter.superclass.constructor.call(this); + + // setting filtered to true on all filter instances ensures that the filter won't be blurred when the mouse leaves the component + this.menu = this.createMenu(Ext.applyIf({filtered: true}, config)); + this.init(config); + if(config && config.value){ + this.setValue(config.value); + this.setActive(config.active !== false, true); + delete config.value; + } + }, + + /** + * Destroys this filter by purging any event listeners, and removing any menus. + */ + destroy : function(){ + if (this.menu){ + this.menu.destroy(); + } + this.clearListeners(); + }, + + /** + * Template method to be implemented by all subclasses that is to + * initialize the filter and install required menu items. + * Defaults to Ext.emptyFn. + */ + init : Ext.emptyFn, + + /** + * @private @override + * Creates the Menu for this filter. + * @param {Object} config Filter configuration + * @return {Ext.menu.Menu} + */ + createMenu: function(config) { + return Ext.create('Ext.menu.Menu', config); + }, + + /** + * Template method to be implemented by all subclasses that is to + * get and return the value of the filter. + * Defaults to Ext.emptyFn. + * @return {Object} The 'serialized' form of this filter + * @methodOf Ext.ux.grid.filter.Filter + */ + getValue : Ext.emptyFn, + + /** + * Template method to be implemented by all subclasses that is to + * set the value of the filter and fire the 'update' event. + * Defaults to Ext.emptyFn. + * @param {Object} data The value to set the filter + * @methodOf Ext.ux.grid.filter.Filter + */ + setValue : Ext.emptyFn, + + /** + * Template method to be implemented by all subclasses that is to + * return true if the filter has enough configuration information to be activated. + * Defaults to return true. + * @return {Boolean} + */ + isActivatable : function(){ + return true; + }, + + /** + * Template method to be implemented by all subclasses that is to + * get and return serialized filter data for transmission to the server. + * Defaults to Ext.emptyFn. + */ + getSerialArgs : Ext.emptyFn, + + /** + * Template method to be implemented by all subclasses that is to + * validates the provided Ext.data.Record against the filters configuration. + * Defaults to return true. + * @param {Ext.data.Record} record The record to validate + * @return {Boolean} true if the record is valid within the bounds + * of the filter, false otherwise. + */ + validateRecord : function(){ + return true; + }, + + /** + * Returns the serialized filter data for transmission to the server + * and fires the 'serialize' event. + * @return {Object/Array} An object or collection of objects containing + * key value pairs representing the current configuration of the filter. + * @methodOf Ext.ux.grid.filter.Filter + */ + serialize : function(){ + var args = this.getSerialArgs(); + this.fireEvent('serialize', args, this); + return args; + }, + + /** @private */ + fireUpdate : function(){ + if (this.active) { + this.fireEvent('update', this); + } + this.setActive(this.isActivatable()); + }, + + /** + * Sets the status of the filter and fires the appropriate events. + * @param {Boolean} active The new filter state. + * @param {Boolean} suppressEvent True to prevent events from being fired. + * @methodOf Ext.ux.grid.filter.Filter + */ + setActive : function(active, suppressEvent){ + if(this.active != active){ + this.active = active; + if (suppressEvent !== true) { + this.fireEvent(active ? 'activate' : 'deactivate', this); + } + } + } +}); + === added file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/filter/ListFilter.js' --- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/filter/ListFilter.js 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/ext-ux/grid/filter/ListFilter.js 2012-06-01 02:04:54 +0000 @@ -0,0 +1,192 @@ +/* + +This file is part of Ext JS 4 + +Copyright (c) 2011 Sencha Inc + +Contact: http://www.sencha.com/contact + +GNU General Public License Usage +This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. + +If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact. + +*/ +/** + * @class Ext.ux.grid.filter.ListFilter + * @extends Ext.ux.grid.filter.Filter + *

List filters are able to be preloaded/backed by an Ext.data.Store to load + * their options the first time they are shown. ListFilter utilizes the + * {@link Ext.ux.grid.menu.ListMenu} component.

+ *

Although not shown here, this class accepts all configuration options + * for {@link Ext.ux.grid.menu.ListMenu}.

+ * + *

Example Usage:

+ *

+var filters = Ext.create('Ext.ux.grid.GridFilters', {
+    ...
+    filters: [{
+        type: 'list',
+        dataIndex: 'size',
+        phpMode: true,
+        // options will be used as data to implicitly creates an ArrayStore
+        options: ['extra small', 'small', 'medium', 'large', 'extra large']
+    }]
+});
+ * 
+ * + */ +Ext.define('Ext.ux.grid.filter.ListFilter', { + extend: 'Ext.ux.grid.filter.Filter', + alias: 'gridfilter.list', + + /** + * @cfg {Array} options + *

data to be used to implicitly create a data store + * to back this list when the data source is local. If the + * data for the list is remote, use the {@link #store} + * config instead.

+ *

Each item within the provided array may be in one of the + * following formats:

+ *