Selecting Multiple Cells on a KendoGrid in Javascript - javascript

I have a scenario where a user is able to select multiple cells on a grid. I would like to validate the users selection and deselect any invalid cells. I am trying to achieve this in the change function on my grid. My current approach is to get the currently selected cells, determine which cells are valid and select the list of valid cells I have found.
According to Kendo's documentation, the grid.select() function does take a parameter, but only takes an array of rows as a parameter. This explains why the below isnt working.
I need a solution which allows me to set which cells are highlighted and also retrieve the highlighted cells (I currently can get selected cells via the grid.select() method).
Is there a reasonable way to do this?
$scope.myKendoGridOptions = {
selectable: "multiple, cell",
change: function (e) {
if (!$scope.jsSelectChangeEvent) {
var grid = $('#myKendoGrid').data("kendoGrid");
var selectedItems = grid.select();
if (selectedItems.length > 1) {
var validItems = [];
var validRowIndex = selectedItems[0].closest("tr").rowIndex;
for (var i = 0; i < selectedItems.length; i++) {
if (selectedItems[i].closest("tr").rowIndex === validRowIndex) {
validItems.push(selectedItems[i]);
}
}
//Avoids Infinite loop
$scope.jsSelectChangeEvent = true;
//im expecting this to select my valid cells, but it has no effect
grid.select(validItems);
}
} else {
$scope.jsSelectChangeEvent = false;
}
}
};

The grid.select(validItems) does accept cells as a parameter. The problem is that I need to call grid.clearSelect() prior to calling grid.select to get the desired effect.

Related

Why doesn't Interactive Grid process save values set using the Model interface in JavaScript?

I'm writing a JavaScript function to allow users of an application built using Oracle Application Express 21.1 to paste data from Excel spreadsheets into an Interactive Grid and save the data. Using the APEX JavaScript API I can update the model of the Interactive Grid with the data; the pasted values display correctly and when I subsequently access the model the correct values are returned.
However when the Interactive Grid is saved, those values aren't saved to the underlying database table. What happens is:
Null columns updated by the JavaScript function remain null
Columns with existing data and then set by the JavaScript function become null
Null columns and columns with existing data that are then changed normally by the user are updated correctly
Columns initially set by the JavaScript function and then changed normally by the user are updated correctly
The grid is a simple Interactive Grid region based on the default EMP table, with a static ID of EmployeeGrid, and saves using the Interactive Grid process that is automatically generated when the grid was created.
I have entered the following code in the Execute when Page Loads section:
$("#EmployeesGrid_ig").on('paste', onPaste);
I have entered the following code in the Function and Global Variable Declaration section:
function onPaste(e) {
if (!e.originalEvent.clipboardData ||
!e.originalEvent.clipboardData.items) return;
let items = e.originalEvent.clipboardData.items;
let data;
for (let i = 0; i < items.length; i++) {
if (items[i].type == 'text/plain') {
data = items[i];
break;
}
}
if (!data) return;
data.getAsString(function(text) {
// Split the clipboard data into rows.
text = text.replace(/\r/g, '').trim('\n');
let rowsOfText = text.split('\n');
let rows = [];
// Iterate over each row of text and push the trimmed data into rows[]
rowsOfText.forEach(function(rowOfText) {
let row = rowOfText.split('\t').map(function(colAsText) {
return colAsText.trim().replace(/^"(.*)"$/, '$1');
});
rows.push(row);
});
// We get the focused element (i.e. where the user wants to paste).
let $focused = $('.is-focused');
// We get metadata from the Interactive Grid.
let rowId = $focused.closest('tr').data('id');
let columnIndex = $focused.index();
let headerIndex = $focused.closest('table').find('th').eq(columnIndex).data('idx');
let ig$ = apex.region("EmployeesGrid").widget();
let grid = ig$.interactiveGrid("getCurrentView");
let model = grid.model;
let columns = grid.getColumns();
let record = model.getRecord(rowId);
//Map visible columns
let visibleColumns = columns.filter(function (val) { return !val.hidden; });
visibleColumns.sort(function(a,b){return a.index - b.index;});
// Complete the Promise after the grid is out of editing mode.
rows.forEach(function(row) {
row.forEach(function(value, offset) {
if (record !== null) {
visibleColumns.forEach(function(column, visColIdx) {
if (visColIdx === (headerIndex + offset)) {
if (model.allowEdit(record)) {
model.setValue(record, column.property, Number(value));
}
}
});
}
});
// To change record, get current record index and then get next record.
let recordIndex = model.indexOf(record);
record = model.recordAt(recordIndex + 1);
});
});
}
I have created a sample application on apex.oracle.com to demonstrate the behaviour, please note that I have set the grid to allow updates to existing rows only and that only the Sal and Comm number columns can be updated.
I found a similar question raised on Oracle Communities where user Woodrow could visually see values that were automatically updated in an Interactive Grid column but those values weren't present when the page was submitted.
The answer they found was to set the value as a string:
model.setValue(record, column.property, value);
instead of a number:
model.setValue(record, column.property, Number(value));
This was necessary even if the column was declared as a 'Number' column in APEX.
Another approach is to use apex.locale JSAPI, is more APEX native way and won't cause issues in the future with APEX upgrades
var number = apex.locale.toNumber( "1,234.56" );
number = apex.locale.toNumber( "$1,234.56", "FML999G999G990D00" );
number = apex.locale.toNumber( "$1234.56", "FML999G999G990D00" );
Check this out
https://docs.oracle.com/en/database/oracle/application-express/21.2/aexjs/apex.locale.html#.toNumber

