jqGrid boolean or enumeration dropdown filtering for columns - asp.net-mvc

I'm using jqGrid to display tabular data on my first ASP.NET MVC 3 and find it really useful, particularly filtering down data. For string-type I use the column-filtering with "contains" and that works wonderfully for culling out the strings. For the date data I use the date picker. Great.
Now I've got a few columns (e.g., "Contains nuts") which are essentially boolean values. I want to provide a way to filter these. Right now they are displayed as "true" and "false" and use the same string-based filtering that my string-type columns use. That's a bit clunky. I think what I'd like to do instead is have a way to choose one of three values (true/false/both) via a dropdown mechanism.
My current colModel has an entry like so for my 'boolean' field:
{ name: 'ContainsNuts',
index: 'ContainsNuts',
align: 'left',
searchoptions: { sopt: ['eq, 'ne']}
}
which only works when the user types in 'false' or 'true' - again, clunky.
For a few other columns, I wanted to use dropdowns for enumerations, e.g., I have a 'Cones' column, since there are quite a few rows and I page the results - using the auto complete text filtering is a bit hit-or-miss for the user to find all the possible values. Hope that makes sense.
So what I've tried is this - I created a controller action that looks like so:
public JsonResult AvailableCones()
{
var context = new IceCreamEntities();
var query = context.Cones.AsQueryable().Distinct();
List<string> all = query.Select(m => m.Name).ToList();
return Json(all, JsonRequestBehavior.AllowGet);
}
And I did something like this [perhaps convoluted approach] to create a dropdown selection in the filtering dialog for Cones in my document ready:
...
createSearchSelection = function (someValues) {
var outputValues = "";
if (someValues && someValues.length) {
for (var i = 0, j = someValues.length; i < j; ++i) {
var entry = someValues[i];
if (outputValues.length > 0) {
outputValues += ";";
}
outputValues += entry + ":" + entry;
}
}
return outputValues;
}
setTheSearchSelections = function (colName, url){
$('#icecreamgrid').jqGrid('setColProp', colName,
{
stype: 'select',
searchoptions: {
value: createSearchSelection(url),
sopt: ['eq']
}
});
}
gotData = function(data) {
setTheSearchSelections('ConeType', data);
}
var url = "/IceCream/AvailableConeTypes";
$.get(url, null, gotData);
The result is that I get a drop-down for the ConeType column in the search dialog and the correct rows shows up in the column. Great. That's pretty cool that it works.
What I don't know how to do, however, is to get a dropdown to show up in my column header filter just like the one that now shows up in the filter dialog. How can I augment what I have to make this happen? Secondly, how can I make what I've got work for boolean values?

First part of your question is the displaying and filtering of the boolean values. I use checkboxes to display such values. In difference on your case I have typically many such columns. To reduce the size of the JSON data I use "1" and "0" instead of "true" and "false". Next I use the following column settings
formatter: 'checkbox', align: 'center', width: 20,
edittype: 'checkbox', editoptions: { value: "1:0" },
stype: "select", searchoptions: { sopt: ['eq', 'ne'], value: "1:Yes;0:No"
So for the searching the user have to choose "Yes" or "No" in the select box. Because I have many of such columns I defined templateCeckbox object in one JavaScript file which I include on every page of the project:
my.templateCeckbox = {
formatter: 'checkbox', align: 'center', width: 20,
edittype: 'checkbox', editoptions: { value: "1:0" },
stype: "select", searchoptions: { sopt: ['eq', 'ne'], value: "1:Ja;0:Nein" }
};
Then the typical column definition is
{
name: 'IsInBasis', index: 'InBasis', template: my.templateCeckbox,
cellattr: function () { return ' title="TS-Basis"'; }
},
(see the answer for details about the column templates). I find also practical if the tooltip shown if one hover the checkbox will be the text close to the column header. So I use cellattr attribute. In case of having many columns with the checkboxes it improves the usability a little.
To be able to display many columns with chechboxes I use personally vertical column headers. I recommend you to read the old answer which could be additionally interesting because it describes how to implement quick filtering of the data with respect of external checkbox panel.
Now about the building of the selects for the 'Cones' column. If you has AvailableCones action which provide the list of possible options like array (list) of strings you should use dataUrl:'/IceCream/AvailableConeTypes' instead of value: createSearchSelection(url) as the searchoptions. You well add only the buildSelect function which I described in "UPDATED" part of the answer.
{
name: 'ConeType', width: 117, index: 'ConeType', editable: true, align: 'center',
edittype: 'select', stype: 'select',
editoptions: {
dataUrl: urlBase + '/AvailableConeTypes',
buildSelect: my.buildSelect
},
searchoptions: {
dataUrl: urlBase + '/AvailableConeTypes',
buildSelect: my.buildSelect
}
}
where
my.buildSelect = function(data) {
var response = jQuery.parseJSON(data.responseText),
s = '<select>', i, l, ri;
if (response && response.length) {
for (i=0, l=response.length; i<l; i += 1) {
ri = response[i];
s += '<option value="' + ri + '">' + ri + '</option>';
}
}
return s + '</select>';
};

This line of code shows a True,False dropdownlist for a column that has true, false values:
{
name: 'SReqdFlag', index: 'SReqdFlag', editable: true, edittype: 'select', editoptions: { value: '"":Select;true:True;false:False' }
}
Hope that helps!

Related

Tabulator copyToClipboard method isn't working as anticipated

I am new to the Tabulator plug-in and I am attempting to copy data from one table to another using the Tabulator copyToClipboard method but with no success.
In my application I have created eleven <div> elements (one for the [Crew Leader] and one each for up to ten [Crew Members]) to serve as containers for the Tabulator tables to be instantiated. I am hoping to copy data from the [Crew Leader] table and paste it into each of the affected [Crew Member] tables thus mitigating data re-entry. This sequence of copy/paste events is triggered by the click event bound to a <button> in the header of the [Crew Leader] table. The following function is called by the <button> click event:
function CloneTable() {
// Verify the [Crew Leader] Tabulator table is present....
var tableLeader = Tabulator.prototype.findTable("#CrewLeaderTable");
if (tableLeader.length > 0) {
alert("The Tabulator table #CrewLeaderTable was found.\ntable.length = " + tableLeader.length);
tableLeader.copyToClipboard("all");
alert("The table contents were copied to the clipboard.");
}
else {
alert("The Tabulator table #CrewLeaderTable was not found.");
}
}
The first alert message verifies that the #CrewLeaderTable object has been found as anticipated. However, the second alert verification is never received thus indicating failure of the Tabulator copyToClipboard method.
I have read through as much of the relevant Tabulator documentation as I can find and I am hoping I have simply overlooked something in my setup.
The following is a copy of my Tabulator constructor:
var table = new Tabulator(divid, {
height: "100%",
layout: "fitDataFill",
movableRows: true, //enable user movable rows
tabEndNewRow: true, //create empty new row on tab
rowContextMenu: myActionContextMenu,
keybindings: {
"navUp": true, //enable navUp keybinding using the "up arrow" key
"navDown": true, //enable navDown keybinding using the "down arrow" key
},
columns: [
{ title: "Phase Code", field: "phaseCode", width: 144, editor: "select", editorParams: { values: function (cell) { return window.laborPhaseCodes; } } },
{ title: "Date Worked", field: "dateComp", hozAlign: "center", sorter: "date", editor: dateEditor },
{ title: "Start Time", field: "timeStart", hozAlign: "center", sorter: "time", editor: timeEditor },
{ title: "Finish Time", field: "timeFinish", hozAlign: "center", sorter: "time", editor: timeEditor },
{ title: "Memo", field: "memo", width: 144, hozAlign: "left", editor: "input" },
{ title: "<button type='button' id='btnClone' class='btn btn-success btn-sm py-0' style='font-size:10px;'>Clone</button>", headerSort: false, headerClick: tabCloneTable }
],
cellEdited: function (cell) {
}
});
I have spent a couple of days trying to figure out the best way to "clone" the data from one table into another. The Tabulator documentation is fairly comprehensive but I fear I have overlooked something. Any assistance is greatly appreciated.
It looks like copyToClipboard doesn't return the data, it is maintained internally and not accessible. But, with what you are doing set/getData works fine.
Here is an example, https://jsfiddle.net/nrayburn/19sjg74k/7/.
Basically, what you want to do is call getData on the parent table and setData on the child table.
const crewLeaderTable = Tabulator.prototype.findTable('#CrewLeaderTable')[0];
const crewMemberTable = Tabulator.prototype.findTable('#CrewMember1Table')[0];
const crewLeaderData = crewLeaderTable.getData();
// You could also use replaceData or addData, depending on the requirements
crewMemberTable.setData(crewLeaderData);

Jqgrid dynamic dropdownlist based on value in some other column

I have a JQGrid with 3 columns. The third column, I want it as a dropdownlist based on some value in the second column. Is this Possible?
Also in the JQGrid demo, do add a dropdown we need to set the edittype to "select" and pass the values in JSON.
edittype:"select",editoptions:{value:"FE:FedEx;IN:InTime;TN:TNT;AR:ARAMEX"}},
My next question is how to pass values to this column from a model object in the format it is expecting.
You can use dataUrl or postData property of editoption defined as function. See the answer or here and here. If you need to send standard URL parameters like /mySetectUrl?status=StatusValue then you can use
name: "Field5", edittype: "select",
editoptions: {
// it produces URL like /mySetectUrl?status=StatusValue
dataUrl: "/mySetectUrl",
postData: function (rowid, value, name) {
return { status: $(this).jqGrid("getCell", rowid, "Status") };
}
}
If you need to construct some another URL like /mySetectUrl/StatusValue you can use dataUrl defined as function:
name: "Field5", edittype: "select",
editoptions: {
// it produces URL like /mySetectUrl/StatusValue
dataUrl: function (rowid, value, name) {
return "/mySetectUrl/" +
encodeURIComponent($(this).jqGrid("getCell", rowid, "Status"));
}
}

Paging not working when loadonce:true in jqGrid

In jqGrid have property loadonce:true then i am getting only first page records. how can i get second and third page records.
code:
$(function () {
$("#pendingAppgrid").jqGrid({
colNames: ['Name', 'Email'],
colModel: [
{ name: 'Name', index: 'Name', sortable: true, align: 'left', width: '200',
editable: false, edittype: 'text',search:true
},
{ name: 'Email', index: 'Email', sortable: false, align: 'left', width: '200',
editable: false, edittype: 'text',search:true
},
],
pager: jQuery('#pager'),
sortname: 'Name',
rowNum: 15,
rowList: [10, 20, 25],
sortorder: "desc",
height: 345,
ignoreCase: true,
viewrecords: true,
rownumbers: true,
caption: 'Pending Approvals',
width: 660,
url: "#Url.Content("~/Home/PendingApprovals")",
datatype: 'json',
mtype: 'GET',
loadonce: true
})
jQuery("#pendingAppgrid").jqGrid('filterToolbar', { searchOnEnter: true, enableClear: false });
});
Server code
public ActionResult PendingApprovals(int page, int rows, string sidx, string sord)
{
//return View(GetPendingApprovals());
int currentPage = Convert.ToInt32(page) - 1;
int totalRecords = 0;
List<ViewModels.Channel.UserChannels> lTemChannel = new List<ViewModels.Channel.UserChannels>();
List<ViewModels.Channel.UserChannels> lstChannel = new List<ViewModels.Channel.UserChannels>();
lTemChannel = GetPendingApprovals();
foreach (ViewModels.Channel.UserChannels cha in lTemChannel)
{
ViewModels.Channel.UserChannels channelWithLogo = new ViewModels.Channel.UserChannels();
channelWithLogo.ID = cha.ID;
channelWithLogo.Name = cha.Name;
channelWithLogo.Email = cha.Email;
lstChannel.Add(channelWithLogo);
}
totalRecords = lstChannel.Count;
var totalPages = (int)Math.Ceiling(totalRecords / (float)rows);
lstChannel = lstChannel.ToList<ViewModels.Channel.UserChannels>();
IPagedList<ViewModels.Channel.UserChannels> ilstChannel;
switch (sord)
{
case "desc":
ilstChannel = lstChannel.OrderByDescending(m => m.Name).ToPagedList(page, rows);
break;
case "asc":
ilstChannel = lstChannel.OrderBy(m => m.Name).ToPagedList(page, rows);
break;
default:
ilstChannel = lstChannel.OrderBy(m => m.Name).ToPagedList(page, rows);
break;
}
var data = ilstChannel;
var jsonData = new
{
total = totalPages,
page,
records = totalRecords,
rows = (from m in data
select new
{
id = m.ChannelID,
cell = new object[]
{
m.Name,
m.Email
}
}).ToArray()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
Here i am getting only first page records. i have more pages. search functionality working fine. problem is i am only first page records. not getting other pages records. how can i get another page records. Please help this.
If you use loadonce:true jqGrid change the datatype parameters to 'local' after the first load of data from the grid. All next grid reloading (sorting, paging, filtering) works local. If you want refresh the grid data from the server one more time you should set datatype to its original value ('json' or 'xml'). For example:
$("#pendingAppgrid").setGridParam({datatype:'json', page:1}).trigger('reloadGrid');
Please visit http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options for more details of the jqGrid options.
The problem is complex, read all even the comentaries, you need to recover the actual page of the Jgrid with:
var llPage = $("#listaCfdi").jqGrid('getGridParam', 'page');
//1 but this page is not the page of the "pager" and is before to call the method that fill the jgrid again
//, so the page will change after to fill the grid, you must call "onPaging" to fill again the Jgrid, like this:
//2
, onPaging: function (pgButton) {
FillAgainJgrid();
}
and in FillAgainJgrid(); (in this example) you need to call again other method that will have the real page of the "pager" that the user want, in example, the jgrid is in the page:5, and you want the las page, something like page:15, so the second call will have the real page, that the importan thing of the second call. Only it needs two calls to be sure that the page is the correct. And in this example, FillAgainJgridEnsure(); is exact to FillAgainJgrid(); except that doesnt have onPaging again
//3. In the server side, the page you are passed to the method only will work in the first call, because have all the data
//, and all the pages, but in example in the second call with the page:3, the jgrid will lost all the real information about the size of page, and only will return page:3 like if was the only one, you need a trick like this
//, (they are two list, one with all the data, and then you will get only the page you need, and the second list will have all the other rows in empty
//, so in this way you will get always all the correct rows number of the query, you need Concat in this example because we need all the rows, and if you are filled with dataset, in the sql server you need to use union all) :
ENTIDADES.Cfdi[] resultadoDos = null; //This is the class to provide the correct number of rows
//this is the query that always will have all the data
resultado = new CLASES.DAL.Facturacion().ObtenerCfdiDisponibles(criteriosBusqueda);
if (resultado.Length > 100)
{
resultadoDos = new ENTIDADES.Cfdi[resultado.Length - 100];
for (int i = 0; i < resultado.Length - 100; i++)
{
ENTIDADES.Cfdi referenciaVacia = new ENTIDADES.Cfdi();
resultadoDos[i] = referenciaVacia;
}
}
//Paging here, in the server size
resultado = resultado.OrderBy(p => p.Cliente).Skip(((Convert.ToInt32(_Pagina)) - 1) * 100).Take(100).ToArray();
if (resultadoDos != null) //if there are more that 100 rows, in this example
{//concat the page and the fill data empty
resultado = resultado.Concat(resultadoDos).OrderByDescending(x => x.Cliente).ToArray();
}
regresar = Newtonsoft.Json.JsonConvert.SerializeObject(resultado);
//Note, dont forget to OrderByDescending (in this example), because it will show first the data that have the rows filled, 100 is the size of rows of the page.
Omar Romero Mexico City

jqgrid column linq with ASP.NET MVC

I have a class (Person) with 4 properties : Id, FirstName, LastName, Code.
I show a person list in a jqgrid. I'd like a link column with this format (the code has this format 2011.0001)
/Person/Detail/Code => /Person/Code/2011.0001
But I have this with the code below :
/Customer/Detail?Code=2
I tried this :
colModel: [
{ name: 'Code', index: 'Code', formatter: 'showlink', formatoptions: { baseLinkUrl: '/Customer/Detail', idName: 'Code' }},
{ name: 'LastName', index: 'LastName', align: 'left' },
{ name: 'FirstName', index: 'FirstName', align: 'left' }
],
How can I correct this ?
Thanks,
Update1, Data format
[
{"Id":1,"FirstName":"AAAA","LastName":"AA","Code":"2011.0001"},
{"Id":3,"FirstName":"BBBB","LastName":"BB","Code":"2011.0003"}
]
You can try to use the custom formatter in the form
formatter: function (cellvalue, options, rowObject) {
return '' + cellvalue + '';
}
instead of predefined formatter 'showlink':
formatter: 'showlink', formatoptions: {baseLinkUrl:'/Customer/Detail', idName:'Code'}
You can easy receive /Customer/Detail?Code=2011.0001 if you have unique data in the 'Code' column and if you would add key: true to the column definition. In the case the Id value from the JSON input will be ignored and the value from the 'Code' column will be used as the rowid. If you do need to have the href value in the form /Customer/Detail/Code/2011.0001 or instead of /Customer/Detail?Code=2011.0001 you will need use custom formatter as I described before.

How to implement search on jqgrid?

So I've got basic example of jqgrid working in ASP.NET MVC, the javascript looks like this:
$(document).ready(function() {
$("#list").jqGrid({
url: '../../Home/Example',
datatype: 'json',
myType: 'GET',
colNames: ['Id', 'Action', 'Parameters'],
colModel: [
{ name: 'id', index: 'id', width: 55, resizable: true },
{ name: 'action', index: 'action', width: 90, resizable: true },
{ name: 'paramters', index: 'parameters', width: 120, resizable: true}],
pager: $('#pager'),
rowNum: 10,
rowList: [10, 20, 30],
sortname: 'id',
sortorder: 'desc',
viewrecords: true,
multikey: "ctrlKey",
imgpath: '../../themes/basic/images',
caption: 'Messages'
});
Now I am trying to implement the search button that they have in the jqgrid examples (click on Manipulating/Grid Data). But I don't see how they implement it. I'm expecting e.g. a "search:true" and a method to implement it.
Has anyone implemented search on jqgrid or know of examples that show explicitly how to do it?
I recently implemented this myself (yesterday actually) for the first time. The biggest hurdle for me was figuring out how to write the controller function. The function signature is what took me the longest to figure out (notice the _search, searchField, searchOper, and searchString parameters as those are missing from most of asp.net mvc examples I've seen). The javascript posts to the controller for both the initial load and for the search call. You'll see in the code that I'm checking whether the _search parameter is true or not.
Below is the controller and the javascript code. My apologies for any formatting issues as this is my first time posting on here.
public ActionResult GetAppGroups(string sidx, string sord, int page, int rows, bool _search, string searchField, string searchOper, string searchString)
{
List<AppGroup> groups = service.GetAppGroups();
List<AppGroup> results;
if (_search)
results = groups.Where(x => x.Name.Contains(searchString)).ToList();
else
results = groups.Skip(page * rows).Take(rows).ToList();
int i = 1;
var jsonData = new
{
total = groups.Count / 20,
page = page,
records = groups.Count,
rows = (
from appgroup in results
select new
{
i = i++,
cell = new string[] {
appgroup.Name,
appgroup.Description
}
}).ToArray()
};
return Json(jsonData);
}
And here is my HTML/Javascript:
$(document).ready(function() {
$("#listGroups").jqGrid({
url: '<%= ResolveUrl("~/JSON/GetAppGroups/") %>',
datatype: 'json',
mtype: 'GET',
caption: 'App Groups',
colNames: ['Name', 'Description'],
colModel: [
{ name: 'Name', index: 'Name', width: 250, resizable: true, editable: false},
{ name: 'Description', index: 'Description', width: 650, resizable: true, editable: false},
],
loadtext: 'Loading Unix App Groups...',
multiselect: true,
pager: $("#pager"),
rowNum: 10,
rowList: [5,10,20,50],
sortname: 'ID',
sortorder: 'desc',
viewrecords: true,
imgpath: '../scripts/jqgrid/themes/basic/images'
//});
}).navGrid('#pager', {search:true, edit: false, add:false, del:false, searchtext:"Search"});
See my article on codeproject, which explains how we can do multiple search in jqgrid:
Using jqGrid’s search toolbar with multiple filters in ASP.NET MVC
I use IModelBinder for grid's settings binding, expression trees for sorting and filtering data.
In case you're still wondering about dealing with optional parameters, just declare them as nullables by adding a ? after the type name.
Now you'll be able to compare them with null to check if they are absent.
Note that you don't need to do this with strings, as they are already nullable.
#Alan - ok, I used your method and extended my webservice to expect those additional three parameters and check for "_search" is true/false. But, in order to make this work, I had to add this to my ajax call in the JavaScript:
if (!postdata._search) {
jQuery("#mygrid").appendPostData( {searchField:'', searchOper:'', searchString:''});
}
Just follow this link. It has all implementations explained...
You can create a button searchBtn and can invoke search form on click
$("#searchBtn").click(function(){
jQuery("#list4").searchGrid(
{options}
)});

Resources