JavaScript/Local Storage - javascript

Good Afternoon, I'm hoping for some guidance. I am new to JavaScript and struggling with a project. We have been asked to store new entries into a website application, which once added store into the local storage.
This is what I have so far, but I am at a loss where I am going wrong to get this stored.
The first section surrounded by ** works fine (albeit it doesn't store the entires). It is the last bit where I'm trying to add new code to store the message and invoke the event listener which doesn't work.
Any guidance would be greatly appreciated.
**function addTextEntry(itemKey, initialText, isNewEntry) {
// Create a text element to edit the entry
var textElement = document.createElement("textarea");
textElement.rows = 5;
textElement.placeholder = "(new entry)";
// Set the textarea's value to the given text (if any)
textElement.value = initialText;
// Add a section to the page containing the textarea
addSection(itemKey, textElement);
// If this is a new entry (added by the user clicking a button)
// move the focus to the textarea to encourage typing
if (isNewEntry) {
textElement.focus();
}**
// Create an event listener to save the entry when it changes
function saveEntry() {
// A new version of this function is created every time addTextEntry is called,
// so it can access all the variables available in this call to addTextEntry
console.log("saveEntry called with variables in scope:", {
itemKey,
initialText,
isNewEntry,
textElement,
});
document.getElementById(initialText);
item = makeTextItem(initialText);
localStorage.setItem(itemKey);
textElement.addEventListener("initialText", saveEntry);
}
}

setItem method takes 2 parameters:
keyName: The name of the key in the Local Storage
keyValue: The value for a given key
In you code, you are passing only one parameter itemKey. You should also pass the value for that key as a second parameter.

document.getElementById(initialText);
This statement doesn't do anything with the result. You may want to assign it to a value like const textElement = document.getElementById(initialText);
item = makeTextItem(initialText);
You are missing the code for makeTextItem but that's still irrelevant since item isn't being used anywhere in the code below it.
localStorage.setItem(itemKey);
setItem takes TWO params, the key and the thing you want to save storage.setItem(keyName, keyValue);
textElement.addEventListener("initialText", saveEntry);
addEventListener's first argument should be a valid event. addEventListener(type, listener);

Related

Javascript: Values of array disappear after referring to it from other function

I currently try to build a form with javascript which has two functionalities:
1) Adding elements dynamically to a list
2) Identify through a button click a certain element (e.g. with the highest value)
See (wanted to add pictures directly to my post, but I am lacking StackOverflow reputation - so here are they as links):
https://i.ibb.co/KxvV5Ph/Bildschirmfoto-2019-11-03-um-19-12-51.png
First functionality works fine (see above, added installations). The second doesnt. My plan was the following:
1) When an element gets added to the list I also push it as an object of the class "installation" to the array installations = []
2) When I click on "Identify Longest Duration" I iterate through a map function over the installations array (and output the highest value as an alert).
Unfortunately, the installations array is empty when I call it from another function.
Get values from form
var instStorage = document.getElementById("instStorage");
var instMasse = document.getElementById("instMasse");
var instPrice = document.getElementById("instPrice");
var instDischarge = document.getElementById("instDischarge");
const installations = [] ; // empty installations array
Adding values to DOM, Calling another function to add values to installations array
const createInstallation = () => {
... (working code to add vars from 1) to list element in DOM)...
addInstallation(); // calling another function to add installation to installations array
}
Class Definition of installation
class installation {
constructor(storage, masse, price, discharge) {
this.storage = storage;
this.masse = masse;
this.price = price;
this.discharge = discharge;
}
... (getter functions here) ...
summary = () => {
return `Installation Specs: Storage ${this.getStorage()},
Mass ${this.getMasse()}, Price ${this.getPrice()} and Discharge
Rate
${this.getDischarge()}`;
}
}
Adding installation to installations array
const addInstallation = () => {
installations.push(new installation(instStorage, instMasse, instPrice, instDischarge));
(...)
}
When I call for test purposes my summary function within the createInstallation() function (after calling addInstallation()) everything works fine; the first element of the installations array gets displayed:
alert(installations[0].summary());
See:
https://i.ibb.co/Khc6R7r/Bildschirmfoto-2019-11-03-um-19-32-41.png
When I call the summary function from the event listener of the "Identify Longest" button the installations array is suddenly empty, even though I added an element a second before (which should have been added to the installations array).
See:
https://i.ibb.co/80bTFWY/Bildschirmfoto-2019-11-03-um-19-36-48.png
I am afraid that's a problem of scope; but I don't see how to fix it.
Help is appreciated :-)
Thanks a lot!
Get values from form
var instStorage = document.getElementById("instStorage");
That is what is not happening. getElementById() gets you an element, by id. Not its value.
Assuming that instStorage and friends are input elements (which is not shown unfortunately), you may want to change the code to actually get their value:
var instStorage = document.getElementById("instStorage").value;
var instMasse = document.getElementById("instMasse").value;
var instPrice = document.getElementById("instPrice").value;
var instDischarge = document.getElementById("instDischarge").value;

