Count repeated items in an observableArray in knockout js - arrays

i have an observableArray with about 540 records
in the records it has an advert property and a date property i would like to do a function that finds all the adverts with on a particular date and counts the number of records eg
Date = 23/02/2015
Advert 1 (16)
Advert 2 (5)
Advert 3 (10)
Total (31)

This should help you get along, but basically you can have a function that iterates through the object and creates an array of counts:
self.counts = ko.observable();
function updateCounts() {
var leads = self.leads(),
counts = {};
for (var i = 0, j = leads.length; i < j; i++) {
var prop = leads[i].date_enquired();
if (counts[prop] == null) {
counts[prop] = {
name : prop,
count : 1
};
}
else {
counts[prop].count++;
}
}
// set it as an array instead of an object
self.counts(Object.keys(counts).map(function (key) { return counts[key]; }));
}
Run this function at the start and whenever the list updates:
updateCounts();
self.leads.subscribe(updateCounts);
Then display the object in the view in whatever way you'd like:
<div data-bind="foreach: counts">
<div data-bind="text: name + ': ' + count"></div>
</div>
JSFiddle
You could also create your own object that handles pushing to the array and updating the list of counts. That way additions are O(1) instead of O(n) each time an item is added or removed. Overall, it's not too big of a deal because iterating over 500 items is really fast.

Related

angularjs filter with devider functionality

I tried to implement a filter that returns a list as well as entries for divider. TextSearch should also be available with this filter.
Template:
<input type="search" placeholder="Search" ng-model="searchText" autocorrect="off" >
<li class="item item-checkbox"
ng-repeat="destination in destinations | (a)
destinationFilter:searchText:true" (b)
ng-class="destination.letter? 'item-divider':''"> (c)
a iterate over the array destinations
b use the custom filter
c if the item comming from the filter has a letter-field, it's marked as divider
Filter:
app.filter('myFilter', function() {
return function(input, key, startsWith) {
if(input){
var filteredInput = []; (1)
var lastChar = ''; (2)
var re = /.*/i; (3)
if(key){
if(startsWith) (4)
key = "^"+key;
re = new RegExp(key, "i");
}
for(var i=0; i<input.length; i++){
var item = input[i];
if(item.name.match(re)){ (5)
if(item.name.charAt(0) !== lastChar) { (6)
filteredInput.push({name:item.name.charAt(0),letter:true});
lastChar = item.name.charAt(0);
}
filteredInput.push(item); (7)
}
};
return filteredInput;
}
return input;
};
});
1 array to contain the filtered list-elements from input
2 stores last divider character
3 regular expression to match everything
4 if statsWith -> only check reg expression from the beginning of the word
5 if the regular expression-match is successful -> push the item to the filteredInput
6 if last character matches actual item-firstChar dont generate a new list-divider-entry
7 push the item to the filteredInput array
Unfortunately this filter is called very often by a list of 30 elements. It also causes this error:
Error: [$rootScope:infdig] http://errors.angularjs.org/1.2.25/$rootScope/infdig?
Divider are added for each element in the input I guess. It looks very odd.
FIDDLE: HERE
The problem is that your filter produces a new array each call, which leads angular to an infinite loop.
You better provide ng-repeat a constant array instead of a function (filter).
In your example it can be done simply by applying the filter in the controller instead of a view (your real world case might be different though):
$scope.filtered = $filter('destinationFilter')($scope.destinations, undefined, true);
In your html:
<li class="item item-checkbox" ng-repeat="destination in filtered" ng-class="destination.letter? 'item-divider':''" >
See this fiddle.

AngularJS - $index not updated with keyPress