How to display value in grid column.

I have a extJS grid with four column. On third column I am modifying the value by button and able to display. In Fourth column I am getting empty string "" as data. I am giving some input and trying to save this in store but it not happening. How to save value in extjs grid store.
var grid = Ext.getCmp('gridID');
var gridstore = grid.getStore();
var modify = gridstore.modified;
for (var i = 0; i < modify.length; i++) {
modifyRec[i].data.S = "Hello";
}
S is dataIndex of the column.
Better way is to use set, than changing property directly.
var grid = Ext.getCmp('gridID');
var gridstore = grid.getStore();
var modify = gridstore.modified;
for (var i = 0; i < modify.length; i++) {
modifyRec[i].set('S', "Hello");
}
Edit:
In Ext-data-AbstractStore afterEdit fires update event. Which is being called from Model set
I prepared some fiddle for you. Hope it will help you:
https://fiddle.sencha.com/#fiddle/1f56
To get modiified records i used getModifiedRecords() fuction.

How to update Kendo grid aggregates without refreshing

I am working on a kendo grid with remote data, and currently when updating my grid, I use the dataItem.set() method to manually sync the grid with the data. This works fine, but unfortunately the aggregates in the footer are not refreshed. They refresh only when calling dataSource.fetch(), which hangs up the application for about 10 seconds. The users however, would like more excel-like responsiveness where every change they make is immediately reflected in the total, but also persisted to the database. Is this possible using the kendo api? Or do I have to do this manually with jQuery?
Edit: doesn't look like there's a built-in way so I fixed manually w/ jQuery.
Edit 2: Here's the code I used, generalized a bit and taking out some application specific quirks.
Kendo Grid Configuration:
$(gridId).kendoGrid({
columns: [
{
field: fieldToUpdate,
editor: customEditor,
//add 'data-field' attribute to footer/group footer
footerAttributes: { 'data-field': fieldToUpdate },
groupFooterAttributes: { 'data-field': fieldToUpdate }
},
//other fields...
],
//other config...
});
Custom Editor:
function customEditor(data) {
//store original and new value
//append textbox
//call custom update passing td and data w/ original/new values
}
Find Affected Aggregate Cells:
//Gets all affected aggregate cells after an update
function getTotalsCells($container, updatedField) {
var groups = $('#grid').data('kendoGrid').dataSource.group(),
$totals = $('.k-footer-template>td[data-field="' + updatedField + '"]'),
$row = $container.parent('tr');
for (var i = 0; i < groups.length; i++) {
var $groupTotal = $row.nextAll('.k-group-footer')
.eq(i)
.find('[data-field="..."]');
$totals = $totals.add($groupTotal);
}
return $totals;
}
Update Totals
$.fn.updateTotal = function (delta) {
this.each(function () {
var $container = $(this);
var origTotal = parseFloat($container.text() || 0);
var total = origTotal + delta;
$container.text(total);
});
};
Custom Update:
function updateGrid($container, data) {
var difference, field;
//get difference and updatedField
var $totals = getTotalsCells($container);
$totals.updateTotal(difference);
}
I feel like there must be a better way to do this, but the aggregate model doesn't seem to update.
My solution was to define a function that manually calculates the results and call this from within the footer template. Whenever the grid is refreshed the footer is also updated.
Client Template: #: sumDebits() #
function sumDebits() {
var $grid = $('#GridId');
var kendo = $grid.data().kendoGrid;
var data = kendo.dataSource.data();
var total = 0;
for (var i = 0; i < data.length; i++) {
var debit = parseFloat(data[i].Form.debit);
if (debit == NaN) {
debit = 0;
}
total = total + debit;
}
return total;
}
I had almost similar problem. I had a KendoGrid which i needed to refresh the row only (update HTML and Data) and update the groupFooterTemplateand and the footerTemplate after a modal close (which it had the edit cells i needed to update to the grid). I had "#progress/kendo-ui": "^2019.2.626". I know that is uses set but in this version set updates everything.
Bellow is the Code where you update groupFooterTemplateand ,footerTemplate , Html(row) and also excel is updated withoute Total Refresh of the Grid.
let grid = $('#grid').getKendoGrid(); // Kendo Grid
let dataItem = grid.dataItem(tr); // tr: JQuery (selected Row)
let index= grid.items().index(grid.select()); // or the selected Row
dataItem = data.length > 0 && data.length === 1 ? data : dataItem; // data is the new item with the same properties as the item datasource of the Grid. You can update all properties like below or just set one property.
dataItem.dirty = true;
let rowItem = grid.dataSource.at(index);
for (let [key, value] of Object.entries(dataItem )) {
rowItem.set(key, value); //
}
var grid = $("#gridName").data('kendoGrid');
var aggregateSum = grid.dataSource._aggregateResult;
//below code give you current aggregate value.
var sum = aggregateSum.<your_aggregate_columnname_here>.sum;
//assuming my Grid Column name is 'Amount'
var sum = aggregateSum.Amount.sum;
To change the aggregate value without refreshing page or without fetching the datasource, follow below steps
// Set the current aggregate value to 0
aggregateSum.<your_aggregate_columnname_here>.sum = 0;
// i.e. aggregateSum.Amount.sum = 0;
// Loop trough Grid data row by row
var gridData = grid.dataSource.data();
for (var i = 0; i < gridData.length; i++) {
var dataRow = gridData[i];
// to set the aggregate sum value
aggregateSum.Amount.sum += value;
// to set the cell value for that particular row
dataRow.set(ColumnName, value);
}
Note : Make Sure Call the Set() function in the end after updating the aggregate sum. If you will call Set function before setting aggregate sum, you will not be able to see the last iteration changes, as the changes will only reflect after set() function will get executed

