Sort divs numerically (by download count) - javascript

I've made a simple code for sorting the parent divs by their title, however when I tried to do the same with downloads nothing has happened, nor a error or any action of the code.
I have two functions, each of them are called by button with onclick(). The first one works perfectly fine, however I didn't get how to properly sort the parent divs by the download count although I assume it should have the similar code.
Here's the example:
// Sort by name with a click of a button
function sortbyname() {
let parent = $("#content");
let divs = parent.children();
var OrderedDivsByName = divs.sort(function(a, b) {
return $(a).find(".name").text() >= $(b).find(".name").text();
});
parent.append(OrderedDivsByName);
}
// Let's try and sort all of the divs by download count with a click of a button
function sortbymostdwnl() {
let parent = $("#content");
let divs = parent.children();
var OrderedDivsByDwnl = divs.sort(function(a, b) {
return $(a).find(".downloads").text() - $(b).find(".downloads").text();
})
parent.append(OrderedDivsByDwnl);
}
<div id="content">
<div class="wrapper">
<div class="name">Test</div>
<div class="downloads">
45
<em class="download_icon"></em>
</div>
</div>
<div class="wrapper">
<div class="name">Awesome Test</div>
<div class="downloads">
15
<em class="download_icon"></em>
</div>
</div>
</div>
Can somebody give me an example of how to properly sort it by numbers inside the div and explain why it doesn't work like that? I am not very experienced with javascript and I've never found someone having a similar issue.

You are using the raw values coming from the div content (via .text()) to make the comparison inside your sort callback like they were numbers.
You should parse first those values with parseInt and then use those numbers to make the comparison.
Here I added to your snippet the buttons bound to the 2 sorting functions, and inside the sortbymostdwnl function, I added the diagnostic to show on console the culprit and better tuned the comparison expression to make it meaningful.
*I also added an item to the list to better show the sorting
Adding noise to the html
Following the latest comments I added some noise in the .downloads elements with some <script> blocks both before and after the text content inside the .download divs.
As pointed out, the previous strategy wasn't working because the .text() method doesn't limit its action at returning the content of the children text nodes only.
So following the suggestion I found here:
Using .text() to retrieve only text not nested in child tags
I added this function:
function fetchTextNodesContent($target){
return $target
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.text();
}
// Sort by name with a click of a button
function sortbyname() {
let parent = $("#content");
let divs = parent.children();
var OrderedDivsByName = divs.sort(function(a, b) {
return $(a).find(".name").text() >= $(b).find(".name").text();
});
parent.append(OrderedDivsByName);
}
// Let's try and sort all of the divs by download count with a click of a button
function sortbymostdwnl() {
let parent = $("#content");
let divs = parent.children();
var OrderedDivsByDwnl = divs.sort(function(a, b) {
//fetches the raw values from a and b
const aRaw = fetchTextNodesContent($(a).find(".downloads"));
const bRaw = fetchTextNodesContent($(b).find(".downloads"));
//shows them on console
//console.log('raw values:', `a: "${aRaw}"`, `b: "${bRaw}"`);
//parses the values
const aParsed = parseInt(aRaw);
const bParsed = parseInt(bRaw);
//show them on console
console.log('parsed values:', `a: "${aParsed}"`, `b: "${bParsed}"`);
//performs the comparison on numbers
return bParsed - aParsed;
})
parent.append(OrderedDivsByDwnl);
}
function fetchTextNodesContent($target){
return $target
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.text();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="content">
<div class="wrapper">
<div class="name">Test</div>
<div class="downloads">
<!-- Adding noise before -->
<script>/**/</script>
45
<!-- Adding noise after -->
<script>/**/</script>
<em class="download_icon"></em>
</div>
</div>
<div class="wrapper">
<div class="name">111Awesome Test</div>
<div class="downloads">
<!-- Adding noise before -->
<script>/**/</script>
7
<!-- Adding noise after -->
<script>/**/</script>
<em class="download_icon"></em>
</div>
</div>
<div class="wrapper">
<div class="name">Awesome Test</div>
<div class="downloads">
<!-- Adding noise before -->
<script>/**/</script>
15
<!-- Adding noise after -->
<script>/**/</script>
</script>
<em class="download_icon"></em>
</div>
</div>
</div>
<button onclick="sortbyname()">SortByName</button>
<button onclick="sortbymostdwnl()">SortByMostDownloaded</button>

Besides the other answers, i will suggest a CSS based re-ordering.
If you set the container to be display: flex the you can use order: n for its children with css to re-order them (in view, be careful if you have other CSS or JS that depends on the DOM order)
Additionally, you can use Intl.Collator to make a compare function that can handle both strings and numbers correctly
function sortElementsBySelector(elements, selector) {
const collator = new Intl.Collator('en', {
numeric: true
});
const sortableElements = Array.from(elements, (element) => ({
element,
content: element.querySelector(selector).textContent.trim()
}));
sortableElements.sort((a, b) => {
return collator.compare(a.content, b.content);
});
sortableElements.forEach(({element}, index) => element.style.order = index);
}
// Sort by name with a click of a button
function sortbyname() {
const elements = document.querySelector("#content").children;
sortElementsBySelector(elements, '.name');
}
// Let's try and sort all of the divs by download count with a click of a button
function sortbymostdwnl() {
const elements = document.querySelector("#content").children;
sortElementsBySelector(elements, '.downloads');
}
document.querySelector('#by-name').addEventListener('click', sortbyname);
document.querySelector('#by-downloads').addEventListener('click', sortbymostdwnl);
#content{
display: flex;
flex-direction: column;
}
<div id="content">
<div class="wrapper">
<div class="name">Awesome Test</div>
<div class="downloads">
45
<em class="download_icon"></em>
</div>
</div>
<div class="wrapper">
<div class="name">Zoom Test</div>
<div class="downloads">
15
<em class="download_icon"></em>
</div>
</div>
<div class="wrapper">
<div class="name">Binary Test</div>
<div class="downloads">
1
<em class="download_icon"></em>
</div>
</div>
</div>
<button id="by-name">sort by name</button>
<button id="by-downloads">sort by downloads</button>

