Compare two arrays in Apps Script not working - javascript

I´m a newbie to apps script, I want to do a price comparison script, I got two columns one column gets data via blockspring addon and import.io, this provide info about a website, one column name of the product and the other the price of this product. I want some way to notice me when price change, so I create a script that copy the values of this two columns , store them , and when i trigger it , it get the data from both columns and compare it , now for the test I have a simple alter box that tells me if some data changes.
For now , I was able to wrote a script that get the values and compare it , but for some unknown reason it does not work.
This is my script. Maybe in the future I would like to alert what data in particular changes, but this is my first script son I try to take it easy.
This is the spreadsheet I´m working with
https://docs.google.com/spreadsheets/d/1HleYu-dCbUReOH-7SnEFipJa6E9wEcEzasQ7ns2tXso/edit?usp=sharing
function moveValuesOnly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source1 = ss.getRange("A15:A105");
var source2 = ss.getRange("B15:B105");
var source3 = ss.getRange("A16:A105").getValues();
var source4 = ss.getRange("E16:E105").getValues();
var source5 = ss.getRange("E15:E105");
if (source5.isBlank()) {
source1.copyTo(ss.getRange("E15:E105"), {contentsOnly: true});
source2.copyTo(ss.getRange("F15:F105"), {contentsOnly: true});
Browser.msgBox('VALUES COPIED !', Browser.Buttons.OK);
}
if (source3 == source4) {
Browser.msgBox('NOTHING CHANGES !', Browser.Buttons.OK);
}
else {
Browser.msgBox('SOMETHING CHANGES!', Browser.Buttons.OK);
}
}

It is not possible to compare arrays by == method. You need to traverse through each and every element and do a comparison.
var Inspector = false;
for(var i=0;i<source3.length;i++)
{
if(source3[i][0].toString() != source4[i][0].toString())
{
Inspector = true;
}
}
if(Inspector)
{
Browser.msgBox('SOMETHING CHANGES!', Browser.Buttons.OK);
}
else
{
Browser.msgBox('NOTHING CHANGES !', Browser.Buttons.OK);
}

You can't compare ranges like that because they are 2-dimensional arrays. I modified your code and included a function to compare 2 columns. It won't work for ranges with more columns but it's easy to enhance it if you need it.
function moveValuesOnly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source1 = ss.getRange("A15:A105");
var source2 = ss.getRange("B15:B105");
var source3 = ss.getRange("A16:A105").getValues();
var source4 = ss.getRange("E16:E105").getValues();
var source5 = ss.getRange("E15:E105");
if (source5.isBlank()) {
source1.copyTo(ss.getRange("E15:E105"), {contentsOnly: true});
source2.copyTo(ss.getRange("F15:F105"), {contentsOnly: true});
Browser.msgBox('VALUES COPIED !', Browser.Buttons.OK);
}
function equalColumns(col1, col2) {
if (col1.length !== col2.length) return false;
var equal = true;
for (var i = 0; i < col1.length; i ++) {
if (col1[i][0] !== col2[i][0]) {
equal = false;
break;
}
};
return equal;
}
if (equalColumns(source3,source4)) {
Browser.msgBox('NOTHING CHANGES !', Browser.Buttons.OK);
}
else {
Browser.msgBox('SOMETHING CHANGES!', Browser.Buttons.OK);
}
}

Related

JavaScript - Issues recovering a map in an object after being saved in localStorage

