Filtering Kendo Grid Array Column - kendo-ui

I have a Kendo grid that I am using to display a typescript structure that looks like this.
{
companyId: string,
name: string,
inceptionDate: Date,
tags: string[]
}
I would like to be able to filter on the tags column, but I am unsure as to how this should actually work. I can quite easily filter on the name column, either with the built in grid filtering functionality or via code:
baseFilter.filters.push({ field: "name", operator: "contains", value: "myValue" });
But I don't see a way to do this against an array object. I would prefer to be able to select a tag from the a list in the grid filter option, but if that's not possible I'd be quite happy to use a multiselect and set the filter manually.
Is this something that is built in or that is relatively easy (or even just not insanely hard) to implement?

As long as you're doing the client side filtering, you can use a function for the operator field.
a simple example (in js):
baseFilter.filters.push({
field: "tags",
operator: function (tags, value) {
for (var i = 0; i < tags.length; i++) {
var tag = tags[i];
if (tag === value)
return true;
}
return false;
}});

TerminalSamurai's answer did the trick for me !
Here is my full code (with checking if there is already a filter existing)
var grid = $("#GridListeModelesDocumentPatient_#Model.PatientId").data("kendoGrid");
if (grid != null) {
var filterHandler = grid.dataSource.filter();
if (dataItem.Nom == "Tous") {
if (filterHandler != null) {
var existingFilters = filterHandler.filters;
for (var i = existingFilters.length - 1; i >= 0; i--) {
if (existingFilters[i].field == "ModelesMotifs") {
existingFilters.splice(i, 1);
}
}
grid.dataSource.filter(existingFilters);
} else {
grid.dataSource.filter({});
}
} else {
if (filterHandler != null) {
var existingFilters = filterHandler.filters;
for (var i = existingFilters.length - 1; i >= 0; i--) {
if (existingFilters[i].field == "ModelesMotifs") {
existingFilters.splice(i, 1);
}
}
var new_filter = {
field: "ModelesMotifs",
operator: function (modelesMotifs, value) {
for (var i = 0; i < modelesMotifs.length; i++) {
var modeleMotif = modelesMotifs[i];
if (modeleMotif.MotifId === value)
return true;
}
return false;
},
value: dataItem.MotifId
};
existingFilters.push(new_filter);
grid.dataSource.filter(existingFilters);
} else {
grid.dataSource.filter({
field: "ModelesMotifs",
operator: function (modelesMotifs, value) {
for (var i = 0; i < modelesMotifs.length; i++) {
var modeleMotif = modelesMotifs[i];
if (modeleMotif.MotifId === value)
return true;
}
return false;
},
value: dataItem.MotifId
});
}
}
}

Related

p:selectOneMenu filter not working with accented characters

