Javascript Node.AppendChild() in loop losing elements - javascript

I have a basic function that is used to remove items from one div and place them in another (drag and drop widget). When moving multiple items, the appendchild function seems to be altering the list in place and losing elements.
function resetm2mAlerts(dbField) {
selectBox = document.getElementById(`${dbField}_selected`).childNodes; //List of nodes to remove
availBox = document.getElementById(`${dbField}_available`); //destination of nodes
console.log(selectBox); //validating contents before loop
for(index of selectBox){
availBox.appendChild(index); //appending items to destination
}
}
Relevant HTML:
<div class="form-group">
<label for="id_default_operating_schedule">Day Of Week</label>
<div id="default_operating_schedule" class="col-md-12 nopadding twocolumndroppable">
<div id="default_operating_schedule_selected" class="col-md-6 droppable available ui-droppable">
<div id="default_operating_schedule_code_1" class="col_md_5 draggable bg-primary ui-draggable ui-draggable-handle" data-id="1">Sunday</div>
<div id="default_operating_schedule_code_4" class="col_md_5 draggable bg-primary ui-draggable ui-draggable-handle" data-id="4">Wednesday</div>
</div>
<div id="default_operating_schedule_available" class="col-md-6 droppable ui-droppable">
<div id="default_operating_schedule_code_2" class="col_md_5 draggable bg-primary ui-draggable ui-draggable-handle" data-id="2">Monday</div>
<div id="default_operating_schedule_code_3" class="col_md_5 draggable bg-primary ui-draggable ui-draggable-handle" data-id="3">Tuesday</div>
<div id="default_operating_schedule_code_5" class="col_md_5 draggable bg-primary ui-draggable ui-draggable-handle" data-id="5">Thursday</div>
<div id="default_operating_schedule_code_6" class="col_md_5 draggable bg-primary ui-draggable ui-draggable-handle" data-id="6">Friday</div>
<div id="default_operating_schedule_code_7" class="col_md_5 draggable bg-primary ui-draggable ui-draggable-handle" data-id="7">Saturday</div>
</div>
</div>
</div>
Contents of selectBox simple example: [<div element 1>, <div element 4>]
after the first (and only) iteration of the for loop, <div element 1> is moved to availBox, the loop terminates and the contents of selectBox remains [<div element 4>].
I have attempted an indexed forloop which provides either index out of bounds or the same result of items being left in select box.
A reverse iteration of an indexed forloop moves everything as necessary, the major focus of the question is more why does append alter the list dynamically and cause this side effect.

You need to cast the child nodes to an array so it's iterable.
function resetm2mAlerts(dbField) {
selectBox = document.getElementById(`${dbField}_selected`).childNodes; //List of nodes to remove
availBox = document.getElementById(`${dbField}_available`); //destination of nodes
[...selectBox].forEach(child => availBox.appendChild(child));
}

Related

How do I set data attribute value by the index of element in jQuery?

I have multiple sibling elements, some that are added dynamically and others manually or using JS. Each one has a data attribute of "data-sec_number" with a number value that increases by one for each new element down the page. At the moment I am manually writing these in.
E.g.
<div class="container">
<div class="row resource_sec" data-sec_number=1>
<div class="content sec_number" data-sec_number=1></div>
</div>
<div class="row resource_sec" data-sec_number=2>
<div class="content sec_number" data-sec_number=2></div>
</div>
<div class="row resource_sec" data-sec_number=3>
<div class="content sec_number" data-sec_number=3></div>
</div>
</div>
How can I get the value of attribute "data-sec_number" to match the index of the element .resource_sec within the .container element? E.g. the 3rd .resource_sec element will always have a "data-sec_number" value of 3.
I also have the child element of .resource_sec called .sec_number, which has the same data attribute that should mirror the parent.
The JS I have been trying to use is:
var items = $('.container .resource_sec');
var lastItem = $('.container .resource_sec:last');
var index = items.index(lastItem);
$('.resource_sec').attr('data-sec_number', index);
$('.sec_number').attr('data-sec_number', index);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div class="row resource_sec" data-sec_number=1>
<div class="content sec_number" data-sec_number=1></div>
</div>
<div class="row resource_sec" data-sec_number=2>
<div class="content sec_number" data-sec_number=2></div>
</div>
<div class="row resource_sec" data-sec_number=3>
<div class="content sec_number" data-sec_number=3></div>
</div>
</div>
The meaning of this line
$('.resource_sec').attr('data-sec_number', index);
is to set the attribute value. to select (query) a specific index please use:
$('.resource_sec[data-sec_number=' + index + ']')
or
$(`.resource_sec[data-sec_number=${index}]`)
If you support ES6
UPDATE:
To add a new element in jQuery you may use:
var container = $('.container')
var newElement = $('<div class="row resource_sec"></div>').appendTo(container)
Then, you can fetch the new element index by passing it to the index method,according to Jquery documentation:
If .index() is called on a collection of elements and a DOM element or jQuery object is passed in, .index() returns an integer indicating the position of the passed element relative to the original collection.
var index = container.index(newElement)
To append index to the new element attribute:
newElement.attr('data-sec_number', index)

