jqGrid: using multiple methods to filter data - asp.net

My requirement is to show a page with multiple filters to apply to grid data.
Suppose we are talking about Orders and an order has the following attributes
public class Order {
public int OrderID
public DateTime OrderDate
public DateTime ShipmentDate
public int OrderTotal
public int OrderStatus
}
Inside the jqgrid object I am showing all the attributes except the OrderStatus
The requirement is to create a view that has
the jqGrid on the left part
a panel on the right
Inside the right panel the user will see a list of checkboxes that represents every possible OrderStatus value and he want to search using both methods (for example selecting the checkbox "Shipped Orders" and then filtering the grid with Amount greater than a value)
I have already configured the advanced filtering (multiplesearch:true) inside the jqGrid object and I am able to create complex filters combining fields and logical operators.
Any ideas on how I can submit even the data from the right panel when the user press the search button?
Update 1:
Preamble: Oleg sample is fantastic but unfortunately does not fits requirements of my customer :(
#Oleg: I do not understand why you think that:
If the data are outside of the grid
you will be show the order details on
the right pane only for selected row.
So the user will have not so good
overview of the data.
maybe my description was not so clear but I am not going to show any order detail. To better clarify my requirement I have modified your sample to show you the desired final UI which is as in the following image:
The customer want to filter the data in the grid using two methods or both together:
Using the multiplesearch facilities provided by the grid itself (thanks for the workaround mention)
Using the custom Search Panel (the one with the checkboxes on the right) provided
From a functional point of view the requirement is very easy to express: When the user click on a checkbox or make a search using the native multiplesearch I should post values to the server including also the checkboxes state.
To summarize I should:
Add the checkboxes state when a post is made through the native multiplesearch
Add the current multiplesearch state (if any) when the user click on a checkbox
Is there a way to do this?

I understand this requirement very good. In the close case I used checkboxes inside of jqGrid. The most advantage to having the information inside of jqGrid is not only the possibility of easy searching. If the data are outside of the grid you will be show the order details on the right pane only for selected row. So the user will have not so good overview of the data.
To be able to place many checkboxes in the table without permanent horizontal scrolling I rotated headers of the columns having "checkbox with the technique described in Vertical text inside table headers using a JavaScript-based SVG library. This rotation looks not perfect in IE, but in other browser it works perfect.
You can hold the data from the OrderStatus field in a hidden column and decode the bitmask to boolean which build checkboxes either on the client or on the server side.
Because use want to use multiplesearch:true I have to mention about a bug in jQuery.clone which follow to the bug in jqGrid multi-search in all versions of IE browsers. If you define more as one search filters only the first one will be used because the operation field of all other filters will be read as undefined. It's a pity, but the bug is also not fixed in the jQuery 1.4.3 just published. To be able to use multiplesearch:true you can use workaround suggestion by Jiho Han on trirand.com forum.
All together you can see in the demo example which produce the grid
where you can search for multiple fields
The corresponding code:
var myData = [
{ orderID: "10", orderDate: "2010-09-18", shipmentDate: "2010-09-20", orderStatus: "2" },
{ orderID: "15", orderDate: "2010-09-20", shipmentDate: "2010-09-24", orderStatus: "3" },
{ orderID: "20", orderDate: "2010-10-16", shipmentDate: "2010-10-17", orderStatus: "1" }
];
// decode 'orderStatus' column and add additional boolean data based on the bitmap mask
for (var i=0, l=myData.length; i<l; i++) {
var myRow = myData[i];
var orderStatus = parseInt(myRow.orderStatus, 10);
myRow.airPost = (orderStatus & 2) != 0? "1": "0";
myRow.heavy = (orderStatus & 1) != 0? "1": "0";
}
var grid = jQuery('#list');
grid.jqGrid({
data: myData,
datatype: 'local',
caption: 'Order Details',
height: 'auto',
gridview: true,
rownumbers: true,
viewrecords: true,
pager: '#pager',
rownumbers: true,
colNames: ['Order ID', 'Order', 'Shipment', 'Air-Post', 'Heavy', 'RowVersion'],
colModel: [
{ name: 'orderID', index: 'orderID', key:true, width: 120, sorttype: 'int' },
{ name: 'orderDate', index: 'orderDate', width: 180,
sorttype: 'date', formatter: 'date' },
{ name: 'shipmentDate', index: 'shipmentDate', width: 180,
sorttype: 'date', formatter: 'date' },
{ name: 'airPost', width: 21, index: 'airPost', formatter: 'checkbox', align: 'center',
editoptions: { value: "1:0" }, stype: 'select', searchoptions: { value: "1:Yes;0:No" } },
{ name: 'heavy', width: 21, index: 'heavy', formatter: 'checkbox', align: 'center',
editoptions: { value: "1:0" }, stype: "select", searchoptions: { value: "1:Yes;0:No" } },
{ name: 'orderStatus', index: 'orderStatus', width: 50, hidden: true }
]
}).jqGrid ('navGrid', '#pager', { edit: false, add: false, del: false, refresh: true, view: false },
{},{},{},{multipleSearch:true})
.jqGrid ('navButtonAdd', '#pager', { caption: "", buttonicon: "ui-icon-calculator", title: "choose columns",
onClickButton: function() {
grid.jqGrid('columnChooser');
}
});
where rotateCheckboxColumnHeaders and the bugfix in the advanced search defined so
// we use workaround from http://www.trirand.com/blog/?page_id=393/bugs/in-multiple-search-second-and-subsequent-ops-are-sent-as-undefined-in-ie6/
// to fix the bug in the jQuery.clone (see http://bugs.jquery.com/ticket/6793 and
// dscussion on the http://api.jquery.com/clone/
jQuery.event.special.click = {
setup: function() {
if (jQuery(this).hasClass("ui-search")) {
jQuery(this).bind("click", jQuery.event.special.click.handler);
}
return false;
},
teardown: function() {
jQuery(this).unbind("click", jQuery.event.special.click.handler);
return false;
},
handler: function(event) {
jQuery(".ui-searchFilter td.ops select").attr("name", "op");
}
};
var rotateCheckboxColumnHeaders = function (grid, headerHeight) {
// we use grid as context (if one have more as one table on tnhe page)
var trHead = jQuery("thead:first tr", grid.hdiv);
var cm = grid.getGridParam("colModel");
jQuery("thead:first tr th").height(headerHeight);
headerHeight = jQuery("thead:first tr th").height();
for (var iCol = 0; iCol < cm.length; iCol++) {
var cmi = cm[iCol];
if (cmi.formatter === 'checkbox') {
// we must set width of column header div BEFOR adding class "rotate" to
// prevent text cutting based on the current column width
var headDiv = jQuery("th:eq(" + iCol + ") div", trHead);
headDiv.width(headerHeight).addClass("rotate");
if (!jQuery.browser.msie) {
if (jQuery.browser.mozilla) {
headDiv.css("left", (cmi.width - headerHeight) / 2 + 3).css("bottom", 7);
}
else {
headDiv.css("left", (cmi.width - headerHeight) / 2);
}
}
else {
var ieVer = jQuery.browser.version.substr(0, 3);
// Internet Explorer
if (ieVer !== "6.0" && ieVer !== "7.0") {
jQuery("span", headDiv).css("left", 0);
headDiv.css("left", cmi.width / 2 - 4).css("bottom", headerHeight / 2);
}
else {
headDiv.css("left", 3);
}
headDiv.parent().css("zoom",1);
}
}
}
};
If you do prefer to hold the checkboxes outside of the grid you can do the decoding of the bit-mask OrderStatus inside of onSelectRow event handler.
UPDATED: I really something misunderstood your requirements at the beginning. Look at the modified example. Now it looks like
and it is more close to what you need.

Just as a follow up I am including here another method that I have found to abtain the same result.
This method suppose to use the postData parameter of jqGrid. Inside the method I have defined various function that verify the checkboxes current status and sends a parameter to the server where it can be used for filtering.
This is a sample
postData: {
pending: function () {
if ($("#cb_pending").is(':checked')) {
return true;
} else {
return false;
}
}
}
The advantage of this solution respect to the one depicted by Oleg is that it is possible to use mixed logical operators (AND/OR) on the server side whereas using the filters section as in the Oleg answer it is not possible.
Happy coding!

Related

ExtJS 4 button next

Problem is probably simple but I can't find the proper answer.
I have a panel with only four buttons. Just vbox with buttons. And now I want to select and press this buttons not only with mouse click but also via keyboard. So I used Ext.KeyMap and I catch every key I want to: ENTER, TAB, DOWN and UP. But I can't find simple way to move focus betveen this buttons.
E.g. I'm focused on first button, press DOWN key and I want to be focused on the second button. No way. I tried
but1.down('button[action=next]').focus(false, 100))
but it can't work. Be so kind as to prompt please.
Well what can I say, just VERY BIG thank you – it is working obviously.
I had no idea to use KeyNav.
But it’s working in such a simple example My case is a little bit more complicated and I asked this question just simplified my situation. So this menu is the second screen in my application and it is constructed in such a way:
PANEL
Panel1
Label1
Button 1
Button 2
Button 3
Panel2
Label2
Button 4
Button 5
Panel3
Label3
Button 6
Button 7
Button 8
and every user can see (panels are hidden on the start) and use parts of this structure (e.g. Panel1, 3 and Button 8). I decide about it one step before (login step) and I show to the user menu only for him in the container defined:
Ext.define('Gromel.view.Menu', {
extend: 'Ext.container.Container',
requires:[
'Ext.tab.Panel',
'Ext.layout.container.Border',
'Ext.form.Label',
'Ext.form.Panel'
],
xtype: 'app-menu',
fullscreen: true,
layout: 'fit',
...
I place your code on the PANEL level and it's working almost properly. And I use the following default definition for above items:
defaults: {
margin: '0 0 10 0',
baseCls: 'x-btn-default-large',
cls: 'cap-btn',
style: 'font-size:18px;text-align: center',
height: 40,
width: '50%',
// yours:
handler: function () {
this.up('panel').activeButton = this.activeIndex;
}
}
so I modified your cls change method to:
button[method]('x-focus x-btn-focus x-btn-default-large-focus')
and your procedure is working.
Problems I can't manage now are:
as you see on the same level as buttons I have labels and I don't know how to bypass them;
more important - I want to run this procedure only on menu panel not in login panel and the rest (deeper in my structure) but this is global, so if I press ENTER on login panel I see the effect on next, menu panel; I replaced ENTER effect in the following way (I want to press ENTER on every button in menu and run different presses events):
case e.ENTER: butt.fireEvent('click', butt); break;
So if it isn’t to much for you be so kind and prompt me more please.
Try to use ExtJS KeyNav.
KeyNav provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind navigation keys to function calls that will get called when the keys are pressed, providing an easy way to implement custom navigation schemes for any UI component.
As you said
I have a panel with only four buttons.
I have work around that, I have created an small sencha fiddle demo. It will show you how is working. I hope this will help you to solve your problem.
You can also refer this Key Navigation Sample
Ext.create('Ext.panel.Panel', {
renderTo: Ext.getBody(),
title: 'Button Example with keyNav',
margin: 10,
bodyStyle: {
'border-width': '2px'
},
layout: {
type: 'vbox',
align: 'center'
},
defaults: {
xtype: 'button',
height: 50,
width: '70%',
margin: '20 0',
handler: function () {
this.up('panel').activeButton = this.activeIndex;
}
},
items: [{
text: 'My button 1',
activeIndex: 0
}, {
text: 'My button 2',
activeIndex: 1
}, {
text: 'My button 3',
activeIndex: 2
}, {
text: 'My button 4',
activeIndex: 3
}],
listeners: {
afterrender: function () {
var panel = this;
panel.keynav = Ext.create('Ext.util.KeyNav', {
target: Ext.getBody(),
scope: panel,
up: panel.doFocusOnButton,
down: panel.doFocusOnButton,
enter: panel.doFocusOnButton,
tab: panel.doFocusOnButton
});
//Remove cls on panel click
panel.el.on('click', function () {
this.doAddRemoveCls('removeCls');
}, panel)
panel.focus();
}
},
/*
* Common event will fire on key : ENTER, TAB, UP, DOWN
* #param {Ext.EventObject} e Just as Ext.Element wraps around a native DOM node
*/
doFocusOnButton: function (e) { //{ http://docs.sencha.com/extjs/4.2.5/#!/api/Ext.EventObject }
var panel = this,
button = '',
totalItem = panel.items.length - 1;
if (Ext.isDefined(panel.activeButton) == false) {
panel.activeButton = 0;
} else {
this.doAddRemoveCls('removeCls');
switch (e.getKey()) {
case e.ENTER:
case e.DOWN:
case e.TAB:
panel.activeButton++;
if (panel.activeButton > totalItem) {
panel.activeButton = 0;
}
break;
case e.UP:
panel.activeButton--;
if (panel.activeButton < 0) {
panel.activeButton = totalItem;
}
break;
}
}
this.doAddRemoveCls('addCls');
},
//This function will add or remove cls..
doAddRemoveCls: function (method) {
var panel = this,
index = panel.activeButton,
button = Ext.isDefined(index) ? panel.down('[activeIndex=' + index + ']') : null;
if (button) {
button[method]('x-focus x-btn-focus x-btn-default-small-focus')
}
}
});

Datagrid based on input from textbox and combobox

Part of the program I am creating includes having a drop down menu where the user can select a type of load, before inputting the position of the load. The load and position would then appear in a datagrid and as many as the user pleases can be added.
I am not very sure where I went wrong but this is my code:
function loadlist():void{
combobox1.addItem ( { label: "Choose a Load" } );
combobox1.addItem ( { label: "Point Load" } );
combobox1.addItem ( { label: "Bending Moment" } );
combobox1.addItem ( { label: "Uniformly Distributed Load" } );
combobox1.addItem ( { label: "Varying Distributed Load" } );
combobox1.addItem ( { label: "Nonlinear Distributed Load" } );
}
function loadbuttonclick (event:MouseEvent):void{
combobox1.removeAll();
loadlist();
trace("load");
//datagrid
var myTextFormat: TextFormat = new TextFormat();
myTextFormat.font = "Comic Sans MS";
var datagrid:DataGrid = new DataGrid;
datagrid.columns = ["Type of Load", "Position of Load"];
datagrid.resizableColumns = true;
datagrid.setRendererStyle("textFormat", myTextFormat);
datagrid.addItem(Load type: "combobox1.selectedItem.label", Load position: "loadposition.text");
addChild(datagrid);
datagrid.addEventListener(Event.CHANGE, gridItemClick);
function gridItemClick (event:Event):void{
trace("The Selected Load is " + combobox1.selectedItem.label);
}
}
}
You should be getting an error from this line, the syntax seems to be wrong:
datagrid.addItem(Load type: "combobox1.selectedItem.label", Load position: "loadposition.text");
It's not an object, an object has to defined with curly brackets {}.
Object keys can't have spaces when defined using curly brackets.
You're trying to pass dynamic values as strings, you should write combobox1.selectedItem.label instead of "combobox1.selectedItem.label".
It's been a while wince I've worked with datagrids, but if I recall correctly, the item is an object, which keys should match the columns.
Since your columns include spaces you wouldn't be able to write:
datagrid.addItem({Load type: combobox1.selectedItem.label, Load position: loadposition.text});
However, this should work:
var object:Object = new Object();
object["Type of Load"] = combobox1.selectedItem.label;
object["Position of Load"] = loadposition.text;
datagrid.addItem(object);
I didn't have the time to test it, but that should be the right direction.

ExtJs button's menu contents not rendered until button is clicked

I have a simple button with menu. There is a treepanel inside the menu.
Whenever user selects some node in tree, i update the container buttons text.
In treepanel's afterrender event i make a default node selection in the tree and this fire selection event and the button's text is updated.
However, when the button is rendered for the very first time, the treepanel inside the menu is not yet rendered.
How can i make menu & treepanel render silently (adding to dom but not shown to user until button is clicked) after button is rendered?
Actually there is a workaround which i hesitate to use:
btn.showMenu();
btn.hideMenu();
Any better ideas?
JsFiddle: http://jsfiddle.net/exGk3/
Code:
var selectedNodeIndex = 1;
var onItemSelect = function (selModel, node, index) {
var treePanel = selModel.view.up();
var btn = treePanel.up("button");
btn.setText(node.data.text);
};
var afterTreeRender = function (t) {
t.selModel.select(selectedNodeIndex);
}
Ext.create('Ext.Button', {
text: 'Click me',
renderTo: Ext.getBody(),
menu: {
items: {
xtype: "treepanel",
id: "tree",
indent: false,
width: 150,
height: 200,
rootVisible: false,
root: {
children: [{
text: "item 1",
leaf: true
}, {
text: "item 2",
leaf: true
}, {
text: "item 3",
leaf: true
}]
},
listeners: {
select: {
fn: onItemSelect
},
afterrender: {
fn: afterTreeRender
}
}
},
showSeparator: false
}
});
I think the easiest thing to do here is to pass along the text needed for the button. It seems that you already know which node to select in the tree, then you probably know which text corresponds to the selected index.
If that's somehow not possible or API is not changeable here is a way for you to set button text programmatically:
http://jsfiddle.net/dbrin/XSn7X/3/
The changes from what you have done are 2 fold:
Use Ext.define method to define your class with initComponent method.
The initComponet method is a hook after the constructor to setup aditional properties. The key here is that the instance of the class exists at this point and *this* context references the class instance.
Use Ext.create to create an instance of your customized button component.
In the initComponment method you just traverse the tree looking for the data you need and set the button text.

resize columns if kendo grid is bound to dynamic data source?

I am trying to enable horizontal scrolling for my kendo grid. So far I've heard if you have added width to the columns definitions. But what do you do if data is dynamic?
I've tried a couple of things. This code can help you understand that.
var kgrid = $("#grid").kendoGrid({
height: 155,
pageable: true,
dataSource:ds,
dataBound:function(e){
var m = kgrid.data('kendoGrid');
console.log('dataBound: ', m.columns);
},
dataBinding:function(e){
var m = kgrid.data('kendoGrid');
var obj = ds.view()[0];
console.log('dataBinding columns before: ', m.columns);
//for(x in obj){
// if(x[0] == '_')
// continue;
// m.columns.push({field: x, width:'200px'});
//}
console.log('dataBinding columns after: ', m.columns);
}//,
//columns:[
// {field:'col1', width: '200px'},
//{field:'col2', width: '200px'},
//{field:'col3', width: '200px'},
//{field:'col4', width: '200px'}
//]
});
Also here is the link to my page: http://jsfiddle.net/deostroll/497zM/3/
I want to set some size to the column, and hence enable horizontal scrolling.
The only way I can see as of now you can do this is to completely destroy the grid and re-bind it every time new data arrives. And when it arrives we'd have to take the first item, read the properties and create the columns array. We'd have to set each object's width property here like mentioned below:
success: function (data) {
var cols = [];
var item = data[0];
$.each(item, function (key, val) {
cols.push({
field: key,
width: '325px'
});
});
theGrid.kendoGrid({
dataSource: {
data: data,
pageSize: 5
},
pageable: true,
columns: cols,
height: 225
});
} //end success
Fiddle: http://jsfiddle.net/deostroll/497zM/5/

Kendo UI Grid: How To Keep Custom Filter Row In Synch With Column Reordering And Hiding

How can I achieve to keep my custom filter row in synch with column reordering and column hiding? I am currently investigating Telerik kendo UI grid v2013.1.514.
The code is as follows:
var grid = $("#grid").kendoGrid({
dataSource: {
type: "odata",
transport: {
read: "http://demos.kendoui.com/service/Northwind.svc/Products"
},
pageSize: 15,
serverPaging: true,
serverSorting: true,
serverFiltering: true
},
height: 450,
reorderable: true,
pageable: true,
columns: [{ field: "ProductID"}, { field: "ProductName"}, { field: "UnitPrice"}]
});
var filterRow = $('<tr><td><input type="search" id="ProductIDSearchBox" value="EnterProductID"/></td>' +
'<td><input type="search" id="ProductNameSearchDD" value="Enter ProductName"/></td>' +
'<td><input type="search" id="UnitPriceSearchBox" value="Enter UnitPrice"/></td></tr>');
grid.data("kendoGrid").thead.append(filterRow);
Please find an example here: http://jsfiddle.net/WrqmD/4/.
Just drag'n'drop the column headers around and see that the custom filter row is NOT reordered!
EDIT:
Btw., the solution to append the custom filter is described here: http://www.kendoui.com/forums/kendo-ui-web/grid/grid-header-filtering-row-that-contains-1-element-for-each-column-in-grid-with-the-same-width.aspx
After coresponding with the Telerik Support I would like to share the solutions below:
Implementing the columnReorder event adding the following method declaration to the grid options. This was suggested by Telerik Support. Please find a working JsFiddle demo here:
columnReorder: function(e) {
var selector = this.thead.find(".filterRow td"),
source = selector.eq(e.oldIndex),
dest = selector.eq(e.newIndex);
source[e.oldIndex > e.newIndex ? "insertBefore" : "insertAfter"](dest);
}
Append the Custom Elements to the original header elements. This is my own solution where I go with, because both reordering and hiding of columns is solved with the same code. Please find a working JsFiddle demo here:
var columns = grid.data("kendoGrid").columns;
for(var i = 0; i < columns.length; i++) {
var column = columns[i];
var columnName = column.field;
var columnHeader = $('div#grid th.k-header[data-field="' + columnName + '"]');
if(undefined != columnHeader[0]) {
var fieldValue = $('<input type="text" style="display:block;">');
fieldValue.val(columnName);
fieldValue.click(function(evt) {
evt.stopPropagation();
$(evt.target).focus().select();
});
columnHeader.append(fieldValue);
}
}

Resources