I'm pretty new in AngularJS so this might be easy to solve,
I have a list of meetings I'm trying to navigate between them with my keyboard using keyPress,
The problem is that after the first navigation (UP or DOWN) using the keyboard, the $index is not updating, so for instance if I'm on meeting number 4, and clicking on the "UP" key, I will navigate to meeting 3, but clicking 'DOWN' afterwards will navigate me to meeting number 5 (and not back to 4, becuase my $index is stuck at 4)
the HTML looks like this:
<ul class="meeting-list">
<div ng-repeat="meeting in meetings" ng-init="meeting.innerIndex = $index">
<div class="meeting-block" ng-keydown="KeyPress($event, $index)" ng-click="showMeeting(meeting)" >
<span class="title">{{meeting.Title}}</span><br/>
</div>
</div>
</ul>
This is my JS code:
$scope.meetings = [{"title":"sub1","id":123},{"id":124,"title":"sub2"},{"title":"sub3","id":125},{"title":"sub4","id":126}];
$scope.KeyPress = function(keypress, index) {
if (keypress.which == 38) { //UP
if (index != 0) {
for (var i = 0; i < $scope.meetings.length; i++) {
if ($scope.meetings[i].innerIndex == (index - 1)){
$scope.showMeeting($scope.meetings[i]);
return ;
}
}
}
if (keypress.which = 40) { //DOWN
if ($scope.meetings.length - 1 != index) {
for (var i = 0; i < $scope.meetings.length; i++) {
if ($scope.meetings[i].innerIndex == (index + 1)) {
$scope.showMeeting($scope.meetings[i]);
return ;
}
}
}
};
$scope.showMeeting = function(meeting) {
$location.path('meetings/view/' + meeting.Id);
};
$index is a property of an ng-repeated item, it is always equal to the position in the ng-reapeated array. I am not sure if it's implemented to be unassignable, but I expect it should be and you should treat it that way.
Instead of trying to change the index, you should have your own property that keeps track of the index in the array that you're selected on.
This is trivial to do, just initialise it in your controller, then use it instead of your $index when navigating.
$scope.selectedIndex = 0; //start with the first meeting selected.
You have 2 problems here.
You cannot use the $index to navigate up and down the list
When you do index -= 1; this as no effect whatsoever on the $index or the selected meeting. You are passing the $index's value (an immutable number) and not it's reference. So, if you have $index = 5 and do index -= 1 (on the KeyPress), you will end up with: $index = 5 and index = 4.
You need to change the focus of the selected meeting in order to update the element that will receive the keyboard event
In order to change the focus you could use something like this:
// UP
$(keypress.target).prev().trigger('focus');
...
// DOWN
$(keypress.target).next().trigger('focus');
But I'm not sure if it's a 100% applicable to your case. You may need to define another way to select the element that will receive the focus. A directive here would be a good idea...

jQuery 2.1.0 | Reorder li based on separate delimited string

I need to reorder li containing unique same-length text based on position of identical unique same-length semicolon-delimited text found in a separate string, as in following example:
li elements ...
<ul>
<li><span>28dg4</span></li>
<li><span>jjk63</span></li>
<li><span>HN3gE</span></li>
<li><span>k213C</span></li>
</ul>
... sorted by order of semicolon delimited substrings (left to right),
<div id="string">HN3gE;28dg4;k213C;jjk63</div>
... must reorder li like this:
<ul>
<li><span>HN3gE</span></li>
<li><span>28dg4</span></li>
<li><span>k213C</span></li>
<li><span>jjk63</span></li>
</ul>
I've set up a fiddle but can't formulate the approrpiate comparison function (new to me):
http://jsfiddle.net/ysec0ydp/16/
Maybe I cannot use sort and should use something else? I haven't found anything on here or elsewhere that does specifically what I want ;-(
Thx for pointers.
UPDATE
The following code outputs the proper order of string, but doesn't display as appended li:
<div id="string">tree$1234567890;boat$4567321890;9876512340;1736925408</div>
<ul>
<li><span>1234567890</span></li>
<li><span>1736925408</span></li>
<li><span>4567321890</span></li>
<li><span>9876512340</span></li>
</ul>
var ul = $('ul');
var li = ul.children("li");
var my_list = li.map(function() {
return $(this).find('span').text();
}).get();
var my_string = $.map($("#string").text().split(/;/), function (t) {
return t.replace(/^.*\$/, "");
});
li.detach();
temp = [];
for (i = 0; i < my_string.length; i++) {
for (j = 0; j < my_list.length; j++) {
if ( my_string[i] == my_list[j] ) {
temp.push(my_list[j]);
}
}
}
$("ul").append( temp );
// RESULT is one li: 1234567890456732189098765123401736925408
// instead of the 4 reordered li: 1234567890
4567321890
9876512340
1736925408
How can this be fixed? See fiddle: http://jsfiddle.net/ysec0ydp/19/
Here you go:
var orderBy = $('#string').text().split(';');
var $newUl = $('<ul></ul>');
var $myList = $('#my-list'); // gave id="my-list" to your list to identify it.
orderBy.forEach(function(key){
$newUl.append($myList.find('li').filter(function(idx, li){ return $(li).text() === key; }).detach());
});
$myList.append($newUl.html());
I took the liberty of giving an id to your list to identify it.
Fiddle here.

Elegant way in Scala to render List of objects(columns) with List attribute(cell values) in a grid?

What is the "best" way to render a List of model objects(matrix) in a Scala template such as:
public class Column extends Model {
public String columnLabel;
#OneToMany
public List cells;
}
public class Cell extends Model {
public String rowLabel; //index
public BigDecimal value;
}
For the sake of this question, cells.size() and rowLabel are consistent for all column objects. The controller returns a List[Column] to the view. I have tried to convert the List to an Array with a helper:
#matrix(list: List[Column]):Array = #{
var rowCount = list.head.values.size()
var colCount = list.size()
var m = Array.ofDim[String](rowCount,colCount)
for (i <- 0 to rowCount-1) {
for ( j <- 0 to colCount-1) {
matrix(i)(j) = list(j).cells(i).value.toString();
}
}
return m;
}
and then in the view:
<div>
#for(i <- 1 to currentPage.getList.head.values.size()) {
<div class="row">
#for(j <- 1 to currentPage.getList.size()) {
<div class="col-md-1">#matrix(currentPage.getList)(i)(j)</div>
}
</div>
}
</div>
but of course this is only extracting the matrix values and not the column or row labels.
Is there some Scala array goodness that can be used on the List of Lists? Efficiency is important as the array size will be approx. 20 columns x 2000 rows. Or is it a better approach to have the controller return the matrix rows explicitly rather than try to convert them in the view?
Use for-comprehensions instead of imperative-style cycles, they are more natural in Scala.
And for your task, there are many ways to do it, one of them is like this:
// transform your data to grid, i.e. Map((row,col) -> value).
// Do this outside the template and pass the result (so that you get the immutable map as a template input)
val toGrid = {
currentPage.getList.map{ col => col.cells.map(cell =>
(cell.rowLabel, col.columnLabel, cell.value)
)}.flatten.map{ case (row, col, value) => ((row,col)->value) }.toMap
}
#rows = #{toGrid.map{ case ((row,col),value) => row }.toSet.toList.sorted}
#columns = #{toGrid.map{ case ((row,col),value) => col }.toSet.toList.sorted}
<div>
#for(row -> rows) {
<div class="row">
#for(col -> columns) {
<div>#toGrid.get(row,col).getOrElse("")</div> //if you might have missing rows/cols
}
</div>
}
</div>
UPDATE. If, for some reason, you can't have Scala class outside the template, then the following should perform better(assuming there are no gaps in rows or columns):
#data = #{currentPage.getList}
#triplets = #{data.map{
col => col.cells.map(cell => (cell.rowLabel,col.columnLabel,cell.value)
)}.flatten
#rows = #{triplets.groupBy(_._1).map{ case (row,cols) =>
(row, cols.map{ case (row,col,value) => (col,value) })
}}
<div>
#for((row,cols) -> rows.sortBy(_._1)) {
<div class="row">
#for((col,value) -> cols.sortBy(_._1)){
<div>#value</div>
}
</div>
}
</div>