How to store reference to function?

Given a list of functions, I wish the user to be able to select any of the functions to run at startup. How can this be done so that the user can "save" their choice of function to run the next time the code is run ie what would function runSelectedFunction (below) look like since you can't "save" a javascript function to file? Also, assume the list of potential functions is extensible.
const first = ()=>{
console.log('first');
}
const second = ()=>{
console.log('second');
}
const third = ()=>{
console.log('third');
}
loadUserSelectedFunctionFromDB()
.then(runSelectedFunction)
To be clear, the goal is to persist the user choice even if the code stops executing and is restarted. Normally, this would be done by storing a value in a database but the question is how to store a reference to a function in a database given an extensible set of functions?
Use a map like this:
const m = {
first, second, third
};
let selectFuncName = "first"; // from user selection, maybe click a button
let selectFunc = m[selectFuncName];
loadUserSelectedFunctionFromDB()
.then(runSelectedFunction)

Firebase functions - adding new value to the oldvalue

I'm creating an android app that logs how long a person spends on certain things. I want to add the time spent to the total time spend, so I know how long a user has spent on an exercise type
I want to do it in a function, since I think it's easier than transactions.
exports.addExerciseTime = functions.database.ref('users/{userid}/stats/exerciseTime/{type}').onWrite( event =>{
console.log("Exercise time updates...");
var newdata = event.data.val();
var oldData = event.data.previous.val();
return event.data.ref.update(oldData+ newdata);
});
Now, I know that this function will loop until firebase shuts it down.
But how would I do this? Is there an easier way to do this?
you have an easy option of adding a flag indicating that you updated the data. next time you will get into the function, just start by checking if the flag exists in if so, exit the function. the con of this one is that you will run the function at least n+1
another option, according to their latest post, you know have a "onUpdate" and "onCreate" triggers as well. you might be able to use them smartly to optimize this even more (for example: only on first creation do XYZ, so it won't run on each update).
https://firebase.googleblog.com/2017/07/cloud-functions-realtime-database.html
Like you are saying, onWrite will capture every writing event. My solution would be replacing onWrite with onCreate, however let the user write to another path because Firebase will keep triggering the function. Besides that, your approach this is not the best solution since the updates can conflict. The use of transactions is better. That would look like this:
exports.addExerciseTime = functions.database.ref('users/{userid}/stats/exerciseTime/{type}').onCreate( event =>{
console.log("Exercise time updates...");
var newdata = event.data.val();
const pathToValue = //create the path here to exercisetime
return pathToValue.transaction(function(exercisetime) {
return (exercisetime || 0) + newdata;
});
});
*Notice the onCreate event instead of onWrite. Again: You will need to write it to a other path.

How can I pass data to an element's change() method?

I wrote an app where multiple users can edit a database in realtime. I am using socket-io to keep all users' pages up to date with any changes to the database.
All input values broadcast a change.
Say I bind a function to an input's change event:
$(".input-field").change(function(ev) {
codeThatChangesOtherInputValuesOnMyPage1;
codeThatChangesOtherInputValuesOnMyPage2;
codeThatChangesOtherInputValuesOnMyPage3;
codeThatChangesOtherInputValuesOnMyPage4;
codeThatChangesOtherInputValuesOnMyPage5;
codeThatChangesOtherInputValuesOnMyPage6;
codeThatChangesOtherInputValuesOnMyPage7;
codeThatChangesOtherInputValuesOnMyPage8;
var tableColumn = $(ev.target).attr('table-col');
var newFieldValue = $(ev.target).val()
broadcastChange(tableColumn, newFieldValue); // this is pseudo code for a socket-io emit()
});
socket.on('runThisWhenReceivingBroadcastFromServer', function(response) {
// response.data has the input element id of element I should update.
// Get the input field i should update
var theInputField = getInputField(response.data)
$(theInputField).val(getNewInputValue(response.data))
$(theInputField).change();
// I call change because I want all my code in the input's change function to run, except for the last line.
});
I have already fixed this problem, but I am repeating myself by just copying all my code from on change function and pasting it in the broadcast receiving and then just omitting the broadcastChange line. But i want to follow DRY (Don't Repeat Yourself).
Also codeThatChangesOtherInputValuesOnMyPage1; is just that, code. Its tons of code. How would you go about restructuring the code?
My first thought was to do something like this (pseudo code):
$(".input-field").change(function(ev) {
codeThatChangesOtherInputValuesOnMyPage1;
codeThatChangesOtherInputValuesOnMyPage2;
codeThatChangesOtherInputValuesOnMyPage3;
codeThatChangesOtherInputValuesOnMyPage4;
codeThatChangesOtherInputValuesOnMyPage5;
codeThatChangesOtherInputValuesOnMyPage6;
codeThatChangesOtherInputValuesOnMyPage7;
codeThatChangesOtherInputValuesOnMyPage8;
var tableColumn = $(ev.target).attr('table-col');
var newFieldValue = $(ev.target).val()
if (!ev.data.comingFromBroadcastReceiverFunction) {
broadcastChange(tableColumn, newFieldValue); // this is pseudo code for a socket-io emit()
}
});
but you can't pass data to change(); only when binding the function.
What do you guys think is the functional programming approach to this?
If you are copying and pasting code over and over, you could avoid that by separating the duplicate code into a new function, and have both the change and the socket functions call it.
For example, it could work like this:
function updateInputValue(inputField) {
codeThatChangesOtherInputValuesOnMyPage1;
codeThatChangesOtherInputValuesOnMyPage2;
codeThatChangesOtherInputValuesOnMyPage3;
codeThatChangesOtherInputValuesOnMyPage4;
codeThatChangesOtherInputValuesOnMyPage5;
codeThatChangesOtherInputValuesOnMyPage6;
codeThatChangesOtherInputValuesOnMyPage7;
codeThatChangesOtherInputValuesOnMyPage8;
}
$(".input-field").change(function(ev) {
updateInputValue($(ev.target));
var tableColumn = $(ev.target).attr('table-col');
var newFieldValue = $(ev.target).val()
broadcastChange(tableColumn, newFieldValue); // this is pseudo code for a socket-io emit()
});
socket.on('runThisWhenReceivingBroadcastFromServer', function(response) {
// response.data has the input element id of element I should update.
// Get the input field i should update
var theInputField = getInputField(response.data)
$(theInputField).val(getNewInputValue(response.data))
updateInputValue($(theInputField));
// I call change because I want all my code in the input's change function to run, except for the last line.
});

Creating javascript objects using jQuery's each() method

Im curious about what might be a larger question than I think.
I am using the following code to listen for 'keyup' on a group of text input fields. If the user stops typing for a given amount of time, I send the data to a controller using AJAX.
I decided to try my hand at OOP in javascript to accomplish this. This is because I want a new instance of the timer method for each input field. (To be absolutely clear, Im very new to OOP in javascript so this might be dreadful. Let me know.)
Here is the main class with its methods:
function FieldListener(entity){
t = this;
t.typingTimer; // Timer identifier
t.doneTypingInterval = 1000; // Time in ms. e.g.; 5000 = 5secs
t.entity = entity;
entity.bind("keyup", function(){t.setTimer();});
}
FieldListener.prototype.setTimer = function(){
t = this;
// User is still typing, so clear the timer.
clearTimeout(t.typingTimer);
// Get the field name, e.g.; 'username'
t.entityType = t.entity.attr("name");
// If the value is empty, set it to a single space.
if(!(t.val = t.entity.val())){
t.val = ' ';
}
t.noticeSpan = t.entity.siblings("span");
// Display 'waiting' notice to user.
t.noticeSpan.html('...')
t.typingTimer = setTimeout(function(){t.doneTyping();},t.doneTypingInterval);
}
FieldListener.prototype.doneTyping = function(){
// Encode for passing to ajax route.
t = this;
valueType = encodeURIComponent(t.entityType);
value = encodeURIComponent(t.val);
$.ajax({
url: '/check/'+valueType+'/'+value,
type: 'GET',
processData: false
})
.done(function(validationMessage){
t.noticeSpan.html(validationMessage);
})
.fail(function(){
t.noticeSpan.html("Something went wrong. Please try again.");
});
}
So from here I'd like to be able to create an object of the FieldListener class for every input field.
I know I can do it easily if I have an id for each like so:
var fieldListener = new FieldListener($("#someFieldID"));
But I'd like to iterate over every field with a given class name. Something close to this perhaps?:
i = 0;
$(".info-field").each(function(){
i = new FieldListener($(this));
});
But that doesn't work (and doesn't look very nice).
Any thoughts? (Im also curious about critiques/improvements to the class/methods code as well.)
edit: As per #ChrisHerring's question: The issue is that it seems to create the object but only for the last element in the each() method. So the span associated with the last input field with the class '.info-field' displays the validationMessage returned from AJAX regardless of which field I am typing in.
UPDATE:
It seems like something is wrong with the creation of new objects. For example, if, rather than iterating through the each() method, I simply follow one class initiation with another, like so:
var fieldListener1 = new FieldListener($("#someFieldID"));
var fieldListener2 = new FieldListener($("#someOtherFieldID"));
that fieldListener2 overwrites variables being saved when initiating fieldListener1. This means that when I type into the input field with id "#someFieldID", it behaves as if I am typing into the input field with id "#someOtherFieldID". Thoughts?
UPDATE #2 (solved for now):
It seems that I have solved the issue for now. I needed to add 'var' before 't = this;' in the FieldListener class. Any comments/critiques are still welcome of course. ;)
The t variable is global. The function for the "keyup" event is evaluated dynamically which means it picks up the last value of t.
Change
t = this;
to
var t = this;
I think you want an array of FieldListener objects.
var myListeners = [];
i = 0;
$(".info-field").each(function(){
myListeners[i] = new FieldListener($(this));
i++
});
This'll give you a list of FieldListeners, where myListeners[0] is the listener for the first .info-field on the page, myListeners[1] is the listener for the second, etc.
Edit: It would appear you have solved the problem. This answer may still come in handy later on, though, so I won't delete it. =)
I think you should be using jquery's .on() to handle the binding.
$(body).on({
keyup: function () { HandleKeyUpEvent($(this)); },
keydown: function () { HandleKeyDownEvent($(this)); }
}, ".info-field");
I realize this is a departure from your original coding idea (using prototypes) but it will still be OOP, if that's what you intented to do.

Categories

Resources