Javascript/jQuery re-ordering of li's in array - javascript

I have an array of DOM elements (lis) that I want to re-order based on an attribute of the lis.
Currently I try to:
Store the lis in an array.
Initiate a jQuery animation queue.
Then add to the queue the following:
Animating ALL the lis away
Detach said lis from dom with jQuery.detach().
Applying a sort() function to the array.
ADD the re-ordered lis back to the DOM and animate them in to position << breaks here
Then I run the queue.
At the moment due to some sort of issue with the elements stored in the array when I try to add the elements from the array back in to the DOM nothing is added.
Here's my code:
jQuery.each(self.filterSet, function (i, e) {
//loop thru array queing up hiding of elements
var self = this;
if ((i + 1) < filterSetLength) {
theQueue.queue("Q1", function (next) {
self = $(self).detach();
next();
});
} else {
//break on last element so that animation doesn't overlap with showing of filtered elements
theQueue.queue("Q1", function (next) {
self = $(self).detach();
next();
});
}
});
self.filterSet.sort(function (a, b) {
var c = parseInt($(a).attr('data-views'));
var d = parseInt($(b).attr('data-views'));
if (c < d) {
return 1;
}
if (c > d) {
return -1;
}
return 0;
});
self.location.find('li:not(.cloned) ul.tiles').each(function (i) {
//per panel....
var limit = 11 * (i + 1);
var self = this;
for (e = 0; e < limit; e++) {
if (filterSet[e] != undefined) {
theQueue.queue("Q2", function (next) {
$(self).append(filterSet[e]).show().fadeIn();
next();
});
} else {
break;
}
}
});
//add second queue in to end of first queue
theQueue.queue("Q1", function (next) {
theQueue.dequeue("Q2");
next();
});
//run everything
theQueue.dequeue("Q1");
Basically I simply have an array of things from jQuery:
var filterSet = new Array();
var filterSet = this.find('li:not(.cloned) ul.tiles li').each(function () {
filterSet.push(this);
});
and I want to sort them and then put them in to the DOM.... for some reason it won't work...

I really can't follow what you're trying to do with your code. Here's a simple jQuery code block that takes a set of li tags, removes them from the DOM, sorts them by their data-views data and reinserts them in sorted order which I think is what you asked for.
You can see it work here: http://jsfiddle.net/jfriend00/xwTsb/
HTML:
<button id="pressMe" type="button">Sort</button><br><br>
<ul id="master">
<li data-views="5">Fifth</li>
<li data-views="2">Second</li>
<li data-views="6">Sixth</li>
<li data-views="3">Third</li>
<li data-views="1">First</li>
<li data-views="4">Fourth</li>
</ul>
JS:
$("#pressMe").click(function() {
var savedObjects = [];
$("#master li").each(function(i, o){
var data = new Object();
data.views = $(this).attr("data-views"); // get data for sorting
data.o = $(this).detach(); // detach and save
savedObjects.push(data); // save for later
});
// sort by view data (treated as a number)
savedObjects.sort(function(a,b) {return(Number(a.views) - Number(b.views))});
var master = $("#master");
$.each(savedObjects, function(i, o) {
master.append(this.o); // add back to DOM
});
});
I leave it to you to add whatever animation you want to this.

Related

Iterating over a nodelist, getting the length of a value of a node item

