How to sort nested divs depending upon content - javascript

I've the following HTML schema
<div id="txm_left_column">
<div class="id">
<h2>Some Name1</h2>
<div>// another list</div>
</div>
<div class="id">
<h2>Some Name2</h2>
<div>// another list</div>
</div>
</div>
The user is able to add items to that list and all Divs should be in alphabetical order by the name in the h2.
I've two options
1) i need to be able to insert a new item in between the 2 divs where the alphabetical order is correct
2) re organise the list entirely.
My approach was option 2, to sort the Divs in alphabetical order by the name in the h2
I came up with the following code to try to order it but this code creates a new List of ordered H2s without the divs. then I tried to do option 1) by using the same function but trying to insert into something like this (upA < upB) ? -1 : (NEW_ITEM_NAME> upB) ? 1 : 0 but that will cause a problem as that doesn't break the sort.
I was wondering 2 things, one is how could i break the sort as return 0 would not break it. or any help on how could i organise my list.
Thanks
jQuery("#txm_left_column > div").children("h2").sort(function(a, b) {
var upA = jQuery(a).text().toUpperCase();
var upB = jQuery(b).text().toUpperCase();
return (upA < upB) ? -1 : (upA > upB) ? 1 : 0;
}).appendTo(selector);

but this code creates a new List of ordered H2s without the divs.
That's because you are sorting h2 elements and not the div elements:
jQuery("#txm_left_column > div").sort(function(a, b) {
var upA = jQuery('> h2', a).text().toUpperCase();
var upB = jQuery('> h2', b).text().toUpperCase();
// ...
}).appendTo(selector);

wanting to comment but have not met the "50 reputation" quota,
the Vohuman answer is problematic when introducing duplicates:
<div id="txm_left_column">
<div class="id">
<h2>80</h2>
<div>// another list for name 80</div>
</div>
<div class="id">
<h2>80</h2>
<div>// another list for name 80</div>
</div>
<div class="id">
<h2>81</h2>
<div>// another list for name 81</div>
</div>
<div class="id">
<h2>100</h2>
<div>// another list for name 100</div>
</div>
<div class="id">
<h2>100</h2>
<div>// another list for name 100</div>
</div>
</div>
-
jQuery("#txm_left_column > div").sort(function(a, b) {
var upA = jQuery('h2', a).text().toUpperCase();
var upB = jQuery('h2', b).text().toUpperCase();
return upA < upB;
}).appendTo('#txm_left_column');
result:
81
// another list for name 81
80
// another list for name 80
80
// another list for name 80
100
// another list for name 100
100
// another list for name 100
see http://jsfiddle.net/std57rm4/
this only happens when a duplicate name or number you are sorting on is introduced
at this moment I don't have a solution, but it should be something along the lines of counting how many times a duplicate has been encountered when sorting and then adding or subtracting that number from the next sort item or items depending on the ascending or descending direction you are sorting in

Related

AngularJS - How to sum filtered grandchild items in nested ng-repeat

I created a kanban board app with Angular, now I'd like to put the sum of filtered Post-Its points, and a count, like so:
<input type="text" ng-model="searchText">
<div ng-init="col.TotalPoints = 0" ng-repeat="col in columns">
<h3>{{col.Title}} - ({{ col.TotalPoints?? }} Pts.)</h3>
<div ng-init="subCol.Points = 0" ng-repeat="subCol in col.SubColumns">
<h3>{{subCol.Title}} - {{ subCol.Points }} Pts.</h3>
<div ng-show="false" ng-bind="subCol.Points= (filteredPostIts| sumPointsFilter:'Points')" ></div>
<div ng-repeat="postIt in filteredPostIts = (subCol.PostIts | filter:searchText)">
<p>#{{postIt.Id}}</p>
<p>{{postIt.Title}}</p>
<p>{{postIt.UserName}}</p>
<p>{{postIt.Points}} Points</p>
</div>
</div>
</div>
I assign my filtered Post-Its to filteredPostIts alias, then used to ng-bind to use the sumPointsFilter to iterate over all filtered post-its and sum their points, which is working fine. The Sub-Column Points is recalculated as soon as we start filtering some post-its by typing some text in the search input.
Now I'd like to calculate the total points for the whole column, which is the sum of its subcolumns (considering the filtered post-its).
This is a JSFiddle. The first column is "Não Iniciado" (Not started), which has one subcolumn called "Backlog". The second column is "Em andamento", which has two subcolumns ("Executando" and "Aguardando").
Try typing marcello for example, and it will filter Marcello's post-its, and recalculate the points for the subcolumns.
How could I achieve this?
You do have quite a bit going on that jsfiddle but I think I see what you could do...
Reuse that same summation filter like this:
<h3>{{col.Titulo}} - ({{ col.Divisorias | sumPointsFilter: 'Points'}} Pts.)</h3>
https://jsfiddle.net/r0m4n/9zbrohhy/54/

why splice not work properly in angular js

