Moving element from one array into another - javascript

I've tried to make the following Stackblitz which use ng2-dragula. My problem laid in the moveback() and moveto() function which are supposed to move from one array into another when the element was selected. I was able to detect which element was selected but unable to move them completely (some element was left over).
Related Code:
moveback() {
let target = document.getElementsByClassName('target');
for (let i = 0; i < target.length; i++) {
if (target[i].className.includes('ex-over')) {
this.removeClass(target[i], 'ex-over');
this.data.push({ name: target[i].innerHTML });
this.target.splice(i, 1);
}
}
}
moveto() {
let target = document.getElementsByClassName('data');
for (let i = 0; i < target.length; i++) {
if (target[i].className.includes('ex-over')) {
this.removeClass(target[i], 'ex-over');
this.target.push({ name: target[i].innerHTML });
this.data.splice(i, 1);
}
}
}
I've found two related(1), related(2) question which asks similar thing but it's not working in my case. My approach was detecting either the element contains a certain class name then remove the class and move them to another array and remove it from the original array. But it's not working like intended.

Update your for condition as below.
for (let i = target.length - 1; i >= 0; i--) {...}
The issue with your logic is, If you select 2nd & 3rd element and apply moveto then for 2nd element it will work fine. But then in your actual this.target array will be changed. Now 3rd element will become 2nd because of your line this.target.splice(i, 1);. So when you move 3rd element in for loop iteration, it will actually move 4th one.
Check with Updated fiddle Here

Related

JS - add click event listener for all elements matching a querySelectorAll

