Today’s coding session went well. The kids were reticent, allowing me to focus on my work. That’s my new word: reticent. Although I wouldn’t say they were reticent. More likely, I couldn’t hear them that well. Kel told me I’m lucky I can’t hear very well. I think being deaf is sometimes a pleasant thing. But in all, being deaf can be frustrating. I really do need hearing aids. I can’t hear regular conversations at times. I feel a sense of accomplishment today. I got a lot done. However, I still need to put away my laundry, which I plan to do later.
My physical condition has improved today. Tommy provided me with Omeprazole, which has significantly reduced my heartburn. I’ve had heartburn since I was a child. Additionally, we recently acquired an outdoor griddle! I’m excited to experiment with it and see what we can cook. With the weather warming up, we can use it more often.
That’s all I have to share for now.
JavaScript notes…
———————————
Spreadsheet steps 26 – 50
Step 26
Object properties consist of key/value pairs. You can use shorthand property names when declaring an object literal. When using the shorthand property name syntax, the name of the variable becomes the property key and its value the property value.
The following example declares a user object with the properties userId, firstName, and loggedIn.
const userId = 1;
const firstName = “John”;
const loggedIn = true;
const user = {
userId,
firstName,
loggedIn,
};
console.log(user); // { userId: 1, firstName: ‘John’, loggedIn: true }
To keep track of all of your spreadsheet’s functions, declare a spreadsheetFunctions object. Using the shorthand notation syntax, set sum, average, and median as properties on the spreadsheetFunctions object.
const spreadsheetFunctions = { sum, average, median, }
Step 27
Now you can start using your spreadsheet functions. Begin by declaring an update arrow function. It should take an event parameter.
const update = (event) => { }
Step 28
In your window.onload function, you need to tell your input elements to call the update function when the value changes. You can do this by directly setting the onchange property.
Set the onchange property to be a reference to your update function.
input.onchange = update;
Step 29
Since your update event is running as a change event listener, the event parameter will be a change event.
The target property of the change event represents the element that changed. Assign the target property to a new variable called element.
const update = event => { const element = event.target; }
Step 30
Because the change event is triggering on an input element, the element will have a value property that represents the current value of the input.
Assign the value property of element to a new variable called value, and use .replace() to remove all whitespace.
const update = event => { const element = event.target; const value = element.value.replace(/\s/g, "") }
Step 31
Now you need to check if the value does not include the id of the element. Create an if condition to do so.
const update = event => { const element = event.target; const value = element.value.replace(/\s/g, ""); if (!value.includes(element.id)) { } }
Step 32
Spreadsheet software typically uses = at the beginning of a cell to indicate a calculation should be used, and spreadsheet functions should be evaluated.
Use the && operator to add a second condition to your if statement that also checks if the first character of value is =.
const update = event => { const element = event.target; const value = element.value.replace(/\s/g, ""); if (!value.includes(element.id) && value.startsWith("=")) { } }
Step 33
In order to run your spreadsheet functions, you need to be able to parse and evaluate the input string. This is a great time to use another function.
Declare an evalFormula arrow function which takes the parameters x and cells.
const evalFormula = (x, cells) => { }
Step 34
In your evalFormula, declare an idToText arrow function which takes an id parameter.
Your idToText function should return the result of calling .find() on the cells array with a callback function that takes an cell parameter and returns cell.id === id.
const evalFormula = (x, cells) => { const idToText = (id) => cells.find(cell => cell.id === id); }
Step 35
Your idToText function currently returns an input element. Update it to return the value of that input element.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; }
Step 36
You need to be able to match cell ranges in a formula. Cell ranges can look like A1:B12 or A3:A25. You can use a regular expression to match these patterns.
Start by declaring a rangeRegex variable and assign it a regular expression that matches A through J (the range of columns in your spreadsheet). Use a capture group with a character class to achieve this.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])/; }
Step 37
After matching a cell letter successfully, your rangeRegex needs to match the cell number. Cell numbers in your sheet range from 1 to 99.
Add a capture group after your letter capture group. Your new capture group should match one or two digits – the first digit should be 1 through 9, and the second digit should be 0 through 9. The second digit should be optional.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?)/; }
Step 38
Ranges are separated by a colon. After your two capture groups, your rangeRegex should look for a colon.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):/; }
Step 39
After your rangeRegex finds the :, it needs to look for the same letter and number pattern as it did before.
Copy your two existing capture groups and paste them after the colon.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/; }
Step 40
Finally, make your rangeRegex global and case-insensitive.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; }
Step 41
Declare a rangeFromString arrow function that takes two parameters, num1 and num2. The function should implicitly return the result of calling range with num1 and num2 as arguments.
To be safe, parse num1 and num2 into integers as you pass them into range.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2)); }
Step 42
Declare a function elemValue which takes a num parameter. The function should be empty.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2)); const elemValue = (num) => { } }
Step 43
In your elemValue function, declare a function called inner which takes a character parameter.
Then, return your inner function.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2)); const elemValue = num => { const inner = (character) => { }; return inner(); } }
Step 44
In your inner function, return the result of calling idToText with character + num as the argument.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2)); const elemValue = num => { const inner = character => { return idToText(character + num) } return inner; } }
Step 45
The concept of returning a function within a function is called currying. This approach allows you to create a variable that holds a function to be called later, but with a reference to the parameters of the outer function call.
For example:
const innerOne = elemValue(1);
const final = innerOne(“A”);
innerOne would be your inner function, with num set to 1, and final would have the value of the cell with the id of A1. This is possible because functions have access to all variables declared at their creation. This is called closure.
You’ll get some more practice with this. Declare a function called addCharacters which takes a character1 parameter.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2)); const elemValue = num => { const inner = character => { return idToText(character + num); } return inner; } const addCharacters = (character1) => { } }
Step 46
In your elemValue function, you explicitly declared a function called inner and returned it. However, because you are using arrow syntax, you can implicitly return a function. For example:
const curry = soup => veggies => {};
curry is a function which takes a soup parameter and returns a function which takes a veggies parameter. Using this syntax, update your addCharacters function to return an empty function which takes a character2 parameter.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2)); const elemValue = num => { const inner = character => { return idToText(character + num); } return inner; } const addCharacters = character1 => character2 => { } }
Step 47
Your inner functions can also return a function. Using the same arrow syntax, update your addCharacters function to return a third function which takes a num parameter.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2)); const elemValue = num => { const inner = character => { return idToText(character + num); } return inner; } const addCharacters = character1 => character2 => num => { } }
Step 48
Now update your innermost function in the addCharacters chain to implicitly return the result of calling charRange() with character1 and character2 as the arguments.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2)); const elemValue = num => { const inner = character => { return idToText(character + num); } return inner; } const addCharacters = character1 => character2 => num => charRange(character1, character2); }
Step 49
Use the same syntax as your addCharacters function to update your elemValue function. It should no longer declare inner, but should return the function implicitly.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2)); const elemValue = num => character => idToText(character + num); const addCharacters = character1 => character2 => num => charRange(character1, character2); }
˘
Step 50
Your addCharacters function ultimately returns a range of characters. You want it to return an array of cell ids. Chain the .map() method to your charRange() call. Do not pass a callback function yet.
const evalFormula = (x, cells) => { const idToText = id => cells.find(cell => cell.id === id).value; const rangeRegex = /([A-J])([1-9][0-9]?):([A-J])([1-9][0-9]?)/gi; const rangeFromString = (num1, num2) => range(parseInt(num1), parseInt(num2)); const elemValue = num => character => idToText(character + num); const addCharacters = character1 => character2 => num => charRange(character1, character2).map(); }