I am trying to dynamically grab ZIPcodes and validate them when the length is 5.
I used querySelectorAll to grab the Zipcode fields on the page, as well as a few other fields I will use after validating.
I iterate over the nodelist and pass it to another function, where the eventlistener kicks off if the value is the correct length.
function GetZipCodeDetails() {
var zipId = document.querySelectorAll("[id*='ZipCode']");
var countyId = document.querySelectorAll("[id*='CountyId']");
var stateId = document.querySelectorAll("[id*='StateId']");
var phoneId = document.querySelectorAll("[id*='PhoneNumber']");
for (var i = 0; i < zipId.length; i++) {
if (zipId[i].length = 5)
AssortedZipCodeFunctions(zipId[i], countyId[i], stateId[i], phoneId[i]);
}
}
function AssortedZipCodeFunctions(zipId, countyId, stateId, phoneId) {
//Runs auto-county/state function only when zipcode field is completed
document.addEventListener("keyup", (e) => {
if (zipId.value.length == 5) {
GetCountyAndStateFromIds(zipId, countyId, stateId, phoneId);
} });
}
The code works perfectly for me as it is listed above; I am just trying to move the second function into the first function, but I can't figure out how. I am just stuck on how come I can't do the following:
function GetZipCodeDetails() {
var zipId = document.querySelectorAll("[id*='ZipCode']");
var countyId = document.querySelectorAll("[id*='CountyId']");
var stateId = document.querySelectorAll("[id*='StateId']");
var phoneId = document.querySelectorAll("[id*='PhoneNumber']");
for (var i = 0; i < zipId.length; i++) {
document.addEventListener("keyup", (e) => {
if (zipId[i].value.length == 5) {
GetCountyAndStateFromIds(zipId[i], countyId[i], stateId[i], phoneId[i]);
}
});
}
}
The above gives: "TypeError: Cannot read property 'value' of undefined
at HTMLDocument."
I have figured out that the for loop is calling the second function, instead of waiting until the Zipcode value is 5... so all that happened is I passed it to another function? Or maybe I am stuck on how to get the length of the value of a node item? Please help.
In your event listener you are adding it to the document instead of each element separately
for (var i = 0; i < zipId.length; i++) {
zipId[I].addEventListener("keyup", (e) => {
if (zipId[i].value.length == 5) {
GetCountyAndStateFromIds(zipId[i], countyId[i], stateId[i], phoneId[i]);
}
});
}

Compare original ordered list with current

I'm using jquery sortable, along with a load of custom functions to manage a list. I have functions that trigger the sortable, but I need to change this so that those the sortable only runs if the list has changed in any way.
Is there a way of reading the whole list into a variable, so that I can later compare it with the current list?
Something like this maybe?:
var mylist=$('#myol').attr(ids);
and then later:
if(mylist != $('#myol').attr(ids)) {
$('#myol').trigger('sortupdate');
}
Here is a simple example based on my comment:
https://jsfiddle.net/Twisty/3x64npfw/
JavaScript
$(function() {
var preChange, postChange;
function equalArr(a, b) {
console.log("Compare", a, b);
var eq = 0;
$.each(a, function(k, v) {
if(a[k] != b[k]){
eq++;
}
});
return (eq ? false : true);
}
$("#sortable").sortable({
start: function(event, ui) {
preChange = $(this).sortable("toArray");
},
stop: function(event, ui) {
postChange = $(this).sortable("toArray");
$(".results").html("After change, the list is the " + (equalArr(preChange, postChange) ? "same." : "different."));
}
});
});
This requires that each item in the list has a id attribute. If you're choosing not to use them, you'll want to make a function to iterate the list, read each item, and spit out an array.
function listToArray(target){
var items = target.children();
var myArray = [];
items.each(function(){
myArray.push($(this).text());
});
if(myArray.length == 0){
return false;
}
return myArray;
}

Underscorejs 'find' not working as expected

I use the following code, in a nodejs app, to build a tree from an array of database rows that form an adjacency list:
// Lay out every node in the tree in one flat array.
var flatTree = [];
_.each(rows, function(row) {
flatTree.push(row);
});
// For each node, find its parent and add it to that parent's children.
_.each(rows, function(row) {
// var parent = _.find(flatTree, function(p) {
// p.Id == row.ParentId;
// });
var parent;
for (var i = 0; i < flatTree.length; i++){
if (flatTree[i].Id == row.ParentId) {
parent = flatTree[i];
break;
}
};
if (parent){
if (!parent.subItems) {
parent.subItems = [];
};
parent.subItems.push(row);
}
});
I expect the commented out _.find call to do exactly the same as what the work-around for loop below it does, but _.find never finds the parent node in flatTree, while the for loop always does.
Similarly, a call to _.filter just doesn't work either, while the substitute loop does:
// var rootItems = _.filter(flatTree, function (node) {
// //node.ParentId === null;
// node.NoParent === 1;
// })
var rootItems = [];
for (var i = 0; i < flatTree.length; i++){
if (flatTree[i].ParentId == null){
rootItems.push(flatTree[i]);
}
}
I am using the underscore-node package, but have tried and had the same results with the regular underscore package.
Just missed the return.
var parent = _.find(flatTree, function(p) {
return p.Id == row.ParentId; // Return true if the ID matches
^^^^^^ <-- This
});
In your code nothing is returned, so by default undefined will be returned and parent will not contain any data.

