Compare original ordered list with current - javascript

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;
}

Related

indexof - check if a value is already in array with javascript)

I am working with angular and I am trying to create a "select all" button.
I have a list of items, each item has a toggle and what I am doing is, on change (everytime the toggle changes from true (selected) to false (not selected), I run a function to create an array with all the IDs of the selected elements.
This works almost perfectly, the problem is that I am facing some issues with the indexfOf method to check if the ID is already in the array.
var isInArray;
isInArray = function(arr, id) {
console.log("index of ", arr.indexOf(id));
return arr.indexOf(id);
};
scope.evtSelectAll = function() {
return angular.forEach(scope.listToDisplay, function(element) {
element.copyTo = true;
return scope.selectFromList(element.iID, element.copyTo);
});
};
scope.selectFromList = function(id, copy) {
if (copy === true && isInArray(scope.selected, id) === -1) {
scope.selected.push(id);
} else {
scope.selected.pop(id);
}
console.log("scope.selected - ", scope.selected);
if (scope.selected.length > 0) {
console.log("Emitted event: can proceed!");
scope.$emit('enough-elements');
} else {
console.log("Emitted event: can not proceed!");
scope.$emit('not-enough-elements');
}
return scope.result = scope.selected;
};
the problem I've got is when the array (scope.selected) has multiple IDs.
Let's say, for example, that my scope.selected looks like this:
scope.selected = [2,3,4,7]
if I click on select all, nothing gets added (and this is correct)
Now, let's say I untick 4 and 7 for example, and my scope.selected now looks like this:
scope.selected = [2,3]
If I now click on select all, my result is the following: [2,4,7].
I lose the 3
I think this is due to the fact that my array doesn't have one single item?
thanks for any help. Here's also a quick codepen to explain the problem. If you check the console and play with the toggles you should be able to see straight away what I am referring to.
Thanks in advance
Thanks to Matthias and Christian Bonato for their suggestions.
At the end, I solved using both of their suggestions and the final result seems to work as expected.
Here's a codepen with the final version: http://codepen.io/NickHG/pen/KNXPBb
Basically, I changed
scope.selected.pop(id);
with
$scope.selected.splice( isInArray($scope.selected, id),1);
and in the selectAll event function, I always empty scope.selected[] before adding elements to the array
$scope.evtSelectAll = function() {
$scope.selected = []
angular.forEach($scope.list, function(element) {
element.copyTo = true;
return $scope.selectFromList(element.id, element.copyTo);
});
};
thank you for your help!
I think mostly your code contains a logical error. You are using the function selectFromList to de-select (when done individually) and for the select all (which you don't want to use to de-select).
As someone pointed out in a for some reason now deleted answer, the pop.() function shouldn't be called with any arguments (it is only for removing the last element), you should use splice like this:
$scope.selected.splice( isInArray($scope.selected, id),1);
Unless you really need the emitted functionality to run on a select all, you can try if this is the answer for you:
var isInArray;
isInArray = function(arr, id) {
console.log("index of ", arr.indexOf(id));
return arr.indexOf(id);
};
scope.evtSelectAll = function() {
return angular.forEach(scope.listToDisplay, function(element) {
element.copyTo = true;
if (isInArray($scope.selected, element.id) === -1) {
$scope.selected.push(element.id);
}
});
};
scope.selectFromList = function(id, copy) {
if (copy === true && isInArray(scope.selected, id) === -1) {
scope.selected.push(id);
} else {
$scope.selected.splice(isInArray($scope.selected, id), 1);
}
console.log("scope.selected - ", scope.selected);
if (scope.selected.length > 0) {
console.log("Emitted event: can proceed!");
scope.$emit('enough-elements');
} else {
console.log("Emitted event: can not proceed!");
scope.$emit('not-enough-elements');
}
return scope.result = scope.selected;
};
Now the select all only adds to scope.selected if it doesn't find the id in the scope.selected list.

Save and restore Sortable positions from two lists

I'm using RubaXa's excellent Sortable JS library to allow drag-and-drop rearranging of divs on a Bootstrap-based dashboard. Since the divs are all in 2 columns (left and right), I have the columns defined with ids of "leftColumn" and "rightColumn".
In order to allow dragging between columns, I set up both sortables with the same group, like this:
Sortable.create(leftColumn, {
group: 'dash_sections',
});
Sortable.create(rightColumn, {
group: 'dash_sections',
});
Now I am trying to load and save the order from both lists (the entire group). I placed data-id fields in each of the div tags, and I'm trying to use the following code to save and restore the order of everything.
Sortable.create(rightColumn, {
group: 'dash_sections',
store: {
get: function (sortable) {
var order = localStorage.getItem(sortable.options.group);
return order ? order.split('|') : [];
},
set: function (sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.group, order.join('|'));
}
}
});
However, I'm only saving and restoring the order for that column, not the entire group. I eventually want to have the group's order stored in a single string in the database. How do I go about saving and restoring the entire group's order?
Update:
I put similar code in both sortable.create functions, using "leftcol" and "rightcol" instead of sortable.options.group. This properly saves the order of each sortable as long as you don't drag between columns. I'm still looking for a way to save the order even when dragging between columns.
Here's how I implemented a similar functionality
Introduced a category flag to sortable options:
var leftColumnOptions = {
group: "dash_sections",
category: "left_column",
store: setupStore()
};
var rightColumnOptions = {
group: "dash_sections",
category: "right_column",
store: setupStore()
}
setupStore function checks for localStorage availability and applies get and set
function setupStore() {
if (localStorageAvailable) { // basic localStorage check: (typeof (localStorage) !== "undefined")
return {
get: getValue,
set: setValue
};
}
return {};
}
getValue and setValue retreive and store item ids based on category name defined in options above
function getValue(sortable) {
var order = localStorage.getItem(sortable.options.category);
return order ? order.split('|') : [];
}
function setValue(sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.category, order.join('|'));
}
It is a good idea to check for stored order information in localStorage before initializing Sortable, I'm using lodash for convenience
function applyState($section, categoryName) {
if (localStorageAvailable) {
var order = localStorage.getItem(categoryName);
var itemIds = order ? order.split('|') : [];
var $items = _.map(itemIds, function(itemId, index) {
return $("[data-id='" + itemId + "'");
});
$section.append($items);
}
}
usage would be:
applyState($(".js-left-column"), "left_column");
applyState($(".js-right-column"), "right_column");
// initialize sortable
Entire code:
HTML:
<div class="js-two-column-sortable js-left-column" data-category="left_column">
<!-- elements -->
</div>
<div class="js-two-column-sortable js-right-column" data-category="right_column">
<!-- elements -->
</div>
JS:
var localStorageAvailable = (typeof localStorage !== "undefined");
function getValue(sortable) {
var order = localStorage.getItem(sortable.options.category);
return order ? order.split('|') : [];
}
function setValue(sortable) {
var order = sortable.toArray();
localStorage.setItem(sortable.options.category, order.join('|'));
}
function setupStore() {
if (localStorageAvailable) {
return {
get: getValue,
set: setValue
};
}
return {};
}
function onAdd(evt) {
setValue(this);
}
function applyState($section, categoryName) {
if (localStorageAvailable) {
var order = localStorage.getItem(categoryName);
var itemIds = order ? order.split('|') : [];
var $items = _.map(itemIds, function(itemId, index) {
return $("[data-id='" + itemId + "'");
});
$section.append($items);
}
}
var options = {
group: "two-column-sortable",
store: setupStore(),
onAdd: onAdd
};
function init() {
$(".js-two-column-sortable").each(function(index, section) {
var $section = $(section);
var category = $section.attr("data-category");
var sortableOptions = _.extend({}, options, { category: category });
applyState($section, category);
$section.data("twoColumnSortable", new Sortable(section, sortableOptions));
});
}
init();