I use PrimeFaces SelectOneMenu advanced. Filter is wrong working when I input the i and ı character.
For example http://www.primefaces.org/showcase/ui/input/oneMenu.xhtml demo advanced one menu I search arI and arİ strings and it finds Aristo element.
In my application, my menu contains Isparta element. I input Isp and İsp and filter finds Isparta.
How can I solve this problem?
I resolve this problem with autocomplete component. Primefaces autocomplete component with dropdown="true" property works like one menu and this component don't have Turkish character problem.
Reported to PrimeFaces Team: https://github.com/primefaces/primefaces/issues/9629
Fixed for 13.0.0 but MonkeyPatch provided here:
if (PrimeFaces.widget.SelectOneMenu) {
PrimeFaces.widget.SelectOneMenu.prototype.normalize = function(string, lowercase) {
if (!string) return string;
var result = string.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
return lowercase ? result.toLowerCase() : result;
}
PrimeFaces.widget.SelectOneMenu.prototype.filter = function(value) {
this.cfg.initialHeight = this.cfg.initialHeight || this.itemsWrapper.height();
var filterValue = this.normalize(PrimeFaces.trim(value), !this.cfg.caseSensitive);
if (filterValue === '') {
this.items.filter(':hidden').show();
this.itemsContainer.children('.ui-selectonemenu-item-group').show();
} else {
var hide = [];
var show = [];
for (var i = 0; i < this.options.length; i++) {
var option = this.options.eq(i),
itemLabel = this.normalize(option.text(), !this.cfg.caseSensitive),
item = this.items.eq(i);
if (item.hasClass('ui-noselection-option')) {
hide.push(item);
} else {
if (this.filterMatcher(itemLabel, filterValue)) {
show.push(item);
} else if (!item.is('.ui-selectonemenu-item-group-children')) {
hide.push(item);
} else {
itemLabel = this.normalize(option.parent().attr('label'), !this.cfg.caseSensitive);
if (this.filterMatcher(itemLabel, filterValue)) {
show.push(item);
} else {
hide.push(item);
}
}
}
}
$.each(hide, function(i, o) {
o.hide();
});
$.each(show, function(i, o) {
o.show();
});
hide = [];
show = [];
//Toggle groups
var groups = this.itemsContainer.children('.ui-selectonemenu-item-group');
for (var g = 0; g < groups.length; g++) {
var group = groups.eq(g);
if (g === (groups.length - 1)) {
if (group.nextAll().filter('.ui-selectonemenu-item-group-children:visible').length === 0)
hide.push(group);
else
show.push(group);
} else {
if (group.nextUntil('.ui-selectonemenu-item-group').filter('.ui-selectonemenu-item-group-children:visible').length === 0)
hide.push(group);
else
show.push(group);
}
}
$.each(hide, function(i, o) {
o.hide();
});
$.each(show, function(i, o) {
o.show();
});
}
var firstVisibleItem = this.items.filter(':visible:not(.ui-state-disabled):first');
if (firstVisibleItem.length) {
this.highlightItem(firstVisibleItem);
PrimeFaces.scrollInView(this.itemsWrapper, firstVisibleItem);
}
if (this.itemsContainer.height() < this.cfg.initialHeight) {
this.itemsWrapper.css('height', 'auto');
} else {
this.itemsWrapper.height(this.cfg.initialHeight);
}
this.alignPanel();
}
};
From PrimeFaces 13 normalization can be applied on both the item label and the filter value by setting the filterNormalize attribute to true. You can do the same in older versions using a filterFunction. For example:
<script>
function searchable(string) {
return !string ? '' : string.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
}
function customFilter(itemLabel, filterValue) {
return searchable(itemLabel).includes(searchable(filterValue));
}
</script>
<p:selectOneMenu filter="true" filterMatchMode="custom" filterFunction="customFilter"
.../>
See also:
https://primefaces.github.io/primefaces/12_0_0/#/components/selectonemenu?id=filtering

How to map column to complex object in SlickGrid

var data = [{"Id":40072,"Id2":40071,"SmDetails":{"Id1":40071,"Id2":40072}}]
I want to display SmDetails.Id1 in a column. How is this possible? I tried:
var columns = [{name:'Personnel',field:SmDetails.id1,id:'detailId'}];
Please help me
Please help me
**My latest code**
var data = [{"Id":40072,"Id2":40071,"allocationDetails":{"Id1":40071,"allocationDetails":{"accommodationId":4007}}}]
var grid;
var columns = [ {name:"Personnel",field:"allocationDetails",fieldIdx:'accommodationId', id:"accommodationId"}];
var options = {
enableCellNavigation: true,
enableColumnReorder: false,
dataItemColumnValueExtractor:
function getValue(item, column) {
var values = item[column.field];
if (column.fieldIdx !== undefined) {
return values && values[column.fieldIdx];
} else {
return values;
}}};
var gridData=$scope.Vo;//This return as json format
grid = new Slick.Grid("#testGrid",gridData, columns);
This is the code tried recently.
You'll need to provide a custom value extractor to tell the grid how to read your object.
var options = {
enableCellNavigation: true,
enableColumnReorder: false,
dataItemColumnValueExtractor:
// Get the item column value using a custom 'fieldIdx' column param
function getValue(item, column) {
var values = item[column.field];
if (column.fieldIdx !== undefined) {
return values && values[column.fieldIdx];
} else {
return values;
}
}
};
The column definitions would look like:
{
id: "field1",
name: "Id1",
field: "SmDetails",
fieldIdx: 'Id1'
}, {
id: "field2",
name: "Id2",
field: "SmDetails",
fieldIdx: 'Id2'
} //... etc
Check out this fiddle for a working example.
try this to convert your data into object of single length values ...
newData = {};
for(key in data[0]){
parentKey = key;
if(typeof(data[0][key]) == "object"){
childData = data[0][key];
for(key in childData){
childKey = key;
newKey = parentKey+childKey;
newData[newKey] = childData[childKey];
}
} else {
newData[key] = data[0][key];
}
}
This will convert your data object like this
newData = {Id: 40072, Id2: 40071, SmDetailsId1: 40071, SmDetailsId2: 40072};
Now use this newData to map your data items in grid
I find this works well for nested properties, eg:
var columns = [
{ id: "someId", name: "Col Name", field: "myRowData.myObj.myProp", width: 40}
..
];
var options {
...
dataItemColumnValueExtractor: function getItemColumnValue(item, column) {
var val = undefined;
try {
val = eval("item." + column.field);
} catch(e) {
// ignore
}
return val;
}
};

