DOM does not update when element changes (VUE) - javascript

for a vue/mvc project i am making a page divided into html sections.
If the user clicks on a button a javascript function is called that changes the display properties of the sections so that only the clicked section is shown.
When the dom is created, it calls the function and correctly shows one section.
However when the button is clicked, the function is called again, but the dom does not change.
Here is the code for the created function:
created: function () {
var self = this;
var sectionElements = document.getElementsByTagName("section");
for (var i = 0; i < sectionElements.length; i++) {
self.sections.push({ isSelected: false, object: sectionElements[i] });
}
for (var i = 0; i < self.sections.length; i++) {
self.sections[i].isSelected = false;
}
this.showSelectedSection(0);
},
Here is the code of the javascript function.
showSelectedSection(index) {
for (var i = 0; i < this.sections.length; i++) {
if (i == index) {
this.sections[i].isSelected = true;
this.sections[i].object.style.display = "block";
}
else {
this.sections[i].isSelected = false;
this.sections[i].object.style.display = "none";
}
}
Does anyone know why this is happening and how i can fix it?
Any tips or help is greatly appreciated.

First of all, I don't totally get why you're using self = this in this example, seems like it's not necessary. Nevertheless that is not your problem. You're modifying an object inside an array, and you're doing this by accessing the index. Normally that would be ok, but vue is not aware of this change. Try either passing the direct reference to the object inside the array or add a deep watch to your array so vue can hear this changes and make the proper modifications to your DOM.

Related

JS -> Accessing a variable from another function

I'm new to this site and to programming in general and I'm a bit stuck on a school assignment.
For reference here is my code I'm currently working on: Pastebin
function initChests(){
let gameChests = document.getElementById('chests');
for(let i = 0; i < 3; i++) {
let singleChest = document.createElement('img');
singleChest.src = 'images/chest-closed.png';
singleChest.alt = 'A chest'
singleChest.style.marginRight = '20px';
gameChests.appendChild(singleChest);
}
}
function initChestEventListeners() {
singleChest.addEventListener('click', chestClicked);
}
function chestClicked(e){
console.log("hello");
}
Now to my problem. What I want to do is access the variable singleChest in my initChestEventListeners function without making the variable global.
I am aware I can just put the singleChest addEventListener inside the initChests function, but that's not quite what I want to do either. I want to make it work with the function structure I have if possible.
Is this something that is possible and if so, can someone please explain what I would need to do or redirect me to a guide that could help explain the solution?
Thank you in advance!
Kind regards.
One option is to add an event listener to the parent element instead, the gameChests, and on click, if the clicked element is an img, do something with it:
function initChests() {
const gameChests = document.getElementById('chests');
for (let i = 0; i < 3; i++) {
let singleChest = document.createElement('img');
singleChest.src = 'images/chest-closed.png';
singleChest.alt = 'A chest'
singleChest.style.marginRight = '20px';
gameChests.appendChild(singleChest);
}
}
function initChestEventListeners() {
const gameChests = document.getElementById('chests');
gameChests.addEventListener('click', chestContainerClicked);
}
function chestContainerClicked(e) {
if (!e.target.matches('img')) {
return;
}
console.log("hello", e.target);
}
initChests();
initChestEventListeners();
<div id="chests"></div>
This method of watching for events that bubble up to the parent element is called event delegation.

Warning not to make function within a loop

I've written a code to create modal windows for div container. Once the button is clicked, I get the button's number and display a related modal window. Tested, works on all browsers.
myModalContent = new tingle.modal();
var myBtn = document.querySelectorAll("button.project__btn");
for (var i = 0; i < myBtn.length; i++) {
myBtn[i].addEventListener("click", function () {
myModalContent.open();
if (this.hasAttribute("data-btn")) {
myModalContent.setContent(document.querySelector(".project" + this.getAttribute("data-btn") + "-modal").innerHTML);
} else {
myModalContent.setContent(document.querySelector(".project1-modal").innerHTML);
}
});
}
A js validator gives one warning "Don't make functions within a loop."
Read some posts related to this topic, especially that the function must be created outside of the loop, I created a function:
function handler(modalDiv, trigBtn, index){
modalDiv.open();
if (trigBtn[index].hasAttribute("data-btn")) {
modalDiv.setContent(document.querySelector(".project" + trigBtn[index].getAttribute("data-btn") + "-modal").innerHTML);
} else {
modalDiv.setContent(document.querySelector(".project1-modal").innerHTML);
}
}
Then called it from within a loop:
for (var i = 0; i < myBtn.length; i++) {
myBtn[i].onclick = handler(myModalContent, myBtn, i);
}
It doesn't seem to work properly, it displays a last modal window right after the web page loads. My understanding that the function must be connected with the click event listener, ie when a button is clicked, the modal window should pop up. Now, the modal window pops up without any click event. Could you give me an idea how to properly write a function? Or if I should just simply ignore this js validation warning or not.
Keep it simple! You do not have to change anything about your code but to move the function expression to a named function declaration outside of the loop body:
var myModalContent = new tingle.modal();
var myBtn = document.querySelectorAll("button.project__btn");
function myHandler() {
myModalContent.open();
if (this.hasAttribute("data-btn")) {
myModalContent.setContent(document.querySelector(".project" + this.getAttribute("data-btn") + "-modal").innerHTML);
} else {
myModalContent.setContent(document.querySelector(".project1-modal").innerHTML);
}
}
for (var i = 0; i < myBtn.length; i++) {
myBtn[i].addEventListener("click", myHandler);
}
The warning is trying to prevent a problem with "modified closures". If your function did anything with the variable i, then you'd find that the value of the variable i at the time when users click the button is always myBtn.length because that's the value it ends up with at the end of the loop.
This:
for (var i = 0; i < myBtn.length; i++) {
...
Is treated like this:
var i;
for (i = 0; i < myBtn.length; i++) {
...
Since you don't use i anywhere in your function, you're technically safe, but there's a possibility that other developers in the future could change the code and end up running into this problem.
In order to fix this code in the way it looks like you're trying to fix it, you'd need to have the handler function return a function itself.
myBtn[i].addEventListener("click", createHandler());
function createHandler() {
return function() {
myModalContent.open();
if (this.hasAttribute("data-btn")) {
myModalContent.setContent(document.querySelector(".project" + this.getAttribute("data-btn") + "-modal").innerHTML);
} else {
myModalContent.setContent(document.querySelector(".project1-modal").innerHTML);
}
};
}
This has the same effect as your working code, but prevents someone from trying to use i inside of the closure. If someone needs i there, they can add it to the createHandler's argument list, where it's not reusing the same variable for each pass through the loop.
Alternatively, if you can use modern versions of javascript, you can use the let keyword instead of var.
This:
for (let i = 0; i < myBtn.length; i++) {
...
Is treated more like how this code would work in a language like C#:
for (var _ = 0; _ < myBtn.length; _++) {
var i = _;
...
In other words, the scope of the i variable is internal to the for loop, rather than global to the function you're in.

pure javascript onclick becomes undefined after assigning functions scope ends

It's been a while since I wrote Javascript without jQuery, so please bear with me. I'm assuming I'm just doing something silly. I have this function that converts link urls to an internal representation that I use with a router I wrote.
Templater.prototype.replace_links = function() {
this.links = document.getElementsByTagName("a");
for (i = 0; i < this.links.length; i++) {
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF))) {
this.links[i].setAttribute(this.HREF, this.links[i].getAttribute("href"));
this.links[i].setAttribute("href", this.VOID);
this.links[i].onClick = function(self, link) {
return function() { self.router.go(link.getAttribute(self.HREF)); };
}(this, this.links[i]);
}
}
}
This function is called the first time when Templater is initialized. The first time it works correctly. However, I run it a second time after I append some html into the body of the document. I run it again just in case that appended html has links in it too:
<body>
<!-- arbitrary new html is loaded in here -->
Login <!-- becomes Login correctly -->
Home <!-- becomes Home correctly -->
</body>
When I console.log(this.links[0], this.links[0].onClick) after the function has been run but still within a Templater function, I get the correct html and then undefined for the onClick event:
Discover undefined
When I log the same to values within the replace_links scope, I get what I'm expecting. I.e. the function is shown:
Discover function () { self.router.go(link.getAttribute(self.HREF)); }
I was playing around with it some more and tried this way and got the same kind of thing.
Templater.prototype.replace_links = function() {
this.links = document.getElementsByTagName("a");
for (i = 0; i < this.links.length; i++) {
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF))) {
(function(self, link) {
link.setAttribute(self.HREF, link.getAttribute("href"));
link.setAttribute("href", self.VOID);
link.onClick = function() { self.router.go(link.getAttribute(self.HREF)); };
})(this, this.links[i]);
}
}
}
I console.log after the replace_link scope ends like before and this time I still get:
Discover undefined
I'd really appreciate any help and/or suggestions! Please let me know if I'm missing anything helpful.
The key points here have been treated as minor details.
I append some html into the body of the document
and
this.links[i].onClick = function(self, link) {
My point is, if you alter innerHTML, which I assume is the way you "append some html into the body of the document," the browser will serialize the DOM objects into HTML, do the string concatenation, and then parse it again. This results in new objects which no longer have the expandos, such as onClick. onClick is a custom property; you probably meant onclick anyway.
However, some of your changes will be serialized and parsed successfully, namely the setAttribute operations. Thus, when you run replace_links after the HTML appending, the
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF)))
check will treat the link as already replaced and not assign the onClick again.
Here's a fiddle that shows this in action. http://jsfiddle.net/k9d7b2ds/
UPDATE: Made some additional changes. The onclick event's default this object is always referencing the window object. You need to pass over the closure.
Check sample code here:
http://jsfiddle.net/y0443fz6/
Templater.prototype.replace_links = function() {
var that = this;
this.links = document.getElementsByTagName("a");
for (i = 0; i < this.links.length; i++) {
if (!(this.links[i].getAttribute("href") === this.VOID && this.links[i].getAttribute(this.HREF))) {
this.links[i].setAttribute(this.HREF, this.links[i].getAttribute("href"));
this.links[i].setAttribute("href", this.VOID);
this.links[i].onclick = function(self, link) {
return function() {
self.router.go(link.getAttribute(self.HREF));
};
}(that, this.links[i]);
}
console.log(this.links[i], this.links[i].onclick);
}
}
hope that helps. gl

