I have an application where I am adding li elements to the web page. I need to change the class name of the element to "done" inside of local storage when I mark it as "done" on the webpage. (It should say done: true). With my current code I am unintentionally making two items in local storage, one which is done: true and the other which is done: false. I'll show my code here:
function updateDone(e) {
var spanClicked = e.target;
var id = spanClicked.parentElement.id;
var done = spanClicked.parentElement.className;
spanClicked.innerHTML = " ✔ ";
spanClicked.setAttribute("class", "done");
var key = "todos" + id;
for(var i = 0; i < todos.length; i++) {
if(todos[i].id == id) {
var mark = todos[i];
mark.done = true;
console.log(mark);
spanClicked.setAttribute("class", "done");
var newKey = JSON.stringify(mark);
localStorage.setItem(key, newKey);
if(mark.done == false) {
spanClicked.setAttribute("class", "not done");
spanClicked.innerHTML = "not done";
}
}
}
}
They are both labeled with the same id which is how I keep track of each item, yet there are two of them. Also, when i refresh the page there are two list items shown, one which is marked done. My question is how do I prevent another item from being created and instead mark just one item as done in localStorage?
You need a way to uniquely identify each item, so you can ensure your marks are being set on the items you intend and are not overwriting because you might have, say, two items with the same key. Since you are looping through a list, maybe you can change your keys to be composed of two parts.
var parent_key = "todos" + parent_id;
And then, in the loop :
var store_key = parent_key + ":" + i;
...
localStorage.set(store_key,newKey);
This way (so long as the order is going to be consistent), you can separate multiple list elements from the same parent.
As commented, a live example in jsFiddle or something would help better address your requirement.
However if this solution is insufficient you could try the following idea, effectively setting a "table" within localstorage.
var parent_key = "todos" + id;
var parent_table = {};
// for loop
parent_table[i] = newKey;
// end of for loop
localStorage.set(parent_key,parent_table);
So you have a table inside of local storage, to give you finer granularity.
Related
I'm creating a document merge (mail merge) from Google App Maker to a Google Document template. I can do so successfully when merging one single record, but how do you merge several records into the one document?
I have an purchase_orders parent record which has several purchase_order_line_items child records but I can't seem to get all of these records into a single document merge.
A similar question (Document Merge with Google App Maker) was asked by by Johan W with a comprehensive answer by Markus Malessa and Pavel Shkleinik (thank you!). However, it only caters for cases when you are merging one single record.
I have tried to build on their answer by using a second for loop to get the data of all associated child records. The script runs but only seems to merge the first child record; not all of them.
Here is an example of the server-side code I've tried to use:
function Export(key, key2) {
// Get the parent record by its key, which was passed by the first parameter above
var record = app.models.Purchase_Orders.getRecord(key);
// Get the first child record by its key, which was passed by the second parameter above
var childRecord = app.models.Purchase_Order_Line_Items.getRecord(key2);
// Get the Google Document which will be used as a template for this merge
var templateId = '1Xbt8camqHJYrhBnx0a6G2-RvTvybqU0PclHifcdiLLA';
//Set the filename of the new merge document to be created
var filename = 'Document for Customer ' + new Date();
//Make a copy of the template to use as the merge document
var copyFile = DriveApp.getFileById(templateId).makeCopy(filename);
//Get the Google Docs ID of the newly created merge document
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getBody();
// Replace the field names in the template with the field data from the parent record
var fields = app.metadata.models.Purchase_Orders.fields;
for (var i in fields) {
console.log(i);
var text = '<<' + fields[i].name + '>>';
var data = record[fields[i].name];
if (data !== null) {
copyBody.replaceText(text, data);
} else {
// do nothing
}
}
// Replace the field names in the template with the field data from the child records
childFields = app.metadata.models.Purchase_Order_Line_Items.fields;
for (i in childFields) {
console.log(i);
var childtext = '<<' + childFields[i].name + '>>';
var childdata = childRecord[childFields[i].name];
if (childdata !== null) {
copyBody.replaceText(childtext, childdata);
} else {
// do nothing
}
}
}
How can I improve my code so that all associated child records are merged into a single document?
How can I set up my Google Document template to cater for any number of child records?
Rather than passing in the child record key via a second parameter, I would suggest just passing in the parent key and then changing your function as follows:
function Export(key) {
// Get the parent record by its key, which was passed by the first parameter above
var record = app.models.Purchase_Orders.getRecord(key);
// Get the first child record by its key, which was passed by the second parameter above
var childRecords = record.Purchase_Order_Line_Items;
// Get the Google Document which will be used as a template for this merge
var templateId = '1Xbt8camqHJYrhBnx0a6G2-RvTvybqU0PclHifcdiLLA';
//Set the filename of the new merge document to be created
var filename = 'Document for Customer ' + new Date();
//Make a copy of the template to use as the merge document
var copyFile = DriveApp.getFileById(templateId).makeCopy(filename);
//Get the Google Docs ID of the newly created merge document
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getBody();
// Replace the field names in the template with the field data from the parent record
var fields = app.metadata.models.Purchase_Orders.fields;
for (var i in fields) {
console.log(i);
var text = '<<' + fields[i].name + '>>';
var data = record[fields[i].name];
if (data !== null) {
copyBody.replaceText(text, data);
} else {
// do nothing
}
}
// Replace the field names in the template with the field data from the child records
var childFields = app.metadata.models.Purchase_Order_Line_Items.fields;
var table = [];
var tableheader = [];
for (i in childFields) {
console.log(i);
tableheader.push(childFields[i].displayName);
}
table.push(tableheader);
for (i in childRecords) {
var data = [];
for (var j in childFields) {
data.push(childRecords[i][childFields[j].name]);
}
table.push(data);
}
copyBody.appendTable(table);
The table building is based on a 2D array and the documentation is here https://developers.google.com/apps-script/reference/document/table. But you will also need to remove your prebuilt table in favor of just appending a table instead. This way you are not dependent on the quantity of child records being fixed like they currently are in your document template. Also, the variable for childRecords may or may not work, I have not tested this since I am unsure if prefetch works in conjunction with .getRecord(key). This may require some additional testing but hopefully this will provide enough guidance.
Thought I would add this as an alternative. Lets say you keep your table but remove all the rows with exception for the header row then you could still use DocumentApp service to add your rows to the table like so:
var tableheaderfieldnames = ['Quantity_for_PO', 'Inventory_Item.id', 'Unit_Price']; //set a fixed table header with the field names, uncertain if the table header for the related inventory item will work or not
var table = copyBody.getTables()[0];
for (i in childRecords) {
var row = table.appendRow();
for (var j in tableheaderfieldnames) {
row.appendTableCell(childRecords[i][tableheaderfieldnames[j]]);
}
}
Keep in mind that AM does not allow you to use FK references, so for your inventory item that appears to use a fk field you may need to tinker around with setting the proper name reference for when you are trying to fill in the item in your table.
I'm trying to build 'wishlist' functionality. When you click a button, it saves the name and link for the particular 'property' in an array in local storage. I will then output this on the page for the user.
So far it works, my only problem is that it will always insert the property name and link, even if it's already present in the array. I need to create a check, to see if it's already there, and only push it if it can't be found.
Here's a JSFiddle that works. Click the "save" buttons and check localStorage and you will see that the data is added. But click the same button again and you'll see it's added, again.
https://jsfiddle.net/g9kkx3fh/3/
Here's the basic code. It grabs the property name and link from the closest clicked button, it pulls data back from the array in localStorage, then uses .push to add the new data, then re-stringifys it and adds it back to localStorage.
var name = $(this).closest('.text-long').find('.intro-text h4').text();
var permalink = $(this).closest('.text-long').find('.button-slot a').attr('href');
var property = [name, permalink]; // create an array of the name + permalink
var wishlist = JSON.parse(localStorage.getItem("wishlist")); // get the wishlist from storage
wishlist.push(property); // append the new array into the wishlist from storage
localStorage.setItem('wishlist', JSON.stringify(wishlist)); // put the wishlist back in storage
I knew I needed to iterate over the array, being multi-dimensional, and look for the same name var. So I started with this:
for (var i = 0; i < wishlist.length; i++) {
var isPresent = ($.inArray(name, wishlist[i]));
}
if (isPresent == -1){
wishlist.push(property);
localStorage.setItem('wishlist', JSON.stringify(property));
}
Here's the problem with this. If the localStorage var wishlist is empty, then its length is 0. Therefore the for loop never works, because i < wishlist.length is never true, because wishlist is always 0.
So how do I fix this? I'm never able to add anything to the array, because I can never get the value of isPresent, because my for loop never works.
Here's a JSFiddle for the broken code, but with the for loop and if statement added:
https://jsfiddle.net/bdxa0sgz/1/
I've also tried the following:
if (isPresent == -1 || wishlist.length == 0){
...
}
So that if the wishlist is empty, it'll still run. However this seems to jumble together the name data and overwrite the array. I'm very confused.
https://jsfiddle.net/bdxa0sgz/5/
Before you push the property array to the wishlist array you have to check if wishlist contains it right. So you might simply do it like this. Let's invent a generic Array.prototype.compare() method. However since this is going to be a generic method it also takes care of the possibility of array elements being objects. So we have an Object.prototype.compare() too (in this case we don't need it but it's good to know) So here is the code;
Object.prototype.compare = function(o){
var ok = Object.keys(this);
return typeof o === "object" && ok.length === Object.keys(o).length ? ok.every(k => this[k] === o[k]) : false;
};
Array.prototype.compare = function(a){
return this.every((e,i) => typeof a[i] === "object" ? a[i].compare(e) : a[i] === e);
};
var button1 = ["29 Melton Road32 York Road","http://localhost:8888/test-properties/properties/29-melton-road/"],
button2 = ["32 York Road","http://localhost:8888/test-properties/properties/32-york-road/"],
wishlist = [],
push2wl = btn => wishlist.some(a => a.compare(btn)) ? wishlist : wishlist.concat([btn]); // if exists return wishlist untouched
wishlist = push2wl(button1);
wishlist = push2wl(button2);
wishlist = push2wl(button1); // this won't get inserted
wishlist = push2wl(button2); // this won't get inserted
console.log(wishlist);
I use arrow functions but you may replace them with their conventional counterparts if you want to see your code work on Safari 9 or IE.
Use Map (key, value) pair to store your properties so that you can check using
myMap.get(key)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get
Here, have a look at this detailed article: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map
For your example,
var myMap = new Map();
$('body').on('click', '.icon.heart button', function(e) {
var name = $(this).closest('.text-long').find('.intro-text h4').text();
var permalink = $(this).closest('.text-long').find('.button-slot a').attr('href');
var property = [name, permalink];
console.log(property);
if (myMap.get(name) === null) {
var wishlist = JSON.parse(localStorage.getItem("wishlist"));
console.log('Wishlist from localStorage = ' + wishlist);
wishlist.push(property);
console.log(wishlist);
localStorage.setItem('wishlist', JSON.stringify(wishlist));
myMap.set(name, permalink);
}
});
You can also iterate through the map to get all the properties like this:
var mapIter = myMap[Symbol.iterator]();
console.log(mapIter.next().value);
I hope this works for you.
I have the following javascript code that does not work as I would expect it to. I have a list of checkboxes of which two of the items are "TestDuration" and "AssessmentScores". I'm trying to iterate through the list (which works fine) and have it add the values that are checked to the array.
var SAIndex = 0;
var SSIndex = 0;
var ScoresIndex = 0;
var SubAssessments = [];
var SubAssessmentScores = [];
//Get to the container element
var SSList = document.getElementById("islSubAssessmentScore_container");
//turn it into an array of the checkbox inputs
SSList = SSList.getElementsByTagName("input");
//create a temporary object to store my values
var tempPair = new Object();
//iterate through the checkbox lists
for(var i = 1; i < SSList.length;i++)
{
//if the value is checked add it to the array
if (SSList[i].checked)
{
var P = SubAssessments[SAIndex];
var V = SSList[i].value;
//tempPair.Parent = SubAssessments[SAIndex];
tempPair.Parent = P;
//tempPair.Value = SSList[i].value;
tempPair.Value = V;
//show me the values as they exist on the page
alert(tempPair.Parent + "|" + tempPair.Value);
SubAssessmentScores.push(tempPair);
//show me the values I just added to the array
alert(SubAssessmentScores.length-1 + "|" + SubAssessmentScores[SubAssessmentScores.length-1].Parent + "|" + SubAssessmentScores[SubAssessmentScores.length-1].Value);
//uncheck the values so when I refresh that section of the page the list is empty
SSList[i].checked = false;
}
}
//output the list of objects I just created
for (i = 0;i < SubAssessmentScores.length;i++)
alert(i + "|" + SubAssessmentScores[i].Parent + "|" + SubAssessmentScores[i].Value)
Now what happens is that when I iterate through the list I get the following alerts:
-first pass-
StudentID|TestDuration
0|StudentID|TestDuration
-second pass-
StudentID|AssessmentScores
1|StudentID|AssessmentScores
This is what I expect to output... However at the end of the code snippet when it runs the for loops to spit out all the values I get the following alerts...
0|StudentID|AssessmentScores
1|StudentID|AssessmentScores
I can't for the life of me figure out why it's replacing the first value with the second value. I thought it might be using a reference variable which is why I added in the P and V variables to try to get around that if that was the case, but the results are the same.
This is because you are adding the same variable every iteration of the loop.
Try changing your push like this:
SubAssessmentScores.push({
Parent: P,
Value: V
});
That said, I recommend you study a little more javascript and conventions in the language, for example your variable naming is frowned upon because you should only use capital letters on the beginning of a name for constructor functions.
A good book is Javascript the good parts by Douglas Crockford.
In my Notes Database, I perform an audit when the document is saved. Pretty easy in LotusScript. I grab the original document (oDoc) from the server, then in the document I modified (mDoc), I do a Forall loop that gets the names of each item; forall item in mDoc.items. Grab the same item from oDoc, execute a function with the new item as an argument that will run down a case statement that will see if its a field we care about. if so, I update a set of list values in the document with "When", "Who", "What field", and the "New Value".
I'm doing this in a server side script. In trying this, I discovered a couple of interesting things;
currentDocument is the NotesXSPDocument that contains everything that was just changed.
currentDocument.getDocument() contains the pre-change values. It also returns a NotesDocument which has the "items" field that I can run through.
Thing is, I need something similar in the NotesXSPDocument. Is there a way in an iterative loop to grab the names and values of all items from there?
Here's the broken code. (Currently it's walking through the NotesDocument items, but those are the old values. I'd rather walk down the XSP document items)
function FInvoice_beginAudit() {
var original_doc:NotesDocument = currentDocument.getDocument();
var oItem:NotesItem;
var oItems:java.util.Vector = original_doc.getItems();
var iterator = oItems.iterator();
while (iterator.hasNext()) {
var oItem:NotesItem = iterator.next();
item = currentDocument.getItemValue(oItem.getName());
if (oItem == undefined) {
var MasterItem = ScreenAudit(doc,item,True)
if (MasterItem) { return true }
} else {
if (item.getValueString() != oItem.getValueString()) {
var MasterItem = ScreenAudit(doc,Item,True);
if (MasterItem) { return true }
}
}
}
}
You can get both versions of a document after submit - the original and the one with changed/new values:
original: var original_doc:NotesDocument = currentDocument.getDocument();
changed: var changed_doc:NotesDocument = currentDocument.getDocument(true);
This way you can compare the items for changes.
But, there is a pitfall: after assigning "changed_doc" to currentDocument.getDocument(true) the "original_doc" has the changed values too because both variables point to the same document. That's why we have to copy all items from currentDocument.getDocument() to a new temporary document first and only after get the changed values with currentDocument.getDocument(true). As an alternative you could read the original document from server like you do in LotusScript.
This is a code for detecting changed items as a starting point:
var original_doc:NotesDocument = database.createDocument();
currentDocument.getDocument().copyAllItems(original_doc, true);
var changed_doc:NotesDocument = currentDocument.getDocument(true);
var oItems:java.util.Vector = original_doc.getItems();
var iterator = oItems.iterator();
while (iterator.hasNext()) {
var oItem:NotesItem = iterator.next();
var itemName = oItem.getName();
var cItem:NotesItem = changed_doc.getFirstItem(itemName);
if (cItem.getText() !== oItem.getText()) {
print("changed: " + itemName);
}
oItem.recycle();
cItem.recycle();
}
original_doc.remove(true);
original_doc.recycle();
this is my first post in stackoverflow.. I am trying to iterate over an object(my implementation is an associative array) which in turn has some properties. Now I wish to construct another array out of it in order to use it as a localsource in jquery autocomplete widget for seach operations. Now the problem is that i am using for in loop to that according to the documenations available... However the output is always one less than the original object. The itearation involving the last element is not performed at all. Below is the sample object that I am using as input.
SubTeachPair = object{"5CS1":{SubAbbrev:"CA-L",SubCode:"5CS1",SubName:"Computer Architecture",TeacherId:"1",TeacherName:"Ayush Pandey",label:"Computer Architecture",value:"5CS1"},"5CS2":{SubAbbrev:"CA-P",SubCode:"5CS2",SubName:"Computer Engg",TeacherId:"10",TeacherName:"MAyush Pandey",label:"Computer Engg",value:"5CS2"}}
It has this kind of elements and is dynamically generated so the property names are variable. The loop construct that I have written is
var SubSource = [];
console.log(SubTeachPair);
var count = 0;
for(sub in SubTeachPair){
console.log(count);
SubSource[count] = {};
SubSource[count]['label']=SubTeachPair[sub]['label'];
SubSource[count]['value']=SubTeachPair[sub]['value'];
count++;
}
However, the result for the given input is only:
object{{ label: "Computer Architecture", value: "5CS1"}}
Am I missing something here?
edit-- The function that produces the input object is as follows(It is triggered onclick by the next button).
$('#' + $(this).attr("id")).autocomplete({
source : 'search',
minLength : 1,
change : function(event, ui) {
if( typeof ui.item != 'undefined') {
SubTeachPair[$(this).attr("id")] = {};
// console.log(ui.item);
SubTeachPair[$(this).attr("id")]['value'] = $(this).attr("id");
SubTeachPair[$(this).attr("id")]['label'] = $('label[for="' + this.id + '"]').html();
SubTeachPair[$(this).attr("id")]['SubCode'] = $(this).attr("id");
SubTeachPair[$(this).attr("id")]['SubName'] =$('label[for="' + this.id + '"]').html();
SubTeachPair[$(this).attr("id")]['SubAbbrev'] =$('label[for="' + this.id + '"]').attr('id');
SubTeachPair[$(this).attr("id")]['TeacherId'] = ui.item.id;
SubTeachPair[$(this).attr("id")]['TeacherName'] = ui.item.value;
// console.log(SubTeachPair);
//window.SubTeachPair = SubTeachPair;
}
}
});
I think I have found the cause of the error -- the object that is the input is actually the out put of another form that uses jquery autocomplete . Now when I enter something in the input and then click on the suggestion, the suggestion is filled in the text input, however if i do not click outside the input text and directly click the button which triggers my script, I get that error. Otherwise its fine. Is there any way to avoid that?
In your code, the array SubSource and count are not defined, You have to declare:
var SubSource = [];
var count = 0`
before for(sub in SubTeachPair) {...}
See http://jsfiddle.net/abu5C/
Try this:
SubSource[count] = {};
for(sub in SubTeachPair) {
console.log(count);
SubSource[count]['label']=SubTeachPair[sub]['label'];
SubSource[count]['value']=SubTeachPair[sub]['value'];
count++;
}