Javascript: Add margin-top to an object, object with other objects is inside object A, multiple objects A on page

I have an object (div) which has four elements (with classes) inside.
What I am trying to do: When height of the element A is lower than 40px then add to element B 20px margin-top.
However there are many objects on the page.
<div class="list">
<div class="block">
<div class="list-name" style="height: 20px">element A</div>
<div class="div1">another div here</div>
<div class="div2">another div here</div>
<div class="product-image-container">element B</div>
</div>
<div class="block">
<div class="list-name" style="height: 50px">element A</div>
<div class="div1">another div here</div>
<div class="div2">another div here</div>
<div class="product-image-container">element B</div>
</div>
(...)
</div>
So far I tried this. Nevertheless it works only if there are only two elements in the div.
$(document).ready(function(){
$('.list-name').each(function(index, obj){
console.log($(obj).height())
if($(obj).height() < 40)
{
$(obj).next('.product-image-container').css('margin-top', 20)
}
});
});
Any help is much appreciated.
Rob
next() only works for the very next element.
Use siblings() instead which includes all elements on same level
$(obj).siblings('.product-image-container').css('margin-top', 20)
Or another commonly used pattern is to look up to a common ancestor and find() within that parent
$(obj).closest('.block').find('.product-image-container').css('margin-top', 20)

jQuery Advanced Selector for Multiple Elements

I am trying to add hidden class on two elements.
$("#grid").closest(".ui-jqgrid").addClass("hidden");
$("#grid").closest(".ui-jqgrid").prev("h3").addClass("hidden");
For the following markup,
<div class="col-sm-12">
<h3>Heading 1</h3>
<div class="ui-jqgrid hidden" id="" dir="rtl" style="width: 1035px;">
<div class="jqgrid-overlay ui-overlay" id=""></div>
<div class="loading row" id="" style="display: none;"></div>
<div class="ui-jqgrid-view" role="grid" id="">
<div class="ui-jqgrid-titlebar ui-jqgrid-caption-rtl" style="display: none;">
<a role="link" class="ui-jqgrid-titlebar-close HeaderButton " title="" style="left: 0px;">
<span class="ui-jqgrid-headlink glyphicon glyphicon-circle-arrow-up"></span></a>
<span class="ui-jqgrid-title"></span>
</div>
<div class="ui-jqgrid-bdiv">
<div style="position: relative;">
<div></div>
<table id="grid" class="ui-jqgrid-btable">
</table>
</div>
</div>
</div>
</div>
</div>
Can I do this in one line without finding the closest(".ui-jqgrid") twice? I don't want to add more classes to markup and also I don't want to use a JS variable here. Can anybody with strong selectors suggest a solution?
Just chain the methods. jQuery returns the manipulated object every time you call a method, so you can just call the next method on the returned object.
This is known as chaining
$("#grid").closest(".ui-jqgrid").addClass("hidden").prev("h3").addClass("hidden");
Explanation
$("#grid") // returns #grid
.closest(".ui-jqgrid") // returns .ui-jqgrid
.addClass("hidden") // returns .ui-jqgrid
.prev("h3") // returns the previous h3 element of .ui-jqgrid
.addClass("hidden"); // returns the h3
UPDATE (Chained and no need for new class)
// Select the closest '.ui-jqgrid', find its parent and hide it's direct children ('h3' and '.ui-jqgrid' div)
$('#grid').closest('.ui-jqgrid').parent().children().addClass('hidden');
You can do this:
$("#grid").closest('.col-sm-12').find(".ui-jqgrid, h3").addClass("hidden");
As you have the id of the grid #grid the traverse up at the wrapper element of all then use .find() method which can take multiple comma separated selectors then add the class on the found elements.
As per your comment you can change to this:
$("#grid").closest('[class^="col-sm"]')

Dragula drop in multiple divs