Take elements while a condition evaluates to true (extending ElementArrayFinder)

We have a menu represented as a ul->li list (simplified):
<ul class="dropdown-menu" role="menu">
<li ng-repeat="filterItem in filterCtrl.filterPanelCfg track by filterItem.name"
ng-class="{'divider': filterItem.isDivider}" class="ng-scope">
Menu Item 1
</li>
...
<li ng-repeat="filterItem in filterCtrl.filterPanelCfg track by filterItem.name"
ng-class="{'divider': filterItem.isDivider}" class="ng-scope">
Menu Item 2
</li>
</ul>
Where somewhere at position N, there is a divider, which can be identified by evaluating filterItem.isDivider or by checking the text of the a link (in case of a divider, it's empty).
Now, the goal is to get all of the menu items that are located before the divider. How would you approach the problem?
My current approach is rather generic - to extend ElementArrayFinder and add takewhile() function (inspired by Python's itertools.takewhile()). Here is how I've implemented it (based on filter()):
protractor.ElementArrayFinder.prototype.takewhile = function(whileFn) {
var self = this;
var getWebElements = function() {
return self.getWebElements().then(function(parentWebElements) {
var list = [];
parentWebElements.forEach(function(parentWebElement, index) {
var elementFinder =
protractor.ElementFinder.fromWebElement_(self.ptor_, parentWebElement, self.locator_);
list.push(whileFn(elementFinder, index));
});
return protractor.promise.all(list).then(function(resolvedList) {
var filteredElementList = [];
for (var index = 0; index < resolvedList.length; index++) {
if (!resolvedList[index]) {
break;
}
filteredElementList.push(parentWebElements[index])
}
return filteredElementList;
});
});
};
return new protractor.ElementArrayFinder(this.ptor_, getWebElements, this.locator_);
};
And, here is how I'm using it:
this.getInclusionFilters = function () {
return element.all(by.css("ul.dropdown-menu li")).takewhile(function (inclusionFilter) {
return inclusionFilter.evaluate("!filterItem.isDivider");
});
};
But, the test is just hanging until jasmine.DEFAULT_TIMEOUT_INTERVAL is reached on the takewhile() call.
If I put console.logs into the loop and after, I can see that it correctly pushes the elements before the divider and stops when it reaches it. I might be missing something here.
Using protractor 2.2.0.
Also, let me know if I'm overcomplicating the problem.
Maybe I'm missing something, but couldn't you just go through ul li a elements while they gave you something from getText(), and store them to some array, or do something with them directly in that loop?
var i = 0;
var el = element.all(by.css('ul li a'));
var tableItems = [];
(function loop() {
el.get(i).getText().then(function(text){
if(text){
tableItems.push(el.get(i));
i+=1;
loop();
}
});
}());
takewhile() actually worked for me once I removed the protractor.promise = require("q"); from onPrepare() - this was there to replace protractor.promise with q on the fly to be able to use the syntactic sugar like spread() function. Apparently, it is not safe to use q in place of protractor.promise.
All I have to do now is to add this to onPrepare():
protractor.ElementArrayFinder.prototype.takewhile = function(whileFn) {
var self = this;
var getWebElements = function() {
return self.getWebElements().then(function(parentWebElements) {
var list = [];
parentWebElements.forEach(function(parentWebElement, index) {
var elementFinder =
protractor.ElementFinder.fromWebElement_(self.ptor_, parentWebElement, self.locator_);
list.push(whileFn(elementFinder, index));
});
return protractor.promise.all(list).then(function(resolvedList) {
var filteredElementList = [];
for (var index = 0; index < resolvedList.length; index++) {
if (!resolvedList[index]) {
break;
}
filteredElementList.push(parentWebElements[index])
}
return filteredElementList;
});
});
};
return new protractor.ElementArrayFinder(this.ptor_, getWebElements, this.locator_);
};
The usage is very similar to filter():
element.all(by.css("ul li a")).takewhile(function (elm) {
return elm.getText().then(function (text) {
return text;
});
});
FYI, proposed to make takewhile() built-in.

Not allowing to push duplicate items into array

Basically I'm pushing containers into an array, and once one has been pushed, I don't want to allow that same one to be pushed again.
Here is my JSfiddle: http://jsfiddle.net/9Dmcg/3/
Javascript:
$(document).ready(function(){
var favorites = [];
var counter = 0;
$('.containers').on('click', function(){
favorites.push($(this).clone())
$('.favorite').append(favorites);
});
});
I need to find a way to work around that.
Unless there's more to that click event, you can use the .one() method in place of .on to get this functionality.
$(document).ready(function(){
var favorites = [];
var counter = 0;
$('.containers').one('click', function(){
favorites.push($(this).clone())
$('.favorite').append(favorites);
});
});
http://jsfiddle.net/9Dmcg/4/
Even if there were more to it, you could still use .one():
$(document).ready(function(){
var favorites = [];
var counter = 0;
$('.containers').one('click', function(){
favorites.push($(this).clone())
$('.favorite').append(favorites);
$(this).on("click",function(){
alert("Favorite Added!");
}).triggerHandler("click");
});
});
http://jsfiddle.net/9Dmcg/5/
Try to check for element id's I would say. Something like this:
$(document).ready(function(){
var favorites = [];
var counter = 0;
$('.containers').bind('click', function(){
var isAdded = false;
for (var f = 0; f < favorites.length; f++) {
console.log(favorites[f].id + "|" + this.id);
if (favorites[f].id === this.id)
isAdded = true;
}
if (!isAdded)
favorites.push($(this).clone()[0])
console.log(favorites);
$('.favorite').append(favorites);
});
});
And here's working example -> http://jsfiddle.net/9Dmcg/7/
mz
This can be done using the JS Proxy (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
Following is the code to create an array which will not accept duplication entry
var arr = new Proxy([], {
get: function(target, key) {
return target[key];
},
set: function(target, key, value){
if(target.indexOf(value)>=0) return false;
target[key] = value;
return true
}
});
arr.push(100);
arr.push(100); // this will throw error
In the above code, we are modifying the default behaviour of array as the set method will be called on any modification done on the array.
You can check if the element already exists:
$(document).ready(function(){
var favorites = [];
$('.containers').on('click', function(){
var found = false;
for(var i = 0; i < favorites.length; i++)
{
if (favorites[i] == $(this)) found = true;
}
if (!found) favorites.push($(this).clone())
$('.favorite').append(favorites);
});
});
Add a class to items that have been pushed, and then do a basic check:
Instead of:
favorites.push($(this).clone())
you can do:
if( !$(this).hasClass("pushed") ) {
favorites.push( $(this).addClass("pushed").clone() );
}
Just push the element itself instead of cloning it:
favorites.push($(this))
The added benefit is that it gives the user a clue that the item has already be added.
Live demo: http://jsfiddle.net/9Dmcg/6/
Seems like your looking for Array.indexOf. It returns the index of the value on an array. Returns -1 if not found.
It is a new array method, so you will need a polyfill for it to work on old browsers.
$(document).ready(function(){
var favorites = [];
var counter = 0;
$('.containers').on('click', function(){
var clone = $(this).clone(); // caching element
if (favorites.indexOf(clone) !== -1) {
favorites.push(clone);
}
$('.favorite').append(favorites);
});
});

Categories