PDA

View Full Version : Ext.ux.touch.grid.FilterRow plugin



tino7_03
9 Jan 2012, 1:21 PM
I have created a new plugin for a Ext.ux.touch.grid component.
Now it's possible to display a filterrow to the top of the grid like this:
30576

when the user writes into the specific textfield after one second the store is filtered by this text.


thanks to mitchelsimoens and estesbubba for the support

AndreaCammarata
10 Jan 2012, 2:01 AM
Nice!
Mitchell Simoens will be happy about it :)

tino7_03
13 Jan 2012, 2:37 AM
Now it's also possible set the config item nemed "filtersDisabled" to disable filtering actions and to have a personal filtering in "filter" event.
In this way it's possible to filter not downloaded data with paging feature because I can reload data every filter event.




{ ftype : 'Ext.ux.touch.grid.feature.FilterRow', launchFn : 'initialize',
filtersDisabled:true
}
Ext.create('Ext.ux.touch.grid.View', {
fullscreen : true,
store : myStore,
features : [
{ ftype : 'Ext.ux.touch.grid.feature.HeaderMenu', launchFn : 'initialize' },
{ ftype : 'Ext.ux.touch.grid.feature.Paging', launchFn : 'initialize' },
{ ftype : 'Ext.ux.touch.grid.feature.Sorter', launchFn : 'initialize' },
{ ftype : 'Ext.ux.touch.grid.feature.FilterRow', launchFn : 'initialize',
filtersDisabled:true
}
], columns : [
{ header : 'Code', dataIndex : 'code', width : '40%' }, { header : 'Description', dataIndex : 'descr', width : '60%' },

],
listeners:{ filter:function(filterComponent,grid,store,filtersArray){
var where=""; var c=0;
for (; c < filtersArray.length; c++){
where=where+" AND [myTab].["+filtersArray[c].property+"] LIKE '"+filtersArray[c].value+"%'";
} store.getProxy().url=myProxyUrl + where;
store.load();
}
}
});


For mitchellsimoens: I hide "Goto Page" button in Paging feauture because it has problems. If you have a time to test why...
Thanks in advance

shaneavery
19 Jan 2012, 9:48 PM
Thanks for this addition. FYI, there is a typo on line 25 of your script. It reads "cope:this," and should probably read "scope:this,"

Also, I have implemented your plugin in mitchellsimoens example, and I can't see the fields display using Chrome. The bar displays, and I can trace the HTML elements in the inspector, but the fields just don't display. See attached picture...

30846

Any guess why this is not working?

tino7_03
22 Jan 2012, 1:27 AM
I have had some problems using the plugin in MVC app and I have modified the feature in a new grid class that inherits from mitchellsimoens class.
I attach my class and an example.
I hope it's a good solution for you.
30887

shaneavery
23 Jan 2012, 6:05 PM
Tino, this is a good solution for me. Thank you very much. Unfortunately, I get an error in my console using Chrome:



Uncaught TypeError: Cannot call method 'call' of undefined





It references line 78 in your Ext.gp.Grid class which reads:


store.filter(filtersArray);

Thus, although the filter fields are displaying correctly now, the filters do not actually function. Also, I have come across an anomaly with mitchellsimoens example, which still shines through using your class. After a row is selected in the grid, if you choose a column to sort on, additional rows become painted as if they were selected in the grid display. See attached picture:

30929
Have you run into this before? Have you any suggestions for a workaround? Thanks again.

tino7_03
24 Jan 2012, 9:30 AM
Have you cheked the vale of
filtersArray?
can you try to write console.log(filtersArray); before my row 78 for to give me a result?
Thanks

shaneavery
24 Jan 2012, 11:01 AM
Tino, here is what is returned from filtersArray:




