I have a list of items and two arrays:
favouriteItems
blockedItems
I want to reorder items by:
only "favourites"
not "favourites", not "blocked"
only "blocked"
The problem with my script is that "blocked" items are above "not marked" ones.
What's wrong there?
var favouriteItems = [2];
var blockedItems = [1, 3];
function isFavourite(id) {
return (favouriteItems.includes(id));
}
function isBlocked(id) {
return (blockedItems.includes(id));
}
function isNotMarked(id) {
return ((!isFavourite(id) && (!isBlocked(id))));
}
$("div.container div.item").sort(function(a, b) {
var aId = $(a).data("id");
var bId = $(b).data("id");
// check favourites
if ((isFavourite(aId)) && (!isFavourite(bId)))
return 1;
if ((isFavourite(bId)) && (!isFavourite(aId)))
return -1;
if ((isFavourite(aId)) === (isFavourite(bId)))
return 0;
// check blocked
if ((isBlocked(aId)) && (!isBlocked(bId)))
return -1;
if ((isBlocked(bId)) && (!isBlocked(aId)))
return 1;
if ((isBlocked(aId)) === (isBlocked(bId)))
return 0;
// both are not marked
return 0;
}).appendTo("div.container");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="item" data-id=1>Banana B</div>
<div class="item" data-id=2>Carrot F</div>
<div class="item" data-id=3>Orange B</div>
<div class="item" data-id=4>Apple</div>
<div class="item" data-id=5>Carrot</div>
</div>
Here is the list of problems and improvements required in the code
check blocked section and subsequent return was never executed because the function returned before that only as it covered all the 4 cases of equality between 2 variables.
If the order is the same as required, then return -1 and to swap return 1. Hence, when a is favorite and b is not, return -1. Similarly, when a is blocked and b is not, return 1.
When a and b are either both favorites or both not favorites, let the code check for being a and b are blocked or not and then finally return 0 (no change)
There is an unused function isNotMarked which should be removed
You can simplify your sort logic as following.
var favouriteItems = [2];
var blockedItems = [1, 3];
function isFavourite(id) {
return (favouriteItems.includes(id));
}
function isBlocked(id) {
return (blockedItems.includes(id));
}
$("div.container div.item").sort(function(a, b) {
var aId = $(a).data("id");
var bId = $(b).data("id");
return isFavourite(bId) - isFavourite(aId) || isBlocked(aId) - isBlocked(bId);
}).appendTo("div.container");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="item" data-id=1>Banana B</div>
<div class="item" data-id=2>Carrot F</div>
<div class="item" data-id=3>Orange B</div>
<div class="item" data-id=4>Apple</div>
<div class="item" data-id=5>Carrot</div>
</div>
Also, you can correct the logic in the same coding style as follows.
var favouriteItems = [2];
var blockedItems = [1, 3];
function isFavourite(id) {
return (favouriteItems.includes(id));
}
function isBlocked(id) {
return (blockedItems.includes(id));
}
$("div.container div.item").sort(function(a, b) {
var aId = $(a).data("id");
var bId = $(b).data("id");
// check favourites
if ((isFavourite(aId)) && (!isFavourite(bId)))
return -1;
if ((isFavourite(bId)) && (!isFavourite(aId)))
return 1;
// check blocked
if ((isBlocked(aId)) && (!isBlocked(bId)))
return 1;
if ((isBlocked(bId)) && (!isBlocked(aId)))
return 0;
// both are not marked
return 0;
}).appendTo("div.container");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="item" data-id=1>Banana B</div>
<div class="item" data-id=2>Carrot F</div>
<div class="item" data-id=3>Orange B</div>
<div class="item" data-id=4>Apple</div>
<div class="item" data-id=5>Carrot</div>
</div>
For reference, Array.sort
It looks to me like you are sorting your items the wrong way around!
The sorting function f(a,b) should return 1 if b comes before a.
In your solution the exact opposite happens
if ((isFavourite(aId)) && (!isFavourite(bId)))
return 1;
If a is a favourite and b is not, then a should come before b. Yet returning 1 indicates b becomes before a.
See docs
The way your sort function is written, the code after "check block" will never be executed because the function will necessarily end at one of the three first returns.
If you write a single function for attributing value to the items that takes all the criteria in consideration, and then use it to sort them, It will work:
var favouriteItems = [2];
var blockedItems = [1, 3];
function isFavourite(id) {
return (favouriteItems.includes(id));
}
function isBlocked(id) {
return (blockedItems.includes(id));
}
function isNotMarked(id) {
return ((!isFavourite(id) && (!isBlocked(id))));
}
function sortValue(id) {
var sortValue = 0;
if (isFavourite(id)) sortValue += 2;
if (isNotMarked(id)) sortValue += 1;
return sortValue;
}
$("div.container div.item").sort(function(a, b) {
var aId = $(a).data("id");
var bId = $(b).data("id");
return sortValue(bId) - sortValue(aId);
}).appendTo("div.container");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="item" data-id=1>Banana B</div>
<div class="item" data-id=2>Carrot F</div>
<div class="item" data-id=3>Orange B</div>
<div class="item" data-id=4>Apple</div>
<div class="item" data-id=5>Carrot</div>
</div>
Related
I have this HTML which is a list of elements:
<div class="container">
<div class="apple-0">first-apple</div>
<div class="apple-1">second-apple</div>
<div class="apple-2">third-apple</div>
<div class="apple-3">forth-apple</div>
<div class="apple-4">fifth-apple</div>
</div>
I've gotten an array, for example, which is [3,4,0,2,1] I need to sort the list in to this order.By this I mean that the third element <div class="apple-3">third-apple</div> should be the first. The second element should be the forth-apple.
How can I change it in an efficient way? This is the expected output:
<div class="container">
<div class="apple-3">forth-apple</div>
<div class="apple-4">fifth-apple</div>
<div class="apple-0">first-apple</div>
<div class="apple-2">third-apple</div>
<div class="apple-1">second-apple</div>
</div>
jQuery can be used.
You can do this by looping through the array and appending each div by it's matched index. Try this:
var $divs = $('.container > div').detach();
[3, 4, 0, 2, 1].forEach(function(value) {
$divs.eq(value).appendTo('.container');
});
Working example
Note that if you need to support older browsers (< IE9) then you would need to replace forEach() with a standard for loop.
You can try something like this:
$("#sort").on("click", function() {
var data = [3, 4, 0, 2, 1];
var result = "";
data.forEach(function(item) {
result += $(".container").find(".apple-" + item)[0].outerHTML;
});
$(".container").html(result);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
<div class="container">
<div class="apple-0">first-apple</div>
<div class="apple-1">second-apple</div>
<div class="apple-2">third-apple</div>
<div class="apple-3">forth-apple</div>
<div class="apple-4">fifth-apple</div>
</div>
<button id="sort">Sort</button>
Simply iterate the indexes array and keep pushing the child at nth-index
var output = [];
var indexes = [3,4,0,2,1];
indexes.forEach(function(value, index){
output.push($(".container div").eq(indexes[index])[0].outerHTML);
});
console.log(output);
$(".container").html(output.join(""));
Demo
you can try:
UPDATE:
var arr = [3,4,0,2,1];
var nodes = [];
arr.forEach(funtion(value){
var node = $('.container .apple-'+value)[0];
nodes.push(node);
});
$('.container').html(nodes);
demo
Other answers with eq are good, but if you want to sort again with a different array, or the array is unsorted initially, then they would fail. Also you asked for an efficient method, using native loops instead of jquery's each gives performance benefits. So my answer to this is
$(document).ready(function () {
var inputEls = $('#awesomeContainer').find('>').get(),
$output = $('#awesomeOutput'),
order = [3,4,0,2,1],
output = [],
myValue,
newIndex,
i,
length = inputEls.length;
for (i = 0; i < length; i += 1) {
myValue = Number((inputEls[i].className || "").replace("apple-", ""));
if (myValue >= 0) {
myValue = order.indexOf(myValue);
myValue > -1 && (output[myValue] = inputEls[i].outerHTML);
}
}
$output.append(output.join(''));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<b>Input: </b>
<div id="awesomeContainer" class="container">
<div class="apple-0">first-apple</div>
<div class="apple-1">second-apple</div>
<div class="apple-2">third-apple</div>
<div class="apple-3">forth-apple</div>
<div class="apple-4">fifth-apple</div>
</div>
<br/>
<b>Sorted: </b>
<div id="awesomeOutput" class="container">
</div>
I have the following html structur (endless):
<div class="wrapper">
<div class="content"> Its block 3
<div class="number">3</div>
</div>
</div>
<div class="wrapper">
<div class="content"> Its block 2
<div class="number">2</div>
</div>
</div>
I want to sort it by clicking a button like this:
<div class="wrapper">
<div class="content"> Its block 2 <--- new order
<div class="number">2</div> <--- new order
</div>
</div>
<div class="wrapper">
<div class="content"> Its block 3 <--- new order
<div class="number">3</div> <--- new order
</div>
</div>
... but with my script it doesn´t work (because of the same div class name, I think?). So, how can I sort this and toggle the sort by highest number and lowest number? Can anybody help me?
function sortHigh(a, b) {
var date1 = $(a).find(".content .number").text()
var date2 = $(b).find(".content .number").text();
return $(a).find(".content .number").text() > $(b).find(".content .number").text();
};
function sortLow(a, b) {
var date1 = $(a).find(".content .number").text()
var date2 = $(b).find(".content .number").text();
return $(a).find(".content .number").text() < $(b).find(".content .number").text();
};
//how to toggle?
$(function () {
$('.sort').click(function () {
$('.content').sort(sortHigh).appendTo('.wrapper');
}, function () {
$('.content').sort(sortLow).appendTo('.wrapper');
});
});
Thats my bad try: fiddle
try to change your code with this:-
var toggle="high";
//how to toggle?
$(function(){
$('.sort').click(function () {
if (toggle == "high") {
toggle = "low";
$('.list').html($('.list .wrapper').sort(sortLow));
} else {
toggle = "high"
$('.list').html($('.list .wrapper').sort(sortHigh));
}
});
});
Demo
Using jQuery, you can add the sort functionality as such:
jQuery.fn.sortDomElements = (function() {
return function(comparator) {
return Array.prototype.sort.call(this, comparator).each(function(i) {
this.parentNode.appendChild(this);
});
};
})();
var srtdesc = true;
$(function() {
$(".sort").click(function() {
srtdesc = !srtdesc;
$(".list").children().sortDomElements(function(a, b) {
if (srtdesc) {
return Number($(a).find('.number').text()) - Number($(b).find('.number').text());
} else {
return Number($(b).find('.number').text()) - Number($(a).find('.number').text());
}
});
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class="sort">Sort-Button</button>
<div class="list">
<div class="wrapper">
<div class="content">Its block 3
<div class="number">3</div>
</div>
</div>
<div class="wrapper">
<div class="content">Its block 1
<div class="number">1</div>
</div>
</div>
<div class="wrapper">
<div class="content">Its block 2
<div class="number">2</div>
</div>
</div>
</div>
You have two issues with your code.
The first is that your sorts are sorting strings. return "2" > "3" for example.
The other issue is that the click function you're using isn't toggling correctly. I'm guessing you're familiar with the .hover() syntax which is why you've done it that way.
As you can see, I'm forcing sortHigh and sortLow to return Numbers. I've also done a sorting low/high check and toggle within the click function.
function sortHigh(a, b) {
var date1 = Number($(a).find(".number").text());
var date2 = Number($(b).find(".number").text());
return date1 > date2;
};
function sortLow(a, b) {
var date1 = Number($(a).find(".number").text());
var date2 = Number($(b).find(".number").text());
return date1 <= date2;
};
$(function(){
var sortHighCheck = null;
$('.sort').click(function(){
if (sortHighCheck === true) {
$('.wrapper').sort(sortLow).appendTo('.list')
sortHighCheck = false;
} else {
$('.wrapper').sort(sortHigh).appendTo('.list')
sortHighCheck = true;
}
});
});
Edit: Forgot to add the jsfiddle link
If you want to sort, you can add data-val attribute to each content div:
<div class="content" data-val="2"> Its block 2 <--- new order
and sort each wrapper div with this code:
jQuery("#sort").click( function() {
jQuery('.wrapper').sort(function (a, b) {
return jQuery(a).find('.content').data('val') - jQuery(b).find('.content').data('val');
}).each(function (_, container) {
jQuery(container).parent().append(container);
});
});
I'm ordering some div with jQuery/Javascript: first for an attribute, than for another.
This is my code:
<div id="parent">
<div class="item" data-title="Marco" data-count="1">Marco (1)</div>
<div class="item selected" data-title="Fabio" data-count="5">Fabio (5)</div>
<div class="item selected" data-title="Edoardo" data-count="4">Edoardo (4)</div>
<div class="item " data-title="Paolo" data-count="8">Paolo (8)</div>
<div class="item selected" data-title="Luca" data-count="0">Luca (0)</div>
<div class="item" data-title="Andrea" data-count="4">Andrea (4)</div>
</div>
Order('#parent');
function Order(type) {
var items = $(type + ' > .item');
items.sort(function (a, b) {
return +b.getAttribute('data-count') - +a.getAttribute('data-count') || a.getAttribute('data-title') > b.getAttribute('data-title');
}).detach().appendTo($(type));
}
How you can see in the example, first is ordered by data-count, than by the data-title of the content.
What I'd like to do now is to put first the elements with the selected class on top; the other later.
But each "block" must be ordered as well by data-count and data-title. So in the example the result should be:
Fabio (5)
Edoardo (4)
Luca (0)
Paolo (8)
Andrea (4)
Marco (1)
how would you add a "order by class" here?
Start by comparing the results of hasClass():
(+$(b).hasClass('selected') - +$(a).hasClass('selected'))
(+ boolean is evaluated as 1 or 0 for true or false)
Order('#parent');
function Order(type) {
var items = $(type + ' > .item');
items.sort(function(a, b) {
return (+$(b).hasClass('selected') - +$(a).hasClass('selected')) ||
(+b.getAttribute('data-count') - +a.getAttribute('data-count')) ||
(a.getAttribute('data-title').localeCompare(b.getAttribute('data-title')));
}).appendTo($(type));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="parent">
<div class="item" data-title="Marco" data-count="1">Marco (1)</div>
<div class="item selected" data-title="Fabio" data-count="5">Fabio (5)</div>
<div class="item selected" data-title="Edoardo" data-count="4">Edoardo (4)</div>
<div class="item " data-title="Paolo" data-count="8">Paolo (8)</div>
<div class="item selected" data-title="Luca" data-count="0">Luca (0)</div>
<div class="item" data-title="Andrea" data-count="4">Andrea (4)</div>
</div>
Note the corrected string comparison (via localeCompare()), and that detach() isn't needed.
To sort on a primary and a secondary key, the general approach is to return a -1 or +1 when you detect that the primary keys are different, and only move on to the secondary key when the primary keys are the same. edit In your case you have three keys: the presence/absence of "selected" in the "class", the count, and the title:
items.sort(function (a, b) {
// test for the "selected" class
var aSel = /\bselected\b/.test(a.className), bSel = /\bselected\b/.test(b.className);
if (aSel && !bSel) return -1;
if (bSel && !aSel) return 1;
// test the counts
var prim = +b.getAttribute('data-count') - +a.getAttribute('data-count');
if (prim) return prim;
// compare titles
var aTitle = a.getAttribute('data-title'), bTitle = b.getAttribute('data-title');
return aTitle < bTitle ? -1 :
aTitle > bTitle ? 1 :
0;
}).detach().appendTo($(type));
That way, if the count on one row is larger than the count on the next, there's no need to bother with the title so we don't even compare them. But if the counts are equal, then the title comparison determines the ordering.
That can be generalized to as many keys as you wanted of course.
Order('#parent');
function Order(type) {
var items = $(type + ' > .item');
items.sort(function (a, b) {
return $(b).hasClass('selected') || a.getAttribute('data-count').toLowerCase().localeCompare(b.getAttribute('data-count').toLowerCase())
|| a.getAttribute('data-title').toLowerCase().localeCompare(b.getAttribute('data-title').toLowerCase());
}).detach().appendTo($(type));
}
Trying to sort children div based on data attributes
The html code below is being generated by a CM and the data can be retrieved in any random order.
the html code is
<section class="box explore">
<div id="ProductContainer" class="row">
<div id="1232132" data-name="B" data-category="Category_A" class="explore-cell">
<h>B</h>
<p>Category_A</p>
</div>
<div id="123" data-name="A" data-category="Category_A" class="explore-cell">
<h>A</h>
<p>Category_A</p>
</div>
<div id="1232152351" data-name="C" data-category="Category_A" class="explore-cell">
<h>C</h>
<p>Category_A</p>
</div>
<div id="12342341" data-name="E" data-category="Category_B" class="explore-cell">
<h>E</h>
<p>Category_B</p>
</div>
<div id="1325321" data-name="D" data-category="Category_B" class="explore-cell">
<h>D</h>
<p>Category_B</p>
</div>
</div>
java
$('div').sort(function (a, b) {
var contentA = $(a).attr('data-name');
var contentB = $(b).attr('data-name');
return (contentA < contentB) ? -1 : (contentA > contentB) ? 1 : 0;
})
Jsfiddle http://jsfiddle.net/w8gkshue/
if someone can point me in the right direct on how to best sort either by Product Name or Category.
Updated hope this gives better explination
EDIT: I missed the jQuery tag... leaving the answer still.
var productCt = document.getElementById('ProductContainer'),
reInsertProductCt = tempRemove(productCt);
[].slice.call(productCt.children)
.sort(function (a, b) {
var aName = a.dataset.name,
bName = b.dataset.name;
return aName < bName? -1 : +(aName > bName);
})
.forEach(productCt.appendChild.bind(productCt));
reInsertProductCt();
function tempRemove(el) {
var parent = el.parentNode,
nextSibling = el.nextSibling;
parent.removeChild(el);
return function () {
if (nextSibling) parent.insertBefore(el, nextSibling);
else parent.appendChild(el);
};
}
<div id="ProductContainer" class="row">
<div id="1232132" data-name="B" data-category="Category_A" class="explore-cell">
<h>TEST NAME B</h>
<p>TEST</p>
</div>
<div id="123" data-name="A" data-category="Category_A" class="explore-cell">
<h>TEST NAME A</h>
<p>TEST</p>
</div>
<div id="1232152351" data-name="C" data-category="Category_A" class="explore-cell">
<h>TEST NAME C</h>
<p>TEST</p>
</div>
<div id="12342341" data-name="E" data-category="Category_B" class="explore-cell">
<h>TEST NAME E</h>
<p>TEST</p>
</div>
<div id="1325321" data-name="D" data-category="Category_B" class="explore-cell">
<h>TEST NAME D</h>
<p>TEST</p>
</div>
</div>
You can use .sort method like this
var $wrapper = $('#ProductContainer');
$wrapper.find('.explore-cell').sort(function (a, b) {
return a.getAttribute('data-name') > b.getAttribute('data-name');
})
.appendTo( $wrapper );
But I don't sure about the cross browsing support
Calling only sort on them won't actually visually change the DOM, it just returns a sorted collection. So basically you just need to get the collection, sort it, then return it. Something like this should work:
$('#ProductContainer > div').detach().sort(function (a, b) {
var contentA = $(a).data('name');
var contentB = $(b).data('name');
return (contentA < contentB) ? -1 : (contentA > contentB) ? 1 : 0;
}).appendTo('#ProductContainer');
You'll want to make sure that you use the detach() method and not remove(), as detach() will retain all of the data and events associated with the collection items.
Why choose to sort by category or by name when you can sort by both?
I tried to write a generic multisort function generator, which should also work with the native array sort function.
JSFIDDLE HERE
A function that generates the multisort, it takes two parameters.
The column priority list order (first by category or by name? You decide).
I also wanted a way to provide values for columns (since you might not retrieve them the same way for each of them), it is an object that describes for each column a function to retrieve data.
Here it is
function getMultisortFn(columns, provideColumnData) {
return function (a, b) {
for (var i = 0, l = columns.length; i < l; i++) {
var column = columns[i];
var aColumnData = provideColumnData[column.name](a, column.name);
var bColumnData = provideColumnData[column.name](b, column.name);
if (aColumnData !== bColumnData) {
if (column.asc) {
return String.prototype.localeCompare.call(aColumnData, bColumnData);
}
return String.prototype.localeCompare.call(bColumnData, aColumnData);
}
}
};
}
Now this is the part where you actually use the multisort generated
function retrieveDataAttribute(item, attribute) {
return $(item).data(attribute);
}
var $container = $('#ProductContainer');
var $products = $container.find('div');
var multisort = getMultisortFn([{
name: 'category',
asc: false
}, {
name: 'name',
asc: true
}], {
name: retrieveDataAttribute,
category: retrieveDataAttribute
});
$products.sort(multisort);
And finally the DOM manipulation to apply the new order
$products.detach().appendTo($container);
EDIT thanks to plalx:
$container.detach().append($products).appendTo('section.box.explore');
So what's the problem. I want to exclude item from WinJS List View with specific parameter (ID - passed from JSON). How to do that?
Things i've tried:
a) Before pushing data to someView.itemDataSource process it with this function (It work's, but looks dirty).
fldView.itemDataSource = this._processItemData(Data.items.dataSource);
....
_processItemData: function (data) {
for (var i = data.list.length; i >= 1; i--) {
if (data.list._groupedItems[i]) {
if (data.list._groupedItems[i].groupKey == 'Folders')
continue;
else {
if (data.list._groupedItems[i].data.folderID) {
data.list.splice(i - 1, 1);
}
}
}
}
return data;
}
b) The traditional way with two conditional templates (Doesn't work):
fldView.itemTemplate = this.getItemTemplate;
....
getItemTemplate: function(promise){
return promise.then(function(item){
var
itemTemplate = null,
parent = document.createElement("div");
if(item.data.folderID){
itemTemplate = document.querySelector('.hideItemTemplate')
}else{
itemTemplate = document.querySelector('.itemTemplate')
}
//console.log(item.data.folderID);
itemTemplate.winControl.render(item.data, parent);
return parent;
})
}
2 HTML templates
<div class="itemTemplate" data-win-control="WinJS.Binding.Template">
<div class="item">
<img class="item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
<div class="item-overlay">
<h4 class="item-title" data-win-bind="textContent: title" style="margin-left: 0px;"></h4>
<h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle" style="margin-left: 0px; margin-right: 4.67px;"></h6>
</div>
</div>
</div>
<div class="hideItemTemplate" data-win-control="WinJS.Binding.Template">
<div class="display-none"></div>
</div>
and CSS display: none
.hideItemTemplate, .display-none{
display:none;
}
Thank's in advance!
Suggest to filter the item either before building the WinJS.Binding.List using array.filter or do a filter projection on the list after it is built. if grouping is required, grouping can be done on the filtered list.
var list; // assuming this is all data items
var filteredList = list.createFiltered(function filter(item)
{
if (item.FolderID)
return false;
else
return true;
});
var groups = filteredList.createGrouped(...);