Changing multiple different classes with javascript - javascript

I have been building a standalone web app for myself to learn about asp.net MVC4 and in so doing have been learning more about HTML 5, CSS and JavaScript. Needless to say I have learned alot and realized their is much more to learn. Before I ask any questions however I do a thorough search of this site and others to try and find things myself as I find I learn better by doing.
My issue here is just trying to find a way to simplify my JavaScript code if at all possible. I am not looking for JQuery at this time but will be looking into it in the future. For now strictly JavaScript.
Here is a portion of my JavaScript code that I am looking to truncate.
window.addEventListener('load', tap);
function tap() {
var elements = document.getElementByClassName('tap');
for ( var i = elements.length - 1; i >= 0; --i)
{
elements[i].innerHTML = '<img src="/Content/Images/tap_icon.jpg>")';
}
}
This code works great for what I intended it to do which is change the element class of 'tap' to a specific image no matter how many elements have the class name.
The problem is I have 8 different classes that I am doing this with one for each image that needs to be displayed. Shown below are 2 of the functions.
window.addEventListener('load', tap);
function tap() {
var elements = document.getElementsByClassName('tap');
for ( var i = elements.length - 1; i >= 0; --i)
{
elements[i].innerHTML = '<img src="/Content/Images/tap_icon.jpg">';
}
}
window.addEventListener('load', redMana);
function redMana() {
var elements = document.getElementsByClassName('red');
for (var i = elements.length - 1; i >= 0; --i)
{
elements[i].innerHTML = '<img src="/Content/Images/Red_Mana.jpg">';
}
}
Is there a way to bundle this all into one function or would it be better to keep them separate to maintain readability and easier updating if new file names are created for images/new classes added to the app.
Anything that would point me in the right direction would be much appreciated. Thank you all for you knowledge and assistance.
Edit:
Sorry I am coming back to this so late. I am in the midst of moving and starting a new job.
I have updated my JavaScript File to reflect the answer provided however when I run the application, only the first call fires.
Here is the full script for right now I will need to add further calls.
window.addEventListener('load', setImage);
function setImage(className, imageName) {
var elements = document.getElementByClassName(className);
for (var i = elements.length - 1; i >= 0; --i) {
elements[i].innerHTML = '<img src="/Content/Images/' + imageName + '.jpg">';
}
setImage('tap', 'tap_icon');
setImage('c2', '2cmana');
setImage('red', 'Red_Mana');
}
When I run the app the first call
setImage('tap', 'tap_icon');
fires, but then the following calls do nothing. I don't know what I am missing on this.

In order to not repeat the same code over and over you could add parameters to the function assuming it's the same function with different input:
//For Example:
function setImage(className, imageName) {
var elements = document.getElementsByClassName(className);
for (var i = elements.length - 1; i >= 0; --i)
{
elements[i].innerHTML = '<img src="/Content/Images/' + imageName + '.jpg">';
}
}
You could then call the function like this:
setImage('red', 'Red_Mana');
setImage('tap', 'tap_icon');

Since you mentioned you're looking to the future I'm proving an answer with ES6 features.
To simplify things you could abstract all similar codes and build a configuration-base code like so:
const baseImgPath = "/Content/Images/",
classImageMap = [
{ className: 'tap', imageName: 'tap_icon.jpg' },
{ className: 'red', imageName: 'Red_Mana.jpg' }
];
window.addEventListener('load', onWindowLoad);
function onWindowLoad() {
classImageMap.forEach(setImageToClass);
}
function setImageToClass(mapping) {
let { className, imageName } = mapping;
document
.getElementsByClassName(className)
.forEach(elem => elem.innerHTML = `<img src="${baseImgPath+imageName}"/>` );
}

Related

DOM does not update when element changes (VUE)

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.

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.

Storing variables used in greasemonkey script from website

