Reorder a list without ng-repeat - angularjs

For reasons I won't go into, I'm not using ng-repeat for a list of items.
I have a list like this:
<p>Search: <input type="text" ng-model="search"></p>
<div id="grid" filter-list="search">
<div id="item1" class="[list of properties]">
//item content
</div>
<div id="item2" class="[list of properties]">
//item content
</div>
<div id="item3" class="[list of properties]">
//item content
</div>
<div id="item4" class="[list of properties]">
//item content
</div>
</div>
As you can see I already have a search function working well.
My app script looks like this:
<script>
var app = angular.module('myApp', []).controller('MainCtrl', function ($scope, $timeout) {
});
app.directive('filterList', function ($timeout) {
return {
link: function (scope, element, attrs) {
var div = Array.prototype.slice.call(element[0].children);
function filterBy(value) {
div.forEach(function (el) {
el.className = el.textContent.toLowerCase().indexOf(value.toLowerCase()) !== -1 ? '' : 'ng-hide';
});
}
scope.$watch(attrs.filterList, function (newVal, oldVal) {
if (newVal !== oldVal) {
filterBy(newVal);
}
});
}
};
});
</script>
The problem is I need to be able to reorder the list based on class values or even the ids (At this stage it doesn't matter).
Every tutorial/guide online assumes that the code uses "ng-repeat" ... which I simply can't use here.
Is there any way I can get the items to reorder without using ng-repeat?

Instead of using ng-repeat just sort the data.
$scope.items = $scope.items.sort(yourSortFn);

You can modify this sorting script:
<ul id="id01">
<li>Oslo</li>
<li>Stockholm</li>
<li>Helsinki</li>
<li>Berlin</li>
<li>Rome</li>
<li>Madrid</li>
</ul>
<script>
function sortList() {
var list, i, switching, b, shouldSwitch;
list = document.getElementById("id01");
switching = true;
/* Make a loop that will continue until
no switching has been done: */
while (switching) {
// Start by saying: no switching is done:
switching = false;
b = list.getElementsByTagName("LI");
// Loop through all list items:
for (i = 0; i < (b.length - 1); i++) {
// Start by saying there should be no switching:
shouldSwitch = false;
/* Check if the next item should
switch place with the current item: */
if (b[i].innerHTML.toLowerCase() > b[i + 1].innerHTML.toLowerCase()) {
/* If next item is alphabetically lower than current item,
mark as a switch and break the loop: */
shouldSwitch= true;
break;
}
}
if (shouldSwitch) {
/* If a switch has been marked, make the switch
and mark the switch as done: */
b[i].parentNode.insertBefore(b[i + 1], b[i]);
switching = true;
}
}
}
</script>
Just target the div elements instead of li elements, and compare them based on their .className property (after formating them propelly depending on what data you get) instead of their .innerHTML

Sort or filter or reduce your array of elements any way you need then append the results
Very basic example using descending id:
scope.sortDesc = function() {
div.sort(function(a, b) {
return /\d+/.exec(b.id) - /\d+/.exec(a.id);
});
element.append(div);
}
scope.sortDesc();
Plunker demo

Related

AngularJS: Why custom filter triggered on ng-Click?

I have a a custom filter to filter list of item name based on user input
.filter('searchFor', function () {
return function (arr, searchText) {
var result = [];
debugger
if (!arr || !arr.length) {
return;
}
if (!searchText) {
return arr;
}
......
return result;
}
})
I use in my View like this:
<ion-item ng-repeat="item in items | searchFor:searchText">
<div ng-click="DisplayItem(item.id)">
....
The problem is, when I try to trigger DisplayItem function or even tapping on the back button (both using ng-Click), the searchFor filter got triggered and the ng-Click function does not work.
If I replace the searchFor with `filter like this:
<ion-item ng-repeat="item in items | filter:searchText">
Then the ng-Click works but I can't use my custom filter.
Why is this happening?
The filter function should be a pure function, which means that it should always return the same result given the same input arguments and should not affect the external state.
Try following it will get a new copy of array input with filters applied.
.filter('searchFor', function () {
return function (arr, searchText) {
var result = [];
if (!arr || !arr.length) {
return;
}
if (!searchText) {
return [];
}
angular.forEach(arr, function(item) {
//your filtering logic should come here
result.push(item);
});
return result;
}
see more details on Custom filters

Compare two arrays and concat without duplicates

I have two arrays. I can push and splice by clicking on a word in searchWords, which adds or removes a word to the currentWordlist.
What I want to have is a button that transfers all the searchWords to the currentWordlist, without overwriting the words that are actually on the currentWordlist.
I came up with this code:
$scope.addAll = function () {
var searchWords = [];
var currentWords = [];
// safes all searchwords to the array
for (var i = 0; i < $scope.searchWords.length; i++) {
searchWords.push($scope.searchWords[i]);
}
// safes all currentwords to the array
for (var j = 0; j < $scope.currentWordlist.length; j++) {
currentWords.push($scope.currentWordlist[j]);
}
console.log("searchWords " + searchWords.length);
console.log("currentWords " + currentWords.length);
angular.forEach(searchWords, function(value1, key1) {
angular.forEach(currentWords, function(value2, key2) {
if (value1._id !== value2._id) {
$scope.currentWordlist.push(value1);
}
});
});
};
I go through both of the arrays and safe them so that I can use the arrays inside my two angular.forEach to check if there are duplicates. If I don't push to the currentWordlist. But it's not working. I get an [ngRepeat:dupes] error, but I cannot use track by $index because otherwise removing from the list removes the wrong word. I think I am doing something critically wrong here, but I couldn't find out what so far (hours of trial and error :0)
I would suggest to use angular unique filter with ng-repeat directive. The code could be as follows:
$scope.addAll = function () {
// use angular.copy to create a new instance of searchWords
$scope.combinedWords = angular.copy($scope.searchWords).concat($scope.currentWordlist);
};
And then in your view:
<div ng-repeat="word in combinedWords | unique:'_id'">
{{word}}
</div>
Usage:
colection | uniq: 'property'
It also possible to filter by nested properties:
colection | uniq: 'property.nested_property'
You can simply do like this
angular.forEach($scope.searchWords, function(value1, key1) {
var temp=true;
angular.forEach($scope.currentWordlist, function(value2, key2) {
if (value1.id === value2.id)
temp=false;
});
if(temp)
$scope.currentWordlist.push(value1);
});
var app = angular.module("app", []);
app.controller("ctrl", function($scope) {
$scope.searchWords=[{id:1,name:'A'},{id:2,name:'B'},{id:1,name:'A'},{id:4,name:'D'}];
$scope.currentWordlist=[];
$scope.addAll = function() {
angular.forEach($scope.searchWords, function(value1, key1) {
var temp=true;
angular.forEach($scope.currentWordlist, function(value2, key2) {
if (value1.id === value2.id)
temp=false;
});
if(temp)
$scope.currentWordlist.push(value1);
});
console.log($scope.currentWordlist);
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<button ng-click="addAll(newWord)">Add</button>
<div>{{currentWordlist}}</div>
</div>

Angular, Ionic Swappable Grid does not swap the right elements

On the following project: https://github.com/pc-magas/faster
I generate a grid like that:
<div ng-repeat="(i,row) in grid.value" class="row">
<div ng-repeat="(j,item) in row" class="col">
<img on-swipe-up="swipeup({{i}},{{j}})" on-swipe-down="swipeDown({{i}},{{j}})" on-swipe-left="swipeLeft({{i}},{{j}})" on-swipe-right="swipeRight({{i}},{{j}})" src="{{item.icon}}"/>
</div>
</div>
And His is the code on my controller:
.controller('Game',function($scope,$state,$ionicModal,Game,MenuItem)
{
/*###################### Modal Area ######################*/
$ionicModal.fromTemplateUrl('gameOverModal.html',
{
scope: $scope,
animation: 'slide-in-up'
})
.then(function(modal)
{
$scope.gameOverModal = modal;
});
$scope.$on('$destroy', function()
{
$scope.gameOverModal.remove();
});
$scope.closeGameOverModal=function()
{
$scope.gameOverModal.hide();
$state.go("menu");
}
/*###############################################################*/
/*################### Controller Initialization ####################*/
var GameItem=Game.item;
var GameClass=Game.game;
/*##################### End Controller Initialization ##############*/
/**
*Function That does all the dirty job for initialization
*/
var init_game=function()
{
console.log(Game.current_game);
if(typeof Game.current_game === 'undefined' || Game.current_game === null)
{
/**
*Items for the Game
*/
var items=[
new GameItem('img/icon1.jpg','img/icon1.jpg','img/icon1.jpg','trolley'),
new GameItem('img/icon2.jpg','img/icon2.jpg','img/icon2.jpg','metro'),
new GameItem('img/icon3.jpg','img/icon3.jpg','img/icon3.jpg','bus'),
new GameItem('img/icon4.jpg','img/icon4.jpg','img/icon4.jpg','tram'),
];
/**
*Callbacks for Game
*/
var callbacks={
'pause':function(time)
{
console.log("Game Paused");
$state.go('menu');
},
'afterInit':function(game)
{
MenuItem.items.play.name_="Continue Game";
MenuItem.items.play.clickFunction=function()
{
console.log("clicked");
$state.go('game');
Game.current_game.play();//Do not comment unlsess game will not resume
};
/*Making An Option For saving*/
var saveItem=new MenuItem.MenuItem("Save Game",'regular-btn',"",false,function()
{
game.save();
});
//Add on the top an Option to save the game
MenuItem.items.others.unshift(saveItem);
console.log(MenuItem.items.others);
},
'over':function()
{
ionic.EventController.trigger('gameOver',{});
}
};
Game.current_game=new GameClass(items,60,5,5,callbacks,$scope);
Game.current_game.init();
}
else // We may need to go to another page and return Therefore we must need a way to resume
{
console.log("Here resuming the game");
Game.current_game.play();
}
$scope.timer = Game.current_game.timer;
$scope.points=Game.current_game.getScore();
$scope.grid=Game.current_game.grid;
/*Functions that do all the swipe*/
$scope.swipeup=function(i,j)
{
Game.current_game.swap(i,j,'up');
$scope.grid=Game.current_game.grid;
};
$scope.swipeDown=function(i,j)
{
Game.current_game.swap(i,j,'down');
};
$scope.swipeLeft=function(i,j)
{
Game.current_game.swap(i,j,'left');
};
$scope.swipeRight=function(i,j)
{
Game.current_game.swap(i,j,'right');
};
/*End of: "Functions that do all the swap"*/
};
ionic.EventController.on('gameOver',function()
{
console.log("GameOver Event");
MenuItem.items.play.name_="New Game";
MenuItem.items.others.shift();
Game.current_game=null;
$scope.gameOverModal.show();
});
init_game();
$scope.pause=function()
{
console.log("Pausing Game");
Game.current_game.pause();
}
});
As you can See the lines that does the swap are:
/*Functions that do all the swipe*/
$scope.swipeup=function(i,j)
{
Game.current_game.swap(i,j,'up');
$scope.grid=Game.current_game.grid;
};
$scope.swipeDown=function(i,j)
{
Game.current_game.swap(i,j,'down');
};
$scope.swipeLeft=function(i,j)
{
Game.current_game.swap(i,j,'left');
};
$scope.swipeRight=function(i,j)
{
Game.current_game.swap(i,j,'right');
};
/*End of: "Functions that do all the swap"*/
};
And on my service the functions that does the swap is:
game.swap=function(i,j,direction)
{
console.log("i: "+i,"j: "+j)
switch(direction)
{
case 'up':
if(i!==0) //Cannot swap first line elements
{
console.log("Can swap Up");
swapAction(i,j,i-1,j);
}
break;
case 'down':
if(i!==game.grid.value.length-1) //cannot swap last line elements
{
console.log("Can swap Down");
swapAction(i,j,i+1,j);
}
break;
case 'left':
if(j!==0) //Cannot swap first column elements
{
console.log("Can swap Left");
swapAction(i,j,i,j-1);
}
break;
case 'right':
if(j!==game.grid.value[i].length-1) //Cannot swap last column elements
{
console.log("Can swap Right");
swapAction(i,j,i,j+1);
}
break;
}
};
var swapAction=function(i,j,newi,newj)
{
var temp=game.grid.value[i][j];
game.grid.value[i][j]=game.grid.value[newi][newj];
game.grid.value[newi][newj]=temp;
}
As I noticed after a few swaps the swapped elements are not the correct one. Eg. If an element is in Position 4,3 and I swap with the element in the position 4,4 then in initially swaps but I cannot swap it back.
And this happend because the {{i}} and {{j}} passed as parameters are not the correct one. Do you have an Idea how on swipe will get the correct {{i}} and {{j}} each time?
One soulution That I thought is what If "baptized" each element with a unique number and look for the posizition of each element on the loop but I think is not the best one performance-wize.
An another solution that I thought is for each swappable object to have an swipe() method but still I will need to find the Object's Coordinates on the grid. Also for that I am not too sure if it is Possible.
Finally I solved it by doing this:
I chaned the GameItem like that:
function GameItem(icon,icon_destroyed,icon_marked,name,unique)
{
item.unique=(unique)?unique:0;//A unique number for new items
...
item.uniqueId=function()
{
return item.name+item.unique;
}
....
item.clone=function()
{
var newClone=new GameItem(item.icon,item.icon_destroyed,item.icon_marked,item.name,item.unique);
item.unique++;//After a clone refresh the unique number in order the next clones to have new name
return newClone;
}
}
Also now on the services on Game I set a new method:
game.swapById=function(unique,direction)
{
for(var i=0;i<game.grid.value.length;i++)
{
for(var j=0;j<game.grid.value[i].length;j++)
{
var item=game.grid.value[i][j];
console.log(item.uniqueId(),unique);
if(item.uniqueId()===unique)
{
game.swap(i,j,direction);
return;
}
}
}
}
Therefore I look for a uniqie Id ;)
SO my html is:
<div ng-repeat="(i,row) in grid.value" class="row">
<div ng-repeat="(j,item) in row" class="col">
<img on-swipe-up="swipeup('{{item.uniqueId()}}')" on-swipe-down="swipeDown('{{item.uniqueId()}}')" on-swipe-left="swipeLeft('{{item.uniqueId()}}')" on-swipe-right="swipeRight('{{item.uniqueId()}}')" src="{{item.icon}}"/>
</div>
</div>
And I call the following methods from the controller:
$scope.swipeup=function(unique)
{
Game.current_game.swapById(unique,'up');
};
$scope.swipeDown=function(unique)
{
Game.current_game.swapById(unique,'down');
};
$scope.swipeLeft=function(unique)
{
Game.current_game.swapById(unique,'left');
};
$scope.swipeRight=function(unique)
{
Game.current_game.swapById(unique,'right');
};
I think is a helpfull solution ;)

scope variable not updating with ng-change - angularjs

Seems like a simple problem though but finding it hard to fix.
There is a pagination component, that has a button & a dropdown. User can go to a page by either clicking the button or selecting that page number in dropdown.
The problem is, when I select a value in the dropdown, nothing happens. Because the scope variable doesnt change from the previous one.
aspx:
<div data-ng-app="app" data-ng-controller="ReportsCtrl">
<div id="paging-top">
<div>
<ul>
<li>
<select data-ng-model="SelectedPage" data-ng-change="ShowSelectedPage();"
data-ng-options="num for num in PageNumbers track by num">
</select>
</li>
<li data-ng-click="ShowNextPage();">Next</li>
</ul>
</div>
</div>
app.js
var app = angular.module("app", ["ngRoute"]);
ReportsCtrl.js
app.controller("ReportsCtrl", ["$scope","ReportsFactory",function ($scope,ReportsFactory) {
init();
var init = function () {
$scope.ShowReport(1);
}
$scope.ShowReport = function (pageNumber) {
GetUserResponsesReport(pageNumber);
}
function GetUserResponsesReport(pageNumber) {
$scope.UserResponsesReport = [];
var promise = ReportsFactory.GetReport();
promise.then(function (success) {
if (success.data != null && success.data != '') {
$scope.UserResponsesReport = success.data;
BindPageNumbers(50, pageNumber);
}
});
}
function BindPageNumbers(totalRows, selectedPage) {
$scope.PageNumbers = [];
for (var i = 1; i <= 5 ; i++) {
$scope.PageNumbers.push(i);
}
$scope.SelectedPage = selectedPage;
}
$scope.ShowSelectedPage = function () {
alert($scope.SelectedPage);
$scope.ShowReport($scope.SelectedPage);
}
$scope.ShowNextPage = function () {
$scope.SelectedPage = $scope.SelectedPage + 1;
$scope.ShowReport($scope.SelectedPage);
}
}]);
Say, the selected value in dropdown is 1. When I select 2 in the dropdown, the alert shows1. When Next is clicked, the dropdown selection changes to 2 as expected. Now, when I select 1 in the dropdown, the alert shows 2.
Tried to make a fiddle, but do not know how to do with a promise - http://jsfiddle.net/bpq5wxex/2/
With your OP SelectedPage is just primitive variable.
With every angular directive new scope is get created.
So,SelectedPage is not update outside the ng-repeat scope after drop-down is changed i.e. in parent scope which is your controller.
In order to do this,use Object variable instead of primitive data types as it update the value by reference having same memory location.
Try to define SelectedPage object in controller in this way.
$scope.objSelectedPage = {SelectedPage:''};
in HTML
<select data-ng-model="objSelectedPage.SelectedPage" data-ng-change="ShowSelectedPage();"
In ShowSelectedPage
$scope.ShowSelectedPage = function () {
console.log($scope.objSelectedPage.SelectedPage);
$scope.ShowReport($scope.objSelectedPage.SelectedPage);
}

Issue with view updation in AngularJS directive

I am using the following directive for 'add tag' functionality in my application:
directives.addTag = function ($http) {
return {
link: function (scope, element, attrs) {
element.bind('keypress', function (event) {
if (event.keyCode == 13) { /*If enter key pressed*/
if (!scope.$parent.post) { //For KShare
var newTagId = "tagToNote";
}
else { //For KB
var newTagId = "tagToAddFor" + scope.post.meta.id;
}
var tagValue = element[0].value;
if (tagValue == "")
return;
if (!scope.$parent.post) {
scope.$parent.tags.push(tagValue);
scope.addTagButtonClicked = false;
}
else {
scope.post.tags.push(tagValue);
scope.addTagButtonClicked = false;
}
scope.$apply();
element[0].value = "";
}
});
}
}
}
This is the HTML code for rendering the tags:
<div class="tagAdditionSpan" ng-repeat="tag in post.tags" ng-mouseenter="hover = true" ng-mouseleave="hover = false">
<span>{{tag}}</span>
<span class="deleteIconSpan" ng-class="{deleteTagIcon: hover}" ng-click="$parent.deleteTag($index,$parent.$index);"></span>
</div>
I have a textbox to add tags when a user types the name of the tag in it and presses 'Enter' key. On page load, I am statically populating 1 tag into the 'tags' array.
I am even able to add tags using the tags and it is reflected in the view. However after adding 2 or 3 tags, it starts misbehaving and the view is no longer updated with the added tags.
I tried debugging this and found that it is being updated in the 'scope.post.tags' array but is not reflected in the view.
What am I doing wrong?
Based on the comments received, I was able to solve the issue. 'ng-repeat' used to break the loop on addition of duplicate tags and hence the view was not updated accordingly.
This fixed the issue(added 'track by' in ng-repeat):
<div class="tagAdditionSpan" ng-repeat="tag in post.tags track by $index" ng-mouseenter="hover = true" ng-mouseleave="hover = false">
<span>{{tag}}</span>
<span class="deleteIconSpan" ng-class="{deleteTagIcon: hover}" ng-click="$parent.deleteTag($index,$parent.$index);"></span>
</div>

Resources