jQuery Replace With only works once for an object reference - javascript

I'm having a strange issue with replaceWith (or more likely with object referencing).
I am trying to create a kind of table of rows that either have empty slots or full slots. As a demonstration I made this simple fiddle. http://jsfiddle.net/Ltxtvyn3/3/ In this fiddle 4 empty slots are initialized. Then one is filled. Then the same one should be emptied. But instead it is remaining filled. It is as if I can only use replaceWith once, or I am not understanding something about my object references.
HTML
<div class = "slot empty">Empty</div>
<div class = "slot full">Full</div>
<div class = "wrapper"></div>
CSS
.slot{
width:50px;
height:50px;
display:none;
}
.empty{
background-color:red;
}
.full{
background-color:blue;
}
Javascript
var wrapper = $('.wrapper');
var empty = $('.slot.empty');
var full = $('.slot.full');
var slots = {};
for(var i = 0; i < 4; i++){
slots[i] = empty.clone().show();
wrapper.append(slots[i]);
}
function fillSlot(id){
slots[id].replaceWith(full.clone().show());
}
function emptySlot(id){
slots[id].replaceWith(empty.clone().show());
}
fillSlot(1);
emptySlot(1);
I am hoping that the object var slots maintains a reference to the divs and I'm not sure if it is doing that or not.