Return from a jQuery $.each loop

If a given array doesn't contain a given value, I wish to open a confirm dialog. The following works, however, my use of intermediate variable t seems a little excessive and I expect there is a more elegant way to do so. Could I return from the $.each loop and cause the upstream anonymous function to return false?
$(function(){
myArr=[['de'],['df','de'],['df','dz'],['de']];
if((function(){
var t=true;
$.each(myArr, function() {
console.log($.inArray('de', this)=='-1');
if($.inArray('de', this)=='-1') {t=false;return false;}; //Doesn't return true to parent
})
return t;
})() || confirm("Continue even though one of the choices doesn't contain 'de'?") ){
console.log('proceed');
}
});
You can use Array.prototype.some method, it will make code more comprehensive and simpler:
var myArr=[['de'],['df','de'],['df','dz'],['de']];
if (myArr.some(function(el) {
return el.indexOf('de') === -1;
}) && confirm("Continue even though one of the choices doesn't contain 'de'?")) {
document.write('proceed');
}
You could use grep instead, filtering out values that include 'de' and then counting the remaining:
$(function(){
var myArr=[['de'],['df','de'],['df','dz'],['de']];
var notDe = $.grep(myArr, function(item, index) {
return ($.inArray('de', this)=='-1');
});
if(notDe.length == 0 || confirm("Continue even though one of the choices doesn't contain 'de'?") ){
console.log('proceed');
}
});
Another more readable solution:
$(function () {
myArr = [
['de'],
['df', 'de'],
['df', 'dz'],
['de']
];
var t = 0;
$.each(myArr, function (k, v) {
if ($.inArray('de', v) === -1) {
t++;
}
});
if (t > 0) {
if (confirm("Continue even though " + t + " of the choices do not contain 'de'?")) {
console.log('proceed');
}
}
});