Duplicating elements with Javascript and naming w/ counter

I think I've searched long enough to warrant asking this, and I hope I'm not missing something obvious, but I'm at my wits' end with this. I'm a complete JavaScript noob, and I'm having difficulty getting a script I found online to work correctly.
The project I was assigned was to make it so this form could be extended by clicking a button, and I thought I'd be able to accomplish it with HTML alone, but that doesn't seem possible. I found this script, and was able to get the duplication part of it to work:
http://www.quirksmode.org/dom/domform.html
However, the part of the script that's supposed to append a counter to the names of the fields isn't working, and therefore when the form is submitted, everything is recorded under the first form's name value. My guess is that that part of the script is trying to get the name of the wrong node, but I really don't know. Here's a shortened version of what I have. Ugly, but hopefully it gets the point across...
http://pastebin.com/nQhnXXKx
Let me know if I can clarify, and any help would be greatly, greatly appreciated!
Reorganizing the code, you could use something like this:
(function () {
"use strict";
var counter, init, addWorkshop, renameInputs, removeWorkshop;
counter = 0;
init = function () {
document.getElementById("moreWorkshops").onclick = addWorkshop;
addWorkshop();
};
addWorkshop = function () {
var clonedWorkshop, targetInsert;
counter++;
clonedWorkshop = document.getElementById("readroot").cloneNode(true);
clonedWorkshop.id = "";
clonedWorkshop.className = "";
clonedWorkshop.querySelector(".remover").onclick = removeWorkshop;
renameInputs(clonedWorkshop);
targetInsert = document.getElementById("writeroot");
targetInsert.parentNode.insertBefore(clonedWorkshop, targetInsert);
};
renameInputs = function (container) {
var children, i, j, cur, theName;
children = container.children;
for (i = 0, j = children.length; i < j; i++) {
cur = children[i];
if (cur.nodeName.toLowerCase() === "input") {
theName = cur.name;
if (theName) {
cur.name = theName + counter;
}
} else {
renameInputs(cur);
}
}
};
removeWorkshop = function () {
this.parentNode.parentNode.removeChild(this.parentNode);
};
window.onload = init;
}());
DEMO: http://jsfiddle.net/gAaxS/
Note that this is very structure-specific - for example, the this.parentNode.parentNode means that it has exactly two ancestors that you want to target. If you changed the HTML, you'd have to change the JS (which is usual).

