Kendo UI TreeView: Add only nodes not exisisting in the view - kendo-ui

I have tree view on a page which gets data from a ComboBox and a multiselect. The ComboBox contains the name of each ingredient and the multiselect contains the possible amount types which are then used as names for all their child nodes.
The tree looks something like that:
Ingredient 1
100mg
200mg
Ingredient 2
50mg
100mg
Everything works fine except I can add the same value twice because I am not able to validate if a node already exists.
Here is the function I am using to add new elements:
var addElement = function () {
var treeview = $("#ingredientTree").data("kendoTreeView");
var multiselect = $("#ingredientAmount").data("kendoMultiSelect");
var ingredientToAdd= $("#ingredient").val();
// I allways get an empty array at this point.
var exinstingIngredient= treeview.findByText(ingredientToAdd);
var children = new Array();
var amount = multiselect.value();
for (var j = 0; j < amount.length; j++) {
children.push({ text: amount[j] });
}
// it allways adds the items because the length is allways 0
if (exinstingIngredient.length === 0) {
treeview.append({
text: ingredientToAdd,
items: children
});
}
}
I don't understand why it can't find the existing element even I set its name as text and search for this text.
edit:
Here we have the treeview:
#(Html.Kendo().TreeView().TemplateId("treeview-template").Name("ingredientTree"))
That is the source of the ingredients, it handles just plain strings:
#(Html.Kendo().ComboBox()
.Name("ingredient")
.DataSource(source => source.Read(r => r.Url(Url.HttpRouteUrl("DefaultApi", new { controller = "InternationalIngredients" }))))
.Events(events => events.Change("onIngredientChanged"))
)
Following you find the source for amounts, which handles strings to:
#(Html.Kendo().MultiSelect()
.Name("ingredientAmount")
.DataSource(source => source.Read(read => read.Url(Url.HttpRouteUrl("DefaultApi", new { controller = "InternationalIngredientAmount" })).Data("getIngredient")).ServerFiltering(true)))
This is a function to determine the selected ingredient for the service call:
function getIngredient() {
return { ingredient: $("#ingredient").val() }
}

I've found the reason for my problem now. findByText seems to check the Content of the nodes span with class "k-in". Unfortunatly, this Content is modified when you add a template as described here. So if you want to find an element with template, you should use findById or you define your template in a way you can use jQuery.

Related

How to get dataItems of selectedKeyNames of checked row across all pages in kendo grid

I have Kendo grid with local data and check box and pagination. I am getting all checked items Id using selectedKeynames() across all pages.
How can I get dataItems of checked iems across all pages?
If you are using Kendo for paging, sorting and so on and not handling these operations on the server, then there seems to be no other way than skimming over the list of elements in the datasource. Something like this:
var g = $("#grid").getKendoGrid();
var d = g.dataSource.data();
var s = g.selectedKeyNames();
var r = [];
for (var i = 0; i < d.length; i++) {
if (s.indexOf(d[i].Id) >= 0) {
r.push(d[i]);
}
}
Here is a possible solution for you.
Please review the following dojo: https://dojo.telerik.com/ibALanIX
As with the other solution I am obtaining the data but using a forEach loop on the selected items as when you start having a large number of records the for loop could take some time.
So all I am doing is going over the selected items and then grabbing those items from the selected list.
var grid = $("#rowSelection").data('kendoGrid');
var selectedItems = grid.selectedKeyNames();
var actualItems = [];
if (selectedItems.length > 0) {
selectedItems.forEach(function(key) {
actualItems.push(grid.dataSource.get(key));
});
}
This would then reduce the number of loops you are having to do and as you know what the keys are from the selection, then this just uses the default get method of the kendo DataSource which maps to the ID of the model in the datasource schema.

How to determine which row or control is making a DropDownList Read Data call