Replacing specific items within observable arrays in knockout.js

I am quite new to knockout.js, and I am enjoying learning how to make interfaces with it. But I have a bit of a wall while trying to make my interface more efficient. What I am trying to achieve is remove only the elements selected by $('.document_checkbox').serializeArray(), which contains the revision_id. I will then re-add the entries to the view model with a modified call to self.getDocument(), passing only the modified records which will be re-added. Can anyone help me how to remove the entries from the arrays based on the 'revision_id' values of $('.document_checkbox').serializeArray()
?
function Document(data) {
this.line_id = data.line_id
this.revision_id = ko.observable(data.revision_id);
this.status_id = ko.observable(data.status_id);
}
function DocumentViewModel() {
var self = this;
self.documents = ko.observableArray([]);
self.getDocument = function(){
//Reset arrays
self.documents.removeAll();
//Dynamically build section arrays
$.getJSON("/Documentation/Get-Section", function(allData) {
$.map(allData, function(item) {
var section = { name: item.array_name, display_name: item.display_name, documents: ko.observableArray([])};
self.documents.push(section);
})
//Add document objects to the arrays
$.getJSON("/Documentation/Get-Document", function(allData){
$.map(allData, function(item) {
var section = ko.utils.arrayFirst(self.documents(), function(documentSection) {
return documentSection.name === item.array_name;
});
section.documents.push(new Document(item));
});
});
});
}
self.updateStatusBatch = function(data,event){
$.post('/Documentation/Update-Status-Batch',
{
revision_id : $('.document_checkbox').serializeArray(),
status_id : event.currentTarget.value
}).done(
function(){
//This is where I get confused.
});
}
}
You should modify the /Documentation/Update-Status-Batch in order that it returns the deleted item id. So you will be able to remove it on the client side.
Try this "done" function:
function(removedItemId) {
self.documents.remove(function(doc){
return doc.status_id == removedItemId;
})
}
Take a look at the remove function.
I hope it helps.

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

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.

Categories