[Object



property: undefined
root: "data"
value: "dwila"
__proto__: Object


, Object



property: undefined
root: "data"
value: "shane@averydc.com"
__proto__: Object


, Object



property: undefined
root: "data"
value: "Your table is ready"
__proto__: Object


, Object



property: undefined
root: "data"
value: "Try our steak and egg special - just $4.95!"
__proto__: Object


, Object



property: undefined
root: "data"
value: Date
__proto__: Object


, Object



property: undefined
root: "data"
value: 5
__proto__: Object


, Object



property: "Name"
root: "data"
value: "a"
__proto__: Object


]




I am using more than one store in my App. These fields (except for "Name") are from a different store than what I defined in the grid config.

Thanks

tino7_03
24 Jan 2012, 12:14 PM
'property' is always empty. This is the model field name. Now I haven't the code with me for a debug, but tomorrow I'll see it. Can you send me the code used from call my class? And if is it possible the Store model definition?

shaneavery
24 Jan 2012, 3:23 PM
I see you are busy stamping out fires with PR4 on another thread. I am updating my poject code as well, and have run into the same issues. I guess we will need to wait until Mitchell updates his Grid before we can proceed much further with PR4. :-?

At any rate, here is my code from the PR3 version that you requested...

View class calling your class (focus on the "Test" item):




Ext.define('dwila.view.Viewport', {
extend: 'Ext.tab.Panel',

config: {
fullscreen: true,

tabBar: {
docked: 'bottom',
layout: {
pack: 'center'
}
},

defaults: {
scrollable: true
},

items: [
{
title: 'Customers',
iconCls: 'team',
xtype: 'grid',

features: [
{
ftype : 'Ext.ux.touch.grid.feature.HeaderMenu',
launchFn : 'initialize'
},
{
ftype : 'Ext.ux.touch.grid.feature.Sorter',
launchFn : 'initialize'
}/*,
{
ftype : 'Ext.ux.touch.grid.feature.FilterRow',
launchFn : 'initialize'
//filtersDisabled : false
}*/
]
},
{
title: 'Test',
iconCls: 'team',
xtype:'gridfr',
scope:this,
showFilterRow:true,
disableFilterRowActions:false,
store : 'Guests',
features : [
{
ftype : 'Ext.ux.touch.grid.feature.Sorter',
launchFn : 'initialize'
}
],
items: [
{
xtype : 'toolbar',
docked: 'top',
//title: 'Customers',
items: [
{
//text: 'Add',
ui: 'confirm',
id: 'guestAdd',
iconCls: 'add',
iconMask: true
},
{
//text: 'Delete',
ui: 'decline',
id: 'guestDelete',
iconCls: 'trash',
iconMask: true
},
{
text: 'Show Hidden',
ui: 'action',
//badgeText: 'Hidden',
//iconCls: 'search',
//iconMask: true,
id: 'showHidden'
},
{
text: 'Show All',
ui: 'action',
//badgeText: 'All',
//iconCls: 'search',
//iconMask: true,
id: 'showAll'
},
{xtype: 'spacer'},
{
text: 'Phone```',
badgeText: '#',
ui: 'action',
//iconCls: 'add',
//iconMask: true,
//right: 1,
//margin : '6 64 0 0',
id: 'phoneAdd'
},
{
text: 'Notify',
ui: 'confirm',
//iconCls: 'action',
//iconMask: true,
id: 'guestNotify'
}
]
}
],
columns : [
/* {
header : 'ID',
dataIndex : 'id',
sortable : false,
style : 'text-align: right; padding-right: 1em;',
width : '10%'
},
{
header : 'Phone',
dataIndex : 'Phone',
style : 'text-align: center;',
width : '10%',
sortable : false,
filter : { type : 'string' }
},
{
header : 'Carrier',
dataIndex : 'Carrier',
style : 'text-align: center;',
sortable : false,
filter : { type : 'string' },
width : '10%'
},
{
header : 'Seated Time',
dataIndex : 'date_seated',
style : 'text-align: center;',
width : '20%',
filter : { type : 'string' }
},
{
header : 'Notified Time',
dataIndex : 'date_sent',
style : 'text-align: center;',
width : '20%',
filter : { type : 'string' }
},*/
{
header : 'Party',
dataIndex : 'Party',
style : 'text-align: center;',
width : '15%',
filter : { type : 'int' }
},
{
header : 'Name',
dataIndex : 'Name',
style : 'padding-left: 4px;',
width : '25%',
filter : { type : 'string' }
},
{
header : 'Time',
dataIndex : 'date_reserved',
style : 'text-align: center;',
width : '22%',
filter : { type : 'date' },
renderer : function(value, values) {
var showtime = Ext.Date.format(value, 'h:ia');
return '<span>' + showtime + '</span>';
}
},
{
header : 'Notified',
dataIndex : 'Sent',
style : 'text-align: center;',
width : '19%',
filter : { type : 'boolean' },
renderer : function(value, values) {
var color = (value === true) ? '009933' : 'FF0000',
text = (value === true) ? 'Yes' : 'No';
return '<span style="color: #' + color + ';">' + text + '</span>';
}
},
{
header : 'Seated',
dataIndex : 'Seated',
style : 'text-align: center;',
width : '19%',
filter : { type : 'boolean' },
renderer : function(value, values) {
var color = (value === true) ? '009933' : 'FF0000',
text = (value === true) ? 'Yes' : 'No';
return '<span style="color: #' + color + ';">' + text + '</span>';
}
}
]
},
{
title: 'Settings',
iconCls: 'settings',
xtype: 'settings'
},
{
title: 'Help',
iconCls: 'info',
xtype: 'welcomescreen'
},
{
title: 'Exit Dwila',
iconCls: 'reply',
xtype: 'button',
id: 'exitApp'
}
]
}
});