You are nearly there. Here I added some buttons and sorted by different things - I added one for data sort you can "hide" that value but still sort.
// Sort by name with a click of a button
function sortbyname() {
console.log('sorting Name');
let parent = $("#content");
let divs = parent.children();
let OrderedDivsByName = divs.sort(function(a, b) {
return $(a).find(".name").text() > $(b).find(".name").text() ? 1 : -1;
});
parent.append(OrderedDivsByName);
}
function sortbymostdwnl() {
console.log('sorting Download');
let parent = $("#content");
let divs = parent.children();
let OrderedDivsByDwnl = divs.sort(function(a, b) {
return $(a).find(".downloads").text() - $(b).find(".downloads").text();
});
parent.append(OrderedDivsByDwnl);
}
function sortByData() {
console.log('sorting data');
let parent = $("#content");
let divs = parent.children();
let sorted = divs.sort(function(a, b) {
return $(a).data("sortme") > $(b).data("sortme") ? 1 : -1;;
});
parent.append(sorted);
}
$('#sorts-container')
.on('click', '.sort-things[data-sorttype="by-downloads"]', sortbymostdwnl)
.on('click', '.sort-things[data-sorttype="by-names"]', sortbyname)
.on('click', '.sort-things[data-sorttype="by-data"]', sortByData);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="content">
<div class="wrapper" data-sortme="apples">
<div class="name">Test</div>
<div class="downloads">
45
<em class="download_icon"></em>
</div>
</div>
<div class="wrapper" data-sortme="zippers">
<div class="name">Awesome Test</div>
<div class="downloads">
15
<em class="download_icon"></em>
</div>
</div>
<div class="wrapper" data-sortme="gouda">
<div class="name">Cheese gouda</div>
<div class="downloads">
7
<em class="download_icon"></em>
</div>
</div>
<div class="wrapper" data-sortme="chedder">
<div class="name">Cheese - chedder</div>
<div class="downloads">
4
<em class="download_icon"></em>
</div>
</div>
</div>
<div id="sorts-container">
<button class="sort-things by-name" data-sorttype="by-names" type="button">Sort By Name</button>
<button class="sort-things" data-sorttype="by-downloads" type="button">Sort By Downloads</button>
<button class="sort-things" data-sorttype="by-data" type="button">Sort By data</button>
</div>

Related

Javascript - Retrieving values of a div when a button inside that div is clicked

