Sorting an observable array, which has queried data returned by breezeJS - sorting

The above image is of observableArray which is coming while queryinq database using breezeJS (using EntityManager).
My question is that
how do we sort this observable based on some criteria i.e.,
(object.attributeName) ?
So that this array is sorted based on some attribute name and we can simply use the observable within foreach bindings and use them in sorted way because I don't wanna query all the time (locally or from server) to get data in sorted order.

So make a computed
var orderDirection = ko.observable(1);
var orderField = ko.observable("id");
var orderedObsArr = ko.computed(function(){
var oDir = orderDirection();
var oField = orderField();
var newArr = originalObsArr().slice(0);
newArr.sort(function(a,b){
return oDir * (a[oField] > b[oField] ? 1 : -1);
});
return newArr;
});
so to change to a sort by name descending, you simply change:
orderDirection(-1);
orderField("name");
and your computed dependent orderedObsArr will be updated.
See this pen for a working example.

Related

How to store data for use in multiple data validation using GAS?

I have a script running on Google Sheets, which brings data from another spreadsheet/file as an array and sets one of its column's data as a data validation into a cell. Then, as the user picks one option of this data validation, the script goes back to that file and brings its related data and sets it in an adjacent column and this repeats about 3 times, making the process slow.
I was wondering if that would be possible to store the first data collection into the document property and set the data validations by grabbing related information from that data set, instead of going to the other file everytime.
Here's an update, with a working version:
function listaCategorias() {
let listaGeral = sheetBDCadProd.getRange(2, 1, sheetBDCadProd.getLastRow(), 45).getValues();//Gets all values
//Extracts a column of interest for this first data validation setting
let categorias = [];
for (let a = 0; a < listaGeral.length; a++) {
categorias.push(listaGeral[a][17])
}
let uniqueCat = [...new Set(categorias)]; //Gets a list of unique values. Not sure how I'd do that within new Set, so I did a for loop before
//Sets the data validation
const cell = sheetVendSobEnc.getRange('B5');
const validationCat = SpreadsheetApp.newDataValidation().requireValueInList(uniqueCat).setAllowInvalid(false).build();
cell.clearContent();
cell.clearDataValidations();
cell.setDataValidation(validationCat);
//Saves the data into the document property for usage in the next script/data validation
listaGeral = JSON.stringify(listaGeral)
PropertiesService.getDocumentProperties().setProperty('listaGeral', listaGeral);
}
//This is getting one of the columns, based on the option picked..the one generated by the data validation above.
function listaDescricao() {
const categoria = sheetVendSobEnc.getRange('B5').getValue();
const dadosCadProd = PropertiesService.getDocumentProperties().getProperty('listaGeral')
let cadGeral = JSON.parse(dadosCadProd);
//Filters the elements matching the option picked
let filteredNomeSobEnc = cadGeral.filter(function (o) { return o[17] === categoria });
//Filters unique values
let listToApply = filteredNomeSobEnc.map(function (o) { return o[7] }).sort().reverse();
let descUnica = listToApply.filter((v, i, a) => a.indexOf(v) === i);
Logger.log('Descrição Única: ' + descUnica)
}
It's working, but I'd like to know the rooms for improvement here.
Thanks.

Concatenating data from Kendo Datasources in to a new array