Kendo Grid DatePicker Editor Template DateTime Change Value not working

Consider the following code i want to set the grid datePicker column empty if date validation fails WorkOrderDate< task date , any help would be higly appreciable.
***********Grid***************
columns.Bound(c => c.WorkOrderDetailsDate)
.Title("Estimated Start Date")
.EditorTemplateName("WorkOrderDetailsDate")
***********Editor**************
#model DateTime?
#(Html.Kendo().DatePicker()
.Name("WorkOrderDetailsDate")
.Value(Model == null ? DateTime.Now.Date : ((DateTime)#Model).Date)
.Events(d=>d.Change("TaskDateValidate"))
)
*************JavaScript***********
function TaskDateValidate(e)
{
if ($("#workOrder_EstStartDate").val() != null && $("#workOrder_EstStartDate").val() != "") {
var workDate = kendo.parseDate($("#workOrder_EstStartDate").val());
var taskDate = kendo.parseDate(kendo.toString(this.value(), 'd'));
if (taskDate < workDate)
{
showMessage("Task date should be after work order Date");
this.value(""); <-----this is not working want to set to empty to force user to select date again
this.value("28/02/2014");<---this is not working as well...
}
}
}
please advise on this problem
reagrds
Shaz
found the solution my self...
//Add validation on Grid
(function ($, kendo) {
$.extend(true, kendo.ui.validator, {
rules: {
greaterdate: function (input) {
if (input.is("[data-val-greaterdate]") && input.val() != "") {
var date = kendo.parseDate(input.val()),
earlierDate = kendo.parseDate($("[name='" + input.attr("data-val-greaterdate-earlierdate") + "']").val());
return !date || !earlierDate || earlierDate.getTime() < date.getTime();
}
return true;
},//end of greaterdate
shorterdate: function (input) {
if (input.is("[data-val-shorterdate]") && input.val() != "") {
var date = kendo.parseDate(input.val()),
laterDate = kendo.parseDate($("[name='" + input.attr("data-val-shorterdate-laterdate") + "']").val());
return !date || !laterDate || laterDate.getTime() > date.getTime();
}
return true;
},//end of shorter date
// custom rules
taskdate: function (input, params) {
if (input.is("[name=WorkOrderDetailsDate]")) {
//If the input is StartDate or EndDate
var container = $(input).closest("tr");
var tempTask = container.find("input[name=WorkOrderDetailsDate]").data("kendoDatePicker").value();
var tempWork = $("#workOrder_EstStartDate").val();
var workDate = kendo.parseDate(tempWork);
var taskDate = kendo.parseDate(tempTask);
if (taskDate < workDate) {
return false;
}
}
//check for the rule attribute
return true;
}
}, //end of rule
messages: {
greaterdate: function (input) {
return input.attr("data-val-greaterdate");
},
shorterdate: function (input) {
return input.attr("data-val-shorterdate");
},
taskdate: function (input) {
return "Task date must be after work date!";
},
}
});
})(jQuery, kendo);

How can I automatically map a json object to fields based off a viewmodel mapped to fields?

I have a view that is loaded with a blank viewmodel initially. I want to populate that already rendered view with a json object (obtained view ajax post) that was based off the viewmodel for that view.
Is there a way of automatically doing this?
Is there a way of doing it in reverse? (fields to matching viewmodel json object)
The only way I am aware of taking data return from an ajax call and putting it in a field is manually
$('#TextField1').val(result.TextField1);
etc..
to send it back to the controller you can do
data: $('form').serialize(),
this will take all of the fields in that form and send them back to the controller
Ok it looks like this will suit my needs.
I need to follow a convention of naming containers the same name as their respective properties as well as putting a class on them to indicate that they contain subfields.
function MapJsonObjectToForm(obj, $container) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var $field = $container.find('#' + key);
if ($field.is('div')) {
MapJsonObjectToForm(obj[key], $field);
} else {
if (obj[key] == null) {
if ($field.hasClass('select2-offscreen')) {
$field.select2('val', '');
$field.select2().trigger('change');
} else {
$field.val("");
}
} else {
if ($field.hasClass('select2-offscreen')) {
$field.select2('val', obj[key]);
$field.select2().trigger('change');
} else {
$field.val(obj[key]);
}
}
}
}
}
}
function MapFormToJsonObject(containerid) {
var obj = {};
$('.dataitem').each(function () {
var exclude = "s2id";
if ($(this).attr("ID").substring(0, exclude.length) !== exclude) {
var parents = $(this).parents(".has-sub-fields");
if (parents.length > 0) {
obj = FindParents(obj, parents.get(), $(this).attr("ID"), $(this).val());
} else {
obj[$(this).attr("ID")] = $(this).val();
}
}
});
return obj;
}
function FindParents(obj, arr, id, value) {
if (arr.length == 0) {
obj[id] = value;
return obj;
}
var parentID = $(arr[arr.length - 1]).attr("ID");
arr.pop();
if (obj[parentID] == null) {
obj[parentID] = {};
}
obj[parentID] = FindParents(obj[parentID], arr, id, value);
return obj;
}

