How to update Kendo grid aggregates without refreshing - javascript

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

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 find and select table from Document in Apps Script?

I'm creating a function in Google Apps Script. The purpose of this function is selecting the table from the document and move values to the created Spreadsheet. The problem is that I can't get the table from the document (debugging is OK, but logs show selected table as empty {}).
function addAnswersTable() {
var File = function(Path) { // File object constructor
this.Path = Path;
this.Doc = DocumentApp.openById(this.Path);
this.getTable = new function()
// This function defines
// a getTable method to get
// the first table in the Document
{
if (this.Doc != undefined) {
var range = this.Doc.getBody();
var tables = range.getTables();
var table = tables[0];
return table;
}
}
}
// Creating Excel Table, where first column
// of selected table should be moved
var Table = SpreadsheetApp.create("AnswersTable");
// Creating new File object
var TrueAnswersFile = new File
('1_ne9iBaK-Z36yUYrISr3gru3zw3Qdsneiu14sWnjn34');
// Calling getTable method to get the table placed in File
var TrueAnswersTable = TrueAnswersFile.getTable;
for (var i = 1; i <= TrueAnswersTable.getNumRows; i++) {
// Filling spreadsheet "A" column with cells'
// values ​​from table stored in File
Table.getActiveSheet().getRange("A" + i).setValue(TrueAnswersTable.getCell(1, i).getValue());
};
}
I except the output in Spreadsheet column "A" like :
A1. Just
A2. Cells'
A3. List item with
A4. Values From Table
Actually spreadsheet is empty
You want to retrieve the values from the column "A" of Google Document and put the values to the column "A" of the created Spreadsheet.
The table of index 0 in the Document has 4 rows and 1 column.
The values of each row is Just, Cells', List item with, Values From Table.
I could understand like above. If my understanding is correct, how about this modification?
Modification points:
In your script, the method is not used as the function. By this, the method is not run.
For example, TrueAnswersFile.getTable and TrueAnswersTable.getNumRows.
No method is used.
For example, getValue() of TrueAnswersTable.getCell(1, i).getValue().
new of this.getTable = new function() is not required.
In your script, getCell(1, i) of TrueAnswersTable.getCell(1, i) retrieves the values at from column "B" of the row 2.
If you want to retrieve the values from the row 1 of the column "A", please modify to getCell(i - 1, 0). But in this modification, the start of index is 0. So you can use getCell(i, 0).
When setValue() is used in the for loop, the process cost becomes high. In your case, you can use setValues() instead of it.
When above points are reflected to your script, it becomes as follows.
Modified script:
function addAnswersTable() {
var File = function(Path) {
this.Path = Path;
this.Doc = DocumentApp.openById(this.Path);
this.getTable = function() { // Modified
if (this.Doc != undefined) {
var range = this.Doc.getBody();
var tables = range.getTables();
var table = tables[0];
return table;
}
}
}
var Table = SpreadsheetApp.create("AnswersTable");
var TrueAnswersFile = new File('1_ne9iBaK-Z36yUYrISr3gru3zw3Qdsneiu14sWnjn34');
var TrueAnswersTable = TrueAnswersFile.getTable();
var values = []; // Added
for (var i = 0; i < TrueAnswersTable.getNumRows(); i++) { // Modified
values.push([TrueAnswersTable.getCell(i, 0).getText()]) // Modified
};
Table.getRange("A1:A" + values.length).setValues(values); // Added
}
References:
getCell(rowIndex, cellIndex)
getText()
Benchmark: Reading and Writing Spreadsheet using Google Apps Script

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.

Import google spreadsheet data into google forms with app script

I searched the internet and I can't find a response to this nor the documentation for it.
I need to dynamically generate Google forms questions with data from a Google spreadsheet using app script, but I don't know how to reference and read a spreadsheet.
In your spreadsheet select Tools > Script Editor and adapt this to your needs:
/**
After any change in the sheet, update the combobox options in the Form
*/
function onChange(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var range = sheet.getDataRange();
var values = range.getValues();
var comboValues = []; // <-- cheddar will go here
// in this example we are interested in column 0 and discarding row 1 (the titles)
for (var i = 1; i <= values.length; i++) {
var v = values[i] && values[i][0];
v && comboValues.push(v)
}
// Sort the values alphabetically, case-insensitive
comboValues.sort(
function(a, b) {
if (a.toLowerCase() < b.toLowerCase()) return -1;
if (a.toLowerCase() > b.toLowerCase()) return 1;
return 0;
}
);
Logger.log(comboValues);
// Use your form ID here. You can get it from the URL
var form = FormApp.openById('<my-form-id>');
/*
Uncomment this to display the item IDs
and pick the one that you want to modify
var items = form.getItems();
for (i = 0; i < items.length; i++) {
Logger.log("ID: " + items[i].getId(), ': ' + items[i].getType());
}
*/
form.getItemById(807137578).asListItem().setChoiceValues(comboValues);
};
To debug, select the script in the combobox and click either "play" or "debug". The first time you will have to give it permissions to interact with your spreadsheet and form.
Once you are satisfied with the result, in the editor select Resources > Triggers for the active project and add this method to be triggered with any modification on the spreadsheet (on change, not on edit).
After this, your form options will be changed in real time after any change in your spreadsheet.
It's pretty straightforward, see here: https://developers.google.com/apps-script/guides/sheets#reading
You just need to open the sheet by its doc key, select the data and read the cells as a JS object.
Here is an example which works for me, pls kindly check:
function getSpreadsheetData(sheetId) {
// This function gives you an array of objects modeling a worksheet's tabular data, where the first items — column headers — become the property names.
var arrayOfArrays = SpreadsheetApp.openById(sheetId).getDataRange().getValues();
var headers = arrayOfArrays.shift();
return arrayOfArrays.map(function (row) {
return row.reduce(function (memo, value, index) {
if (value) {
memo[headers[index]] = value;
}
return memo;
}, {});
});
}
function makeOurForm() {
var sheetId='input_your_sheet_id'
getSpreadsheetData(sheetId).forEach(function (row) {
// Set your form template as follows
var formName=row.Name
// Create your form programmatically, each row means one form
var form = FormApp.create(formName)
form.setDescription('xxx');
var capitalizedName = row.Name.charAt(0).toUpperCase() + row.Name.slice(1);
form.addSectionHeaderItem().setTitle(capitalizedName);
var item = form.addMultipleChoiceItem();
item.setTitle('xxx')
.setChoices([
item.createChoice('xxx'),
]);
form.addParagraphTextItem().setTitle('xxx');
});
}
You can get your sheet Id from url, for example:
https://docs.google.com/spreadsheets/d/YourSheetId/edit#gid=0
Let me know if you have any further questions.

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"]);

Categories