No, it's not keeping a reference, but you can fix this pretty easily.
Here's some running code:
var wrapper = $('.wrapper');
var empty = $('.slot.empty');
var full = $('.slot.full');
for(var i = 0; i < 4; i++){
wrapper.append(empty.clone().show());
}
function fillSlot(id){
$(".wrapper .slot").eq(id).replaceWith(full.clone().show());
}
function emptySlot(id){
$(".wrapper .slot").eq(id).replaceWith(empty.clone().show());
}
fillSlot(1);
setTimeout(function() {
emptySlot(1);
}, 2000);
.slot{
width:50px;
height:50px;
display:none;
}
.empty{
background-color:red;
}
.full{
background-color:blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class = "slot empty">Empty</div>
<div class = "slot full">Full</div>
<div class = "wrapper"></div>

Thanks for the answers. I know understand why the object doesn't keep a reference, and I really wanted that to be the case. I simply added a wrapper slot and then I will affect the contents of the wrapper. That way I always have a reference to the slot.
HTML
<div class="slot-content empty">Empty</div>
<div class="slot-content full">Full</div>
<div class = "slot"></div>
<div id="wrapper"></div>
Javascript
var wrapper = $('#wrapper');
var slot = $('.slot');
var empty = $('.slot-content.empty');
var full = $('.slot-content.full');
var slots = {};
for(var i = 0; i < 4; i++){
slots[i] = slot.clone().show();
slots[i].html(empty.clone().show());
wrapper.append(slots[i]);
}
function fillSlot(id){
slots[id].html(full.clone().show());
slots[id].find('.slot-content').html('hello');
}
function emptySlot(id){
slots[id].html(empty.clone().show());
}
fillSlot(1);
emptySlot(1);
fillSlot(2);

UPDATED
Your code work fine just if you change the selection method and you don't want slots list no more.
Replace :
function fillSlot(id){
slots[id].replaceWith(full.clone().show());
}
function emptySlot(id){
slots[id].replaceWith(empty.clone().show());
}
BY :
function fillSlot(id){
wrapper.children().eq(id).replaceWith(full.clone().show());
}
function emptySlot(id){
wrapper.children().eq(id).replaceWith(empty.clone().show());
}
Selecting directly from wrapper what means selecting from fresh DOM. that will fix the problem, take a look at updated fiddle bellow.
Updated JSFiddle

The problem is slots[i] isn't pointing to the div - so replaceWith won't pick the right item. Update the loop as follows (adding slots[i] = wrapper.find(':last-child') ):
for(var i = 0; i < 4; i++){
slots[i] = empty.clone().show();
wrapper.append(slots[i]);
slots[i] = wrapper.find(':last-child')
}
Actually this may make the code a little easier to understand (replace loop with this instead)
for(var i = 0; i < 4; i++){
wrapper.append(empty.clone().show());
slots[i] = wrapper.find(':last-child')
}
Tested and works on FF..

It's not keeping a reference to the DOM element. If you still want to use the array, then you can just repopulate the list every time you update one of its elements. Not terribly efficient, but I suppose it saves you from keeping state in the DOM.
function redraw() {
$('.wrapper').empty();
for(var i = 0; i < 4; i++){
wrapper.append(slots[i]);
}
}
JSFiddle

Related

Binding an event listener to multiple elements with the same class

I'm trying to apply the onclick event with JavaScript to the following elements:
<div class="abc">first</div>
<div class="abc">second</div>
<div class="abc">third</div>
If I click on the first element (with index [0]) then this works, but I
need this event applicable for all classes:
document.getElementsByClassName('abc')[0].onclick="function(){fun1();}";
function fun1(){
document.getElementsByClassName('abc').style.color="red";
}
.onclick does not expect to receive a string, and in fact you don't need an extra function at all.
However, to assign it to each element, use a loop, like I'm sure you must have learned about in a beginner tutorial.
var els = document.getElementsByClassName('abc');
for (var i = 0; i < els.length; i++) {
els[i].onclick = fun1;
}
function fun1() {
this.style.color = "red";
}
<div class="abc">first</div>
<div class="abc">second</div>
<div class="abc">third</div>
To expand on the solution provided by #rock star I added two small additions to the function. First it is better to add / reemove a class (with an associated style rule) to an element than directly applying the stylerule to the element.
Secondly - on the click event - this will now remove the red class (and therefore style) from the previously selected element and add it to the new element. This will allow only one element to be red at a time (in the original solution any element that was clicked would become red).
var els = document.getElementsByClassName('abc');
for (var i = 0; i < els.length; i++) {
els[i].onclick = fun1;
}
function fun1() {
var oldLink = document.getElementsByClassName('red')[0];
if(oldLink) {oldLink.classList.remove('red')};
this.classList.add('red');
}
.red {
color:red;
}
<div class="abc">first</div>
<div class="abc">second</div>
<div class="abc">third</div>
This works:
<body>
<div class="abc">first</div>
<div class="abc">second</div>
<div class="abc">third</div>
<script>
var elements = document.getElementsByClassName('abc');
for(var i = 0, max = elements.length; i < max; i += 1) {
var clickedElement = elements[i];
clickedElement.onclick=function (){
fun1(this);
};
}
function fun1(element){
element.style.color="red";
}
</script>
</body>

Smarter way to getElementsByClassName with function

I was wondering if there isn't a smarter way to work with classes between javascript and css. As I understander the "only" / most common way to select all elements with the same class is by making a for loop:
jsfiddle.net/JoshuaChronstedt/obk92sh6/2/
var elems = document.getElementsByClassName("helloClass");
for (var i = 0; i < elems.length; i++) {
elems[i].style.background = "red";
}
Wouldn't it be possible to create a function to hold the for loop? I'm a noob to js and can't seem to make it work:
jsfiddle.net/JoshuaChronstedt/obk92sh6/6/
function getClass(getClassName) {
var elems = document.getElementsByClassName("getClassName");
for (var i = 0; i < elems.length; i++) {
elems[i];
}
}
getClass("helloClass").style.background = "red";
getClass("helloClassTwo").style.background = "blue";
I guess what I am ultimately trying to do is find a more readable and more DRY way of editing elements by class names.
edit:
Thanks for the snippets. I have tried using some of the code that has been sugested. But it still doesn't seem to work:
function getClass(getClassName) {
Array.from(document.querySelectorAll('.' + '\'' + getClassName + '\'')).forEach(e => e);
}
getClass(helloClass).style.background = 'yellow';
getClass(helloClassTwo).style.color = 'red';
<div class="helloClass">
hello class
</div>
<div class="helloClass">
hello class
</div>
<div class="helloClassTwo">
hello class Two
</div>
<div class="helloClassTwo">
hello class Two
</div>
You can implement map function to iterate.map function iterates elements in array.
Hence here you need to change normal object to array first using Array.from() method
var elems;
function getClass(getClassfuncCont) {
return document.getElementsByClassName(getClassfuncCont);
}
elems = getClass("helloClass");
Array.from(elems).map(element=>element.style.background = "red");
Please refer working snippet.
var elems;
function getClass(getClassfuncCont) {
return document.getElementsByClassName(getClassfuncCont);
}
elems = getClass("helloClass");
Array.from(elems).map(element=>element.style.background = "red");
<div class="helloClass">
hello class
</div>
<div class="helloClass">
hello class
</div>
<div class="helloClass">
hello class
</div>
As mentioned in one of the comments, you're passing getClassName as string when you're supposed to pass it as variable. Taking away the double quote should make it work.
However, you won't be able to modify the style property the way you're doing it right now, because your function does not return the elements. If what you're trying to do is batch-changing the background color based on class name, I suggest adding the color name as a second variable:
//renaming the function so it's more representative
function colorBackgroundByClass(getClassName,color) {
var elems = document.getElementsByClassName(getClassName);
for (var i = 0; i < elems.length; i++) {
elems[i].style.background = color;
}
}
colorBackgroundByClass("helloClass","red");
colorBackgroundByClass("helloClassTwo","blue");

Javascript - Toggle Multiple Classes onclick

I am trying to toggle multiple classes onclick using vanilla Javascript. What i am trying to do is when a btn is clicked two classes to toggle with another two classes. I have 5 classes in total which are: .menu_btn , .main_nav, .btn_active, .container, .container_active. When i press the .menu_btn i would like the classes .main_nav to toggle with .btn_active and at the same time i would like to have the .container to toggle with .container_active. The class .container is the only one that has 5 elements of that class, the others are single. I have done this using jQuery but i would like to know the way using vanilla Javascript. Hopefully someone can help.
One thing to point out is when i console.log the .btn_active and .container_active i get back [ ] an empty array. Those 2 css classes are not assigned to any element of my project. They are existing only in the css and their purpose is for toggle.
Thanks
jQuery Code:
$(function(){
$(".menu_btn").on("click", function(){
$(".main_nav").toggleClass("btn_active");
$(".container").toggleClass("container_active");
});
});
Vanilla Javascript Code:
var menuBtn = document.getElementsByClassName("menu_btn");
var mainNav = document.getElementsByClassName("main_nav");
var btnActive = document.getElementsByClassName("btn_active");
var container = document.getElementsByClassName("container");
var containerActive = document.getElementsByClassName("container_active");
menuBtn.onclick = function(){
mainNav.classList.toggle(btnActive);
for ( index = 0; index <= container.lenght -1; index++ ){
container[index].classList.toggle(containerActive);
}
};
I have modified your script and created a fiddle so you see how it works: https://jsfiddle.net/eyrpdsc2/
The toggle accepts a string as a parameter, not a Node. So you need to pass 'btn_active' instead of btnActive. Also keep in mind that querySelectorAll returns a NodeList (not an array) so you cannot use forEach.
var menuBtn = document.querySelectorAll(".menu_btn");
var mainNav = document.querySelectorAll(".main_nav");
var container = document.querySelectorAll(".container");
for (var i = 0; i < menuBtn.length; ++i) {
menuBtn[i].addEventListener('click', toggleClasses);
}
function toggleClasses() {
var i = 0;
for (i = 0; i < mainNav.length; ++i) {
mainNav[i].classList.toggle('btn_active');
}
for (i = 0; i < container.length; ++i) {
container[i].classList.toggle('container_active');
}
}

How to display items of a particular div on mouseover

I have the div structure
<div id="navigate">
<div class="menu">
<div class="group">Mail</div>
<div class="item">Folders</div>
<div class="item">Messages</div>
</div>
<div class="menu">
<div class="group">Contacts</div>
<div class="item">Friends</div>
<div class="item">Work</div>
</div>
<div class="menu">
<div class="group">Setting</div>
<div class="item">General</div>
<div class="item">Account</div>
</div>
</div>
Right now all items are hidden, and only divs with class 'group' is shown. What I would like to do is if I mouse over a specific menu div, only items of that menu would appear.
Right now I have this code:
function initialise()
{
hideAllItems();
setMouseOvers();
}
function hideAllItems()
{
var nav = document.getElementById("navigate");
var items = nav.getElementsByClassName("item");
for(var i = 0; i < items.length; i++)
{
items[i].style.visibility = "hidden";
items[i].style.display = "none";
}
}
function setMouseOvers()
{
var nav = document.getElementById("navigate");
var menuArr = nav.getElementsByClassName("menu");
for(var x = 0; x < menuArr.length; x++)
{
var itemArrs = menuArr[x].getElementsByClassName("item");
/*var show = function(){ show(itemArrs); };
var hide = function(){ hide(itemArrs); };*/
menuArr[x].onmouseover=function(){ show(itemArrs); };
menuArr[x].onmouseout=function(){ hide(itemArrs); };
}
}
function show(itemArr)
{
for(var i = 0; i < itemArr.length; i++)
{
alert(itemArr[i].innerHTML);
itemArr[i].style.visibility = "visible";
itemArr[i].style.display = "block";
}
}
function hide(itemArr)
{
for(var i = 0; i < itemArr.length; i++)
{
itemArr[i].style.visibility = "hidden";
itemArr[i].style.display = "none";
}
}
And this works, thought it only displays General and Account no matter which menu I hover over. I vaguely understand whats going wrong, but I can't see anyway to fix it. Any ideas? I do not want to change the html structure (e.g. add ids, or create specific classes) if i can help it!
I know that you most probably are looking for a javascript solution, but you could use a simple CSS solution:
.group:hover ~ .item {
display: block;
}
Working Fiddle
But be aware that it is not supported by older IE (< 8) browsers SUPPORT. It depends on your target group if you want to use it.
Why not simply using CSS: DEMO
.menu .item{
display:none;
}
.menu:hover .item{
display:block;
}
As you ask for an JavaScript Only solution (no change in HTML/css) i suggest the following:
The problem is using "itemArrs" in an anonymous function, as only the latest written "itemArrs" is used for all of them, use "this" instead.
for example:
...
groups[x].onmouseover=function(){ show(this); };
...
and
function show(item) {
var items = item.parentNode.getElementsByClassName("item");
...
A complete JS-only solution that works can be found here:
http://jsfiddle.net/Wn4d4/3/

Getting an element by id every time I loop through

So my issue is, whenever I run this loop, it only grabs the changes to the element on the first flip through. Is there a way to make it make those changes every time?
<script>
for ( i=0; i<5; i++){
document.write('<div id=\"blah\" >text</div>');
var b = document.getElementById("blah");
b.style.width ="200px";
b.style.backgroundColor="yellow";
}
</script>
id has to be unique in a document. hence the issue. The DOM would return only 1 node even if there are multiple matches.
You can do something like this:
for (var i=0; i<5; i++){
var div = '<div class="blah" >text</div>';
div.style.width ="200px";
div.style.backgroundColor="yellow";
document.write(div);
}
I have two ideas to overcome this. The first is to create the element, change its style, and append it.
<script type="text/javascript">
for (var i = 0; i < 5; i++) {
var div = document.createElement("div");
div.style.width = "200px";
div.style.backgroundColor = "yellow";
document.appendChild(div);
}
</script>
The other idea is that you don't need a reference to the DOM element, because you're only changing style, so you can apply the style with CSS. For example:
<style type="text/css">
div.something {
width: 200px;
background-color: yellow;
}
</style>
<script type="text/javascript">
for (var i = 0; i < 5; i++) {
document.write("<div class='something'>text</div>");
// Or use the createElement/appendChild approach from above,
// where you'd need to set the div.className property as "something"
}
</script>
You need to add the element to the DOM to be able to access it later.
for(var i=0;i<5;i++)
{
//I'm not sure if you are just trying to make these changes each iteration
//or create a new element each time. If you are trying to create a new element
//each time. I'd def consider going a diff route i.e. use classes.
var b;
if( i==0 ){
b = document.createElement("DIV");
b.id = "blah";}
else{
b = document.getElementById("blah");}
b.style.width ="200px";
b.style.backgroundColor="yellow";
}

Categories

Resources