Sorting an array of divs by their ID values - javascript

I have a list of divs that I would like to sort by their ID values. The problem is that the IDs are values like this: "indexNumber123".
I would like to sort them "numerically" as in "indexNumber1","indexNumber2","indexNumber3" to reorder them before displaying their HTML.
The code base for this application is a sloppy mess so I thought I could just get away with doing this via DOM manipulation with jquery and just be done with it.
I tried to do this by getting an array of the divs and then passing a compare function to the sort method of the array. I thought I could parse the ID values in the compare method and compare the numeric values, but this is not sorting them correctly.
function compare(a,b) {
var aId = parseInt(a.id.replace( /^\D+/g, ''));
var bId = parseInt(b.id.replace( /^\D+/g, ''));
if (aId < bId)
return -1;
if (aId > bId)
return 1;
return 0;
}
//fired on a button click
function sortNumeric() {
var arrDivs = [];
$('[id^="indexNumber"]').each(
function() {
arrDivs.push($(this)[0]);
});
var sortedDivs = arrDivs.sort(compare);
console.dir(sortedDivs);
}

function compare(a,b) {
var aId = parseInt(a.id.replace( /^\D+/g, ''));
var bId = parseInt(b.id.replace( /^\D+/g, ''));
return +aId - +bId
}
//fired on a button click
function sortNumeric() {
var arrDivs = $('[id^="indexNumber"]'),
sortedDivs = arrDivs.sort(compare);
console.dir(sortedDivs);
}
Try this. you probably need to convert "stringID" to NumberID

Related

Problem comparing input to array using slice function

I am creating a search bar, that filters an array with French city names that match the input value with each keyup. I am getting some results back (using the console log), but the results are not what I expect and the IF condition I am using doesn't seem to be functioning...
Based in JavaScript and a little jQuery to get my list of cities from a text file, I've tried using indexOf and match function, but using slice() seems to have got me the furthest. I'm missing something somewhere to push the matched results into a new variable/array.
var cityArray = [];
$.get("liste.txt", function (data) {
cityArray = data.split("\n").sort();
console.log(cityArray);
});
$("#myInput").keyup(function () {
var searchedWord = $(this).val();
var counter = searchedWord.length;
var result = [];
console.log(searchedWord);
// console.log(typeof searchedWord);
// console.log(counter);
var matchedCities = 0;
if (counter < 3) {
return;
}
for (var i = 0; i < 100; i++) {
var city = cityArray.slice(0, cityArray.length);
console.log(city[i].slice(0, searchedWord.length));
if (city[i].slice(0, searchedWord.length) == searchedWord) {
result.push(city[i]);
console.log(result);
matchedCities++;
console.log(matchedCities);
if (matchedCities > 5) {
break;
}
}
}
});
So I am expecting to see matched cities with the input, in the console log. BUT, the function seems to break down before then as the result and matchedCities variables don't seem to change even when I know I have typed a city that is in the list.
You can use Array.filter and Array.includes to do your search. Something like this:
$("#myInput").keyup(function () {
var searchedWord = $(this).val();
var result = cityArray.filter(c => c.toLowerCase().includes(searchedWord.toLowerCase()))
console.log(result)
});
You can add the rest of the code you need but the idea is to "search" in this fashion.

Loop, get unique values and update