On a certain homepage I visit I want to hide all links that I click. My idea was to use a Greasemonkey script like this:
var blocklist = JSON.parse(GM_getValue("blocklist"));
var as = document.getElementsByTagName('a');
var alength = as.length;
for(var i=0; i<alength; i++) {
var a = as[i];
if(blocklist.indexOf(a.href) >= 0) {
a.style.display='none';
} else {
a.setAttribute('onclick', 'alert("HELP"); return true;');
}
}
Inside the script I can call this, no problem:
blocklist = blocklist.concat('http://someurl');
GM_setValue("blocklist", JSON.stringify(blocklist));
But in the website itself (read where it says alert("HELP");) I cannot call this function because neither the function nor the blocklist do exist.
Is there a way to access the function from the website? (probably not?) Where else could I store the values to get them back on the next load of the website? The firefox browser is set to sanitize on shutdown, so can't use a:visited or similar.
Don't try to call GM_ functions from a webpage. (1) It's not directly possible, (2) it's a security risk, (3) it's almost never really necessary.
Never use onclick in a Greasemonkey script (or at all, really). A simple alert("HELP"); return true; might work, but anything more will crash and it's bad form anyway.
Also, if you use querySelectorAll versus getElementsByTagName, you can fine-tune what links you process, EG: document.querySelectorAll ("div.main a.user") -- which would get only those links with the CSS class user that were inside the <div> with the class main.
In this case, use addEventListener (or use jQuery) to handle the links so your script code would become like:
var blocklist = JSON.parse (GM_getValue ("blocklist") );
var targlinks = document.querySelectorAll ('a');
for (var J = targlinks.length - 1; J >= 0; --J) {
var targlink = targlinks[J];
if (blocklist.indexOf (targlink.href) >= 0) {
targlink.style.display = 'none';
} else {
targlink.addEventListener ('click', virginLinkHandler, false);
}
}
function virginLinkHandler (zEvent) {
var newURL = zEvent.target.href;
blocklist = blocklist.concat (newURL);
GM_setValue ("blocklist", JSON.stringify (blocklist) );
}
You should use localStorage so that you can retain your list on subsequent page loads. It's really not too different from GM_setValue.
localStorage.setItem("blocklist", JSON.stringify(blocklist));
var blocklist = JSON.parse(localStorage.getItem("blocklist"));

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 variables giving the correct value when stepped through using the console, but not otherwise

I have a variable in a JavaScript constructor that appears to be set to the correct value when stepped through using breakpoints. However, when run without breakpoints, the variable (supposed to be an array that I give it), comes up as an empty array in the console. I don't know whether or not using the get/set property of prototype, as described here. Also-- I'm working in webkit, so if someone could help explain to me why it isn't working there, I'd appreciate it. Thanks!
function Box(inElement){
var self = this;
this.element = inElement;
this.boxes = (function () {
var boxes = [];
for (var i = 0; i < inElement.childNodes.length; ++i) {
if (3 !== inElement.childNodes[i].nodeType) {
boxes.push(inElement.childNodes[i]);
}
}
return boxes;
})();
this.rotation = [-40,-20,0,20,40];
}
Box.prototype =
{
get rotation(){
return this._rotation;
},
set rotation(rotArray){
console.log('rotArray');
console.log(rotArray);
var thisrot;
this._rotation = rotArray;
for(var i=0; i<this.boxes.length; i++){
thisrot = rotArray.shift();
this.boxes[i].style.webkitTransform = 'rotateY(' + thisrot + 'deg) translateZ(170px)';
}
}
}
function loaded()
{
new Box(document.getElementById('area'));
}
window.addEventListener('load',loaded, true);
So, after some fiddling, I discovered that boxes.push(inElement.childnodes[i] is the problematic line. When commented out, the value comes out as expected.
You are removing all elements from your array in the loop inside of set rotation using shift. Arrays are passed by reference in JavaScript, not by value. If you want to create a copy of your array, you will have to use Array.slice:
this._rotation = rotArray.slice();

Categories

Resources