Store class defined in above view:



Ext.define('dwila.store.Guests', {
extend: 'Ext.data.Store',
model: 'dwila.model.Guest',
autoSync: true,
sorters: [
{
property : 'date_reserved',
direction: 'DESC'
}
],
autoLoad: true
});



Model class defined in above store:



Ext.define('dwila.model.Guest', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id', type: 'string' },
{ name: 'Party', type: 'int' },
{ name: 'Name', type: 'string' },
{ name: 'Phone', type: 'string' },
{ name: 'Carrier', type: 'string' },
{ name: 'Tags', type: 'string' },
{ name: 'Sent', type: 'boolean', defaultValue: false },
{ name: 'Seated', type: 'boolean', defaultValue: false },
{ name: 'Hide', type: 'boolean', defaultValue: false },
{ name: 'Table', type: 'int' },
{ name: 'date_reserved', type: 'date', defaultValue: new Date(), dateFormat: 'c' },
{ name: 'date_sent', type: 'date', dateFormat: 'c' },
{ name: 'date_seated', type: 'date', dateFormat: 'c' }
],
proxy: {
type: 'localstorage',
id: 'id',
reader: {
type: 'json',
root: 'guests',
idProperty : 'id'
},
writer: {
root: 'guests',
type: 'json',
writeAllFields: true
}
},
validations: [
{type: 'format', name: 'Party', matcher: /\d+/, message: 'must only include numeric characters'},
{type: 'presence', name: 'Name', message: 'field is required'},
{type: 'length', name: 'Phone', min: 10, max: 10, message: 'numbers must be exactly 10 digits'},
{type: 'format', name: 'Phone', matcher: /\d+/, message: 'must only include numeric characters'},
{type: 'format', name: 'Table', matcher: /\d+/, message: 'must only include numeric characters'}
]
});