I'm writing the code to edit a database table.
I have the following HTML:
<div id="1">
<div contenteditable>aaa</div>
<div contenteditable>bbb</div>
<div contenteditable>ccc</div>
<button onClick="a('save')">SAVE</button>
<button onClick="a('delete')">DELETE</button>
</div>
<div id="2">
<div contenteditable>ddd</div>
<div contenteditable>eee</div>
<div contenteditable>fff</div>
<button onClick="a('save')">SAVE</button>
<button onClick="a('delete')">DELETE</button>
</div>
<div id="3">
<div contenteditable>ggg</div>
<div contenteditable>hhh</div>
<div contenteditable>iii</div>
<button onClick="a('save')">SAVE</button>
<button onClick="a('delete')">DELETE</button>
</div>
And so on.
Using the following function, I can get the clicked button:
function a(value) {
console.log(value);
}
When a button (SAVE or DELETE) is clicked, I need to retrieve:
the id of the "parent" div;
the content of each of the three contenteditable divs inside the same "parent" div.
Is it possible using pure Javascript?
Any suggestion will be very appreciated.
Thanks in advance.
What I would do is implement click listeners in JS, that way I can query elements easily.
Here is the example:
// Query all div.div-editable elements
document.querySelectorAll('div.div-editable')
.forEach((div) => {
// The id of the parent
const divId = div.id;
// Each of content editable divs inside the parent div
const editables = div.querySelectorAll('div[contenteditable]');
// The buttons Save and Delete
const saveBtn = div.querySelector('button.button-save');
const deleteBtn = div.querySelector('button.button-delete');
// Add click listeners to buttons
saveBtn.addEventListener('click', function() {
console.log('Saved: ' + divId);
const contentOfEditableDivs = Array.from(editables).map((div) => div.innerText);
console.log('Values of divs:', contentOfEditableDivs);
});
deleteBtn.addEventListener('click', function() {
console.log('Deleted: ' + divId);
const contentOfEditableDivs = Array.from(editables).map((div) => div.innerText);
console.log('Values of divs:', contentOfEditableDivs);
});
});
<div id="1" class="div-editable">
<div contenteditable>aaa</div>
<div contenteditable>bbb</div>
<div contenteditable>ccc</div>
<button class="button-save">SAVE</button>
<button class="button-delete">DELETE</button>
</div>
<div id="2" class="div-editable">
<div contenteditable>ddd</div>
<div contenteditable>eee</div>
<div contenteditable>fff</div>
<button class="button-save">SAVE</button>
<button class="button-delete">DELETE</button>
</div>
<div id="3" class="div-editable">
<div contenteditable>ggg</div>
<div contenteditable>hhh</div>
<div contenteditable>iii</div>
<button class="button-save">SAVE</button>
<button class="button-delete">DELETE</button>
</div>
EDIT 1: Added code snippet
EDIT 2: Simplified explanation
You can send this keyword in the argument of click's event handler and then access the parent div's id.
So your HTML would look something like:
// rest of the code here
<button onClick="a(this, 'save')">SAVE</button>
<button onClick="a(this, 'delete')">DELETE</button>
// rest of the code here
And your JS code would change to:
function a(elem, value) {
console.log(elem.parentNode.id);
}
More details on the following link:
how i get parent id by onclick Child in js

Matching id with click element id or neatest parent id of Dynamic Content

My HTML:
...included jquery
<div id="step1">
.. there are more child div
<div id="step2">
.. more child div
<div id="step3">
Selection 1
</div>
</div>
</div>
<div class="class-1">
<div class="class-2">
<div class="class-3">
Section 2
</div>
</div>
</div>
...Some more neatest div and element
My Javascript:
$(document).click(function(event){
let idNo = event.target.id;
if(idNo == "step1"){
# Do something
}else{
console.log(idNo); //print "step3"
# Do others
}
});
I'm trying to check if user click div which id is "step1" then do something. But event.target always contain child div information like in this case div which id is "step3".
How can i check if clicking item have any neatest parent div with id "step1"
Remember my content is dynamic so i can't set up click listener like $("step1").click();
We can write a simple function to get all the parent ids of an element:
function getParentIds(el) {
let ids = [];
while (el.parentNode) {
let parentId = el.parentNode.id;
if (parentId)
ids.push(parentId);
el = el.parentNode;
}
return ids;
}
// sample usage
let element = document.querySelector('#three');
console.log(getParentIds(element));
// output: ['two', 'one']
<div id="one">
<div id="two">
<div id="three">
Sample
</div>
</div>
</div>
We can then attach this function our target nodes to determine their nearest parents (assuming there are no intermediate ids).
$('step1 *').click(function() {
var parentIds = getParentIds(this);
var nearestParent = parentIds[parentIds.length - 1];
if (nearestParent == 'step1')) {/* do something for immediate children of step 1 */}
else if (nearestParent == 'step2') {/* do something for immediate children of step 2 */}
else { /* nearestParent is 'step3' */ }
})