I have li elements to which I'm trying to add click events that match the querySelector : .sidebar-nav li:not(.top). I have this code :
var sidebarItems = document.querySelectorAll(".sidebar-nav li:not(.top)");
// For each sidebar item, register a click function
for (var i = 0; i === sidebarItems.length; i++) {
sidebarItems[i].addEventListener("click", function() {
console.log("fromage");
})
};
However, no event listeners are added. The firefox dev tools show nothing, and nothing logs to the console when I click an element.
I checked in the JS console, sidebarItems has the correct node list.
Any help?
A for loop will loop for as long as the condition is true.
Your condition is i === sidebarItems.length so unless the length is 0, it will never be true.
Loop while i is less then the length of the array-like object.
for (let i = 0; i < sidebarItems.length; i++) {

JavaScript: How to skip over current item in array during a for loop? (continue?)

EDIT: I don't want to skip index 1. I want to skip the current (clicked on) element. Also, see below for more of the code as requested. You'll see that I have a class CatListItem and five instances of that class in an array allCatListItems.
Here's some context for the question: I have a list of cats. When I click on a cat's name (list item), I want that cat's picture and other info to be appended to the page (got that down). When a cat is clicked, I also want any other cat that is being displayed to be hidden (that way there is only one cat on the screen at a time).
I'm trying to accomplish this with a for loop, but obviously if it iterates over every item in the array, then when I click an item, the cat being clicked will be hidden as well.
I want to skip the current item in the array and only run the code on the other items. Using continue, I know I can skip a specific item (item 1 in the below example). This will run my code on every item in the array except that at index one. But how can I make that continue dynamic? Meaning... how can I hide all of the cats, except the one being currently clicked?
Here's the loop that skips index 1:
CatListItem.prototype.hideCats = function() {
allCatListItems.forEach(function(cat) {
cat.a.addEventListener('click', function() {
for (var i = 0; i < allCatListItems.length; i++) {
if (i === 1) {
continue;
}
allCatListItems[i].img.className = 'hide';
};
});
});
}
var allCatListItems = [
catListItem1 = new CatListItem('El', 'images/el.jpg', 'el'),
catListItem2 = new CatListItem('Widdle Baby', 'images/widdle-baby.jpg', 'widdle-baby'),
catListItem3 = new CatListItem('Mama', 'images/mama.jpg', 'mama'),
catListItem4 = new CatListItem('Legion', 'images/legion.jpg', 'legion'),
catListItem5 = new CatListItem('Boy', 'images/boy.jpg', 'boy'),
];
EDIT: Here's a fiddle.JSFIDDLE Click the names to see the functionality without the hideCats function. Then uncomment where it says to to see my issue.
I'm starting to think maybe a for loop isn't the best option?
In that case compare the event.target(its the element clicked)
EDIT: allCatListItems[i] needs it's .a property attached to it in the if statement (this is what contains the anchor element). This is because the event listener is grabbing an anchor tag, so e.target will be returning an anchor tag as well. The if statement will never return as true if you aren't comparing the same type of element.
cat.a.addEventListener('click', function(e) {
for (var i = 0; i < allCatListItems.length; i++) {
if (allCatListItems[i].a === e.target) {
continue;
}
allCatListItems[i].img.className += ' hide';
}
});
Here is a jsfiddle, it doesn't use the same element names, but it should be doing what you want. https://jsfiddle.net/5qb4rwzc/
$('li').on('click', function() {
var index = $(this).index();
var items = document.getElementsByTagName('li');
for(var i = 0; i < items.length; i++) {
if(i === index) continue;
items[i].style = "display:none;";
}
});
Its really depend on how you call the function "hideCat". Realizing that each time that function is called, more eventListeners are add to every cat item. Each time a cat is clicked, more than one event fired. Perhaps you should re-structure how to attach eventListeners to each cat item.

Javascript DOM ChildNodes property doesn't capture all children

I want to save a DOM subtree (everything under the div called "block diagram") then paint it on a div called "bus_diagram". Saving the childNodes property doesn't seem to capture all of the elements for some reason.
here's the javascript I'm using. On calling the function "dostuff()" everything under "block_diagram" should go to "bus_diagram"
var SAVED_BLOCK_DOM = null;
function save_block() {
SAVED_BLOCK_DOM = document.getElementById("block_diagram").childNodes;
}
function refresh_block() {
for (var i = 0; i < SAVED_BLOCK_DOM.length; i++) {
document.getElementById("bus_diagram").appendChild(SAVED_BLOCK_DOM[i]);
}
}
function dostuff() {
save_block();
refresh_block();
}
Here's a JSFiddle: http://jsfiddle.net/BFp5s/3/
.childNodes is a live collection of nodes and as you start doing .appendChild() on the nodes (which moves the elements to a different place in the DOM), the live collection changes while you are iterating it, causing you to miss nodes. So, when the index of your for loop is 0, you do .appendChild() on the 0th element of the list. That removes that element from the live list. You then increment your index to 1, but the next item to process is now in the 0th spot in the list causing you to process every other item.
You can either make a copy of the live list into an array (so it won't change while iterating it) or change the way you iterate the list.
For example, you can change save_block() to this:
function save_block() {
SAVED_BLOCK_DOM = Array.prototype.slice.call(document.getElementById("block_diagram").childNodes, 0);
}
This makes SAVED_BLOCK_DOM into a normal array so it won't change while you iterate it.
jsFiddle Demo: http://jsfiddle.net/jfriend00/R8c94/
Or, if you want/need to support IE6/7/8 support which won't work with the above copy mechanism, you can just copy the nodeList manually:
function save_block() {
SAVED_BLOCK_DOM = [];
var list = document.getElementById("block_diagram").childNodes;
for (var i = 0; i < list.length; i++) {
SAVED_BLOCK_DOM.push(list[i]);
}
}
If you don't need SAVED_BLOCK_DOM to continue to hold the list of nodes and want to support IE8, you can change the way you iterate like this:
function refresh_block() {
while (SAVED_BLOCK_DOM.length) {
document.getElementById("bus_diagram").appendChild(SAVED_BLOCK_DOM[0]);
}
}
jsFiddle demo: http://jsfiddle.net/jfriend00/PK7Tg/

Problem with dropdownbox length

I'm creating a javascript method that populates lists depending on a radio button selected previously. As it depends on animals or plants to populate it, my problem comes when I have to populate it after it's already been populated. I mean, the plants dropdownlist has 88 elements, and the animals is 888, when I try to come back from animals to plants, I get some of the animals. I know that my controller method is working properly because it returns the values I select, so the problem is the javascript method. Here is the code:
if(selector == "sOrder")
alert(document.getElementById(selector).options.length);
for (i = 0; i < document.getElementById(selector).options.length; i++) {
document.getElementById(selector).remove(i);
}
if (selector == "sOrder")
alert(document.getElementById(selector).options.length);
document.getElementById(selector).options[0] = new Option("-select-", "0", true, true);
for (i = 1; i <= data.length; i++) {
document.getElementById(selector).options[i] = new Option(data[i - 1].taxName, data[i - 1].taxRecID);}
Here is the strange thing, when I enter the method I try to erase all the elements of the dropdownlist in order to populate it afterwards. As sOrder is the same selector I had previously selected, I get the elements, the thing is that the first alert I get the proper result, 888, but in the second alert, I should get a 0 right? It shows 444, so when I populate it again it just overrides the first 88 plants and then animals till 444. What am I doing wrong?
Thank you all in advance,
Victor
Essentially you are removing from beginning to the end of the list by using the index, but as you do the index of each consecutive item gets decreased. If you remove from the end to the beginning, the indexes remain the same and you can remove them by index.
Replace
for (i = 0; i < document.getElementById(selector).options.length; i++) {
document.getElementById(selector).remove(i);
}
With
for (i = document.getElementById(selector).options.length -1; i > 0 ; i--) {
document.getElementById(selector).remove(i);
}
A simpler way of removing all items from a select box might be to use:
document.getElementById(selector).innerHTML="";
This would yank the html inside the select. (note I've only tested this in Firefox 3.6.2 but believe this should work in most modern browsers)
I think that in the remove loop there is an error
if(selector == "sOrder")
alert(document.getElementById(selector).options.length);
for (i = 0; i <
document.getElementById(selector).options.length;
i++) {
document.getElementById(selector).remove(i);
}
infact while you're removing elements, document.getElementById(selector).options.length changes.
so during the first iteration you have length=888, then you remove an element
during the second iteration you have length=887 (and i=1) etc.
during the 444th iteration, you have i=443 and length=444 and the loop exit after removing the 444th element.

Sorting Divs in jQuery by Custom Sort Order

I'm trying to re-sort the child elements of the tag input by comparing
their category attribute to the category order in the Javascript
variable category_sort_order. Then I need to remove divs whose category attribute
does not appear in category_sort_order.
The expected result should be:
any
product1
product2
download
The code:
<div id="input">
<div category="download">download</div>
<div category="video">video1</div>
<div category="video">video2</div>
<div category="product">product1</div>
<div category="any">any</div>
<div category="product">product2</div>
</div>
<script type="text/javascript">
var category_sort_order = ['any', 'product', 'download'];
</script>
I really don't even know where to begin with this task but if you could please provide any assistance whatsoever I would be extremely grateful.
I wrote a jQuery plugin to do this kind of thing that can be easily adapted for your use case.
The original plugin is here
Here's a revamp for you question
(function($) {
$.fn.reOrder = function(array) {
return this.each(function() {
if (array) {
for(var i=0; i < array.length; i++)
array[i] = $('div[category="' + array[i] + '"]');
$(this).empty();
for(var i=0; i < array.length; i++)
$(this).append(array[i]);
}
});
}
})(jQuery);
and use like so
var category_sort_order = ['any', 'product', 'download'];
$('#input').reOrder(category_sort_order);
This happens to get the right order for the products this time as product1 appears before product2 in the original list, but it could be changed easily to sort categories first before putting into the array and appending to the DOM. Also, if using this for a number of elements, it could be improved by appending all elements in the array in one go instead of iterating over the array and appending one at a time. This would probably be a good case for DocumentFragments.
Just note,
Since there is jQuery 1.3.2 sorting is simple without any plugin like:
$('#input div').sort(CustomSort).appendTo('#input');
function CustomSort( a ,b ){
//your custom sort function returning -1 or 1
//where a , b are $('#input div') elements
}
This will sort all div that are childs of element with id="input" .
Here is how to do it. I used this SO question as a reference.
I tested this code and it works properly for your example:
$(document).ready(function() {
var categories = new Array();
var content = new Array();
//Get Divs
$('#input > [category]').each(function(i) {
//Add to local array
categories[i] = $(this).attr('category');
content[i] = $(this).html();
});
$('#input').empty();
//Sort Divs
var category_sort_order = ['any', 'product', 'download'];
for(i = 0; i < category_sort_order.length; i++) {
//Grab all divs in this category and add them back to the form
for(j = 0; j < categories.length; j++) {
if(categories[j] == category_sort_order[i]) {
$('#input').append('<div category="' +
category_sort_order[i] + '">'
+ content[j] + '</div>');
}
};
}
});
How it works
First of all, this code requires the JQuery library. If you're not currently using it, I highly recommend it.
The code starts by getting all the child divs of the input div that contain a category attribute. Then it saves their html content and their category to two separate arrays (but in the same location.
Next it clears out all the divs under the input div.
Finally, it goes through your categories in the order you specify in the array and appends the matching child divs in the correct order.
The For loop section
#eyelidlessness does a good job of explaining for loops, but I'll also take a whack at it. in the context of this code.
The first line:
for(i = 0; i < category_sort_order.length; i++) {
Means that the code which follows (everything within the curly brackets { code }) will be repeated a number of times. Though the format looks archaic (and sorta is) it says:
Create a number variable called i and set it equal to zero
If that variable is less than the number of items in the category_sort_order array, then do whats in the brackets
When the brackets finish, add one to the variable i (i++ means add one)
Then it repeats step two and three until i is finally bigger than the number of categories in that array.
A.K.A whatever is in the brackets will be run once for every category.
Moving on... for each category, another loop is called. This one:
for(j = 0; j < categories.length; j++) {
loops through all of the categories of the divs that we just deleted from the screen.
Within this loop, the if statement checks if any of the divs from the screen match the current category. If so, they are appending, if not the loop continues searching till it goes through every div.
Appending (or prepending) the DOM nodes again will actually sort them in the order you want.
Using jQuery, you just have to select them in the order you want and append (or prepend) them to their container again.
$(['any', 'product', 'video'])
.map(function(index, category)
{
return $('[category='+category+']');
})
.prependTo('#input');
Sorry, missed that you wanted to remove nodes not in your category list. Here is the corrected version:
// Create a jQuery from our array of category names,
// it won't be usable in the DOM but still some
// jQuery methods can be used
var divs = $(['any', 'product', 'video'])
// Replace each category name in our array by the
// actual DOM nodes selected using the attribute selector
// syntax of jQuery.
.map(function(index, category)
{
// Here we need to do .get() to return an array of DOM nodes
return $('[category='+category+']').get();
});
// Remove everything in #input and replace them by our DOM nodes.
$('#input').empty().append(divs);
// The trick here is that DOM nodes are selected
// in the order we want them in the end.
// So when we append them again to the document,
// they will be appended in the order we want.
I thought this was a really interesting problem, here is an easy, but not incredibly performant sorting solution that I came up with.
You can view the test page on jsbin here: http://jsbin.com/ocuta
function compare(x, y, context){
if($.inArray(x, context) > $.inArray(y, context)) return 1;
}
function dom_sort(selector, order_list) {
$items = $(selector);
var dirty = false;
for(var i = 0; i < ($items.length - 1); i++) {
if (compare($items.eq(i).attr('category'), $items.eq(i+1).attr('category'), order_list)) {
dirty = true;
$items.eq(i).before($items.eq(i+1).remove());
}
}
if (dirty) setTimeout(function(){ dom_sort(selector, order_list); }, 0);
};
dom_sort('#input div[category]', category_sort_order);
Note that the setTimeout might not be necessary, but it just feels safer. Your call.
You could probably clean up some performance by storing a reference to the parent and just getting children each time, instead of re-running the selector. I was going for simplicity though. You have to call the selector each time, because the order changes in a sort, and I'm not storing a reference to the parent anywhere.
It's seems fairly direct to use the sort method for this one:
var category_sort_order = ['any', 'product', 'download'];
// select your categories
$('#input > div')
// filter the selection down to wanted items
.filter(function(){
// get the categories index in the sort order list ("weight")
var w = $.inArray( $(this).attr('category'), category_sort_order );
// in the sort order list?
if ( w > -1 ) {
// this item should be sorted, we'll store it's sorting index, and keep it
$( this ).data( 'sortindex', w );
return true;
}
else {
// remove the item from the DOM and the selection
$( this ).remove();
return false;
}
})
// sort the remainder of the items
.sort(function(a, b){
// use the previously defined values to compare who goes first
return $( a ).data( 'sortindex' ) -
$( b ).data( 'sortindex' );
})
// reappend the selection into it's parent node to "apply" it
.appendTo( '#input' );
If you happen to be using an old version of jQuery (1.2) that doesn't have the sort method, you can add it with this:
jQuery.fn.sort = Array.prototype.sort;

Categories

Resources