I implemented drag 'n' drop on my application using Dragula, but I need to set it so I can drop my elements to more than one div.
I tried to use a class to achieve it, but it just activates the first one of my divs.
THe HTML:
<div role="presentation" class="table table-striped">
<div class="files" id="upload-file"></div>
</div>
<div class="col-md-4">
<div class="drag-container">Test 1</div>
<div class="drag-container">Test 2</div>
<div class="drag-container">Test 3</div>
</div>
The JS calling Dragula:
dragula([document.querySelector('#upload-file'), document.querySelector('.drag-container')]);
How can I drop te items to more than one div?
You can define an array containers:
var containers = $('.drag-container').toArray();
containers.concat($('#upload-file').toArray());
And then, pass the var 'containers' to dragula initializer:
dragula(containers, {
isContainer: function (el) {
return el.classList.contains('drag-container');
}
});
For those without jQuery or $ you can use:
dragula([].slice.call(document.querySelectorAll("drag-container")));
This converts the NodeList to an array and passes it to dragula.
querySelector only returns a single element, you would need to use querySelectorAll in order to get a list of elements. Not sure how dragula would handle getting a two element array, with the second element being an array. You may want to generate the array first, add the '#upload-file' to that array, and then pass the array. Or you could generate the array after initializing, and cycle through the array, adding the elements dynamically using something like "drake.containers.push(container);". I got this construct directly from the github site https://github.com/bevacqua/dragula
HTML:
<div className="wrapper">
<div className="container col-md-4" id="A">
<div className="panel panel-white">
</div>
<div className="panel panel-white">
</div>
</div>
<div className="container col-md-4" id="B">
<div className="panel panel-white">
</div>
<div className="panel panel-white">
</div>
</div>
</div>
JS:
dragula([document.querySelector('#A'), document.querySelector('#B')]);
You can drag each item in one column to the other. You can add more columns with the class name of container.
If you want to specify a custom class name, you should define option like this :
Option:
let options = {
isContainer: (el) => {
return el.classList.contains('drag-container'); // The immediate elements in this container will be draggable
}

How do I bind each child of one element to the child of another element?

There is a bootstrap carousel that is changing automatically:
<div id="carousel">
<div class="active"> </div>
<div class=""> </div>
<div class=""> </div>
<div class=""> </div>
</div>
Only one div is set to active at a time, and it cycles through like that.
How can I bind all of carousel's children to another element so that it can set it's specific child to be active as well.
For example:
<div id="copy-cat">
<li class=""> </li>
<li class=""> </li>
<li class=""> </li>
<li class=""> </li>
</div>
<div id="carousel">
<div class="active"> </div>
<div class=""> </div>
<div class=""> </div>
<div class=""> </div>
</div>
How can I bind copy-cat to have the same child active as carousel? Keep in mind, I don't want to copy all of the class attributes- just detect that one is active, and set that same numbered child to active also.
I'd use Bootstrap's slid event callback:
http://jsfiddle.net/isherwood/6xF7k/
$('#carousel').on('slid.bs.carousel', function () {
// get the index of the currently active panel
var myIndex = $(this).find('div.active').index();
// set the element with that index active, deactivating the others
$('#copy-cat').find('li').removeClass('active').eq(myIndex)
.addClass('active');
})
Notice that I changed #copy-cat to ul to make it valid HTML.
http://getbootstrap.com/javascript/#carousel-usage
Given an element x, you can get the index of x in its parent's .children() by calling x.index(). In your example, $("div.active").index() would return 0, the div following would return 1, etc. So you should be able to do something like the following:
$("#copy-cat :nth-child("+(1+$("#carousel div.active").index()) + ")").addClass("active");
(nth-child is 1-index-based, so you need to add 1. You could also call eq(), which is 0-based.)
See this jsFiddle for a rough example.
But this is only a general answer. If there are Bootstrap methods for this purpose, by all means use them.
The Bootstrap carousel has some events that you can tap into.
Information about the events can be found in the "Events" section under the Carousel documentation.
I would recommend doing something like:
<div id="copy-cat">
<div id="copy-1" class="active"></div>
<div id="copy-2" class=""></div>
<div id="copy-3" class=""></div>
<div id="copy-4" class=""></div>
</div>
<div id="carousel" class="carousel slide">
<div class="carousel-inner">
<div class="item active" data-transition-to="#copy-1">
<p>something1</p>
</div>
<div class="item" data-transition-to="#copy-2">
<p>something2</p>
</div>
<div class="item" data-transition-to="#copy-3">
<p>something3</p>
</div>
<div class="item" data-transition-to="#copy-4">
<p>something4</p>
</div>
</div>
</div>
Add a listener as described in the event section, and inspect the relatedTarget.
Use the data-transition-to to figure out the class to add the active class to.
Javascript to support this approach:
$('#carousel').carousel({
interval:false //or whatever options you want
});
$('#carousel').on('slide.bs.carousel',function (event) {
$('#copy-cat .active').removeClass('active');
$(event.relatedTarget.dataset.transitionTo).addClass('active');
});
$('#carousel').carousel('next');
This is is the fiddle ...I disabled transitioning & you will have to inspect the DOM to see that the copy-cat div is being changed.

Categories

Resources