I am doing the below to get certain nodes from a treeview followed by getting text from those nodes, filtering text to remove unique and then appending custom image to the duplicate nodes.
For this I am having to loop 4 times. Is there is a simpler way of doing this? I am worried about it's performance for large amount of data.
//Append duplicate item nodes with custom icon
function addRemoveForDuplicateItems() {
var treeView = $('#MyTree').data('t-TreeView li.t-item');
var myNodes = $("span.my-node", treeView);
var myNames = [];
$(myNodes).each(function () {
myNames.push($(this).text());
});
var duplicateItems = getDuplicateItems(myNames);
$(myNodes).each(function () {
if (duplicateItems.indexOf($(this).text()) > -1) {
$(this).parent().append(("<span class='remove'></span>"));
}
});
}
//Get all duplicate items removing unique ones
//Input [1,2,3,3,2,2,4,5,6,7,7,7,7] output [2,3,3,2,2,7,7,7,7]
function getDuplicateItems(myNames) {
var duplicateItems = [], itemOccurance = {};
for (var i = 0; i < myNames.length; i++) {
var dept = myNames[i];
itemOccurance[dept] = itemOccurance[dept] >= 1 ? itemOccurance[dept] + 1 : 1;
}
for (var item in itemOccurance) {
if (itemOccurance[item] > 1)
duplicateItems.push(item);
}
return duplicateItems;
}
If I understand correctly, the whole point here is simply to mark duplicates, right? You ought to be able to do this in two simpler passes:
var seen = {};
var SEEN_ONCE = 1;
var SEEN_DUPE = 2;
// First pass, build object
myNodes.each(function () {
var name = $(this).text();
var seen = seen[name];
seen[name] = seen ? SEEN_DUPE : SEEN_ONCE;
});
// Second pass, append node
myNodes.each(function () {
var name = $(this).text();
if (seen[name] === SEEN_DUPE) {
$(this).parent().append("<span class='remove'></span>");
}
});
If you're actually concerned about performance, note that iterating over DOM elements is much more of a performance concern than iterating over an in-memory array. The $(myNodes).each(...) calls are likely significantly more expensive than iteration over a comparable array of the same length. You can gain some efficiencies from this, by running the second pass over an array and only accessing DOM nodes as necessary:
var names = [];
var seen = {};
var SEEN_ONCE = 1;
var SEEN_DUPE = 2;
// First pass, build object
myNodes.each(function () {
var name = $(this).text();
var seen = seen[name];
names.push(name);
seen[name] = seen ? SEEN_DUPE : SEEN_ONCE;
});
// Second pass, append node only for dupes
names.forEach(function(name, index) {
if (seen[name] === SEEN_DUPE) {
myNodes.eq(index).parent()
.append("<span class='remove'></span>");
}
});
The approach of this code is to go through the list, using the property name to indicate whether the value is in the array. After execution, itemOccurance will have a list of all the names, no duplicates.
var i, dept, itemOccurance = {};
for (i = 0; i < myNames.length; i++) {
dept = myNames[i];
if (typeof itemOccurance[dept] == undefined) {
itemOccurance[dept] = true;
}
}
If you must keep getDuplicateItems() as a separate, generic function, then the first loop (from myNodes to myNames) and last loop (iterate myNodes again to add the span) would be unavoidable. But I am curious. According to your code, duplicateItems can just be a set! This would help simplify the 2 loops inside getDuplicateItems(). #user2182349's answer just needs one modification: add a return, e.g. return Object.keys(itemOccurance).
If you're only concerned with ascertaining duplication and not particularly concerned about the exact number of occurrences then you could consider refactoring your getDuplicateItems() function like so:
function getDuplicateItems(myNames) {
var duplicateItems = [], clonedArray = myNames.concat(), i, dept;
for(i=0;i<clonedArray.length;i+=1){
dept = clonedArray[i];
if(clonedArray.indexOf(dept) !== clonedArray.lastIndexOf(dept)){
if(duplicateItems.indexOf(dept) === -1){
duplicateItems.push(dept);
}
/* Remove duplicate found by lastIndexOf, since we've already established that it's a duplicate */
clonedArray.splice(clonedArray.lastIndexOf(dept), 1);
}
}
return duplicateItems;
}

FInd object in array by value and update entire object