ng-repeat does not seem to rebind ng-click on a re-rendering

I have an ng-repeat assigned to a row in a table as shown below. When the user selects a down arrow in the row, the method moveDown gets executed, which reorders the list (see code).
When I look at the DOM, everything looks right - The rows are reordered, and the ng-click sees the newly assigned seqNbr.
Better explanation:
Initially first row shows data-ng-click='moveDown(0);' second data-ng-click='moveDown(1);'
After selecting the first one, the first and second row trade places. The seqNbr are swapped in the objects and list is reordered, then the ng-repeate is reexecuted.
Now the DOM shows that the NEW first row has: data-ng-click='moveDown(0);' and the old first row, now the second row, has data-ng-click='moveDown(1);'
However if I select the new first row, what gets executed is moveDown(1) (the old method associated with that row). Its as if the DOM is updated, but not the method binding.
HTML:
<tr class='evidencerow' data-ng-repeat="e in data.evidence">
<td><div class='assertion webdiv' style='height:4em;'
data-ng-dblclick='openReference(e);'>
<span data-ng-bind-html-unsafe='e.assertion'></span>
</div>
</td>
<td>
<img src='img/UpArrow16x16.png' data-ng-hide='$first'
data-ng-click='moveUp({{e.seqNbr}});' style='width:32px;'>
<img src='img/DownArrow16x16.png' data-ng-hide='$last'
data-ng-click='moveDown({{e.seqNbr}});' style='width:32px;'>
</td>
</tr>
controller code:
$scope.moveUp = function(seq) {
var recs = $scope.data.evidence.slice(0);
recs[seq].seqNbr = seq - 1;
if (_ev.notEmpty(recs[seq - 1])) {
var s2 = seq - 1;
recs[s2].seqNbr = seq;
}
recs.sort(_ev.compareSeqNbr);
$scope.data.evidence = recs;
};
$scope.moveDown = function(seq) {
var recs = $scope.data.evidence.slice(0);
recs[seq].seqNbr = seq + 1;
if (_ev.notEmpty(recs[seq + 1])) {
var s2 = seq +1;
recs[s2].seqNbr = seq;
}
recs.sort(_ev.compareSeqNbr);
$scope.data.evidence = recs;
};
This behavior doesn't seem right to me. The result is instead of the rows moving up and down, they toggle back and forth.
Try out this jsFiddle. I think it does what you're looking for.
I modified your moveUp() and moveDown() functions a bit and they now take the full "evidence" object rather than just a number.
$scope.moveUp = function(e) {
var idx = $scope.data.evidence.indexOf(e);
var removed = $scope.data.evidence.splice(idx, 1);
$scope.data.evidence.splice(idx - 1, 0, removed[0]);
};
$scope.moveDown = function(e) {
var idx = $scope.data.evidence.indexOf(e);
var removed = $scope.data.evidence.splice(idx, 1);
$scope.data.evidence.splice(idx + 1, 0, removed[0]);
};

Resources