Updating localstorage with button click - javascript

I need to update single item of object in localstorage without reloading page.
jsFiddle: https://jsfiddle.net/7xsg8679/
Here is my code:
if (obj.isCompletedTask === false) {
newDone.innerHTML = "Not done";
newDone.dataset.done = false;
newButton.addEventListener('click', function (e) {
newDone.innerHTML = "Done";
newDone.dataset.done = true;
newUl.style.backgroundColor = "red";
e.preventDefault();
})
}
If I use if(obj.isCompletedTask === true) in the if-statement - it does nothing. Why?

When you use obj.isCompletedTask === true the button does nothing isCompletedTask is false, so the code between curly brackets { ... } is not run at all, so the eventListener does not get attached to your button click event.
You may want to move your if statement to inside of event listener, if i understood your intentions correctly.
When you want to update an element in localstorage, you do it like that:
localStorage.setItem('list', JSON.stringify(tasks));
...but when your tasks object changes, you need to keep a track of that yourself, and manually update localStorage. This relation is called data-binding. Moreover, you cannot just update subelement of your tasks directly, you have to replace list in localStorage with fresh version of tasks every time something in tasks changes.`
General remark: from what i see in your fiddle, i do really think you should have a look at some framework (like vue, react or angular). Keeping track of the changes here and there will really be tiresome after a while and besides, why should yo reinvent the wheel?

Related

Javascript - How do I prevent functions from running multiple times, incrementally, after each button click?

I am flabbergasted trying to figure out a way to prevent the button click from rendering multiple times based on the number of clicks. If you I click "submitButton" once, everything runs once. If I click it a second time, everything runs twice. If I click it a third time, everything runs three times... And so on...
Here is the code I am running to initiate the running of several functions. The subsequent functions grab data from a database, creates batches of 100, then inserts that data into an HTTP POST request.
submitButton.addEventListener("click", () => {
if (document.getElementById("subject").value == "") {
alert(
"Please add a Subject for your Push Notification before sending."
);
} else if (document.getElementById("body").value == "") {
alert(
"Please add a Message Body for your Push Notification before sending."
);
} else if (
document.querySelectorAll("input[type=radio]:checked").length < 1
) {
alert("Please select a Jump-To page before sending.");
} else {
btn.classList.add("button--loading");
submitButton.disabled = true;
SelectData();
}
});
Following SelectData(); are the functions that batch, create and send the HTTP POST request. At the end of all of this, I've attempted to add the following to prevent some type of storage of EACH click event, operating under the assumption that is my problem. That is, that each click is being stored locally in the browser, and thus if number of clicks = 2 then SelectData(); will run twice along with all other functions related to the click event.
submitButton.removeEventListener("click", null);
submitButton.disabled = false;
I was hopeful the above would be my answer, but it has not done anything differently. I'd love some help and am happy to provide more specifics if necessary, but i am stumped. I appreciate your help in pointing me in the right direction!
The second argument for removeEventListener must be exactly the function provided to addEventListener
function myOnClickFunc() {
console.log("oh jolly, a click!")
}
myElement.addEventListener("click", myOnClickFunc)
myElement.removeEventListener("click", myOnClickFunc) // note the exact same object is passed
This would probably fix it. Though it is preferable for you to rewrite your code in a way where you use the same event listener repeatedly instead of removing and readding it.

JavaScript Events not adding to the DOM

As an exercise I have to do a little online bike reservation app. This app begins with a header which explains how to use the service. I wanted this tutorial be optional so I wrote a welcome message in HTML and if the user doesn't have a var in his cookies saying he doesn't want to see the tutorial again, the welcome message is replaced by a slider that displays the information.
To achieve that is fetch a JSON file with all the elements I need to build the slider (three divs : the left one with an arrow image inside, the central one where the explanations occur and the right one with another arrow). Furthermore I want to put "click" events on the arrows to display next or previous slide. However, when I do so, only the right arrow event works. I thought of a closure problem since it is the last element to be added to the DOM that keeps its event but tried many things without success. I also tried to add another event to the div that works ("keypress") but only the click seems to work. Can you look at my code give me an hint on what is going on?
Here is the init function of my controller:
init: function() {
var load = this.getCookie();
if(load[0] === ""){
viewHeader.clearCode();
var diapoData = ServiceModule.loadDiapoData("http://localhost/javascript-web-srv/data/diaporama.json");
diapoData.then(
(data) => {
// JSON conversion
modelDiapo.init(data);
// translation into html
controller.initElementHeader(modelDiapo.diapoElt[0]);
controller.hideTuto();
}, (error) => {
console.log('Promise rejected.');
console.log(error);
});
} else {
viewHeader.hideButton();
controller.relaunchTuto();
}
}
There is a closer look at my function translating the JSON elements into HTML and adding events if needed :
initElementHeader: function(data){
data.forEach(element => {
// Creation of the new html element
let newElement = new modelHeader(element);
// render the DOM
viewHeader.init(newElement);
});
}
NewElement is a class creating all I need to insert the HTML, viewHeader.init() updates the DOM with those elements and add events to them if needed.
init: function(objetElt){
// call the render
this.render(objetElt.parentElt, objetElt.element);
// add events
this.addEvent(objetElt);
},
Finally the addEvent function:
addEvent: function(objet){
if(objet.id === "image_fleche_gauche"){
let domEventElt = document.getElementById(objet.id);
domEventElt.addEventListener("click", function(){
// do things
});
}
else if(objet.id === "image_fleche_droite"){
let domEventElt = document.getElementById(objet.id);
domEventElt.addEventListener("click", function(){
// do stuff
});
};
},
I hope being clear enough about my problem. Thank You.
Ok, I found the problem, even if the element was actually created, it somehow stayed in the virtual DOM for some time, when the "getElementById" in "addEvent" was looking for it in the real DOM it didn't find the target and couldn't add the event. This problem didn't occur for the last element since there was nothing else buffering in the virtual DOM.
On conclusion I took out the function adding events out of the forEach loop and created another one after the DOM is updated to add my events.

How do I assign a pre and post function on button click without changing button definition or previously written functions

I have the following buttons:
<button id="abcd" onclick="something()">click</button>
and the following functions are attached to this button apart from the one in its html definition.
$('#abcd').on('click',function(){alert("abcd");});
$('#abcd').on('click',function(){
someAjaxCallWithCallback;
});
Now I want a new function with another ajax call to execute on this button's click, before the above mentioned functions. This new function determines whether the remaining functions would be called or not based on what data is recieved by the ajax call. That is, this pre function should complete its execution before giving control over to the rest of the functions and also determine whether they would run or not.
As an example, without changing the existing validation logics and button code, I have to add a new pre-validation function and similarly and post validation function.
I have a bindFirst method using which I can at least bring my new function to the beginning of the call stack but I have not been able to contain its execution and control further delegation because of callbacks.
If I understand correctly, you are looking for the way to do this, without modifying html and already existing js, only by adding new js-code.
First of all, if onclick handler is set and you want to control it, you should disable it on page load (maybe, saving it to some variable):
$(document).ready(function() {
var onclick = $("#abcd").attr("onclick").split("(")[0];
//to run it in future: window[onclick]();
$("#abcd").attr("onclick", "");
});
Edit: I changed my answer a little, previous approach didn't work.
Now you need to remove all already existing handlers. If number of handlers you want to control is limited, constant and known to you, you can simply call them in if-else after pre-validation inside your pre-function. If you want something more flexible, you are able to get all the handlers before removing, save them and then call them in a loop.
For that "flexible" solution in the end of $(document).ready(); you save all already existing handlers to an array and disable them. Then you write your pre-function and leave it as the only handler.
var handlers = ($._data($("#abcd")[0], "events")["click"]).slice();
$("#abcd").off("click");
$("#abcd").click(function() {
//this is your pre-func
//some code
handlers[1].handler.call();
});
Try console.log($._data($("#abcd")[0], "events")) to see, what it is.
Finally just run your post-function and do whatever you need, using conditions.
So, the general algorithm is as follows:
Disable onclick
Save all handlers
Disable all handlers
Run pre-func first
Run handlers you want to be executed
Run post-func
In fact, you just make your pre-func the only handler, which can run all other handlers you may need.
Although Alex was spot on, I just wanted to add more details to cover certain cases that were left open.
class preClass{
constructor(name,id){
if($(id) && $(id)[0] && $(id)[0]['on'+name])
{
var existing = $(id)[0]['on'+name]
$(id).bindFirst(name,existing);
$(id).removeAttr('on'+name)
alert("here");
}
if($._data($(id)[0],"events")){
this.handlers = $._data($(id)[0],"events")[name].slice();
}
else
{
this.handlers = null;
}
this.id = id;
this.name = name;
}
generatePreMethod(fn,data)
{
$(this.id).off(this.name);
$(this.id).bindFirst(this.name,function(){
$.when(fn()).then(execAll(data));
});
}
}
function exec(item,index){
item.handler.call()
}
function execAll(handlers){
return function(){ handlers.forEach(exec);}
}
This more or less takes care of all the cases.
Please let me know if there is something I missed!

Utilizing Firefox's default/built-in Event Listeners

I have a context menuitem which is activated if an image is right-clicked, the exact same way that 'context-copyimage' is activated.
Is it possible to tie/pair that menuitem to the 'context-copyimage' therefore eliminating the need to add extra (duplicate) event-listeners and show/hide handlers??!!
(Adding an observer to 'context-copyimage' defeats the purpose)
If not, is it possible to use the event-listener that 'context-copyimage' uses?
Update:
I am trying to reduce listeners. At the moment, script has a popupshowing listeners. On popupshowing, it checks for gContextMenu.onImag and if true, it shows the menuitem. Firefox's context-copyimage does the exact same thing. I was wondering if it was possible to tie these 2 in order to remove/reduce the in-script event listeners.
I was also chatting with Dagger and he said that:
... the state of built-in items isn't set from an event handler, it's
set from the constructor for nsContextMenu, and there are no
mechanisms to hook into it
So it seems, that is not possible
No, there is no sane way of avoiding the event listener that would perform better than another event listener and is compatible with unloading the add-on in session.
Hooking nsContextMenu
As you have been already told, the state is initialized via gContextMenu = new nsContextMenu(...). So you'd need to hook the stuff, which is actually quite easy.
var newProto = Object.create(nsContextMenu.prototype);
newProto.initMenuOriginal = nsContextMenu.prototype.initMenu;
newProto.initMenu = function() {
let rv = this.initMenuOriginal.apply(this, arguments);
console.log("ctx", this.onImage, this); // Or whatever code you'd like to run.
return rv;
};
nsContextMenu.prototype = newProto;
Now, the first question is: Does it actually perform better? After all this just introduced another link in the prototype-chain. Of course, one could avoid Object.create and just override nsContextMenu.prototype.initMenu directly.
But the real question is: How would one remove the hook again? Answer: you really cannot, as other add-ons might have hooked the same thing after you and unhooking would also unhook the other add-ons. But you need to get rid of the reference, or else the add-on will leak memory when disabled/uninstalled. Well, you could fight with Components.utils.makeObjectPropsNormal, but that doesn't really help with closed-over variables. So lets avoid closures... Hmm... You'd need some kind of messaging, e.g. event listeners or observers... and we're back to square one.
Also I wouldn't call this sane compared to
document.getElementById("contentAreaContextMenu").addEventListener(...)
I'd call it "overkill for no measurable benefit".
Overriding onpopupshowing=
One could override the <menupopup onpopupshowing=. Yeah, that might fly... Except that other add-ons might have the same idea, so welcome to compatibility hell. Also this again involves pushing stuff into the window, which causes cross-compartment wrappers, which makes things error-prone again.
Is this a solution? Maybe, but not a sane one.
What else?
Not much, really.
Yes this is absolutely possible.
Morat from mozillazine gave a great solution here: http://forums.mozillazine.org/viewtopic.php?p=13307339&sid=0700480c573017c00f6e99b74854b0b2#p13307339
function handleClick(event) {
window.removeEventListener("click", handleClick, true);
event.preventDefault();
event.stopPropagation();
var node = document.popupNode;
document.popupNode = event.originalTarget;
var menuPopup = document.getElementById("contentAreaContextMenu");
var shiftKey = false;
gContextMenu = new nsContextMenu(menuPopup, shiftKey);
if (gContextMenu.onImage) {
var imgurl = gContextMenu.mediaURL || gContextMenu.imageURL;
}
else if (gContextMenu.hasBGImage && !gContextMenu.isTextSelected) {
var imgurl = gContextMenu.bgImageURL;
}
console.log('imgurl = ', imgurl)
document.popupNode = node;
gContextMenu = null;
}
window.addEventListener("click", handleClick, true);
this gives you access to gContextMenu which has all kinds of properties like if you are over a link, or if you right click on an image, and if you did than gContextMenu.imageURL holds its value. cool stuff
This code here console logs imgurl, if you are not over an image it will log undefined

Run a method everytime value changes

I wonder if any of you guys can help me with what I think is observing problem.
I have an element (svg to be more specific) that I want to update every time a value somewhere is changed.
I have variable:
GetThreadTree().treeBoxObject.getFirstVisibleRow() that initially is 0. I want to run a function updateCanvas() every time value of GetThreadTree().treeBoxObject.getFirstVisibleRow() changes.
What I have is:
canvas.observe(GetThreadTree().treeBoxObject.getFirstVisibleRow(), "scroll", updateCanvas());
But it calls updateCanvas() only once, when it's called for the first time, and for some reason does not execute the code that is after it. I checked error console and nothing is there.
Any ideas?
You logic is all in the wrong place. When you update your value what ever it is that needs to be done in one place by a function, something like treeBoxObject.IncrementRow() or similar.
Then you can have that function fire an event, like onTreeBoxRowIncremented. That event is what you listen out for, when that changes then you can do your check and update whatever you like.
Excuse the weird function names just trying to use what you have.
One way of solving this problem is:
var registeredRow = 0;
function checkRow(){
var row = GetThreadTree().treeBoxObject.getFirstVisibleRow();
if (registeredRow != row) {
registeredRow = row;
updateCanvas();
}
window.setTimeout(checkRow, 1);
}
And before checkRow is called:
registeredRow = GetThreadTree().treeBoxObject.getFirstVisibleRow();
checkRow();
But it's not the most elegant solution but it works ;)

Categories