I have a list of objects and sometimes I receive an update from the API for one of those objects and what I need to do is to find the object with the id of the one to update and update the entire object...
I was trying to avoid a for loop because the list could be very very long.
So what I was trying to use is $.grep but it doesn't seem to work as expected.
Here is what I tried so far:
// item is the response data from the API
var item = res.item;
var index = $.grep(arrayOfItems, function (e, i) {
if (e.id === item.id) {
return i;
}
});
arrayOfItems[index] = item;
the item is not updated unfortunately...
If it's speed you're after, especially with a long list, you may consider indexing your list by id when you first retrieve it, making updates later quicker than having to loop the entire array to find an index.
To demonstrate, assume you have retrieved an array of objects
var data = [
{id:1,data:'hello'},
{id:2,data:'world'},
{id:3,data:'foo'},
{id:4,data:'bar'}];
now create an object which represents your data where the property is the Id (object properties cannot start with a number, so if id is numeric, prefix it) and the value is the index back into the original array. So, the above data would be transformed to
var dataIndex = {
id1:0,
id2:1,
id3:2,
id4:3
};
This can be done trivially with a function
function indexDataById(data)
{
var index = {};
$.each(data, function(e,i){
index['id' + e.id] = i;
});
return index;
}
var dataIndex = indexDataById(data);
Now, when it comes to your update, you can find the index instantly using the id
var updateId = 2;
var elementIdx = dataIndex ['id' + updateId];
data[elementIdx] = myNewData;
The one complication is that you need to go back and update the index if the id of the new data has changed:
var updateId = 2;
var elementIdx = dataIndex [`id` + updateId];
data[elementIdx] = myNewData;
delete dataIndex[elementIdx]
dataIndex['id' + myNewData.id] = elementIdx;
This should be easy enough to handle atomically with your update.
$.map and $.grep return both an array so you will never get the index.
Inside $.map or $.grep function you need to return true or false based
on your filter logic. They re not useful in your case.
if your structure is not ordered you can only loop trough it and stop the loop when you find your element... like that:
var item = res.item;
var index = "";
$.each(arrayOfItems, function(i,v){
if(item.id == v.id){
index = i;
return true;
}
});
arrayOfItems[index] = item;
if you wanna order your structure before loop use this:
arrayOfItems.sort(function(a, b) {
return a.id > b.id;
});
i ve made a fiddle with an example https://jsfiddle.net/L08rk0u3/
try this way using $.grep
var arrList = [
{name :11,id :11},{name :12,id :12},{name :111,id :111},
{name :13,id :13},{name :15,id :15},{name :11,id :11},
{name :41,id :41},{name :31,id :31},{name :81,id :81},
{name :91,id :91},{name :13,id :13},{name :17,id :17},
{name :1111,id :1111}
]
console.log(arrList);
var respItem ={name :1111000,id:1111};
var intSearchedIndex;
$.grep(arrList,function(oneItem,index){
if(respItem.id==oneItem.id){
return intSearchedIndex = index;
}
})
arrList[intSearchedIndex] =respItem;
console.log(intSearchedIndex,arrList);
Try with map method like this.
Code snippets:
// item is the response data from the API
var item = res.item;
var index = $.map(arrayOfItems, function (e, i) {
if (e.id === item.id) {
return i;
}
});
if(index.length)
arrayOfItems[index[0]] = item;
Update:
arrayOfItems[index] = item;
This will work if index array has an single element. See fiddle
But,
arrayOfItems[index[0]] = item;
This is the appropriate way since it is an array.

Get HTML of multiple divs with Jquery

I have multiple divs formatted like as follows
<div class="slot_totals">8.00 hrs</div>
And I want to get the values from that and add them, but I can't get it to work for some reason. This is my code so far:
function refreshTotals() {
var $totalHours = 0.00;
for (var $i=0; $i<$('.slot_totals').length; $i++) {
var $slotTotal = $('.slot_totals').html().split(" ");
$slotTotal = Number($slotTotal[0]);
$totalHours += $slotTotal;
}
// $('').html($totalHours.toFixed(2)+' hrs');
console.log($totalHours.toFixed(2)+' hrs');
}
It does calculate fine, but it's only the first value over and over. I think it's got to do with an array but not sure how to select each item.
What am I doing wrong?
$('.slot_totals').html() will always return the html of the first element in the collection represented by the selector. This is basically true for almost all jQuery getters since only one value can be returned
You could use eq() to define the matching indexed element.
var $slotTotal = $('.slot_totals').eq($i).html().split(" ");
Reference: eq() Docs
You can use .each() to iterate over your .slot_totals.
I think parseFloat() should be enough to parse the values.
$(function () {
var sum = 0;
$('.slot_totals').each(function (index, slot) {
sum += parseFloat(slot.innerHTML);
});
alert(sum);
});
http://jsfiddle.net/kta7y5cy/
You need to actually select which .slot_totals element you're operating on in each loop. Change this line:
var $slotTotal = $('.slot_totals').html().split(" ");
to this one:
var $slotTotal = $('.slot_totals').eq($i).html().split(" ");
Your code could be made more expressive and more readable if you used jQuery's .each function instead:
function refreshTotals() {
var $totalHours = 0.00;
$('.slot_totals').each(function () {
var $slotTotal = $(this).html().split(" ");
$slotTotal = Number($slotTotal[0]);
$totalHours += $slotTotal;
});
console.log($totalHours.toFixed(2)+' hrs');
}
Here is the working fiddle
Updated Code:
function refreshTotals() {
var $totalHours = 0.00;
for (var $i=0; $i<$('.slot_totals').length; $i++) {
var $slotTotal = $('.slot_totals:eq('+$i+')').html().split(" ");
$slotTotal = Number($slotTotal[0]);
$totalHours += $slotTotal;
}
// $('').html($totalHours.toFixed(2)+' hrs');
console.log($totalHours.toFixed(2)+' hrs');
}
Try this jquery .each code
$(function () {
var total = 0;
$('.slot_totals').each(function (index, value) {
var thisSlotTotal = $(value).html().split(" ");
thisSlotHours = Number(thisSlotTotal[0]);
console.log(thisSlotHours.toFixed(2) + ' hrs');
total += thisSlotHours;
});
alert(total);
});
DEMO: JSFIDDLE
Simplest way is to convert the jQuery collection to a true array with .get(), then use .reduce() to scan the array and make a running total.
Also, use parseInt() (or parseFloat()) to convert the numeric part of each string to Number.
function refreshTotals() {
var total = $('.slot_totals').get().reduce(function(total, el) {
return total + parseInt($(el).html());//or parseFloat()
}, 0);
console.log($totalHours.toFixed(2)+' hrs');
}