I have a DropDownListFor control inside a Kendo grid, and I need the value of another element in that row in order to pass a parameter to the DropDownList's server-side read method. It's a similar setup to the example here.
Here is the code which defines the DropDownListFor:
#(Html.Kendo().DropDownListFor(m => m)
.Name("LinkIdentifier")
.OptionLabel("---Select Form, etc.---")
.DataValueField("ID")
.DataTextField("Name")
.AutoBind(false)
.DataSource(source =>
{
source.Read(read =>
{
read.Action("LinkTypeIdentifierDdl", "Alerts").Type(HttpVerbs.Post).Data("getCurrentLinkType");
}).ServerFiltering(true);
})
)
And here is the javascript function which is called in .Data:
function getCurrentLinkType() {
var grid = $("#linkGrid").data("kendoGrid");
var data = grid.dataSource.data();
var dataItem = data[0];
var valueForParameter = dataItem.SomeValue
//--snipped for brevity
}
The problem above is data[0]. It only points to the first row in the grid, which won't be correct if editing any other row. If I use a javascript debugger within this method and look at "this", it's the AJAX call which is referenced, not the dropdown control. I can't specify ".Data("getCurrentLinkType(this)")" as the method.
So, how can I determine which row/control has made the call to getCurrentLinkType?
There is no context passed from the Grid to the DropDownList to the Data function, so you need to figure it out yourself.
I have found 2 ways.
1:
// If your grid is single Selectable(), use the current selection
var dataItem = grid.dataItem(grid.select());
2:
// If your grid is not Selectable or is multi Selectable, get the row/tr closest to the editor and then get the dataItem from that
var dataItem = grid.dataItem($("#LinkIdentifier").closest("tr"));

jqGrid Make a Row readonly