I've been dealing with this for some time. I've a list of sections in which the user checks some checkboxes and that is sent to the server via AJAX. However, since the user can return to previous sections, I'm using some objects of mine to store some things the user has done (if he/she already finished working in that section, which checkboxes checked, etc). I'm doing this to not overload the database and only send new requests to store information if the user effectively changes a previous checkbox, not if he just starts clicking "Save" randomly. I'm using objects to see the sections of the page, and storing the previous state of the checkboxes in a Map. Here's my "supervisor":
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
var children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children().length;
for (var i = 0; i < children; i++) {
if (i % 2 == 0) {
var checkbox = $("#ContentPlaceHolder1_checkboxes_div_" + id).children()[i];
var idCheck = checkbox.id.split("_")[2];
this.selections.set(idCheck, false);
}
}
console.log("Length " + this.selections.size);
this.change = false;
}
The console.log gives me the expected output, so I assume my Map is created and initialized correctly. Since the session of the user can expire before he finishes his work, or he can close his browser by accident, I'm storing this object using local storage, so I can change the page accordingly to what he has done should anything happen. Here are my functions:
function setObj(id, supervisor) {
localStorage.setItem(id, JSON.stringify(supervisor));
}
function getObj(key) {
var supervisor = JSON.parse(localStorage.getItem(key));
return supervisor;
}
So, I'm trying to add to the record whenever an user clicks in a checkbox. And this is where the problem happens. Here's the function:
function checkboxClicked(idCbx) {
var idSection = $("#ContentPlaceHolder1_hdnActualField").val();
var supervisor = getObj(idSection);
console.log(typeof (supervisor)); //Returns object, everythings fine
console.log(typeof (supervisor.change)); //Returns boolean
supervisor.change = true;
var idCheck = idCbx.split("_")[2]; //I just want a part of the name
console.log(typeof(supervisor.selections)); //Prints object
console.log("Length " + supervisor.selections.size); //Undefined!
supervisor.selections.set(idCheck, true); //Error! Note: The true is just for testing purposes
setObj(idSection, supervisor);
}
What am I doing wrong? Thanks!
Please look at this example, I removed the jquery id discovery for clarity. You'll need to adapt this to meet your needs but it should get you mostly there.
const mapToJSON = (map) => [...map];
const mapFromJSON = (json) => new Map(json);
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
this.change = false;
this.selections.set('blah', 'hello');
}
Supervisor.from = function (data) {
const id = data.id;
const supervisor = new Supervisor(id);
supervisor.verif = data.verif;
supervisor.selections = new Map(data.selections);
return supervisor;
};
Supervisor.prototype.toJSON = function() {
return {
id: this.id,
verif: this.verif,
selections: mapToJSON(this.selections)
}
}
const expected = new Supervisor(1);
console.log(expected);
const json = JSON.stringify(expected);
const actual = Supervisor.from(JSON.parse(json));
console.log(actual);
If you cant use the spread operation in 'mapToJSON' you could loop and push.
const mapToJSON = (map) => {
const result = [];
for (let entry of map.entries()) {
result.push(entry);
}
return result;
}
Really the only thing id change is have the constructor do less, just accept values, assign with minimal fiddling, and have a factory query the dom and populate the constructor with values. Maybe something like fromDOM() or something. This will make Supervisor more flexible and easier to test.
function Supervisor(options) {
this.id = options.id;
this.verif = null;
this.selections = options.selections || new Map();
this.change = false;
}
Supervisor.fromDOM = function(id) {
const selections = new Map();
const children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children();
for (var i = 0; i < children.length; i++) {
if (i % 2 == 0) {
var checkbox = children[i];
var idCheck = checkbox.id.split("_")[2];
selections.set(idCheck, false);
}
}
return new Supervisor({ id: id, selections: selections });
};
console.log(Supervisor.fromDOM(2));
You can keep going and have another method that tries to parse a Supervisor from localStorageand default to the dom based factory if the localStorage one returns null.

Workaround for new Edge Indexeddb bug?