Select Part of a Attribute - JQuery

I need to get the number only from an attribute (The number is dynamic). The button/anchor looks like this:
Delete Dish
The part I need to dissect is this bit 'bc_inventorybundle_menu_product_0' I only want the number, for use in another function (Delete a LI with an ID of menuitem0_dish)
The code I use for selecting ID's elsewhere is:
function getNum(element, attrPrefix) {
//set prefix, get number
var prefix = attrPrefix;
var num = element.attr("id").substring((prefix.length));
return num;
}
It works great on ID's but I cant seem to get it to work for Attributes instead of ID's
So User clicks delete button bc_inventorybundle_menu_product_0 then jQuery removes the < li id="menuitem0_dish">
I can't add an ID to the button so I have to use the attribute of the button. As I'm sure you can tell I'm a complete noob when it comes to JS/JQuery.
EDIT
Having read all the answers I feel I may need to elaborate a little.
I think the biggest issue is registering when the Button/Anchor is clicked.
What I currently have is this, which I know must be wrong:
$(document).on('click', 'data("field")', function(event) {
deleteDish(this);
});
function getbutNum(element, attrPrefix) {
//set prefix, get number
var prefix = attrPrefix;
var butnum = element.data("field").substring(prefix.length); //Changed as per suggestions
return butnum;
}
function deleteDish(field) {
var numbut = getbutNum();
//Delete the UL/LI
console.log("Num But" + numbut);
}
Asides from all else this gives me an error of 'unrecognized expression: data("field")'
Have you tried selecting your actual data attribute:
var num = element.attr("data-field").substring(prefix.length);
Or:
var num = element.data("field").substring(prefix.length);
EDIT
First add a class to your anchor element (I'm going under the assumption that you have more than one of these):
Delete Dish
Then:
$(".delete-dish").on("click", function (e) {
e.preventDefault();
var fieldData = $(this).data("field"),
num = fieldData.substring(fieldData.lastIndexOf("_") + 1);
console.log("Num But" + num);
});
Here is a fiddle to demonstrate
Using the attribute name that contains your input should work:
function getNum(element, attrPrefix) {
//set prefix, get number
var prefix = attrPrefix;
var num = element.attr("data-field").substring((prefix.length));
return num;
}
http://jsfiddle.net/zf3hmo4q/
Considering you want to parse attributes with "data-*" name:
function getNum(element, dataName, dataPrefix) {
var num = element.data(dataName).replace(dataPrefix, "");
return num;
}
console.log(getNum($(".btn"), "field", "bc_inventorybundle_menu_product_"));
Maybe something like this?
var getNumberFromAttribute = function(id, field) {
var field = $(id).data(field);
var parts = field.split("_");
return parts[parts.length - 1]
}
Here's a jsfiddle http://jsfiddle.net/o6go79cL/
UPDATE
You could just pass in the element. The only purpose of the id was to select the object. So you could also just do:
var getNumberFromAttribute = function(elm, field) {
var field = $(elm).data(field);
var parts = field.split("_");
return parts[parts.length - 1]
}
number = getNumberFromAttribute(anchorTag, "field");

Categories

Resources