I have a function that creates a table with cities and weather conditions in those cities. It checks cityArr and if the city that came from request is not there, it creates a new table row and adds data to it. Otherwise it finds the row with the city and replaces it with fresh data.
I am looking for a way to keep the same functionality but somehow avoid using an array and check for existing cities some other way.
const cityArr = []; // don't like using array
export function makeTable() {
const storageCity = localStorage.getItem('city');
if (cityArr.includes(storageCity)) {
tbody.deleteRow(cityArr.indexOf(storageCity));
makeRowsandCells(cityArr.indexOf(storageCity), " UPDATE");
} else {
makeRowsandCells();
cityArr.push(storageCity);
}
if (tbody.children[0]) {
table.classList.add('table-visible');
}
};
function makeRowsandCells(index = -1, update = '') {
const row = tbody.insertRow(index);
const cityCell = row.insertCell();
cityCell.innerHTML = localStorage.getItem('city') + update;
const cityCell1 = row.insertCell();
cityCell1.innerHTML = localStorage.getItem('country');
const cityCell2 = row.insertCell();
cityCell2.innerHTML = localStorage.getItem('temp');
const cityCell3 = row.insertCell();
cityCell3.innerHTML = localStorage.getItem('feelslike');
};
Related
I am trying to use the OfficeExtension.TrackedObjects class to access a range across different contexts (documentation and similar questions set out below - although slightly outdated). The goal is to have a taskpane search list the results in the taskpane, then select the specific result in-text when clicking the listed result (using javascript).
Here is what I have:
var items = [];
function basicSearch() {
Word.run(function (context) {
const results = context.document.body.search("Online");
results.load("length, text, items");
return context.sync().then(function () {
context.trackedObjects.add(results);
for (let i = 0; i < results.items.length; i++) {
let x = results.items[i].text;
createtable("TestList", i, x, x);
items.push(results.items[i]);
}
});
return context.sync();
});
}
function createtable(id, x, y, z) {
var table = document.getElementById(id);
var row = table.insertRow(-1);
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
var cell3 = row.insertCell(2);
cell1.type = "button";
cell1.onclick = function () { selectrange(x) };
cell2.type = "button";
cell2.onclick = function () { selectrange(x) };
cell3.type = "button";
cell3.onclick = function () { selectrange(x) };
cell1.innerHTML = x;
cell2.innerHTML = y;
cell3.innerHTML = z;
}
function selectrange(x) {
results.load("text");
results.items[x].select();
results.context.sync();
}
Could someone show me where I have gone wrong, or provide a full working example of how to track and call an object (or collection of objects) for use?
Resources:
https://learn.microsoft.com/en-us/javascript/api/office/officeextension.trackedobjects?view=common-js-preview&viewFallbackFrom=office-js
How can a range be used across different Word.run contexts?
Word Online Add-In: Using objects across multiple contexts
Tracked Objects throwing errors in Word Online
https://leanpub.com/buildingofficeaddins
(Building Office Add-ins using Office.js has a working example, but it is in typescript and does not use trackedObjects - I have not been able to replicate it in my add-in).
When I run the above code, it says "ReferenceError: Can't find variable: results". I want it to select the specific search results displayed and pressed in the list. Any assistance would be greatly appreciated.
first, you need to define results as a global variable so that you can pass it to other functions. please reference the following code:
var results;
async function basicSearch() {
await Word.run(async (context) => {
results = context.document.body.search("Online");
results.track();
await context.sync();
});
}
async function selectrange() {
await Word.run(results, async (context) => {
results.load();
await context.sync();
results.items[0].select();
await context.sync();
});
}
i know that the problem is that let todoList is an empty array, but i dont know how to solve it.
the id tags in my created html is so e can create a delete button later
heres my code:
const textArea = document.querySelector("textarea");
const button = document.querySelector("button");
const listContainer = document.querySelector(".list-container");
let id = 0;
let todoList = [];
button.onclick = function () {
const listItem = {
title: textArea.value,
};
todoList.push(listItem);
addToStorage(todoList);
const dataFromStorage = getFromStorage();
createHtml(dataFromStorage);
};
function addToStorage(items) {
const stringify = JSON.stringify(items);
localStorage.setItem("list", stringify);
}
function getFromStorage() {
const data = localStorage.getItem("list");
const unstrigified = JSON.parse(data);
return unstrigified;
}
const createHtml = (data) => {
id++;
listContainer.innerHTML = "";
data.forEach((item) => {
listContainer.innerHTML += `<div class="list-item" data-id=${id}><p>${item.title} </p><button class="remove" data-id=${id}>Delete</button></div>`;
});
};
The problem here is you just forgot to load the data from localStorage when the page loaded like this
window.onLoad = () => {
const dataFromStorage = getFromStorage();
if(dataFromStorage){
createHtml(dataFromStorage);
} else {
createHtml([]);
}
}
The problem in the code is as follows
Initially the todolist will be an empty array. so when you do the below
todoList.push(listItem);
// adding to local storage which will override the existing todos when page is refreshed
addToStorage(todoList);
// So when the below line is executed only the latest todo will be returned
const dataFromStorage = getFromStorage();
createHtml(dataFromStorage);
Fix:
Initialise the todos from localstorage instead of an empty array
let todoList = [];
// change it as below
let todoList = getFromStorage();
Now Modify the getFromStorage() as below
// If the data is present in the localStorage then return it, else return empty array
function getFromStorage() {
const data = localStorage.getItem("list");
if (!data) return [];
const unstrigified = JSON.parse(data);
return unstrigified;
}
Now when the page is loaded, we need to display the todos. Add the below lines of code
window.onload = function () {
createHtml(todoList);
};
That's it. This will fix the issue.
Few minor improvements can be made as well.
todoList.push(listItem);
addToStorage(todoList);
const dataFromStorage = getFromStorage(); // this line is not necessary, remove it
createHtml(dataFromStorage); // change this to createHtml(todoList)
Codepen
Thanks.
I'm creating a library archive site containing a collection of my favorite reads. I have a click event listener on the books, adding them to my local storage key 'books' when doing so. I want it so that, if I click on the same book twice, it won't be added as a duplicate value in my ls. I've tried things I've found on here like an "if" statement checking the index of the new 'book' and comparing it to others, as well as filtering, but to no avail.
document.querySelectorAll('.card').forEach(function(uibook){
let arr = []
for(i=0;i < uibook.length; i++){
arr.push(uibook[i])
console.log(arr)
return arr
}
uibook.addEventListener('click',function(){
window.document.location = './details.html'
const title = uibook.innerText
const summary = uibook.querySelector('.card-summary').innerText
const genre = uibook.querySelector('.card-genre').innerText
const author = uibook.querySelector('.card-author').innerText
const page = uibook.querySelector('.card-page').innerText
const img = uibook.querySelector('img.card-img-top').getAttribute('src');
// Instantiate book
const Nbook = new book(title,genre,author,page,summary,img);
console.log(Nbook)
if (localStorage.getItem('books') === null){
books = [];
}
else{
books = JSON.parse(localStorage.getItem('books'));
}
books.push(Nbook)
localStorage.setItem('books', JSON.stringify(books));
// localStorage.clear();
add('lastKey',Nbook)
})
})
var lastKey;
function add(key,value) {
lastKey = key;
console.log(value)
localStorage.setItem(lastKey, JSON.stringify(value));
}
////////////////DETAILS PAGE/////////////////////////
function getBooks(){
// let lastKey;
if(localStorage.getItem('lastKey') === null){
lastKey = [];
} else {
lastKey = JSON.parse(localStorage.getItem('lastKey'));
}
console.log(lastKey)
//details info UI
document.querySelector('.details-title').innerText = `${lastKey.title}`
var imgTest = document.querySelector('.details-img').src = `${lastKey.img}`
console.log(imgTest)
document.querySelector('.summary-text').innerText = `${lastKey.summary}`
document.querySelector('.genre-text').innerText = `${lastKey.genre}`
console.log(document.querySelector('.author-text').innerText = `${lastKey.author}`)
document.querySelector('.pageNum-text').innerText = `${lastKey.page}`
}
getBooks()
Edit: updated the code I should also add that I have 2 keys in my ls. One is the 'lastkey', which as you may have guessed just has the value of the last book that was clicked on. This is to output the details of the book on the following page. The 'book' key is to have all of the books that I click on w/o duplicates. The purpose is b/c I have a carousel at the bottom of the page where I want to output suggested books by their genre, and I don't want to have duplicates of the same books. knowwhatimean?
To make comparison easy, you could have the books array be an array of JSON strings that you can compare with === or .includes:
if (localStorage.getItem('books') === null) {
books = [];
} else {
books = JSON.parse(localStorage.getItem('books'));
}
const stringified = JSON.stringify(Nbook);
if (!books.includes(stringified)) {
books.push(stringified);
}
localStorage.setItem('books', JSON.stringify(books));
Then, when parsing books elsewhere, call JSON.parse on array items to get to the underlying object, eg
const books = JSON.parse(localStorage.getItem('books')).map(JSON.parse);
// console.log(book[0].title)
Search your books array for another book with the same title.
uibook.addEventListener('click', function() {
window.document.location = './details.html'
const title = uibook.innerText
if (localStorage.getItem('books') === null) {
books = [];
} else {
books = JSON.parse(localStorage.getItem('books'));
}
const foundBook = books.find(book => book.title == title);
if (!foundBook) {
// Instantiate book
const summary = uibook.querySelector('.card-summary').innerText
const genre = uibook.querySelector('.card-genre').innerText
const author = uibook.querySelector('.card-author').innerText
const page = uibook.querySelector('.card-page').innerText
const img = uibook.querySelector('img.card-img-top').getAttribute('src');
const Nbook = new book(title, genre, author, page, summary, img);
books.push(Nbook);
console.log(Nbook)
localStorage.setItem('books', JSON.stringify(books));
add('lastKey', Nbook)
} else {
add('lastKey', foundBook);
}
})
I currently use a Google Apps Script on a Google Sheet, that sends individual row data to AWS API Gateway to generate a screenshot. At the moment, multiple single JSON payload requests are causing some Lambda function failures. So I want to batch the row data and then send as a single payload, so a single AWS Lambda function can then perform and complete multiple screenshots.
How can I batch the JSON payload after iterating the data on each line in the code below?
function S3payload () {
var PAYLOAD_SENT = "S3 SCREENSHOT DATA SENT";
var sheet = SpreadsheetApp.getActiveSheet(); // Use data from the active sheet
// Add temporary column header for Payload Status new column entries
sheet.getRange('E1').activate();
sheet.getCurrentCell().setValue('payload status');
var startRow = 2; // First row of data to process
var numRows = sheet.getLastRow() - 1; // Number of rows to process
var lastColumn = sheet.getLastColumn(); // Last column
var dataRange = sheet.getRange(startRow, 1, numRows, lastColumn) // Fetch the data range of the active sheet
var data = dataRange.getValues(); // Fetch values for each row in the range
// Work through each row in the spreadsheet
for (var i = 0; i < data.length; ++i) {
var row = data[i];
// Assign each row a variable
var index = row[0]; // Col A: Index Sequence Number
var img = row[1]; // Col B: Image Row
var url = row[2]; // Col C: URL Row
var payloadStatus = row[lastColumn - 1]; // Col E: Payload Status (has the payload been sent)
var siteOwner = "email#example.com";
// Prevent from sending payload duplicates
if (payloadStatus !== PAYLOAD_SENT) {
/* Forward the Contact Form submission to the owner of the site
var emailAddress = siteOwner;
var subject = "New contact form submission: " + name;
var message = message;*/
//Send payload body to AWS API GATEWAY
//var sheetid = SpreadsheetApp.getActiveSpreadsheet().getId(); // get the actual id
//var companyname = SpreadsheetApp.getActiveSpreadsheet().getName(); // get the name of the sheet (companyname)
var payload = {
"img": img,
"url": url
};
var url = 'https://requestbin.herokuapp.com/vbxpsavc';
var options = {
'method': 'post',
'payload': JSON.stringify(payload)
};
var response = UrlFetchApp.fetch(url,options);
sheet.getRange(startRow + i, lastColumn).setValue(PAYLOAD_SENT); // Update the last column with "PAYLOAD_SENT"
SpreadsheetApp.flush(); // Make sure the last cell is updated right away
// Remove temporary column header for Payload Status
sheet.getRange('E1').activate();
sheet.getCurrentCell().clear({contentsOnly: true, skipFilteredRows: true});
}
}
}
Example individual JSON payload
{"img":"https://s3screenshotbucket.s3.amazonaws.com/realitymine.com.png","url":"https://realitymine.com"}
Example desired output result
[
{"img":"https://s3screenshotbucket-useast1v5.s3.amazonaws.com/gavurin.com.png","url":"https://gavurin.com"},
{"img":"https://s3screenshotbucket-useast1v5.s3.amazonaws.com/google.com.png","url":"https://google.com"},
{"img":"https://s3screenshotbucket-useast1v5.s3.amazonaws.com/amazon.com","url":"https://www.amazon.com"},
{"img":"https://s3screenshotbucket-useast1v5.s3.amazonaws.com/stackoverflow.com","url":"https://stackoverflow.com"},
{"img":"https://s3screenshotbucket-useast1v5.s3.amazonaws.com/duckduckgo.com","url":"https://duckduckgo.com"},
{"img":"https://s3screenshotbucket-useast1v5.s3.amazonaws.com/docs.aws.amazon.com","url":"https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-features.html"},
{"img":"https://s3screenshotbucket-useast1v5.s3.amazonaws.com/github.com","url":"https://github.com"},
{"img":"https://s3screenshotbucket-useast1v5.s3.amazonaws.com/github.com/shelfio/chrome-aws-lambda-layer","url":"https://github.com/shelfio/chrome-aws-lambda-layer"},
{"img":"https://s3screenshotbucket-useast1v5.s3.amazonaws.com/gwww.youtube.com","url":"https://www.youtube.com"},
{"img":"https://s3screenshotbucket-useast1v5.s3.amazonaws.com/w3docs.com","url":"https://www.w3docs.com"}
]
Modifications
Do not call UrlFetchApp methods in a loop unless no other way. Although Google offers generous quota, it is not unlimited, and you will quickly burn through it on any substantial amount of rows and send frequency.
Use modern ES6 features like map to convert rows of values into objects in the format of the desired payload. Note that you will have to enable V8 runtime to use them.
What follows is a runnable test snippet showcasing how you could have modified your script. I opted to exclude status update logic from it, as it is up to you to decide how to update the status in case of batch update failure:
//TEST MOCKS:
const SpreadsheetApp = {
getActiveSheet() {
const Sheet = {
getLastRow() { return 3; },
getLastColumn() { return 5; },
getDataRange() {
const Range = {
getValues() {
return new Array(Sheet.getLastRow())
.fill([])
.map(
(r,ri) => new Array(Sheet.getLastColumn())
.fill(`mock row ${ri}`)
.map((c,ci) => `${c} cell ${ci}`)
);
}
};
return Range;
}
};
return Sheet;
}
};
const UrlFetchApp = {
fetch(uri, options) {
console.log({ uri, options });
}
};
//END MOCKS;
const sendToS3 = () => {
var PAYLOAD_SENT = "S3 SCREENSHOT DATA SENT";
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2;
var numRows = sheet.getLastRow() - 1;
var lastColumn = sheet.getLastColumn();
var dataRange = sheet.getDataRange();
var data = dataRange.getValues();
var siteOwner = "email#example.com";
const appURI = 'https://hfcrequestbin.herokuapp.com/vbxpsavb';
const payloads = data.map(([index, img, url]) => ({ img, url }));
const options = {
'method': 'post',
'payload': JSON.stringify(payloads)
};
const response = UrlFetchApp.fetch(appURI, options);
}
sendToS3();
Notes
When batching POST payloads, keep in mind that there is a quota on
maximum body size per request (currently 50 MB).
Do not call I/O (input/output) methods such as getRange, getValue in a loop, they are slow by nature, use batch methods like getDataRange, getValues, setValues, etc and perform all modifications on in-memory arrays only.
Use activate methods only when you explicitly want to change the focus, do not rely on it to determine a range. Just use normal references to cells obtained through methods like getRange.
Try sending the data as a list/Array. And on the server side iterate over the list/Array.
eg:
{
"payload": [{
"img": "https://s3screenshotbucket.s3.amazonaws.com/website1.com.png",
"url": "https://website1.com"
}, {
"img": "https://s3screenshotbucket.s3.amazonaws.com/website2.com.png",
"url": "https://website2.com"
}]
}
Hello I am currently using a script that takes your table data and saves it in local storage where I call it in another js file.
I have a script that succesfully can save the table data exactly how I would like, But I have been struggling on how to implement a count for how many table rows there are in the table before the data is saved in local storage.
Here is what I have tried:
$(function() {
loadAllTasks();
$("#addTask").click(function() {
let cells = Array.prototype.map.call($("#items-table")[0].rows, row => {
return Array.prototype.map.call(row.cells, cell => cell.innerHTML);
});
var task = {
cells: cells
};
task.Name = $("#taskName").val();
var itemCount = $("#items-table tr").length - 1;
var count = {
itemCount: itemCount
};
saveTaskInStorage(task);
saveCountInStorage(count);
});
function saveTaskInStorage(task) {
var savedTasks = JSON.parse(localStorage.getItem('tasks'));
if (!savedTasks || typeof(savedTasks) !== "object")
savedTasks = {};
savedTasks[task.Name] = task;
localStorage.setItem('tasks', JSON.stringify(savedTasks));
alert("Task has been Added");
}
function saveCountInStorage(count) {
var savedCount = localStorage.getItem('counts')
savedCount = {};
savedCount[task.Name] = count;
localStorage.setItem('counts', savedCount);
}
function loadCountFromStorage1(taskName) {
var savedCount = localStorage.getItem('counts');
return savedCount[taskName];
}
function loadAllTasks() {
var savedTasks = JSON.parse(localStorage.getItem('tasks'));
if (!savedTasks || typeof(savedTasks) !== "object")
return;
for (var taskName in savedTasks) {
$("#loadTask").append('<option>' + taskName + '</option>')
}
}
});
function loadTaskFromStorage1(taskName) {
var savedTasks = JSON.parse(localStorage.getItem('tasks'));
return savedTasks[taskName];
}
then in the other js file I call these functions:
function loadAllTasks() {
// Get all saved tasks from storage and parse json string to javascript object
var savedTasks = JSON.parse(localStorage.getItem('tasks'));
// To be sure that object exists on localStorage
if (!savedTasks || typeof (savedTasks) !== "object")
return;
// Get all property name of savedTasks object (here it means task names)
for (var taskName in savedTasks){
$("#select-task").append('<option>' + taskName + '</option>')
}
}
function loadTaskFromStorage(taskName) {
var savedTasks = JSON.parse(localStorage.getItem('tasks'));
// Return the task by its name (property name on savedTasks object)
return savedTasks[taskName];
}
function loadCountFromStorage(taskName) {
var savedCount = localStorage.getItem('counts');
return savedCount[taskName];
}
loadAllTasks();
var task = loadTaskFromStorage($("#select-task").val());
then I just do:
alert(task.cells);
this works perfectly, it alerts all the custom saved data in the table that I saved.
I then have tried a bunch of different options for this:
alert(task.itemCount);
and a bunch of variations of that.
I want to be able to do:
alert(task.count);
this then will alert me the number of rows in the table of the saved task I currently have selected in my select html.
I also tried getting rid of the saveCount functions and just modifing this:
var task = {
cells: cells,
count: count
};
but unfortunately this also does not work.
I would really appreciate it if anyone could help me on how I would save the table row count in local storage and be able to call it on each different saved task in the select on my html/ js file.
each saved task will have a different count so I want to do task.count
Thanks for the Help <3!
You should really look into using a front end framework like React or Angular. You are looping through dom elements and saving its inner html as values in your task when what you really need is something data driven. However, I think this might solve your issue.
In your click handler for #addTask you have
let cells = Array.prototype.map.call($("#items-table")[0].rows, row => {
return Array.prototype.map.call(row.cells, cell => cell.innerHTML);
});
var task = {
cells: cells
};
Try adding in a counter here
let count = 0;
let cells = Array.prototype.map.call($("#items-table")[0].rows, row => {
count += 1;
return Array.prototype.map.call(row.cells, cell => cell.innerHTML);
});
var task = {
cells: cells
count: count
};
Hopefully that works for you