I am trying to port an interactive fiction game from inform7 to javascript. A lot of the syntax is easily converted into javascript; some of it can even be optimized after conversion. But the 'Table' feature does not exist natively in javascript, and I can think of many ways to mimic it. This is what the syntax for an inform7 table looks like:
Table of example table
column one column two column three column four
---------- ---------- ------------ ----------
---------- ---------- ------------ ---------
When Play begins:
Choose a blank row from Table of example table;
now column one entry is "text";
now column two entry is 400;
now column three entry is true
now column four entry is [non primitive]
What is the most efficient way to implement this in javascript?
Please keep in mind that I plan on using strict mode, and do not want to even consider the use of 3rd party frameworks/libraries such as JQuery. Only JavaScript as defined in the latest ECMAScript Specs.
Some of my ideas:
using a map:
const tables = new Map();
// to create table
tables.set("wandering monsters", [
new Map([
["monster name","default"]
])
]);
//to get table entry
tables.get("wandering monsters")[0].get("monster name");
using a function:
function table(name, row, key, val, entrylist){
var result;
if (table.data == undefined){
table.data = new Map();
};
switch (arguments.length) {
case 1:
result = table.data.get(name);
break;
case 2:
table.data.set(name, []);
result = table.data.get(name);
break;
case 3:
result = table.data.get(name)[row].get(key);
break;
case 4:
table.data.get(name)[row].set(key, val);
result = table.data.get(name)[row].get(key);
break;
case 5:
table.data.get(name)[row] = new Map(entrylist);
result = table.data.get(name)[row];
break;
default:
result = table.data;
};
return result;
}
im editing on mobile
Related
looking for a way to automatically update a cell value when a record is created.
I use Airtable, and I would like to automatically assign a developer to the newest record.
Ex. I have 3 developer, christophe/thomas/hugo
Using basic algorithm, but the issue is that when there is a new Biz Dev, we have to manually add him to the arrayBizDev and add a new switch case:
`let arrayBizDev = ["Christophe", "Thomas", "Hugo"]
let nbBizDev = arrayBizDev.length;
let bizDev = "";
switch (nbRdvPris % nbBizDev) {
case 0:
bizDev = "Christophe";
break;
case 1:
bizDev = "Thomas";
break;
case 2:
bizDev = "Hugo";
break;
default:
console.log(erreur);
}
output.set('Business Developper', bizDev);`
I currenty use Excel Office Script to get a response from Jotform API.
In my Json response, on the first level of my Array, I get some informations like "Submission ID" and "timestamp from Submission", then I have a Key "Answers" with all the answers from that Submission (could be 150 anwers). So it's a 2D array.
I'm having trouble being able use setValues after all the iterations and write all the answers in Excel in one shot.
Here is my iteration where rows will be "Submission ID + Timestamp", then next to it, subRows which will have all the answers for that submission, each answers being one Excel row each
let rows: (string | boolean | number)[][] = [];
let subRows: (string | boolean | number)[][] = [];
for (const [key, value] of Object.entries(result)) {
rows.push([value["id"], value["created_at"]]);
for (const [subKey, subValue] of Object.entries(value["answers"])) {
if (subValue.hasOwnProperty("answer")) {
subRows.push([checkAnswer(subValue["answer"])]);
}
}
}
// This function is important because sometimes I have several answers in one answer, so I join it when necessary.
function checkAnswer(answer: string[]) {
if (answer.length > 1) {
return (typeof answer == "string" ? answer : answer.join("||"));
}
return answer;
}
// Here are the methods I use to write the result on my Excel file. The first one is great, it is one Excel row/ one result.
const targetRange = workSheet.getRange('A5').getResizedRange(rows.length - 1, rows[0].length - 1);
targetRange.setValues(rows);
// I TRANSPOSE so that the subROWS get in line, not vertically.
subRows = subRows[0].map((_, colIndex) => subRows.map(row => row[colIndex]));
const subTargetRange = workSheet.getRangeByIndexes(4, 2, subRows.length, subRows[0].length).setValues(subRows);
My problem is that all the subrows (all the answers) are being written in one line, there is not one answer/one row.
I could use setValues inside the iteration, but I always try not too as it slows down the work.
The best being able to make one big array and do everything in one setValues.
Any idea ?
Thank you very much,
Cedric
You can definitely create one array and write it out at once using SetValues. Where it gets tricky is that SetValues expects the 2-d array in a certain structure. So you can't just supply any 2d array and expect it to work with SetValues. If you use GetValues on the range and log it to the console, it will let you know of the structure it expects it in. Please see a few examples below. The stringified values are just some random data I put into a worksheet:
function main(workbook: ExcelScript.Workbook) {
let sheet: ExcelScript.Worksheet = workbook.getWorksheet("Sheet1");
let rang1: ExcelScript.Range = sheet.getRange("A1:A5");
console.log(rang1.getValues()); //(5) [Array(1), Array(1), Array(1), Array(1), Array(1)]
console.log(JSON.stringify(rang1.getValues())); //[["A"],[0],[1],[2],[3]]
let rang2: ExcelScript.Range = sheet.getRange("A1:B5");
console.log(rang2.getValues()); //(5) [Array(2), Array(2), Array(2), Array(2), Array(2)]
console.log(JSON.stringify(rang2.getValues())); //[["A","B"],[0,1],[1,2],[2,3],[3,4]]
let rang3: ExcelScript.Range = sheet.getRange("D1:H1");
console.log(rang3.getValues()); //(1) [Array(5)]
console.log(JSON.stringify(rang3.getValues())); //[["A","B","C","D","E"]]
let rang4: ExcelScript.Range = sheet.getRange("D1:H2");
console.log(rang4.getValues()); //(2) [Array(5), Array(5)]
console.log(JSON.stringify(rang4.getValues())); //[["A","B","C","D","E"],["B","C","D","E","F"]]
}
If the array does not come in the necessary structure, one thing you can try to do is write the array values to a 1d array. From there, you'd need to create a 2d array in the same structure as expected by SetValues for the range.
What I'm trying to do is, dynamically create clickable links from user provided textarea data, first being saved to the local machine using localStorage. User data is provided in the following format:
a|aa,
c|cc,
e|ee,
a,c,e are the labels for the links
aa,cc,ee are the links themselves
example of final output:
<a href="aa" />a</a>
<a href="cc" />c</a>
<a href="ee" />e</a>
Step 1: Save data to localStorage in a format that will later be used to create 2 separate arrays.
This is what I've done so far for step 1 (not sure it's correct)
// remove white space and carriage returns
textToSave = textToSave.replace(/(\r\n|\n|\r)/g,"").trim();
// replace | from user data with =>
var textToSaveArrayFormat = textToSave.replace(/(\|)/g, "=>");
// store data to localSorage
localStorage.setItem("appLinkConfig" ,JSON.stringify(textToSaveArrayFormat));
Step 2: Retrieve data from localStorage and create 2 arrays from that data
URLLabels = ["a", "b", "c"];
URLOptions = ["aa", "bb", "cc"];
For step 2 I start with
// get local storage app link config data
var appLinksObj = JSON.parse(localStorage.getItem("appLinkConfig"));
console.log(appLinksObj);
which returns
a=>aa,c=>cc,e=>ee,
From this I need to create my 2 arrays and this is where I'm stuck.
Step 3: I'm currently doing this with hardcoded arrays in my script, but would like to do it with the array data created in step 1 and 2.
// Object created
var obj = {};
// Using loop to insert key
// URLOptions in Object
for(var i = 0; i < URLLabels.length; i++){
obj[URLLabels[i]] = URLOptions[i];
}
// Printing object
for (var URLLabels of Object.keys(obj)) {
lnk.innerHTML += "<a href=\'" + URLLabels + "' target\='" + hotDeck + "'\>" + obj[URLLabels] + "</a>";
}
hotDeck is a flag I'm using to target one of two frames (returns the ID of those frames) contained in the page.
Thanks in advance for your help - please be gentle, I've been away from coding for a long time due to illness and coding again now to help my recovery. I'd be very grateful to solve this problem.
I assume this is what you are expecting. try the snippet on the console as i am not able to aceess localStorage property in the code snippet
var input = 'a|aa, c|cc, e|ee';
var output = input.split(',').map(str => str.split('|'));
window.localStorage.setItem('appLinkConfig', JSON.stringify(output));
var config = JSON.parse(window.localStorage.getItem('appLinkConfig'));
var result = config.map(([text, href]) => `${text.trim()}`).join(' ')
console.log(result);
I'm trying to use Google Apps Script to filter only exact matches. I've seen similar questions, but I can't seem to apply them to my situation.
On one sheet, I have a table of information which has the item name in column A, its' ingredient in column B, and its' quantity in column C. Column A contains items named Test and Test 2. When I filter for Test 2, I get results of both Test and Test 2. Here is the code I'm using:
var costSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Cost');
var ingSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Recipe Ingredient');
// Create list of items to filter from the full list
var rows = costSheet.getLastRow();
var itemList = costSheet.getRange(2, 1, rows - 1).getValues();
// Convert itemList to array to be used in filter
itemList = itemList.join();
// Get full non-filtered data
var fullList = ingSheet.getRange('A2:C514').getValues();
// Apply filter criteria
function checkMatch(item) {
return itemList.indexOf(item[0]) != -1;
}
filterList = fullList.filter(checkMatch);
// Clear target location, then place filtered data
costSheet.getRange('C2:E514').clearContent();
costSheet.getRange(2, 3, filterList.length, 3).setValues(filterList);
I don't have any trouble getting accurate results for multiple items from all three columns, and the only issue I have is when I try to filter an item that begins with the name of another item in the full list (e.g. filtering for Test 2 returns both Test and Test 2, when I want it to just return Test 2).
I am new to working with Google Apps Script/Javascript, hence the 'amateur' coding.
Explanation:
You are very close!
The issue is related to this line:
itemList = itemList.join();
Assuming that itemList = [["Test 2"],["Test 3"]] as a result of the getValues() method.
If you apply join you are converting the above array into the following string:
Test 1,Test 2
Therefore itemList.indexOf("Test") will return 0 which means your filter function will evaluate to true, but you don't want that since Test is not part of your array. You are mistakenly using the indexOf method of strings instead of the indexOf method of arrays.
Having said that, your goal is to use the the indexOf method of arrays. In order to do so, itemList needs to be an array and not a string.
To convert a 2D array [["Test 2"],["Test 3"]] into a 1D array [Test 1, Test 2] you can use flat.
Solution:
Change:
itemList = itemList.join();
To:
itemList = itemList.flat();
Improved Solution:
Going for the extra mile, you can shorten your code and make it more JavaScript modern like that:
function shorterFunction(){
const ss = SpreadsheetApp.getActiveSpreadsheet();
const costSheet = ss.getSheetByName('Cost');
const ingSheet = ss.getSheetByName('Recipe Ingredient');
const itemList = costSheet.getRange(2, 1, costSheet.getLastRow() - 1).getValues().flat();
const fullList = ingSheet.getRange('A2:C514').getValues();
const filterList = fullList.filter(r=>itemList.includes(r[0]))
costSheet.getRange('C2:E514').clearContent();
costSheet.getRange(2, 3, filterList.length, 3).setValues(filterList);
}
In this solution, I used an arrow function and includes.
I need to convert data from a csv file into a JavaScript object where key for each object is the id from the data. One JS object per each user.
Question: Is there a way to solve it using vanilla JS?
I need at the least a general walkthrough of what to do cause all I've been able to find is methods on how to solve this by using jQuery or JSON but for the research I am trying to understand, I can't use a specialized library, I'm only allowed to use plain VanillaJS.
Sample text data:
id first_name last_name email gender
-- ---------- --------- ----- ------
1 Gloria Mendez gmendez#ipu.gov Female
2 Lucy Grey lgrey#gmail.com Female
3 Max Mcolom mmcolom#yahoo.com Male
4 David Cooke dcooke#gmail.com Male
5 Marwin Darge mdarge#gov.com Male
hundreds of other rows
Desired output:
{
1: {
id: 1, first_name: 'Gloria', last_name: 'Mendez', email: 'gmendez#ipu.gov', gender: 'female'
},
2: {
id: 1, first_name: 'Lucy', last_name: 'Grey', email: 'lgrey#gmail.com', gender: 'female'
},
...
}
Using vanilla JS below.
I've added comments to explain what the code is doing. It;s basically splitting the data into rows, and then cells, and doing array manipulation along the way:
let table =
`id first_name last_name email gender
-- ---------- --------- ----- ------
1 Gloria Mendez gmendez#ipu.gov Female
2 Lucy Grey lgrey#gmail.com Female
3 Max Mcolom mmcolom#yahoo.com Male
4 David Cooke dcooke#gmail.com Male
5 Marwin Darge mdarge#gov.com Male`
// Split into rows, and then split each row into cells
let rows = table.split(/[\r\n]+/g).map(row => row.trim().split(/[\s\t]+/g));
// The first row will be the header
let header = rows[0];
// The rest is the data
let data = rows.slice(2);
// Create a function to return the desired object structure
function formatObject(headers, cells) {
return headers.reduce((result, header, idx) => {
result[header] = cells[idx];
return result;
}, {});
}
// Reduce each row into the desired format, and use the ID as a key
let result = data.reduce((res, row, idx) => {
let value = formatObject(header, row);
res[value.id] = value;
return res;
}, {});
// Log the result
console.log(result);
Parse the file
split on line break \n, to get rows
const rows = csv.split('\n');
split each row on comma , to get cells (if it's CSV, otherwise, \t for TSV)
const rawData = rows.map(d => d.split(',');
Extract the headers from the 1st row
The first row of data is you list of keys to build objects
const headers = rawData[0]
Convert rows into objects
const data = rawData.slice(2); // get rid of first 2 rows
const output = data.map(row => {
const obj = {};
headers.forEach((h, i) => obj[h] = row[i] });
return obj;
});
Of course, you need to handle all sorts of edge cases, like commas in the CSV cell, missing data etc...
I hope this gets you started.
Assuming you are loading the data already, you could loop through the data and use javascripts object bracket notation to define the new properties based on the id.
If you are looking for step by step,
first load the csv using something like node's fs module or the filereader in the browser.
for each line read parse the csv with javascript split.
if the assuming the id is always the first column you can now get
the id of the data with the splitArray[0].
Create a new object, then use bracket notation to set the property
of the object to the id. ex.
var obj = {};
obj[splitArray[0]] = data;
This is really rough, untested, but should get you closer to where you need to go.
Assuming a double quoted comma separated file which is about the most common csv format:
/* empty lines shown on purpose to approximate real file */
const csvString =
`"id","first_name","last_name","email","gender"
"1","Gloria","Mendez","gmendez#ipu.gov","Female"
"2","Lucy","Grey","lgrey#gmail.com","Female"
"3","Max","Mcolom","mmcolom#yahoo.com","Male"
"4","David","Cooke","dcooke#gmail.com","Male"
"5","Marwin","Darge","mdarge#gov.com","Male"`;
//split string on line breaks into array of line strings
const csvLines=csvString.split('\n');
// filter empty lines out and map each line to sub array
let arr = csvLines.filter(s=>s.trim()).map(arrFromLine)
// get headings from first line
let headings = arr.shift();
// map all the other lines into objects
let res = arr.map(lineArr =>{
return lineArr.reduce((a,c, i)=>{
a[headings[i]] = c;
return a
},{})
});
// map lines to array helper function
function arrFromLine(str){
// remove quotes on each end, split at `","` to avoid any other commas in text
return str.trim().slice(1, str.length-1).split('","');
}
console.log(res)
.as-console-wrapper { max-height: 100%!important;}