I'm trying to concat the selected items from 2 grids in to an array for further processing but I don't want to affect any change in either data source and this is proving problematic as the first data source seems to (after the concat) contain the items I pull from the first ...
var allItems = JSLINQ(grid1.data("kendoGrid").dataSource.data())
.Concat(grid2.data("kendoGrid").dataSource.data())
.ToArray();
the source code for the concat function in JSLINQ is doing this ...
Concat: function (array) {
var arr = array.items || array;
return new JSLINQ(this.items.concat(arr));
}
this.items is from what I can tell the value of "grid1.data("kendoGrid").dataSource.data()"
and i'm trying to build a new array with the items in "grid2.data("kendoGrid").dataSource.data()" which I then intend to filter based on selection criteria.
does anyone have any experience with this / a means to say "I want a copy of the data item from the source that's not connected to the source"?
UPDATE:
The base functionality here relies on having a standard JS array, it seems that kendo returns an observable array object (specific to kendo, and missing the concat function).
The implementation above results in an exception on the concat call (because it doesn't exist), so I rewrote the function to something like this ...
Concat: function (array) {
//var arr = array.items || array;
//return new JSLINQ(this.items.concat(arr));
var retVal = new Array();
for (var i = 0; i < this.items.length; i++) {
var clone = JSON.parse(JSON.stringify(this.items[i]));
retVal.push(clone);
}
for (var i = 0; i < array.length; i++) {
var clone = JSON.parse(JSON.stringify(array[i]));
this.items.push(clone);
}
return new JSLINQ(retVal);
},
That results in the duplicate problem I mentioned above.
So it seems that the error I have here is something to do with observable array, but I don't know how to get a "detatched item" / "array" from the data source.
Ok so it turns out the toJSON() method on an observable array turns the observable array in to an array (odd naming, but hey this is kendo right!)
In short, by manipulating my call input to the JSLINQ method to include this I then have normal JS behaviour as I would expect ...
var allItems = JSLINQ(grid1.data("kendoGrid").dataSource.data().toJSON())
.Concat(grid2.data("kendoGrid").dataSource.data().toJSON())
.ToArray();

Sort Google Spreadsheet With Multiple Criteria Using Script

I have a spreadsheet that I update on a regular basis. I also have to re-sort the spreadsheet when finished because of the changes made. I need to sort with multiple criteria like the below settings. I have searched for examples but my Google search skills have failed me.
Sort range from A1:E59
[x] Data has header rows
sort by "Priority" A > Z
then by "Open" Z > A
then by "Project" A > Z
Mogsdad's answer works fine if none of your cells have values automatically calculated via a formula. If you do use formulas, though, then that solution will erase all of them and replace them with static values. And even so, it is more complicated than it needs to be, as there's now a built-in method for sorting based on multiple columns. Try this instead:
function onEdit(e) {
var priorityCol = 1;
var openCol = 2;
var projectCol = 3;
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getDataRange();
dataRange.sort([
{column: priorityCol, ascending: true},
{column: openCol, ascending: false},
{column: projectCol, ascending: true}
]);
}
Instead of making a separate function, you can use the built-in onEdit() function, and your data will automatically sort itself when you change any of the values. The sort() function accepts an array of criteria, which it applies one after the other, in order.
Note that with this solution, the first column in your spreadsheet is column 1, whereas if you're doing direct array accesses like in Mogsdad's answer, the first column is column 0. So your numbers will be different.
That is a nice specification, a great place to start!
Remember that Google Apps Script is, to a large extent, JavaScript. If you extend your searching into JavaScript solutions, you'll find plenty of examples of array sorts here on SO.
As it happens, much of what you need is in Script to copy and sort form submission data. You don't need the trigger part, but the approach to sorting can be easily adapted to handle multiple columns.
The workhorse here is the comparison function-parameter, which is used by the JavaScript Array.sort() method. It works through the three columns you've indicated, with ascending or descending comparisons. The comparisons used here are OK for Strings, Numbers and Dates. It could be improved with some cleaning up, or even generalized, but it should be pretty fast as-is.
function sortMySheet() {
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sourceSheet.getDataRange();
var data = dataRange.getValues();
var headers = data.splice(0,1)[0]; // remove headers from data
data.sort(compare); // Sort 2d array
data.splice(0,0,headers); // replace headers
// Replace with sorted values
dataRange.setValues(data);
};
// Comparison function for sorting two rows
// Returns -1 if 'a' comes before 'b',
// +1 if 'b' before 'a',
// 0 if they match.
function compare(a,b) {
var priorityCol = 0; // Column containing "Priority", 0 is A
var openCol = 1;
var projectCol = 2;
// First, compare "Priority" A > Z
var result = (a[priorityCol] > b[priorityCol] ) ?
(a[priorityCol] < b[priorityCol] ? -1 : 0) : 1;
if (result == 0) {
// "Priority" matched. Then compare "Open" Z > A
result = (b[openCol] > a[openCol] ) ?
(b[openCol] < a[openCol] ? -1 : 0) : 1;
}
if (result == 0) {
// "Open" matched. Finally, compare "Project" A > Z
result = (a[projectCol] > b[projectCol] ) ?
(a[projectCol] < b[projectCol] ? -1 : 0) : 1;
}
return result;
}
Try this using the Apps Script sort instead of the native JavaScript. I had the same issue with sorting the header row(s) and this solved the issue.
So I think something like this should work:
function onOpen() {
SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("Form Responses 1").sort(2);
}
Regarding sorting by multiple columns, you can chain that sort() method, with the final sort() having the highest priority, and the first sort() the lowest. So something like this should sort by Start date, then by End date:
function onOpen() {
SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("Form Responses 1").sort(3).sort(2);
}
Reference link:-
https://support.google.com/docs/thread/16556745/google-spreadsheet-script-how-to-sort-a-range-of-data?hl=en
Not sure if this is still relevant, but you can use the sort() function to define another tab as a sorted version of the original data.
Say your original data is in a tab named Sheet1; I'm also going to act as though your Priority, Open, and Project columns are A, B, and C, respectively.
Create a new tab, and in cell A1 type:
=sort(Sheet1!A1:E59, 1, TRUE, 2, FALSE, 3, TRUE)
The first argument specifies the sheet and range to be sorted, followed by three pairs: the first of each pair specifies the column (A=1, B=2, etc.), and the second specifies ascending (TRUE) or descending (FALSE).

How to populate the table dynamically and correctly with Ajax?

I have a form where the user submits a query and then have a Servlet that processes this query and returns the results in XML. With this result trying to populate a table dynamically via Ajax, for such, I use the following code below.
var thead = $("<thead>");
var rowsTHead = $("<tr>");
var tbody = $("<tbody>");
var numberOfColumns;
$(xml).find("head").each(function(){
var variable = $(this).find("variable");
numberOfColumns = variable.length;
for (var i = 0; i < variable.length; i++){
var name = $(variable[i]).attr("name");
rowsTHead.append($("<th>").html(name));
}
});
thead.append(rowsTHead);
$(xml).find("result").each(function(){
var literal = $(this).find("literal");
var rowsTBody = $("<tr class=\"even\">");
literal.length = numberOfColumns;
for (var j = 0; j < literal.length; j++){
var tdBody = $("<td>");
tdBody.html($(literal[j]).text());
rowsTBody.append(tdBody);
}
tbody.append(rowsTBody);
});
$(".tablesorter").empty()
.append(thead)
.append(tbody);
This code works perfectly until it was used in a UNION query. When using a UNION the returned xml comes in the following way http://pastebin.com/y7hXK1Zy
As can be observed, this query has 4 variables that are: gn1, indication1, gn2, indication2.
What is going wrong is that the values of all the variables being written in columns corresponding to gn1 and indication1.
What I wish I was to write the value of each variable in its corresponding column. I wonder what should I change in my code to make this possible.
You need to respect the name values of the binding elements, and relate them back to the columns that you correctly built from parsing the element. When you are doing the find "literal", you are skipping the parsing of the binding elements. You should find "binding", respect the name and look up which column to use based on that, and then for each of those, find the "literal" elements for the actual values.

Get all rows not filtered from jqGrid

I have local data in a grid. How can I get all of the rows or IDs that are not removed after a user uses the filter toolbar? I need to get all filtered rows, regardless of pagination.
For example, say I begin with 50 rows in the grid. The user uses the filter toolbar and the set of rows decreases to 10 rows. How can I get those ten rows?
There are no direct way to get the information which you need. Internally jqGrid uses $.jgrid.from to filter local data. The main code which uses $.jgrid.from in inside of addLocalData. To get results which you need without studying all the code I suggest to use the fact that all filtered data will be returned by select method of $.jgrid.from (see the line of code). My suggestion is to catch the data before the data will be cut to the page size.
To do this I suggest to use sub-classing: overwriting of the method select method of $.jgrid.from. I demonstrate the technique in the examples created for the answer and this one.
In your case the code will be
var oldFrom = $.jgrid.from,
lastSelected;
$.jgrid.from = function (source, initalQuery) {
var result = oldFrom.call(this, source, initalQuery),
old_select = result.select;
result.select = function (f) {
lastSelected = old_select.call(this, f);
return lastSelected;
};
return result;
};
Now the variable lastSelected will save the array of elements which are results of the last sorting or filtering operation. Because $.jgrid.from is global the data are not connected to the grid. If you have more as one grid on the page it will be uncomfortable. One can fix the small disadvantage with the following line in the code of loadComplate of every grid:
loadComplete: function () {
this.p.lastSelected = lastSelected; // set this.p.lastSelected
}
In the way we introduce new jqGrid parameter lastSelected which will have close structure as data parameter, but will hold only last filtered data.
The following code will display the ids of filtered data in alert message
$("#getIds").click(function () {
var filteredData = $grid.jqGrid('getGridParam', 'lastSelected'), i, n, ids = [],
idName = $grid.jqGrid('getGridParam', 'localReader').id;
if (filteredData) {
for (i = 0, n = filteredData.length; i < n; i++) {
ids.push(filteredData[i][idName]);
}
alert("tolal number of filtered data: " + n + "\n" +
"ids of filtered data:\n" + ids.join(', '));
}
});
I used localReader.id parameter because property name used for local data are typically id or _id_. The _id_ will be used in case of data loaded from the server if one uses loadonce: true option.
The demo demonstrate the approach. If one filter for example only the data from FedEx and then clicks on "Show Ids" button one will see information about all filtered and not only about the data displayed on the current page:
UPDATED: free jqGrid provides new lastSelectedData option. See the demo in the list of demos.
You colud use afterSearch option of the search toolbar:
var filteredIDs = new Array(); //Global variable
$("#"+gridId).jqGrid("filterToolbar", { stringResult:true, searchOnEnter:false,
afterSearch:function(){
filteredIDs = $("#"+gridId).getDataIDs();
}
});
If you want to get the filtered rows instead the filtered IDs, use getRowData() instead of getDataIDs().
All, I found another answer which is far easier to include
loadComplete: function (gridData) {
var isSearchPerformed = $grid.getGridParam("postData")._search;
if (isSearchPerformed) {
$("#spanFilterTotal").text(gridData.records);
}
All you want is below:
$.each($grid.getRowData(), function( index, value ) {
a.push(value["COLUMN_NAME"]); //Get the selected data you want
});

Resources