I am trying to make one demo in which i have one checkbox list .I am able to display the list using ng-repeat .
What I need if user click on one check box(only one checkbox is checked) .it display only one columns (100%) width .Which user checked two column it display two columns of equal width (50%).if user check three column it show three column of equal width ..As as if user checked four checkbox it show four column of equal width ..Initially some of checkbox is checked ( checked:true) ..
my first step is to unchecked the checked option "training 3" ..but after unchecked it still display why ? I already use splice. method ?
here is my code
http://codepen.io/anon/pen/adBroe?editors=101
init();
function init(){
for(var i =0;i<self.data.length;i++){
var obj=self.data[i];
if(obj.checked)
{
self.selectedList.push(obj);
}
}
alert('starting '+self.selectedList.length)
}
self.checkBoxClick=function(obj,i){
if(obj.checked)
{
alert('if')
self.selectedList.push(obj);
}else
{
alert('else'+i);
self.selectedList.splice(i,1);
}
alert(self.selectedList.length);
}
})
here is i am trying to display
<div class='container-fluid'>
<div class='row'>
<div ng-repeat="i in vm.selectedList" class='col-xs-{{12/vm.selectedList.length}}'>
{{i.name}}
</div>
</div>
</div>
It can be much simpler. In HTML you don't even need ngChange handler, just bind to checked property:
<div class="checkbox" ng-repeat='v in vm.data'>
<label>
<input type="checkbox" ng-model='v.checked'> {{v.name}}
</label>
</div>
and then render columns with just ngRepeat:
<div ng-repeat="i in filteredList = (vm.data | filter:{checked:true})" class='col-xs-{{12/filteredList.length}}'>
{{i.name}}
</div>
So as the result, clean controller without any logic at all, with Angular doing all necessary column filtering using template vm.data | filter:{checked:true}.
Demo: http://codepen.io/anon/pen/bEBydL?editors=101
This is happening because you are trying to remove it from 2nd index while training 3 is present at 0th index.
else
{
alert('unchecked '+i);
var index = self.selectedList.indexOf(obj);
self.selectedList.splice(index,1);
}
change your else part to this. and it will work fine.
http://codepen.io/anon/pen/yeVWeQ?editors=101

jQuery find not working with object array

This code doesn't work
var next = $("#orders").find(".next");
if (next.length == 1) {
var address = $(next[0]).find(".directionsAddress");
var destination = $(address[0]).text();
}
<div id="orders" class="ui-sortable">
<div id="companyAddress" class="noDisplay">101 Billerica Avenue, North Billerica, MA</div>
<div id="companyPhone" class="noDisplay">9788353181</div><div class="next"></div>
<div class="lat">42.616007</div>
<div class="lng">-71.31187</div>
<div id="section1611" class="sectionMargin borderRad">
<div class="directionsAddress noDisplay">92+Swan+Street+Lowell+MA</div>
It is suppose to find one div with a class of "next" that I know exists on the page, then within that one item of the result set array, there will be one div with a class name of directionsAddress.
The "next" array is coming back with a length of 1, so it looks like the problem is with my $(next[0]).find because the address array is coming back as 0 length and I am making a syntax error of some sort that I don't understand.
This should do what you want. You need to find the parent (or alternatively, the sibling of .next) then try to find the applicable .directionsAddress.
var next = $("#orders").find(".next");
if (next.length == 1) {
var destination = $(next).parent().find(".directionsAddress");
alert($(destination).text());
}
Working fiddle: https://jsfiddle.net/00fgpv6L/

Sorting alphabetically in JQuery with two groups