Microsoft released update kb4088776 in the past couple days, which has had a devastating effect on performance of indexedDb openCursor.
The simple fiddle here shows the problem. With the update, the "retrieval" time is 40 seconds or more. Prior to the update, it is around 1 second.
https://jsfiddle.net/L7q55ad6/23/
Relevant retrieval portion is here:
var _currentVer = 1;
function _openDatabase(fnSuccess) {
var _custDb = window.indexedDB.open("MyDatabase", _currentVer);
_custDb.onsuccess = function (event) {
var db = event.target.result;
fnSuccess(db);
}
_custDb.onerror = function (event) {
_custDb = null;
fnSuccess(null); // should use localData
}
_custDb.onupgradeneeded = function (event) {
var db = event.target.result;
var txn = event.target.transaction;
// Create an objectStore for this database
if (event.oldVersion < _currentVer) {
var customer = db.createObjectStore("customer", { keyPath: "guid" });
var index = customer.createIndex("by_id", "id", { unique: false });
}
};
}
function _retrieveCustomers(fn) {
_openDatabase(function (db) {
if (db == null)
{
alert("not supported");
return;
}
var customers = [];
var transaction = db.transaction("customer", "readonly");
var objectStore = transaction.objectStore("customer");
if (typeof objectStore.getAll === 'function') {
console.log("using getAll");
objectStore.getAll().onsuccess = function (event) {
fn(event.target.result);
};
}
else {
console.log("using openCursor");
objectStore.openCursor().onsuccess = function (event) {
var cursor = event.target.result;
if (cursor) {
customers.push(cursor.value);
cursor.continue();
}
else {
fn(customers);
}
};
}
});
}
The time to create and add the customers is basically normal, only the retrieval is bad. Edge has never supported the getAll method and it still doesn't after the update.
The only workaround I can think of would be to use localStorage instead, but unfortunately our data set is too large to fit into the 10MB limit. It is actually faster now to retrieve from our servers and convert the text to javascript objects, defeating the main purpose of indexeddb.
I don't have Edge so I can't test this, but does it happen with get too, or just openCursor? If get still performs well, you could store an index (in your example, the list of primary keys; in your real app, maybe something more complicated) in localStorage, and then use that to call get on each one.

Persist cookie form values across multiple pages

The setting: I have a form that captures$thisSearch.val(), saves it as a cookie, and pushes to an array. The form is an overlay triggered from a menu item in the header so this can appear on any page in the site.
The issue is that it only seems to save/persist the input values on/from that page it was entered on. I'm trying to collect all these values into one list.items() array that can be entered anywhere in the site.
I've tried pushing the string to the array myself instead of the add function and moved the dom around for the search form.
I can update question when I know what to specifically ask. Any pointers / concepts I should be aware of for this would be great.
var cookieList = function(cookieName) {
var cookie = $.cookie(cookieName);
var items = cookie ? cookie.split(/,/) : new Array();
return {
"add": function(val) {
items.push(val);
$.cookie(cookieName, items.join(','));
},
"items": function() {
return items;
}
}
}
var list = new cookieList("PreviousSearches");
$searchSubmit.on('click', function() {
var $thisSearch = $(this).prev().find($searchInput);
if( $thisSearch.val() == '' ) {
alert('Please enter a search term');
console.log( list );
return false;
} else {
searchTerm = $thisSearch.val()
list.add( searchTerm );
}
});
var searchTerms = list.items();
var total = searchTermsFiltered;
var searchTermsFiltered = searchTerms.filter(Boolean).slice( - 5 ).reverse();
var searchtermClean = searchTermsFiltered.join();
$.each($(searchTermsFiltered), function(i,v){
if (!window.location.origin)
window.location.origin = window.location.protocol+"//"+window.location.host;
var lastURLRaw = window.location.origin+'/bch/?s='+v;
var lastURL = lastURLRaw.replace(/ /g, '+');
listItem = '<li>'+v+'</li>';
$('.tags, .search-tags').append(listItem );
});
I found the path specifier from jquerys $.cookie in this top answer below.
$.cookie(cookieName, items.join(',') , { path: '/' }); from my code.
why are my jquery cookies not available across multiple pages?

Access js array in another js file

I fill my array in the checklistRequest.js and I want to access it in my Termine_1s.html file which contains js code. I can access it but when I want to iterate through it, it gives me only single digits instead of the strings.
How can I solve this?
checklistRequest.js
//Calls the checkbox values
function alertFunction()
{
//Retrieve the object from storage
var retrievedObject = localStorage.getItem('checkboxArray');
console.log('retrievedObject: ', JSON.parse(retrievedObject));
return retrievedObject;
}
Termine_1s.html
//Checks if title was checked already
var checklistRequest = alertFunction();
var titleAccepted = true;
for (var a = 0; a < checklistRequest.length; a++)//Iterates through whole array
{
if(title != checklistRequest[i] && titleAccepted == true)//Stops if false
{
titleAccepted = true;
}
else
{
titleAccepted = false;
}
}
you need to parse the object at some point.
Try:
return JSON.parse(retrievedObject);

Excel Type Filter popup for Jqgrid