tino7_03
24 Jan 2012, 11:24 PM
Sorry, but I don't understand all your code. Is "gridfr" a your class extend my class?
I see you use "items" in "gridfr" and you put in it a toolbar. I think the problem is here.
I explain to you how I write the property value in my "
filtersArray" object.When I create my toolbar I add for each field a property named "
fieldName" with the name of the model field.(line 47).
When I apply the filter i get all textfields in my toolbar (line 67) and for each of them I put the
"
fieldName" property in "
filtersArray.property".
You must verify if the row field "fieldName" property has value. Can you try to put this line after my line 42 for verify if dataIndex returns a value?

console.log(dataIndex)
If yes the problem is in line 67 when I read all textfields in my toolbar (is it possible for your toolbar in items?).
If no I have a new element for understand the problem.
Thanks

shaneavery
25 Jan 2012, 1:23 PM
Hello Tino,


If yes the problem is in line 67 when I read all textfields in my toolbar (is it possible for your toolbar in items?).

Indeed the problem was in line 67, which reads:

var filterRowCell=Ext.ComponentQuery.query('textfield');
That will return all text fields in the entire document. Since I have several views/stores in my app and they reference different model classes, selecting all 'textfields' to create a filterRowCell array returns more fields than are defined in the grid. Therefore, many of the fields were processed with "undefined" properties, which threw the error. As a workaround, I added a conditional to only process textfields in the filterRowCell array that match a DOM property unique to fields associated with my Grid. So, I changed your code from this:

var filterRowCell=Ext.ComponentQuery.query('textfield');
filtersArray=[];
for (c=0; c < filterRowCell.length; c++) {
var textField=filterRowCell[c].getValue();
if(textField!=null&&textField!=''){
filtersArray.push({property: filterRowCell[c].fieldName, value: textField, root: 'data'});
}
}
To this:

var filterRowCell=Ext.ComponentQuery.query('textfield');
console.log(filterRowCell);
filtersArray=[];
for (c=0; c < filterRowCell.length; c++) {
if(filterRowCell[c].parent.flex==1) {
var textField=filterRowCell[c].getValue();
if(textField!=null&&textField!=''){
filtersArray.push({property: filterRowCell[c].fieldName, value: textField, root: 'data'});
}
}
}

This is a hack, but it works for now. Perhaps you can think of an elegant solution?

A few more hacks I had to do may be of interest to you. First, the "clearIcon" does not work correctly in filter fields currently, it does not clear the value, just the display. Therefore, I just set "clearIcon:false" in your config. Second, your implementation for filtering does not support date fields. Since I am using one date field in my grid, I decided to just disable the display of the filter field. This brings me to hack 3, which conditionally displays columns based on their "dataIndex" variable. So, I changed your code from this:

_filterRowToolbarPainted : function (e) {
var filterRowCell=Ext.query("div.x-grid-filterrow-cell");
for (c=0; c < filterRowCell.length; c++) {
var elem = new Ext.Element(filterRowCell[c]);
elem.update('');
var fieldHeight=parseInt(e.element.dom.offsetHeight*70/100)+'px';
var dataIndex = elem.getAttribute('dataindex');
var filterField=Ext.create('Ext.field.Text', {
flex:1,
placeHolder:'Filter...',
height : fieldHeight,
fieldName:dataIndex
});
To This:

_filterRowToolbarPainted : function (e) {
var filterRowCell=Ext.query("div.x-grid-filterrow-cell");
for (c=0; c < filterRowCell.length; c++) {
var elem = new Ext.Element(filterRowCell[c]);
elem.update('');
var fieldHeight=parseInt(e.element.dom.offsetHeight*70/100)+'px';
var dataIndex = elem.getAttribute('dataindex');
if (dataIndex!='date_reserved') {
var filterField=Ext.create('Ext.field.Text', {
flex:1,
placeHolder:'Filter',
clearIcon : false,
height : fieldHeight,
fieldName: dataIndex
});
}
if (dataIndex=='date_reserved') {
var filterField=Ext.create('Ext.Spacer', {
flex:1,
html: '<center style="color:white;">No Filter</center>'
});
}
Again, these are just ugly hacks as a workaround for now. Perhaps you can give this some thought and implement a much better solution? Thanks for providing this, it is a very useful extension, and I am glad you made it available.

tino7_03
26 Jan 2012, 10:16 AM
Thanks for the help!
I have updated my component.
Now I have added a date filter and I have solved the bugs.
I attach new version of my component and an example with mitchellsimoens exemple data.
In the index.js files I have commented all my parameters with default value.
Thanks again.
31050

shaneavery
26 Jan 2012, 2:50 PM
Wow, this is great! Thanks for the quick follow up. This is turning into a fantastically polished extension. Nice Work! Let me know if I can assist with any other testing/evaluating. :D

shaneavery
26 Jan 2012, 3:39 PM
I discovered a condition that may be beyond your control to fix, but perhaps not. I noticed that filters done on datePicker fields will not return results if the time segment is not stored as 00:00:00. I realize that the datePicker does not record time values, so by default, it records the time as 00:00:00 (i.e. "2012-01-08T00:00:00"). However, I am relying heavily on the time segment in my app, and I automate the recording of Javascript dates without using the datePicker field.

So my question is: is there a way to filter datePicker fields by ignoring the time segment, such that all records matching a specific date are returned, regardless of the time value?

Thanks.

shaneavery
26 Jan 2012, 6:38 PM
I found a workaround to my dilemma by adding conditionals to your _applyFilter:function() around line 169. I am basically passing filtersArray objects as normal if the "type" property is a "string". If the "type" property is a "date", then I reformat the date, convert in into a string, and then set that as the "value" property. I also added the "anyMatch : true" property, which is required for this hack to work. The result is that I get returned from the _applyFilter function the dates I expect, irrespective of what time segment values the records contain. See code:



_applyFilter:function(){
//console.log(this);
var store = this.getStore(),
filters = store.filters;

var filterRowCell=Ext.ComponentQuery.query('*[gpGridFilterField=true]');
filtersArray=[];
for (c=0; c < filterRowCell.length; c++) {
var textField=filterRowCell[c].getValue();
if(textField!=null&&textField!=''){
if (filterRowCell[c].gpGridFilterFieldType == 'string') {
filtersArray.push({
property: filterRowCell[c].fieldName,
value: textField,
type:filterRowCell[c].gpGridFilterFieldType,
root: 'data'
});
}
if (filterRowCell[c].gpGridFilterFieldType == 'date') {
textField = Ext.Date.format( textField, 'M d Y' );
filtersArray.push({
property: filterRowCell[c].fieldName,
value: textField.toString(),
type:filterRowCell[c].gpGridFilterFieldType,
root: 'data',
anyMatch : true
});
}
}
}


I don't know if this is of any use to you, but it works for me, at least for now. Perhaps you have a better way?

Thanks.

shaneavery
27 Jan 2012, 8:09 PM
Tino,

I also noticed that there is no way to clear a date filter in the GUI after it is set. This is something I expect will be required. I tried some differing approaches, but am having trouble choosing an event to act on. One idea I have is to automatically clear a date filter whenever any other text field is given focus. What do you think? I have made some attempts, but keep getting lost in scope. How would you implement?

Thanks

tino7_03
27 Jan 2012, 11:25 PM
in my class I have removed and after added again the component.
After I have found this:
http://www.sencha.com/forum/showthread.php?173669-How-to-blank-out-a-DatePickerField&highlight=datePicker

(http://www.sencha.com/forum/showthread.php?173669-How-to-blank-out-a-DatePickerField&highlight=datePicker)

shaneavery
28 Jan 2012, 6:19 PM
Tino,

Until Sencha provides a way to blank out a DatePickerField, I found this approach works for me. I just added a "Show All" button in my controller that clears all filters and resets the datepicker field's "input" value attribute to "". See code:



onShowAllBtn: function() {
var dateField = Ext.ComponentQuery.query('datepickerfield[fieldName=date_reserved]')[0],
partyField = Ext.ComponentQuery.query('textfield[fieldName=Party]')[0],
nameField = Ext.ComponentQuery.query('textfield[fieldName=Name]')[0],
sentField = Ext.ComponentQuery.query('textfield[fieldName=Sent]')[0],
seatedField = Ext.ComponentQuery.query('textfield[fieldName=Seated]')[0];
dateField.setValue(null);
partyField.setValue(null);
nameField.setValue(null);
sentField.setValue(null);
seatedField.setValue(null);
this.getGrid()._applyFilter();
this.getGrid().getStore().clearFilter();
dateField.element.dom.lastChild.firstChild.firstChild.value="";
}


Works for now...

tino7_03
28 Jan 2012, 9:48 PM
I'll try it in my class for remove the code that replaces data field each reset.
Thanks

EDIT:
In my component your solution change the display value only and note the component value. I hope that sencha solve this issue because I don't like remove and after add my datePicker component for to be empty

tino7_03
29 Jan 2012, 3:16 AM
31128
Now it's possible set "showFilterRowPaging:true" for to have a paging feature inside filterRow.
31127

shaneavery
6 Feb 2012, 8:40 PM
Tino,

Thanks for the updates. I am trying your extension with Beta1, and am running into some issues:



DateExtras.js:1310 (http://localhost/dwila/touch/src/DateExtras.js?_dc=1328589405530)Uncaught TypeError: Cannot read property 'Date' of undefined



Ext.gp.Grid.js:47 (http://localhost/dwila/app/view/Grid.js?_dc=1328589406832)Uncaught TypeError: Object [object Object] has no method 'update'



Ext.gp.Grid.js:229 (http://localhost/dwila/app/view/Grid.js?_dc=1328589406832)Uncaught TypeError: Cannot call method 'setDisabled' of undefined

Just curious if you have run into issues using Ext.gp.Grid with ST2 Beta1. Any ideas?

PS: I am using Mitchell Simoens updated Ext.ux.touch.grid for B1.

shaneavery
8 Feb 2012, 9:21 AM
Tino,

I just had to adjust a couple of lines in your class to get it to work in ST2-B1 using Mitchell Simoens updated Ext.ux.touch.grid for B1.

First, I changed line 47 from this:



elem.update('');


to this:



elem.setHtml('');


I also adjusted your _handleGridPaint function to make the painting of the forward and back buttons a conditional so that the relevant block of code only runs if this.showFilterRowPaging == true. This keeps ST2 from complaining to the console that "backButton" etc. is "undefined". So I changed lines 219-227 in your code from this:



var total = grid.store.getTotalCount(),
currentPage = grid.store.currentPage,
pages = Math.ceil(total / grid.store.getPageSize()),
backButton = Ext.ComponentQuery.query('button[gpGridFilterBackBtn='+this.getId()+']')[0],
forwardButton = Ext.ComponentQuery.query('button[gpGridFilterForwardBtn='+this.getId()+']')[0]
console.log();

backButton.setDisabled(currentPage == 1);
forwardButton.setDisabled(currentPage == pages);


to this:



if (this.showFilterRowPaging==true) {
var total = grid.store.getTotalCount(),
currentPage = grid.store.currentPage,
pages = Math.ceil(total / grid.store.getPageSize()),
backButton = Ext.ComponentQuery.query('button[gpGridFilterBackBtn='+this.getId()+']')[0],
forwardButton = Ext.ComponentQuery.query('button[gpGridFilterForwardBtn='+this.getId()+']')[0]
console.log();

backButton.setDisabled(currentPage == 1);
forwardButton.setDisabled(currentPage == pages);
}


Hope this is useful to you. So far, after making these minor adjustments, Ext.gp.Grid is working as expected using Mitchell Simoens Ext.ux.touch.grid for ST2-B1.