Counting divs dynamically in combination with filters

I'm setting up a website with div filters and want to count div's dynamically depending on filter sets.
This code works fine but do not react dynamically on filter changes...
$('.detail').each(function(i) {
var n = $(this).children('.box').length;
$(".countDiv"+i).text("There are " + n + " divs inside parent box detail.");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="content-body" class="clearfix">
<!-- detail div no 1 = 3 items inside -->
<span class="countDiv0"></span>
<div class="detail">
<div class="box">
Div item 1
</div>
<div class="box">
Div item 2
</div>
<div class="box">
Div item 3
</div>
</div>
<br /><br />
<!-- detail div no 1 = 4 items inside -->
<span class="countDiv1"></span>
<div class="detail">
<div class="box">
Div item 1
</div>
<div class="box">
Div item 2
</div>
<div class="box">
Div item 3
</div>
<div class="box">
Div item 4
</div>
</div>
</div>
Can anybody help?
well, too answer properly we would need to see the code that is performing the filter action. anyway, what you need to do is to encapsulate your code in a function and call that function whenever you are done filtering. example:
function countDivs() {
//your code here for counting the divs
}
function filterAction() {
//your code here that filters and creates the divs dynamically
countDivs()
}
$(document).ready(function(){
$("#btnCreate").click(function(){
$('.detail').append("<div class='box'>Div item n</div>");
});
});
$("body").on('DOMSubtreeModified', ".detail", function() {
$('.detail').each(function(i) {
var n = $(this).children('.box').length;
$(".countDiv"+i).text("There are " + n + " divs inside parent box detail.");
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="content-body" class="clearfix">
<!-- detail div no 1 = 3 items inside -->
<span class="countDiv0"></span>
<div class="detail">
<div class="box">
Div item 1
</div>
<div class="box">
Div item 2
</div>
<div class="box">
Div item 3
</div>
</div>
<br /><br />
<!-- detail div no 1 = 4 items inside -->
<span class="countDiv1"></span>
<div class="detail">
<div class="box">
Div item 1
</div>
<div class="box">
Div item 2
</div>
<div class="box">
Div item 3
</div>
<div class="box">
Div item 4
</div>
</div>
</div>
<div>
<button type="button" id="btnCreate">Create a new div inside detail div</button>
</div>
I can't understand what you mean by filter changes. What i understood was any changes made to the document DOM. If so then you need to bind the DivCount event like #folo(above) said to an event such as this..
$("body").on('DOMSubtreeModified', "mydiv", function() {
//countDiv();
});
Refer to this link
I have update my answer. please check
Please do the numbering as i have used n.
you can use mutation observer to listen on changes of children under your detail. more info about mutation can be found here https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver.
for your case, you can add that in the code inside each
1) add observer that takes care of dom changes
function createObserver(targetNode, cb) {
const config = { childList: true };
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
for (var mutation of mutationsList) {
if (mutation.type == 'childList') {
cb();
}
}
};
// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
}
2) hook that into your counting logic
$('.detail').each(function(i) {
const targetNode = this;
const countingDiv = $('.countDiv' + i);
const cb = () =>
countingDiv.text(
`There are ${
$(targetNode).children('.box').length
} divs inside parent box detail.`
);
cb();
createObserver(targetNode, cb);
});
you can see it in action here http://jsfiddle.net/sq5cd3rh/4/

For each of one element, iterate over a list of other elements. Elements have no sibling/parent relationship

Example code:
codepen
<div>
<button class="mainButton">CLICK ME!</button>
</div>
<div>
<div class="subElement">David</button>
<div class="subElement">Joe</button>
</div>
<div>
<button class="mainButton">AND ME!</button>
</div>
<div>
<div class="subElement">Billy</button>
<div class="subElement">Bob</button>
</div>
For each mainButton, I want to iterate over the subElements it corresponds to by either finding the ones below it, or maybe adding some sort of reference?
Can anyone provide any solutions to this issue?
If you cannot change anything to your HTML, then I think this would be a way to do it:
$('.mainButton').click(function() {
var texts = [];
var $all = $('.mainButton,.subElement');
$all.slice($all.index(this)+1).each(function() {
if ($(this).is('.mainButton')) return false; // break out
texts.push($(this).text());
});
console.log(texts);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<button class="mainButton">CLICK ME!</button>
</div>
<div>
<div class="subElement">David</div>
<div class="subElement">Joe</div>
</div>
<span>
<div>
<button class="mainButton">AND ME!</button>
</div>
<div>
<div class="subElement">Billy</div>
<div class="subElement">Bob</div>
</div>
This collects all buttons and sub elements in one collection. Then it finds the index where the currently clicked button is positioned in that collection. All elements that come before this index, and also the clicked button itself, are sliced out of the collection. Now all following sub elements are those we are interested in, up until the end of the collection or the next main button.
For this solution it does not matter how deep things are nested, as long as their sequence in the DOM tree is such that all sub elements follow the main button to which they "belong".
You'll need to group the subElements with separate class names then use jQuery to get all their values.
<div>
<button class="mainButton" onclick="main1()">CLICK ME!</button>
</div>
<div>
<div class="subElement1">David</button>
<div class="subElement1">Joe</button>
</div>
<div>
<button class="mainButton" onclick="main2()">AND ME!</button>
</div>
<div>
<div class="subElement2">Billy</button>
<div class="subElement2">Bob</button>
</div>
I would recommend better class names, but this is just for example. Then you can grab their values in your js:
function main1() {
var sub1s = $('.subElement1').val(); //this will be an array of their values
}
function main2() {
var sub2s = $('.subElement2').val(); //also an array
}

How to get the closest previous element in jQuery?

I want to get the closest object tag from the currently selected object tag. But it has to be strictly above. Suppose I have the object with id A. How can I get the closest object tag above it? In this case I want to get the object tag with id B. Each div container can contain a object tag or something else.
<div class="message">
<span></span>
</div>
<div class="message">
<object id="C"></object>
</div>
<div class="message">
<object id="B"></object>
</div>
<div class="message">
<span></span>
</div>
<div class="message">
<object id="A"></object>
</div>
<div class="message">
<object id="Z"></object>
</div>
Assuming this is the object with ID A
$(this).closest('.message').prevUntil('.message:has(object)').prev().find('object');
FIDDLE
traverses up to the closest .message then checks previous elements until it finds one that contains an object tag, then it stops, but it stops at the element before that tag, so we call prev to go one step further, and then use find to find the object tag.
Another option:
var objs=$('object').toArray();
$('.message').on('click',function(){
var elem=$(this).children().get(0);
if(objs.indexOf(elem)-1>=0){
console.log(objs[objs.indexOf(elem)-1]);
console.log(objs[objs.indexOf(elem)-1].id);
}
});
You can use prev() to get the previous sibling element. That will get you partway there, but since each 'message' div is not guaranteed to contain an object element you'll need to use prevUntil() or run your own iteration/search.
For instance:
var lastObj = $("#A");
var parent = lastObj.parent();
var previousObject = parent.prevUntil(".message:has(object)").prev().find("object");
Or without prevUntil():
var lastObj = $("#A");
var parent = lastObj.parent();
var previousObject = undefined;
while (parent.length > 0 && ! previousObject) {
parent = parent.prev();
if (parent.find("object").length > 0) {
previousObject = parent.find("object");
}
}
Or as a runnable code snippet (using classes instead of object tags):
$(".object").click(function(){
var myId = this.id;
var prevId = undefined;
var parent = $(this).parent();
var previousObject = undefined;
while (parent.length > 0 && ! previousObject) {
parent = parent.prev();
if (parent.find(".object").length > 0) {
previousObject = parent.find(".object");
prevId = previousObject.attr("id");
}
}
alert("clicked=" + myId + ", previous=" + prevId);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="message">
<span></span>
</div>
<div class="message">
<span class="object" id="C">C</object>
</div>
<div class="message">
<span class="object" id="B">B</object>
</div>
<div class="message">
<span></span>
</div>
<div class="message">
<span class="object" id="A">A</object>
</div>
<div class="message">
<span class="object" id="Z">Z</object>
</div>

Categories