Best way to match colName against colModel's object?

I wrote JQuery to grab columns' text in the ColumnChooser pop-up dialog, in order to get colModel's Name (or Index) then I learned it doesn't work that way and I have to somehow use colName against colModel instead.
Problem..
colNames: [ 'Id', 'Stock Number', 'VIN', 'Year' ],
colModel: [
{ name: 'Id', index: 'Id' },
{ name: 'StockNumber', index: 'StockNumber' },
{ name: 'VIN', index: 'VIN' },
{ name: 'Year', index: 'Year' }
As you can see my problem is "Stock Number" is not the same as "StockNumber" when using $ColumnChooserSelectedList against the $jqgridColumnModelSetting. Also, I cannot tell if columns are in proper order (between colName & colModel) as I don't know how it works behind the scene.
var $ColumnChooserSelectedList = $("#colchooser_test ul.selected li");
var $ColumnModelSetting = $("#test").jqGrid('getGridParam', 'colModel');
var returnValue = "";
$.each($ColumnChooserSelectedList, function (i,o) {
if (o.title.length > 0) {
if (returnValue.length > 0) { returnValue += "|"; }
returnValue += o.title; //This o.title need to be changed to match colModel's Name (or Index)...
}
});
Thanks...
Updated - Solution found
Came up with this nice solution but I cannot be sure if it works 100% of the time.
var $ColumnChooserSelectedList = $("#colchooser_test ul.selected li");
var $ColumnModelSetting = $("#test").jqGrid('getGridParam', 'colModel');
var $ColumnNameSetting = $("#test").jqGrid('getGridParam', 'colNames');
var returnValue = "";
$.each($ColumnChooserSelectedList, function (i1,o1) {
if (o1.title.length > 0) {
$.each($ColumnNameSetting, function (i2, o2) {
if ($ColumnNameSetting[i2] == o1.title) {
if (returnValue.length > 0) { returnValue += "|"; }
returnValue += $ColumnModelSetting[i2].name;
return false; //This break the foreach loop...
}
});
}
});
Updated - Solution found
Came up with this nice solution but I cannot be sure if it works 100% of the time.
var $ColumnChooserSelectedList = $("#colchooser_test ul.selected li");
var $ColumnModelSetting = $("#test").jqGrid('getGridParam', 'colModel');
var $ColumnNameSetting = $("#test").jqGrid('getGridParam', 'colNames');
var returnValue = "";
$.each($ColumnChooserSelectedList, function (i1,o1) {
if (o1.title.length > 0) {
$.each($ColumnNameSetting, function (i2, o2) {
if ($ColumnNameSetting[i2] == o1.title) {
if (returnValue.length > 0) { returnValue += "|"; }
returnValue += $ColumnModelSetting[i2].name;
return false; //This break the foreach loop...
}
});
}
});

Resources