Kendo UI Grid select by data item

I have a Kendo UI Grid with a large datasource and paging.
I have an event that fires where I know the underlying data item that I want to select, but am unsure on how to programatically page/select this item in the grid. If the item is not on the current grid page, I cannot use datasource.view() to poke through when the data is not on the current page.
Does anyone know how I can select an item by its underlying data source object?
I've got a similar situation to where i am at #:
http://jsfiddle.net/Sbb5Z/1050/
I can get the data item with the following:
change: function (e) {
var selectedRows = this.select();
var dataItem = this.dataItem(selectedRows[0]);
}
But then I don't know how to select the same row in the other grid.
Basically in the select event of one grid, I want to go select the same item in another grid. These are not the same datasource, as they have different page setups, but it is the same underlying data array.
I have the data item in the target grid -- but I have no clue how to page/select it in the target grid.
Edit:
The best I've come up with sofar is creating a datasource with the same parameters as the original, and paging through it programatically, until I find what I am looking for. Surely there must be a better way?
I've gotten this back from Telerik, and is a little cleaner:
http://jsfiddle.net/RZwQ2/
function findDataItem(theGrid, dataItem) {
//get grid datasource
var ds = theGrid.dataSource;
var view = kendo.data.Query.process(ds.data(), {
filter: ds.filter(),
sort: ds.sort()
})
.data;
var index = -1;
// find the index of the matching dataItem
for (var x = 0; x < view.length; x++) {
if (view[x].Id == dataItem.Id) {
index = x;
break;
}
}
if (index === -1) {
return;
}
var page = Math.floor(index / theGrid.dataSource.pageSize());
var targetIndex = index - (page * theGrid.dataSource.pageSize()) + 1;
//page is 1-based index
theGrid.dataSource.page(++page);
//grid wants a html element. tr:eq(x) by itself searches in the first grid!
var row = $("#grid2").find("tr:eq(" + targetIndex + ")");
theGrid.select(row);
console.log('Found it at Page: ' + page + 'index: ' + targetIndex);
}
You need to have a common id, or field in the data that you can use to uniquely identify the object in the other dataSource, because the kendo generated UID's are not going to be the same accross two different DataSource instances.
Most generally you define the id in the Model you bound to the grid, which you can use to quickly pluck items from the datasource
change: function (e) {
var selectedRows = this.select();
var dataItem = this.dataItem(selectedRows[0]);
var otherItem = otherGrid.dataSource.get(dataItem.id) // will get
}
if you don't have a common ID field specified in the model, but do know how to find the item you can loop through the data source looking for it
var selectedRows = this.select();
var dataItem = this.dataItem(selectedRows[0]);
var data = otherGrid.dataSource.view();
var otherItem;
for ( var i = 0; i < data.length; i++ ){
if( data[i].myCommonField === dataItem.myCommonField ) {
otherItem = data[i];
break;
}
}
UPDATE:
to select the item in the other grid you need to do this:
var elements = otherGrid.items(),
element;
element = elements.filter("[data-uid='" + otherItem.uid + "']")
otherGrid.select(element) // to select just the one item
//OR
otherGrid.select( otherGrid.select().add(element) ) // to add the item to the current selection
I the fiddle you provided uses a really old version of kendo Grid where this won't work...I just realized. are you stuck on the 2011 version? I can probably get something to work at least in theory but the above will work in the newer versions
essentailly you need to match the item you have to a DOM element, in later versions you can use UID because the dom elements all get that on them "data-uid" it looks like if you at id to your model: { } def you can get the tr elements to have data-id which you can use to select the right select using jquery. I use the items()1 method which also doesn't seem to exist on the early version but you can usegrid2.table.find("tr[data-id=]")` instead I believe
Assume div id will be Grid then first we need find the kendoGrid
var grid = $("#Grid").data("kendoGrid");
then call the grid.select() to select the currently selected one
finally call the grid.dataItem() to get the selected item.
var selectedDataItem = grid.dataItem(grid.select());
To expand upon others, I have a method that takes a single (or multiple) ids to match against:
function selectItems(grid, idAr)
{
if(!idAr instanceof Array)idAr = [idAr];
var items = grid
.items()
.filter(function(i, el)
{
return idAr.indexOf(grid.dataItem(el).Id) !== -1;
});
grid.select(items);
}
* Obviously Id could be replaced by any field that is in your data item.
Use for selection:
selectItems(grid, "5");
selectItems(grid, ["6", "7"]);

Select option removal function

The following function is supposed to remove a selected value from the selectbox,
and update a hidden field with all values.
I have two problems with it:
Lets say I have the following options in the selectbox : 1071,1000,1052
After removing the first option (1071), the hidden field is getting a value of 1052,1000,1000,
if I remove the second option (1000) the hidden field is 1052,1052,1071
if I remove the third one (1052), I get an options[...].value is null or not an object
Can someone plz help me fix this ?
function removeOptions() {
var selectbox = document.getElementById('zipcodes');
var zipcodes = document.getElementById('zip');
var tmpArray = [];
var i;
for (i = selectbox.options.length - 1; i >= 0; i--) {
if (selectbox.options[i].selected){
selectbox.remove(i);
}
tmpArray.push(selectbox.options[i].value);
}
zipcodes.value = tmpArray.join(',');
}
Assuming you don't need the selected value in hidden value, place the portion to push in tmpArray in else part
if (selectbox.options[i].selected){
selectbox.remove(i);
}else{
tmpArray.push(selectbox.options[i].value);
}

Categories