I have a problem with JavaScript looping and DOM.
so I have a few div's, each has a background image defined by CSS, however when i rollover a text link, i wish for these background images to change, which ones will change depends on their class name and the link mouseover'ed
<div id="im1" class="web"></div>
<div id="im2" class="logo"></div>
<div id="im3" class="web"></div>
<div id="im4" class="logo"></div>
<div id="5" class="logo"></div>
web
so those are the divs with my link for the mouse over.
then to change these images i have some simple JavaScript, which works fine (if very long)....
function showweb() {
document.getElementById("im1").style.backgroundImage = "url('back/1col.png')";
document.getElementById("im2").style.backgroundImage = "url('back/2col.png')";
however, i wondered if there was a way i would condition by class name, and only change those with a certain class name, eg web, or logo. ive tried various ways and loops and things, but none seemed to work.
e.g
function showweb() {
for(i=0; i=5; i++){
url = "im" + i;
if(document.getElementById("url").className=="web"){
document.getElementById("url").style.backgroundImage = "url('back/"+ i +"col.png')";}
}
}
however this doesn't work, the divs just don't change..... am i doing something wrong? missing something? or doing it the completely wrong way?
all help appreciated, thanks in advance.
Edit: changed the "url" to url, my bad, that was very foolish, however still didnt work. i will try a few other ideas posted.
thanks everyone so far.
You don't need the quotes around url:
document.getElementById(url)
This may work (if the link to your images is right and presumed that by id="5" you mean id="im5". Play with it until it's right. Check your code thourougly, there were several errors (typos or worse 1) in your coding:
function showweb() {
for(var i=1; i < 6; i++){
var el = document.getElementById("im"+(i));
if(el && /web/.test( (el ||{}).className)){
el.style.backgroundImage = "url(back/"+ i +"col.png)";}
}
}
1 a few notes on that
id = "5" should supposedly be id ="im5"
i=0; i=5; i++ => i=5 should be i<5
i=0; i=5; i++ creates a global variable i. Use var i=0;...
given your id's, you should start with var i = 1
though not a real error, you don't need to surround the url value with apostrophes
to optimize call document.getElementById once and use it's result in the rest of the code
You're building the "url" variable, but then you pass the string constant "url" into the "getElementById()" function.
To get only elements of the desired class, you need node.getElementsByClassName('your_class'), where node can be document or some <div> selected by id, or any other node of the DOM tree.
Note: Remember that all methods that start with getElement will return single element, and those with getElements will return an array of elements, so this one returns an array which you need to iterate (even if there is only one element in it)
However, since you are working with DOM elements in JavaScript, you would save tons of time by using jQuery
You have a few of problem. First, you have quotes around the variable "url" so it's looking for an element with that id, which doesn't exist and thus fails when you attempt to access the className of a null lookup result. Second, your loop starts at 0, not one and you don't have a div with id "im0" so it would fail anyway when you attempt to get the className of that element, which also doesn't exist. Third, the check in your loop sets i to 5 instead of checking if it is less than or equal to 5. Your loop with thus continue forever, since 5 equates to true, which I'm sure is not what you want. Fourth, your code may break if you assign additional classes to the elements. Lastly, most of this sort of thing has already been done for you -- might I suggest you look into using a javascript framework, such as jQuery -- with the hover() method, as a better alternative than re-inventing the wheel if this isn't purely a learning exercise.
Related
Lets say I have an empty div:
<div id='myDiv'></div>
Is this:
$('#myDiv').html("<div id='mySecondDiv'></div>");
The same as:
var mySecondDiv=$("<div id='mySecondDiv'></div>");
$('#myDiv').append(mySecondDiv);
Whenever you pass a string of HTML to any of jQuery's methods, this is what happens:
A temporary element is created, let's call it x. x's innerHTML is set to the string of HTML that you've passed. Then jQuery will transfer each of the produced nodes (that is, x's childNodes) over to a newly created document fragment, which it will then cache for next time. It will then return the fragment's childNodes as a fresh DOM collection.
Note that it's actually a lot more complicated than that, as jQuery does a bunch of cross-browser checks and various other optimisations. E.g. if you pass just <div></div> to jQuery(), jQuery will take a shortcut and simply do document.createElement('div').
EDIT: To see the sheer quantity of checks that jQuery performs, have a look here, here and here.
innerHTML is generally the faster approach, although don't let that govern what you do all the time. jQuery's approach isn't quite as simple as element.innerHTML = ... -- as I mentioned, there are a bunch of checks and optimisations occurring.
The correct technique depends heavily on the situation. If you want to create a large number of identical elements, then the last thing you want to do is create a massive loop, creating a new jQuery object on every iteration. E.g. the quickest way to create 100 divs with jQuery:
jQuery(Array(101).join('<div></div>'));
There are also issues of readability and maintenance to take into account.
This:
$('<div id="' + someID + '" class="foobar">' + content + '</div>');
... is a lot harder to maintain than this:
$('<div/>', {
id: someID,
className: 'foobar',
html: content
});
They are not the same. The first one replaces the HTML without creating another jQuery object first. The second creates an additional jQuery wrapper for the second div, then appends it to the first.
One jQuery Wrapper (per example):
$("#myDiv").html('<div id="mySecondDiv"></div>');
$("#myDiv").append('<div id="mySecondDiv"></div>');
Two jQuery Wrappers (per example):
var mySecondDiv=$('<div id="mySecondDiv"></div>');
$('#myDiv').html(mySecondDiv);
var mySecondDiv=$('<div id="mySecondDiv"></div>');
$('#myDiv').append(mySecondDiv);
You have a few different use cases going on. If you want to replace the content, .html is a great call since its the equivalent of innerHTML = "...". However, if you just want to append content, the extra $() wrapper set is unneeded.
Only use two wrappers if you need to manipulate the added div later on. Even in that case, you still might only need to use one:
var mySecondDiv = $("<div id='mySecondDiv'></div>").appendTo("#myDiv");
// other code here
mySecondDiv.hide();
if by .add you mean .append, then the result is the same if #myDiv is empty.
is the performance the same? dont know.
.html(x) ends up doing the same thing as .empty().append(x)
Well, .html() uses .innerHTML which is faster than DOM creation.
.html() will replace everything.
.append() will just append at the end.
You can get the second method to achieve the same effect by:
var mySecondDiv = $('<div></div>');
$(mySecondDiv).find('div').attr('id', 'mySecondDiv');
$('#myDiv').append(mySecondDiv);
Luca mentioned that html() just inserts hte HTML which results in faster performance.
In some occassions though, you would opt for the second option, consider:
// Clumsy string concat, error prone
$('#myDiv').html("<div style='width:'" + myWidth + "'px'>Lorem ipsum</div>");
// Isn't this a lot cleaner? (though longer)
var newDiv = $('<div></div>');
$(newDiv).find('div').css('width', myWidth);
$('#myDiv').append(newDiv);
Other than the given answers, in the case that you have something like this:
<div id="test">
<input type="file" name="file0" onchange="changed()">
</div>
<script type="text/javascript">
var isAllowed = true;
function changed()
{
if (isAllowed)
{
var tmpHTML = $('#test').html();
tmpHTML += "<input type=\"file\" name=\"file1\" onchange=\"changed()\">";
$('#test').html(tmpHTML);
isAllowed = false;
}
}
</script>
meaning that you want to automatically add one more file upload if any files were uploaded, the mentioned code will not work, because after the file is uploaded, the first file-upload element will be recreated and therefore the uploaded file will be wiped from it. You should use .append() instead:
function changed()
{
if (isAllowed)
{
var tmpHTML = "<input type=\"file\" name=\"file1\" onchange=\"changed()\">";
$('#test').append(tmpHTML);
isAllowed = false;
}
}
This has happened to me . Jquery version : 3.3.
If you are looping through a list of objects, and want to add each object as a child of some parent dom element, then .html and .append will behave very different. .html will end up adding only the last object to the parent element, whereas .append will add all the list objects as children of the parent element.
I am trying to write a ToDoList with JavaScript.
I have an input-element. Whenever I type something and press enter, it creates a new fieldset(in my example its a fieldset but it can also be a Div) with the class name ".fieldListClass" and a P-Tag as a child of fieldset. the P-tag innerHTML is the the value of input. I used Click-EventListener for that.
After each click, I assigned the query selector of all .fieldListClass to a nodeList "fieldListQuery". I even converted this nodeList into an Array but no result.
Now I want to create an addEventListner but outside the previous one. it should be a new one. And It should be a click-EventListener for all fieldListQuery which where created inside the previous function.(this part is at the bottom of my code)
When I click on it something should happen like removing the current target etc. But it wont work because outside the function it always says that this variable is undefined. I don't get it because I declared it global outside of the function.
I don't want to use DOMNodeInserted or MutationObserver yet for detecting changes inside the DOM. Simple because the first one is not recommended anymore it and the last one I have no idea how to use it. Many people saying that this is not a safe way.
Any Help please?
let addDiv = document.createElement("div"); addDiv.id = "addDivId";
let listDiv = document.createElement("div"); listDiv.id = "listDivId";
let inputText = document.createElement("input"); inputText.id = "inputTextId";
let fieldList; // = document.createElement("fieldset");
let fieldDiv; // = document.createElement("div");
let fieldDivP; // = document.createElement("P");
let fieldListArr;
let fieldListQuery;
document.body.appendChild(addDiv);
addDiv.appendChild(inputText);
document.body.appendChild(listDiv);
inputText.addEventListener("keypress", event => {
if (event.key === "Enter") {
fieldList = document.createElement("fieldset");
fieldDiv = document.createElement("div");
fieldDivP = document.createElement("P");
listDiv.appendChild(fieldList);
fieldList.className = "fieldListClass";
fieldList.appendChild(fieldDiv);
fieldDiv.appendChild(fieldDivP);
fieldDivP.innerHTML = inputText.value;
fieldListQuery = document.querySelectorAll(".fieldListClass") ;
}
})
fieldListQuery.forEach(element => { // <- it say fieldListQuery is undefined.
fieldListQuery.addEventListener("click", e => {
e.currentTarget.innerHTML="test";
})
});
ยดยดยด
Since I offered critique of your approach, I thought it is only fair I at least try to offer you some code that accomplishes (on the overall level, in light of absence of much detail about your solution) something along of what you have.
First off, I think creating trees of elements through a script when other solutions are more viable, tends to show an anti-pattern. Your script is invariably loaded in the context of an HTML document, which may already contain a lot of useful markup -- including an input field (that you were creating with createElement). If the input field is a "constant" there is no need to waste code on creating it -- just put it in your markup.
Second, even for elements or hierarchies of elements that are created "on demand" -- as a reaction to an event or however else -- it typically is much more readable and manageable to use templates. As a fallback -- if template cannot be used for some reason -- using innerHTML to create entire element trees is actually an appealing and more readable option than a lot of "boilerplate" containing createElement, appendChild, etc.
Third, you should always try to see if you can have your interactive controls be part of a form. I won't go into all reasons to do so, but suffice to say it helps user agents that screen-read content and for other accessibility systems, to name one. There are exceptions to this rule, but I don't recall looking at code where a control should not be part of a form -- so the rule is a good one.
Here is a proof-of-concept bare-bones to-do application:
<html>
<head>
<script>
function submit_create_todo_item_form() {
const new_todo_fragment = document.getElementById("todo-item-template").content.cloneNode(true);
new_todo_fragment.querySelector(".body").textContent = document.forms[0].elements[0].value;
document.body.appendChild(new_todo_fragment);
}
</script>
<template id="todo-item-template">
<div class="todo-item">
<p class="body"></p>
</div>
</template>
</head>
<body>
<form action="javascript: submit_create_todo_item_form()">
<input>
</form>
</body>
<html>
Take note that I use textContent instead of innerHTML to create content for a to-do item's body. innerHTML invokes the HTML parser and unless you plan to be typing hypertext into that single line of input field, innerHTML only costs you extra for no clear benefit. If you need to interpret the value verbatim, textContent is instead exactly what's needed. So, approach your solution with that in mind.
I hope this is useful, I worked with what I thought I had.
I know it is possible to change different parts of an element such as class, value and html content.
What I am looking for is a way to change the values of the function call itself.
document.getElementById(id).onclick = "javascript:FUNCTION('"+id+"','0');";
The second pass value is what I will be changing. So value "0" can be for example "1" or "2"
Is this possible?
EDIT:
In case this gets going down the wrong road.....
This is what I will be changing with that code.
So then the page ( and script ) is ready to run again with new values
<div id="id" class="stuff" onclick="javascript:function('id','0');" style="cursor:pointer;">
hope that clarifies things a bit more.
RE-EDIT:
Well seems this might not be possible. So will start looking at ways round.
It depends on what you are trying to do, but here is a way of doing it which would not even require the changing of the function.
var myValue = 0
document.getElementById("test").onclick = function (aEvent) {
myValue++;
alert(myValue);
}
See fiddle: http://jsfiddle.net/AqZHZ/
I have been looking with no success to see if I can dynamically apply a css style to JSF component or div using javascript. Is this possible.
This is pseudo code
<div style="myJSStyleFunction("#{myBean.value}")"> stuff </div>
And the function would return something like "position:relative;left:25px;"
I've had no luck and maybe it can't be done but would like a second opinion.
Edit:
I'm trying to see if I can keep a separation / reduce the coupling between the presentation/view and the model/controller. This is for indenting commenting or product reviews (to nest replies to comments or reviews). The most I really want to track is an integer on how deep a reply is. First level = 0 second level = 1, and so on. So a comment or product review would be 0 deep, a reply to the comment or review would be 1 and so on.
Then in the EL I wanted to call a javascript function and do something like
<script>
myJSStyleFunction(depth){
if(depth<=5){
var nest=20*depth;
var style="position:relative;left:" + nest + "px;";
return style;
}
}
</script>
And then then say for a third level comment (a reply to a reply) it would look like this:
<div style="position:relative;left:40px;"> stuff </div>
where
#{myBean.value}
evaluates to 2
I suspect like Daniel says I'll have to tightly couple the view but I'd rather not have to. I'd think there has to be a way. But maybe not.
I don't know where there are cleaner solutions for this. However this is one suggestion.
Assume your page looks like below and myBean.getValue() method returns an integer.
<h:form id="frm">
<div style="#{myBean.value}"> div1 </div>
<div style="#{myBean.value}"> div2 </div>
</h:form>
So you can do something like this at 'window.onload'.
<head>
<script>
window.onload = function() {
var childList = document.forms['frm'].childNodes;
for(var i = 0; i < childList.length; i++) {
if(childList[i].nodeName == 'DIV') {
var _div = childList[i];
var depth = _div.getAttribute('style');
_div.setAttribute('style', 'position:relative;left:' +(depth *20)+ 'px;');
}
}
}
</script>
</head>
Note: 1. In above sample code I assume all the DIVs inside the form should be indented.
2. For IE you may need to use _div.style.setAttribute('cssText','position:relative;left:' +(depth *20)+ 'px;')
3. Another solution for your question is using <script> tags immediately after your divs and putting the js part inside them. In this way you don't have to use fake styling style="#{myBean.value}" or window.onload event because you can directly call #{myBean.value} in your script.
I decided to skip the javascript approach and settled on a simpler and I think cleaner method to create the dynamic css classes for my situation. I already capture/calculate the depth value for each comment when it is entered. So I am just returning that value in EL and concatenating it to a 'base name' for the css class like so:
<div class="indent_#{(comment.commentDepth le 5) ? comment.commentDepth : 5}" >
comment comment blah blah blah
</div>
"indent_" is the base name for the css class. So for a 0 level comment it will have a class="indent_0". A reply to that comment will have class="indent_1".
I use the ternary so that if there are lot of replies under a given comment it doesn't indent right off the right hand side of the page. Even though you can keep going deeper, it will only indent up to 5 levels.
For my case at the moment, this is a simpler and cleaner method of adding some dynamically generated css class names. Right now I have to define 6 classes for this in the css file, but perhaps I'll figure out how to nest the boxes but it isn't a priority this works just fine for me for now.
I have have multiple divs' with similar code within it, but also has a unique id within the main div that calls a toggleClass & slideToggle function. I'm trying to create a loop so that I do not have to write similar code for each element.
--- working code --- (where numeric value after the id would change)
$('#er1').click(function() {
$('#er1').toggleClass('but-extra-on');
$('#cr1').toggleClass('but-closerow-on');
$('#er1').next('div').slideToggle('slow');
return false;
});
-- not working code -- (I want to have functions for the click of #er1, #er2, #er3 etc.)
var count = 1;
while (count < 10){
var curER = 'er'+count;
var curCR = 'cr'+count;
$('#'+curER).click(function() {
$('#'+curER).toggleClass('but-extra-on');
$('#'+curCR).toggleClass('but-closerow-on');
$('#'+curER).next('div').slideToggle('slow');
});
count++;
}
* for some reason, when I use the while code, whenever I click #er1, #er2, #er3 etc.. only the event for #er9 toggles.
You can solve this problem, by using the $(this) selector for the one that you are clicking, and attaching an html data attribute to the div, specifying the other div that you want to change when you click it and selecting the other one with that... make sense.. probably not? Check out Solution 1 below.
The second solution is to use jQuery Event Data to pass the count variable into the event listener.
Solution 1: http://jsfiddle.net/Es4QW/8/ (this bloats your html a bit)
Solution 2: http://jsfiddle.net/CoryDanielson/Es4QW/23/
I believe the second solution is slightly more efficient, because you have slightly less HTML code, and the $(this) object is slightly smaller. Creating the map and passing it as event data, I believe, is less intensive... but... realistically... there's no difference... the second solution has cleaner HTML code, so you should use that.
Solution 3 w/ .slideToggle(): http://jsfiddle.net/CoryDanielson/Es4QW/24/
Edit: Updated solutions by passing in the selected elements instead of the selectors. Now each click event will not do a DOM lookup as it did before.
I've run into this problem before. I fixed it by extracting the event listener code into its own function like so:
for (var i = 1; i < 10; i++) {
attachClickEvent('er'+i, 'cr'+i);
)
function attachClickEvent(cr, er)
{
$('#'+er).click(function() {
$(this).toggleClass('but-extra-on');
$('#'+cr).toggleClass('but-closerow-on');
$(this).next('div').slideToggle('slow');
});
}