I've got a todo list. Each row has a star icon that you can click, exactly like gmail. The difference here is that if you click a star it should sort to the top (higher priority), but also re-sort within the starred group by ascending alpha. Unstarred items sort below, also sorted by ascending alpha. Everything is working as expected except for the alpha sorting. Below is the sort function where I'm doing that. I've verified that everything works below except the //sort the arrays by alpha bit...
Sort fail:
function sortTasks(currList) {
var starredTasks = [];
var unstarredTasks = [];
//create arrays
$('li.task').each(function(){
if ($(this).children('img.star').attr('src') == "images/star_checked.gif") {
starredTasks.push($(this));
} else {
unstarredTasks.push($(this));
}
});
//sort the arrays by alpha
starredTasks.sort( function(a,b){ ($(a).children('p.task-name').text().toUpperCase() > $(b).children('p.task-name').text().toUpperCase()) ? 1 : -1;});
unstarredTasks.sort( function(a,b){ ($(a).children('p.task-name').text().toUpperCase() > $(b).children('p.task-name').text().toUpperCase()) ? 1 : -1;});
//draw rows starred first, unstarred second
$(currList).empty();
for (i=0; i < starredTasks.length; i++) {
$(currList).append(starredTasks[i]);
}
for (i=0; i < unstarredTasks.length; i++) {
$(currList).append(unstarredTasks[i]);
}
}
This array has been populated with the task rows in the order they were originally drawn. The data renders fine, but basically stays in the same order.
Example task row:
<div id="task-container" class="container">
<form name="enter-task" method="post" action="">
<input id="new-task" name="new-task" type="text" autofocus>
</form>
<h2 id="today">today</h2>
<ul id="today-list">
<li id="457" class="task">
<img class="star" src="images/star_checked.gif">
<p class="task-name" contenteditable>buy milk</p>
<p class="task-date"> - Wednesday</p>
</li>
</ul>
<h2 id="tomorrow">tomorrow</h2>
<ul id="tomorrow-list">
</ul>
<h2 id="future">future</h2>
<ul id="future-list">
</ul>
<h2 id="whenever">whenever</h2>
<ul id="whenever-list">
</ul>
</div>
Each item in the starredTasks array is an entire task row. I'm assuming that $(a) is the same level as $(li)?
and here's the function that triggers the sort:
$('body').on('click', 'img.star', function(){
var thisList = '#' + $(this).parent('li').parent('ul').attr('id');
if ($(this).attr('src') == 'images/star_checked.gif') {
$(this).attr('src', 'images/star_unchecked.gif');
} else {
$(this).attr('src', 'images/star_checked.gif');
}
sortTasks(thisList);
});
Also, I doubt it's worth mentioning, but the data is stored in mySQL and prepopulated via php.
I wasn't sure of a way to use .sort() directly on the $('li') without splitting it into separate arrays...
Anybody see my goof?
I don't see where you're adding the sorted list back into the DOM. If you're not, then that's the problem. Sorting an array of elements doesn't update the DOM at all.
Furthermore, your sorting is very expensive. It's better to map an array of objects that have the elements paired with the actual values to sort.
Finally, you appear to be using the same ID multiple times on a page. That's just wrong. it may work with jQuery's .children(selector) filter, but it's still wrong. You need to change that.
Here I map an array of objects that contain a text property holding the text to sort and a task property that holds the element.
I changed p#task-name to p.task-name, so you should change that to class="task-name" on the elements.
Then I do the sort using .localeCompare(), which returns a numeric value.
Finally, the .forEach() loop appends the elements to the DOM.
var data = starredTasks.map(function(t) {
return { task: t,
text: $(t).children('p.task-name').text().toUpperCase()
};
}).sort(function(obj_a, obj_b) {
obj_a.text.localeCompare(obj_b.text);
}).forEach(function(obj) {
original_container.append(obj.task);
});
This assumes starredTasks is an actual Array. If it's a jQuery object, then do starredTasks.toArray().map(func....
The original_container represents a jQuery object that is the direct parent of the task elements.

jQuery Concatenate multiple DOM node references into one list

Lets say for example I have the following HTML:
<div class="level0">
<div>level 0 #1</div>
<div class="contents">
<div class="level1">
<div>level 1 #1</div>
<div class="contents">
<div class="level2"><div>level 2 #1</div></div>
</div>
</div>
</div>
</div>
<div class="level0">
<div>level 0 #2</div>
<div class="contents">
<div class="level1"><div>level 1 #2</div></div>
<div class="level1"><div>level 1 #3</div></div>
<div class="level1">
<div>level 1 #4</div>
<div class="contents">
<div class="level2"><div>level 2 #2</div></div>
<div class="level2"><div>level 2 #3</div></div>
</div>
</div>
</div>
</div>
<div class="level0"><div>level 0 #3</div></div>
I want to get all of the references to nodes with class "level0", "level1", or "level2".
Then I want to iterate over them starting with the "level2" references, then going to "level1", then "level0".
For instance, the following code would work for what I am trying to do:
$(".level2").each(function(){
console.log($(this).children().first().text());
});
$(".level1").each(function(){
console.log($(this).children().first().text());
});
$(".level0").each(function(){
console.log($(this).children().first().text());
});
That would make the console output be the following:
level 2 #1
level 2 #2
level 2 #3
level 1 #1
level 1 #2
level 1 #3
level 1 #4
level 0 #1
level 0 #2
level 0 #3
As you can see, they are in order based on class, THEN by order in the HTML from top to bottom. This is what I want
However, I want to store this list of elements in this order so I can just use one loop for all of the elements.
The following code is similar to what I want but displays iterates through the rows in the wrong order.
var $rows = $(".level2, .level1, .level0");
$rows.each(function(){
console.log($(this).children().first().text());
});
This iterates through the rows using in-order traversal.
Is there any way to append these rows into a single variable that I can iterate over in the order I wanted way above?
A rough example using a for loop and _map
var allArr = [],
maxLevel = 2;
for (var i = maxLevel; i > -1; i--) {
var arr = $('.level'+ i ).children('div:first-child').map(function () {
return $.trim($(this).text());
}).get();
allArr = allArr.concat(arr);
}
console.log(allArr);
Needs to be refactored though.
Check Fiddle
I just stumbled upon a working answer looking through a plugins source code.
The solution uses jQuery function $.merge().
The following code accomplishes what I want to do:
var $rows = $(".level2");
var $rows = $.merge($rows,$(".level1"));
var $rows = $.merge($rows,$(".level0"));
$rows.each(function(){
console.log($(this).children().first().text());
});
I supposed if you wanted to get a little more tricky, the following code would also work:
var $allRows = $.merge($.merge($(".level2"),$(".level1")),$(".level0"));
$allRows.each(function(){
console.log($(this).children().first().text());
});
Check out this fiddle to see both options working.

Categories

Resources