I have a grid setup using form editing. I want the the user to be able to edit only some of the rows. As a start, I figured the easiest way to do this was to have a column (probably hidden) in my server query and XML that denotes the Access or Role the user has. So essentially the grid now has a column "Access Role" with 'Y' or 'N' for each row. (where Y = user can edit, N = View/readonly)
I've tried a couple things to implement this. The best I've come up with is using the rowattr function, but my use is flawed since it hides the row in the grid (I don't want it hidden, just readonly):
function (rd) {
console.log('Row = '+rd.WWBPITM_SURROGATE_ID);
if (rd.ACCROLE === "N") {
console.log('RowAttr '+rd.ACCROLE);
return {"style": "display:none"};
}
This might be a start, but I'm not sure where to go from here and I'm not sure if I'm barking up the wrong tree with using rowattr.
I also tried using setCell in a loadComplete function, like this:
function GridComplete() {
var grid = $('#list1');
var rowids = grid.getDataIDs();
var columnModels = grid.getGridParam().colModel;
console.log('Check ACCROLE');
// check each visible row
for (var i = 0; i < rowids.length; i++) {
var rowid = rowids[i];
var data = grid.getRowData(rowid);
console.log('ACCROLE for '+rowid+' is '+data.ACCROLE);
if (data.ACCROLE == 'N') { // view only
// check each column
//console.log(data);
for (var j = 0; j < columnModels.length; j++) {
var model = columnModels[j];
if (model.editable) {
console.log('Is Editable? '+model.editable);
//grid.setCell(rowid, model.name, '', 'not-editable-cell', {editable: false, edithidden: true});
grid.setCell(rowid, model.name, '', 'not-editable-cell', {editoptions: { readonly: 'readonly', disabled: 'disabled' }});
}
}
}
}
}
But the editoptions don't seem to do anything with this.
Any ideas how to do this?
OK thanks for explaining about Form editing. Here's an example of how to prevent edits on certain records for jqGrid with form editing:
Start with this example of jqGrid form edit: http://www.ok-soft-gmbh.com/jqGrid/MulticolumnEdit.htm
Use the beforeInitData event to check your data before the edit form is displayed. Note that this is bound to the pager object.
Use getGridParam and getCell methods to get the current value you want. In my example I grabbed the client name
Add your own business logic for checking (I don't allow edits on 'test2')
Return false to prevent the edit form from popping up.
This example only handles edit, not insert or delete.
Replace $grid.jqGrid("navGrid", "#pager",...) from the example with this:
$grid.jqGrid("navGrid", "#pager", {view: true},
// Events for edit
{
beforeInitData: function (formid) {
var selectedRow = jQuery("#list").jqGrid('getGridParam','selrow'); //get selected rows
var selectedClient = $("#list").jqGrid('getCell', selectedRow, 'name');
if(selectedClient == 'test2')
{
alert('You are not allowed to edit records for client "' + selectedClient + '"');
return false;
}
}
},
// Events for add
{
beforeShowForm: function (formid) {
}
}
);
You didn't provide much information about how you're updating rows (there are various methods as described in JQGrid web page demos, but I took a guess as to a possible solution. I started with the example on the bottom of this page (trirand's web site wiki for inline_editing) http://www.trirand.com/jqgridwiki/doku.php?id=wiki:inline_editing and made a few changes.
Added a new data column securityGroup, and put in data like 'A', 'B', 'C'
Displayed the new data column in the grid
The example used the onSelectRow event to start editing a row if you clicked on a new row. I updated this callback to check the value of row['securityGroup'], and only start .editRow if it's in securityGroupA
JSFiddle at http://jsfiddle.net/brianwoelfel/52rrunar/
Here's the callback:
onSelectRow: function(id){
var row = $(this).getLocalRow(id);
if(id && id!==lastsel2){
jQuery('#rowed5').restoreRow(lastsel2);
if(row['securityGroup'] == 'A') {
jQuery('#rowed5').editRow(id,true);
}
lastsel2=id;
}
},
If this method won't work for you, please provide more information about how you're currently doing edits with jqGrid. This example obviously is very trivial and doesn't even post to PHP or mysql or anything.
In case it will be helpful for others, here is how I am implementing Read Only rows in Form Editing mode, based on a column which designates what level of access the user has to each row:
In editoptions, use the beforeShowForm event, like so:
beforeShowForm: function (formid) {
console.log('Checking for READONLY '+formid.name);
var selectedRow = jQuery("#list1").jqGrid('getGridParam','selrow'); //get selected rows
var selRole = $("#list1").jqGrid('getCell', selectedRow, 'ACCROLE');
if(selRole == 'N' || selRole == 'S' || selRole == 'R')
{
//$("<div>Sorry, you do not have access to edit this record.</div>").dialog({title: "Access Denied"});
formid.find("input,select,textarea")
.prop("readonly", "readonly")
.addClass("ui-state-disabled")
.closest(".DataTD")
.prev(".CaptionTD")
.prop("disabled", true)
.addClass("ui-state-disabled");
formid.parent().find('#sData').hide();
var title=$(".ui-jqdialog-title","#edithd"+"list1").html();
title+=' - READONLY VIEW';
$(".ui-jqdialog-title","#edithd"+"list1").html(title);
formid.prepend('<span style="color: Red; font-size: 1em; font-weight: bold;">You viewing READONLY data.</span>');
}

Kendo - Grid - Remove Multiple Rows

I have the following code that remove rows that are checked (each row has a checkbox and I have a Filter button (and other buttons) in the toolbar).
var grid = $("#resultsGrid").data("kendoGrid");
grid.tbody.find("input:checked").closest("tr").each( function(index) {
grid.removeRow($(this));
});
However, performance starts to become an issue when there are > 20 rows being removed. However, kendo filters (removes) 20 rows or more much faster. How is kendo filtering removing multiple rows from the view? Or is there some other better way to remove multiple rows from the grid? Thanks in advance for your help.
** Updated Fiddle to new location - Same code as before **
Try dealing with the data directly. Have the checkbox hooked to the row's Id and filter on that.
I linked a fiddle that removes array elements and then re-creates the grid. The 2 text boxes at the top of the grid capture the range of Ids that you want to remove (this would be the same array of your checkboxIds). Then I cycled through those "remove Ids", removed them from the data array and remade the grid.
I slightly modified a previous fiddle and that's why I'm re-creating the grid instead of dealing with the DataSource object directly. But I hope you get the gist of what I'm doing.
I have 1000 records in this example (only 3 columns) but it removes 950 records very quickly.
Fiddle- Remove from data array
Let me know if you need an example of this with the KendoUI DataSource object.
I included this code below because StackOverflow wouldn't let me post without it.
function filterData() {
var val1 = $("#val1").val();
var val2 = $("#val2").val();
var removeIndexes = new Array();
for (var i = val1; i <= val2; i++) {
removeIndexes.push(i);
}
$(removeIndexes).each(function(removeIndex) {
var removeItemId = this;
$(randomData).each(function(dataIndex) {
var continueLoop = true;
if (this.Id == removeItemId) {
randomData.splice(dataIndex, 1);
continueLoop = false;
}
return continueLoop;
});
});
createGrid();
}
You should use batch updates:
$("#resultsGrid").kendoGrid({
dataSource: {
batch: true,
...
}, ...});
var grid = $("#resultsGrid").data("kendoGrid");
grid.tbody.find("input:checked").closest("tr").each( function() {
var dataItem = grid.dataItem(this);
grid.dataSource.remove(dataItem);
});
grid.dataSource.sync(); // this applies batched changes

KnockoutJS How to databind a set of drop down lists to an observable array

My View is bound to a Model and displays several dropdown lists, and their number and the options are based on some properties in the model.
I would like to use knockout js to data bind each selected value of the dropdown lists to an observable array, keeping track of which value belongs to which ddl.
Each dropdown list is displayed using the following code:
#for (var i = 0; i < Model.ProductProfiles.Count; i++)
{
var productProfile = Model.ProductProfiles[i];
if (productProfile.IsVarying)
{
var lastItem = 0;
<div class="tab-pane" id="#productProfile.Name">
#for (var j = 0; j < productProfile.Attributes.Count; j++)
{
<text>#productProfile.Attributes[j].Name</text>
#Html.DropDownListFor(m => m.ProductProfiles[i].SelectedValues[j], (SelectList)ViewData["Levels_" + i + "_" + j],
new { #class = "ddl-levels" })
<br />
}
</div>
}
}
The dropdownlists are correctly filled in and the default value is correctly set (using both the model and the select list passed in the ViewData).
Is there a way to databind to an array that looks something like:
array[0]["0", "1", "3", "1",....] where array[0] refers to Model.ProductProfiles[0] and the second dimension of the array holds the selected values for the ddl as displayed in the for loop
?
I tried setting my view model as simple as
var viewModel = {
selectedValues: ko.observableArray([])
};
and out of desperation I tried to modify the data-bind value to simulate a positional access in the observable array:
#Html.DropDownListFor(m => m.ProductProfiles[i].SelectedValues[j], (SelectList)ViewData["Levels_" + i + "_" + j],
new { #class = "ddl-levels", data_bind = "value: productProfiles([" + i + "][" + j + "])" })
Unfortunately it didn't work...
I hope to have been clear, otherwise I would be glad to clarify when asked to.
Is there anyone who can help me?
Thanks
EDIT: based on comment advice, I split the multidimensional array into 3 observable arrays
like the following:
var viewModel = {
selectedValuesProd0: ko.observableArray(),
selectedValuesProd1: ko.observableArray(),
selectedValuesProd2: ko.observableArray()
}
and then I modified the HTML code
var observableArrayProduct = "selectedValuesProd0";
if (i == 1)
{
observableArrayProduct = "selectedValuesProd1";
}
else if (i == 2)
{
observableArrayProduct = "selectedValuesProd2";
}
#Html.DropDownListFor(m => m.ProductProfiles[i].SelectedValues[j], (SelectList)ViewData["Levels_" + i + "_" + j], new { #class = "ddl-levels", data_bind = "value: " + observableArrayProduct + "()[" + j+ "]" })
As you can see I'm still trying a positional access to the mono-dimensional observable array, but it's still not keeping track of the selected values when the user changes something in the DropDown lists.
At this point I'm starting to think that positional access doesn't work, but if that's so, what else can I do?
EDIT 2: SOLVED!
Ok, I was missing a key step: each item in the observable array is itself an observable value, so I fixed it like the following:
var viewModel = {
selectedValuesProd0: ko.observableArray(ko.utils.arrayMap(selectedValuesPerProduct[0], function(item) {
return ko.observable(item);
})),
selectedValuesProd1: ko.observableArray(ko.utils.arrayMap(selectedValuesPerProduct[1], function(item) {
return ko.observable(item);
})),
selectedValuesProd2: ko.observableArray(ko.utils.arrayMap(selectedValuesPerProduct[2], function(item) {
return ko.observable(item);
}))
}
Where selectedValuesPerProduct is a bidimensional array built using the model passed from the server side code to the View (after serialization).
I hope this can help!
I would recommend passing drop downs down like so:
{firstArray: [1,2,3], secondArray: [4,5,6]}
Basically your not saving any work by the way you suggest doing it instead. You could make 'firstArray' and 'secondArray' not human friendly just like your indexer. Though I am not sure why that would be a valuable goal ;) I pursue maintainable code where I can. Anyone who comes in after you and sees multilevel indexer will not appreciate your cleverness or later their headache.
I realised I had never closed this question, so I'm adding a response here:
Ok, I was missing a key step: each item in the observable array is itself an observable value, so I fixed it like the following:
var viewModel = {
selectedValuesProd0: ko.observableArray(ko.utils.arrayMap(selectedValuesPerProduct[0], function(item) {
return ko.observable(item);
})),
selectedValuesProd1: ko.observableArray(ko.utils.arrayMap(selectedValuesPerProduct[1], function(item) {
return ko.observable(item);
})),
selectedValuesProd2: ko.observableArray(ko.utils.arrayMap(selectedValuesPerProduct[2], function(item) {
return ko.observable(item);
}))
}
Where selectedValuesPerProduct is a bidimensional array built using the model passed from the server side code to the View (after serialization).

Resources