I need to have a filter ( like in Excel spread Sheet) to embedded to the 'jquery' dialog popup. in this case i need to show all the unique values in the column and check box just before that value to select to the user. when user pressed filter button i need to filter only the values that user requested through the check boxes.
Can any one please let me any approach that i must follow.
Thanks in advance for your help and valuable time.
I was able to develop basic grid with excel kind of filter feature. any one who will come across this type of requirement can use this answer as a foundation.
I use this answer from 'Oleg' to embed the filter popup screen to the basic 'jqgrid'.
in the jqgrid page declare this array with the attributes (columns) that needs to display the filter screen popup.
var applyFilterColumnNames = ['Id','Type','custId','UserId'];
and the column model should be as follows -
colModel :[
{name:'Id', index:'Id',hidden: true,sortable: true},
{name:'custId', index:'custId', width:140,align:"left",sortable: true,search : false},
{name:'Type', index:'Type', width:120,align:"left",sortable: true,search : false},
{name:'UserId', index:'UserId', width:150,align:"left",sortable: true,search : false},
],
used that reference answer to embed the filter button function.
gr.closest("div.ui-jqgrid-view").find("div.ui-jqgrid-hdiv table.ui-jqgrid-htable tr.ui-jqgrid-labels > th.ui-th-column > div.ui-jqgrid-sortable")
.each(function () {
var idPrefix = "jqgh_" + gr[0].id + "_";
var idIndex = (this.id).substr(idPrefix.length,this.id.length) ;
if(includeInArray(applyFilterColumnNames,idIndex)){
jq('<button id=btn_'+idIndex+'>').css({float: "right", height: "17px"}).appendTo(this).button({
icons: {
primary: "ui-icon-gear"
},
text: false
}).click(function (e) {
var idPrefix = "jqgh_" + gr[0].id + "_";
// thId will be like "jqgh_list_name"
var thId = jq(e.target).closest('div.ui-jqgrid-sortable')[0].id ;
if (thId.substr(0, idPrefix.length) === idPrefix) {
var colName = thId.substr(idPrefix.length);
//alert('Clicked the button in the column "' + colName + '"');
constructFilter(colName);
return false;
}
});
//}
}
});
Below is the script i used to filter the jqgrid according to the filters
//Variables that use in filtering operation
var originalData = null;
var filteredData;
var selectedFilters = new Object();
var chkBoxElement;
var firstSortColumn;
function constructFilter(columnName){
// for the first initiation of the filter populate current grid data to an array
if(originalData == null || originalData == 'null'){
try{
// this array will hold the initail data set of the grid
originalData = gr.jqGrid('getGridParam','data');
// set the first sorting grid column
firstSortColumn = columnName;
// check if column is associated with a formatter. if so format the originalData values accordingly.
formatGridData(columnName);
}catch(e){}
}
var colData = new Array();
var filterDataSet;
// if current column is equal to initial sorted column set all posible values to the check boxes in the
// filter screen to select. ( since this is the master sorting column and other columns will filter according to that)
if(columnName == firstSortColumn){
filterDataSet = originalData;
}else{
// set current grid data set to show as checkboxes in the filter page
filterDataSet = gr.jqGrid('getCol',columnName,false);
}
for(key in filterDataSet){
// check box element object that will hold the checkbox label and its state ( true / false)
chkBoxElement = new Object();
chkBoxElement.id = getValueFromArray(filterDataSet[key],columnName);
if(typeof(chkBoxElement.id)== 'undefined'){
break;
}
// if this id is saved in previous filtering event checked option will set to true.
if(typeof(selectedFilters[columnName]) != 'undefined'){
if (includeInArray(selectedFilters[columnName],chkBoxElement.id)){
chkBoxElement.selected = true;
}else{
chkBoxElement.selected = false;
}
}
colData.push(chkBoxElement);
}
// removing duplicates
var uniqueValues = removeDuplicates(colData);
// sort the array without duplicate with the custom comparator
uniqueValues.sort(sortComparator);
// open the filter screen. return type will captured in the 'seletedElements' variable as pipe separated string
seletedElements = window.showModalDialog(filterUrl,uniqueValues,"dialogWidth:400px;dialogHeight:250px;center:yes;resizable:no;status:no;help:no;");
if(seletedElements != null && seletedElements != 'null'){
// get selected values to the array
selectedFilters[columnName] = seletedElements.split("|");
}else{
//user just close the popup (using close button) will return without doing anything
return;
}
if(columnName == firstSortColumn){
// refine filter with the non filtered data set
refillGrid(seletedElements,columnName,originalData);
}else{
// send current data set to refine
var currentDataSet = gr.jqGrid('getGridParam','data');
refillGrid(seletedElements,columnName,currentDataSet);
}
}
function formatGridData(columnName){
var isFormatter = gr.jqGrid("getColProp",columnName);
if(typeof isFormatter.formatter !== 'undefined') {
if(jq.isFunction( isFormatter.formatter ) ) {
for(key in originalData){
var plainValue = originalData[key][columnName];
var formattedVal = isFormatter.formatter.call(null,plainValue,null,null,null);
originalData[key][columnName] = formattedVal;
}
}
}
}
function resetFilters(){
for(key in applyFilterColumnNames){
jq("#btn_"+applyFilterColumnNames[key]).button("option", {
//icons: { primary: this.checked ? 'ui-icon-check' : 'ui-icon-closethick' }
icons: { primary: 'ui-icon-gear'}
});
}
gr.jqGrid("setCaption",gridCaption);
refreshGrid(originalData);
originalData = null;
firstSortColumn = null;
selectedFilters = new Object();
}
function refillGrid(seletedElements,columnName,filterDataSet){
var filteredData= new Array();
var elementsArray;
try{
elementsArray = seletedElements.split("|");
}catch(e){
// this exception happens when user simply open the filter screen
// do nothing and close it.
trace('Error in filter splitting -'+e);
return;
}
// When user de-select all check boxes from the popup screen
if(elementsArray == ""){
refreshGrid(originalData);
return;
}
// refine the grid data according to the filters
var mydata = filterDataSet;
for(i=0;i<elementsArray.length;i++){
var filterElement = elementsArray[i];
for(j = 0;j<mydata.length;j++){
if(filterElement==getValueFromArray(mydata[j],columnName)){
filteredData.push(mydata[j]);
}
}
}
// change the button icon to indicate that the column is filtered
changeButtonIcon(columnName);
// update the column header to indicate sort by column
changeGridCaption(columnName);
// fill the grid according to the passed array
refreshGrid(filteredData);
}
function changeGridCaption(columnName){
// get columns name array
var columnNames = gr.jqGrid('getGridParam','colNames');
// get column model array
var colModel = gr.jqGrid('getGridParam','colModel');
var colModelIndex=null;
if (firstSortColumn == columnName){
for(key in colModel){
try{
if (colModel[key].name == firstSortColumn){
colModelIndex = key;
break;
}
}catch(e){}
}
if(colModelIndex != null){
var columnName = columnNames[colModelIndex];
gr.jqGrid("setCaption",gridCaption + " - Filtered based on : "+columnName);
}
}
}
function changeButtonIcon(columnName){
//change the button Icon
jq("#btn_"+columnName).button("option", {
//icons: { primary: this.checked ? 'ui-icon-check' : 'ui-icon-closethick' }
icons: { primary: 'ui-icon-link'}
});
}
function getValueFromArray(obj,columnName){
if(obj !=null && typeof(obj)!='undefined'){
// if obj param is string just return it
if(typeof obj =='string'){
return obj;
}else{
return obj[columnName];
}
}
}
function sortComparator(a,b){
try{
var aId = a.id.toLowerCase();
var bId = b.id.toLowerCase();
if (aId < bId) {return 1}
if (aId > bId) {return -1}
}catch(e){
return 0;
}
}
function includeInArray(arr,obj) {
//alert(arr);
return (arr.indexOf(obj) != -1);
}
function refreshGrid(results) {
gr.jqGrid('clearGridData')
.jqGrid('setGridParam', { data: results })
.trigger('reloadGrid');
}
function removeDuplicates(valueArray){
var arr = {};
for ( i=0; i < valueArray.length; i++ ){
if(valueArray[i].id != null){
arr[valueArray[i].id] = valueArray[i];
}
}
valueArray = new Array();
for ( key in arr ){
valueArray.push(arr[key]);
}
return valueArray;
}
If something wrong here please let me know.this solution is working fine. but i really appreciate the comments in therms of performance and code best practices.

Categories

Resources