4 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
5 * @author Cornelius Weiss <c.weiss@metaways.de>
6 * @copyright Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
8 * TODO add year to 'inweek' filter?
10 Ext.ns('Tine.widgets.grid');
15 * @namespace Tine.widgets.grid
16 * @class Tine.widgets.grid.FilterModel
17 * @extends Ext.util.Observable
20 Tine.widgets.grid.FilterModel = function(config) {
21 Ext.apply(this, config);
22 Tine.widgets.grid.FilterModel.superclass.constructor.call(this);
26 * @event filtertrigger
27 * is fired when user request to update list by filter
28 * @param {Tine.widgets.grid.FilterToolbar}
36 Ext.extend(Tine.widgets.grid.FilterModel, Ext.util.Observable, {
39 * label for the filter
45 * name of th field to filter
50 * @cfg {String} valueType
56 * @cfg {String} defaultValue
62 * @cfg {Array} operators
68 * @cfg {String} defaultOperator
69 * name of the default operator
71 defaultOperator: null,
74 * @cfg {Array} customOperators
75 * define custom operators
77 customOperators: null,
80 * @cfg {Ext.data.Store|Array}
81 * used by combo valueType
86 * @cfg {String} displayField
87 * used by combo valueType
92 * @cfg {String} valueField
93 * used by combo valueType
96 filterValueWidth: 200,
99 * holds the future operators of date filters. Auto set by getDateFutureOps
106 * holds the future operators of date filters. Auto set by getDatePastOps
115 initComponent: function() {
116 this.isFilterModel = true;
118 if (! this.operators) {
123 if (this.defaultOperator === null) {
124 switch (this.valueType) {
127 this.defaultOperator = 'within';
137 this.defaultOperator = 'equals';
141 this.defaultOperator = 'contains';
146 if (this.defaultValue === null) {
147 switch (this.valueType) {
150 this.defaultValue = '';
153 this.defaultValue = '1';
156 this.defaultValue = '0';
169 this.datePastOps = this.getDatePastOps();
170 this.dateFutureOps = this.getDateFutureOps();
174 * returns past operators for date fields, may be overridden
178 getDatePastOps: function() {
180 ['dayThis', i18n._('today')],
181 ['dayLast', i18n._('yesterday')],
182 ['weekThis', i18n._('this week')],
183 ['weekLast', i18n._('last week')],
184 ['weekBeforeLast', i18n._('the week before last')],
185 ['monthThis', i18n._('this month')],
186 ['monthLast', i18n._('last month')],
187 ['monthThreeLast', i18n._('last three months')],
188 ['monthSixLast', i18n._('last six months')],
189 ['anytime', i18n._('anytime')],
190 ['quarterThis', i18n._('this quarter')],
191 ['quarterLast', i18n._('last quarter')],
192 ['yearThis', i18n._('this year')],
193 ['yearLast', i18n._('last year')]
198 * returns future operators for date fields, may be overridden
202 getDateFutureOps: function() {
204 ['dayNext', i18n._('tomorrow')],
205 ['weekNext', i18n._('next week')],
206 ['monthNext', i18n._('next month')],
207 ['quarterNext', i18n._('next quarter')],
208 ['yearNext', i18n._('next year')]
212 onDestroy: Ext.emptyFn,
217 * @param {Ext.data.Record} filter line
218 * @param {Ext.Element} element to render to
220 operatorRenderer: function (filter, el) {
221 var operatorStore = new Ext.data.JsonStore({
222 fields: ['operator', 'label'],
224 {operator: 'contains', label: i18n._('contains')},
225 {operator: 'notcontains', label: i18n._('contains not')},
226 {operator: 'regex', label: i18n._('reg. exp.')},
227 {operator: 'equals', label: i18n._('is equal to')},
228 {operator: 'equalsspecial', label: i18n._('is equal to without (-, )')},
229 {operator: 'greater', label: i18n._('is greater than')},
230 {operator: 'less', label: i18n._('is less than')},
231 {operator: 'not', label: i18n._('is not')},
232 {operator: 'in', label: i18n._('one of')},
233 {operator: 'notin', label: i18n._('none of')},
234 {operator: 'before', label: i18n._('is before')},
235 {operator: 'after', label: i18n._('is after')},
236 {operator: 'within', label: i18n._('is within')},
237 {operator: 'inweek', label: i18n._('is in week no.')},
238 {operator: 'startswith', label: i18n._('starts with')},
239 {operator: 'endswith', label: i18n._('ends with')},
240 {operator: 'definedBy', label: i18n._('defined by')}
241 ].concat(this.getCustomOperators() || []),
250 if (this.operators.length == 0) {
251 switch (this.valueType) {
253 this.operators.push('contains', 'notcontains', 'equals', 'startswith', 'endswith', 'not', 'in', 'notin');
256 this.operators.push('contains', 'equals', 'startswith', 'endswith', 'not');
259 this.operators.push('equals', 'before', 'after', 'within', 'inweek');
263 this.operators.push('equals', 'greater', 'less');
266 this.operators.push(this.defaultOperator);
271 if (this.operators.length > 0) {
272 operatorStore.each(function(operator) {
273 if (this.operators.indexOf(operator.get('operator')) < 0 ) {
274 operatorStore.remove(operator);
279 if (operatorStore.getCount() > 1) {
280 var operator = new Ext.form.ComboBox({
283 id: 'tw-ftb-frow-operatorcombo-' + filter.id,
286 emptyText: i18n._('select a operator'),
287 forceSelection: true,
289 triggerAction: 'all',
290 store: operatorStore,
291 displayField: 'label',
292 valueField: 'operator',
293 value: filter.get('operator') ? filter.get('operator') : this.defaultOperator,
294 tpl: '<tpl for="."><div class="x-combo-list-item tw-ftb-operator-{operator}">{label}</div></tpl>',
297 operator.on('select', function(combo, newRecord, newKey) {
298 if (combo.value != combo.filter.get('operator')) {
299 this.onOperatorChange(combo.filter, combo.value);
303 operator.on('blur', function(combo) {
304 if (combo.value != combo.filter.get('operator')) {
305 this.onOperatorChange(combo.filter, combo.value);
309 } else if (this.operators[0] == 'freeform') {
310 var operator = new Ext.form.TextField({
313 emptyText: this.emptyTextOperator || '',
314 value: filter.get('operator') ? filter.get('operator') : '',
318 var operator = new Ext.form.Label({
321 style: {margin: '0px 10px'},
322 getValue: function() { return operatorStore.getAt(0).get('operator'); },
323 text : operatorStore.getAt(0).get('label'),
325 setValue: Ext.emptyFn
333 * get custom operators
337 getCustomOperators: function() {
338 return this.customOperators || [];
342 * called on operator change of a filter row
345 onOperatorChange: function(filter, newOperator) {
346 filter.set('operator', newOperator);
347 filter.set('value', '');
349 // for date filters we need to rerender the value section
350 if (this.valueType == 'date') {
351 switch (newOperator) {
353 filter.numberfield.hide();
354 filter.datePicker.hide();
355 filter.withinCombo.show();
356 filter.formFields.value = filter.withinCombo;
359 filter.withinCombo.hide();
360 filter.datePicker.hide();
361 filter.numberfield.show();
362 filter.formFields.value = filter.numberfield;
365 filter.withinCombo.hide();
366 filter.numberfield.hide();
367 filter.datePicker.show();
368 filter.formFields.value = filter.datePicker;
376 * @param {Ext.data.Record} filter line
377 * @param {Ext.Element} element to render to
379 valueRenderer: function(filter, el) {
381 fieldWidth = this.filterValueWidth,
385 id: 'tw-ftb-frow-valuefield-' + filter.id,
387 value: filter.data.value ? filter.data.value : this.defaultValue
390 switch (this.valueType) {
392 value = this.dateValueRenderer(filter, el);
395 value = new Ext.ux.PercentCombo(Ext.apply(commonOptions, {
397 'specialkey': function(field, e) {
398 if(e.getKey() == e.ENTER){
399 this.onFiltertrigger();
402 'select': this.onFiltertrigger,
408 value = new Tine.Addressbook.SearchCombo(Ext.apply(commonOptions, {
410 emptyText: i18n._('Search Account ...'),
413 nameField: 'n_fileas',
414 useAccountRecord: true,
416 'specialkey': function(field, e) {
417 if(e.getKey() == e.ENTER){
418 this.onFiltertrigger();
421 'select': this.onFiltertrigger,
427 value = new Ext.form.ComboBox(Ext.apply(commonOptions, {
429 forceSelection: true,
430 triggerAction: 'all',
432 [0, Locale.getTranslationData('Question', 'no').replace(/:.*/, '')],
433 [1, Locale.getTranslationData('Question', 'yes').replace(/:.*/, '')]
436 'specialkey': function(field, e) {
437 if(e.getKey() == e.ENTER){
438 this.onFiltertrigger();
441 'select': this.onFiltertrigger,
447 var comboConfig = Ext.apply(commonOptions, {
449 forceSelection: true,
450 triggerAction: 'all',
453 'specialkey': function(field, e) {
454 if(e.getKey() == e.ENTER){
455 this.onFiltertrigger();
458 'select': this.onFiltertrigger,
462 if (this.displayField !== null && this.valueField !== null) {
463 comboConfig.displayField = this.displayField;
464 comboConfig.valueField = this.valueField;
466 value = new Ext.form.ComboBox(comboConfig);
469 value = new Tine.widgets.CountryCombo(Ext.apply(commonOptions, {
476 value = new Ext.form.TextField(Ext.apply(commonOptions, {
477 emptyText: this.emptyText,
480 specialkey: function(field, e){
481 if(e.getKey() == e.ENTER){
482 this.onFiltertrigger();
494 * called on value change of a filter row
497 onValueChange: function(filter, newValue) {
498 filter.set('value', newValue);
502 * render a date value
504 * we place a picker and a combo in the dom element and hide the one we don't need yet
506 dateValueRenderer: function(filter, el) {
507 var operator = filter.get('operator') ? filter.get('operator') : this.defaultOperator;
509 var valueType = 'datePicker';
512 valueType = 'withinCombo';
515 valueType = 'numberfield';
519 var comboOps = this.pastOnly ? this.datePastOps : this.dateFutureOps.concat(this.datePastOps);
520 var comboValue = 'weekThis';
521 if (filter.data.value && filter.data.value.toString().match(/^[a-zA-Z]+$/)) {
522 comboValue = filter.data.value.toString();
523 } else if (this.defaultValue && this.defaultValue.toString().match(/^[a-zA-Z]+$/)) {
524 comboValue = this.defaultValue.toString();
527 filter.withinCombo = new Ext.form.ComboBox({
528 hidden: valueType != 'withinCombo',
530 width: this.filterValueWidth,
535 forceSelection: true,
537 triggerAction: 'all',
541 'specialkey': function(field, e) {
542 if(e.getKey() == e.ENTER){
543 this.onFiltertrigger();
546 'select': this.onFiltertrigger,
551 var pickerValue = '';
552 if (Ext.isDate(filter.data.value)) {
553 pickerValue = filter.data.value;
554 } else if (Ext.isDate(Date.parseDate(filter.data.value, Date.patterns.ISO8601Long))) {
555 pickerValue = Date.parseDate(filter.data.value, Date.patterns.ISO8601Long);
556 } else if (Ext.isDate(this.defaultValue)) {
557 pickerValue = this.defaultValue;
560 filter.datePicker = new Ext.form.DateField({
561 hidden: valueType != 'datePicker',
563 width: this.filterValueWidth,
567 'specialkey': function(field, e) {
568 if(e.getKey() == e.ENTER){
569 this.onFiltertrigger();
572 'select': this.onFiltertrigger,
577 filter.numberfield = new Ext.form.NumberField({
578 hidden: valueType != 'numberfield',
580 width: this.filterValueWidth,
586 allowDecimals: false,
587 allowNegative: false,
590 specialkey: function(field, e){
591 if(e.getKey() == e.ENTER){
592 this.onFiltertrigger();
598 // upps, how to get a var i only know the name of???
599 return filter[valueType];
605 onFiltertrigger: function() {
606 // auto search on filter change only if set in user preferences
607 if (parseInt(Tine.Tinebase.registry.get('preferences').get('filterChangeAutoSearch'), 10) === 1) {
608 this.fireEvent('filtertrigger', this);
614 * @namespace Tine.widgets.grid
615 * @class Tine.widgets.grid.FilterRegistry
618 Tine.widgets.grid.FilterRegistry = function() {
622 register: function(appName, modelName, filter) {
623 var key = appName + '.' + modelName;
624 if (! filters[key]) {
628 filters[key].push(filter);
631 get: function(appName, modelName) {
632 if (Ext.isFunction(appName.getMeta)) {
633 modelName = appName.getMeta('modelName');
634 appName = appName.getMeta('appName');
637 var key = appName + '.' + modelName;
639 return filters[key] || [];