JavaScript - Loop over all a tags, add an onclick to each one

I've got a list of links that point to images, and a js function that takes a URL (of an image) and puts that image on the page when the function is called.
I was originally adding an inline onlick="showPic(this.getAttribute('href'))" to each a, but I want to separate out the inline js. Here's my func for adding an onclick to each a tag when the page loads:
function prepareLinks(){
var links = document.getElementsByTagName('a');
for(var i=0; i<links.length; i++){
var thisLink = links[i];
var source = thisLink.getAttribute('href');
if(thisLink.getAttribute('class') == 'imgLink'){
thisLink.onclick = function(){
showPic(source);
return false;
}
}
}
}
function showPic(source){
var placeholder = document.getElementById('placeholder');
placeholder.setAttribute('src',source);
}
window.onload = prepareLinks();
...but every time showPic is called, the source var is the href of the last image. How can I make each link have the correct onclick?
JavaScript doesn't have block scope, so the closed variable ends up being whatever was last assigned to it. You can fix this by wrapping it in another closure:
function prepareLinks() {
var links = document.getElementsByTagName('a');
for(var i = 0; i < links.length; i++) {
var thisLink = links[i];
var source = thisLink.getAttribute('href');
if(thisLink.getAttribute('class') == 'imgLink') {
thisLink.onclick = (function(source) {
return function() {
showPic(source);
return false;
};
})(source);
}
}
}
Of course, you can make this one simpler and use this:
function prepareLinks() {
var links = document.getElementsByTagName('a');
for(var i = 0; i < links.length; i++) {
var thisLink = links[i];
if(thisLink.getAttribute('class') == 'imgLink') {
thisLink.onclick = function() {
showPic(this.href);
return false;
};
}
}
}
I believe this either breaks compatibility with IE5 or IE6, but hopefully you don't care about either of those =)
Minitech's answer should fix your problem, which is that the source variable is shared by all your onclick handlers
The way you're doing it is very wasteful, there's no need to set a separate handler for each link. Also, it won't work if any links are added dynamically. Event delegation is the way to go.
function interceptLinks() {
// Bad way to set onclick (use a library)
document.onclick = function() {
if (this.tagName.toUpperCase() != 'A' ) {
return;
}
// Bad way to check if it contains a class (use a library)
if (this.getAttribute('class') == 'imgLink') {
showPic(this.getAttribute('href'));
return false;
}
}
}
This is the age-old problem of event handlers inside of a loop that access an outer variable.
Your source variable is pulled off the scope chain at the time of the click event, and by then, it's been set to the last href attribute due to the iteration being finished.
You need to break the closure by doing one of two things.
The easiest but not supported by many browsers is to use let which lets you use block scope.
let source = thisLink.getAttribute('href');
jsFiddle. It worked in Firefox, but not Chrome.
In 2038, when we're dealing with the year 2038 problem and all browsers have implemented ES6, this will be the standard way to fix this problem.
A more difficult to understand and implement method that is compatible with all browsers is to break the closure with a pattern such as...
thisLink.onclick = (function(src) {
return function(){
showPic(src);
return false;
}
})(source);
jsFiddle.
Thanks for all the replies. Turns out I had diagnosed the problem incorrectly, sorry. Actually using a new var and annon. function to add an onclick on each loop iteration works (the passed href is correct). It was not working because I was getting at the a-tags by the "imgLink" class which I had removed from the HTML when I removed the inline onclick handlers (I get them with an ID on a parent now). Also I needed to use "return !showPic(this.href);" to stop the link being followed normally when clicked.
Working code:
function showPic(source){
var placeholder = document.getElementById('placeholder');
placeholder.setAttribute('src',source);
return true;
}
function prepareLinks() {
if(!document.getElementById('imgLinks')){ return false; };
var galLinks = document.getElementById('imgLinks');
var links = galLinks.getElementsByTagName('a');
for(var i=0; i<links.length; i++) {
var thisLink = links[i];
thisLink.onclick = function() {
return !showPic(this.href);
};
}
}
window.onload = function(){
prepareLinks();
}

Categories

Resources