Angular, Ionic Swappable Grid does not swap the right elements - angularjs

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 ;)

Related

Reorder a list without ng-repeat

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

Protractor: How to find an element in an ng-repeat by text?

I'm looking to get a specific element inside an ng-repeat in protractor by the text of one of its properties (index subject to change).
HTML
<div ng-repeat="item in items">
<span class="item-name">
{{item.name}}
</span>
<span class="item-other">
{{item.other}}
</span>
</div>
I understand that if I knew the index I wanted, say 2, I could just do:
element.all(by.repeater('item in items')).get(2).element(by.css('.item-name'));
But in this specific case I'm looking for the 'item in items' that has the specific text (item.name) of say "apple". As mentioned, the index will be different each time. Any thoughts on how to go about this?
public items = element.all(by.binding('item.name'))
getItemByName(expectedName) {
return this.items.filter((currentItem) => {
return currentItem.getText().then((currentItemText) => {
return expectedName === currentItemText;
});
}).first();
}
And invoke method like that this.getItemByName('Item 1'). Replace Item 1 with expected string.
function elementThere(specificText, boolShouldBeThere){
var isThere = '';
element.all(by.repeater('item in items')).each(function (theElement, index) {
theElement.getText().then(function (text) {
// Uncomment the next line to test the function
//console.log(text + ' ?= ' + specificText);
if(text.indexOf(specificText) != -1){
element.all(by.repeater('item in items')).get(index).click();
isThere = isThere.concat('|');
}
});
});
browser.driver.sleep(0).then(function () {
expect(isThere.indexOf('|') != -1).toBe(boolShouldBeThere);
});
}
it('should contain the desired text', function () {
elementThere('apple', true);
}
Does this fit your needs?
I was able to solve this by simplifying #bdf7kt's proposed solution:
element.all(by.repeater('item in items')).each(function(elem) {
elem.getText().then(function(text) {
if(text.indexOf('apple') != -1) {
//do something with elem
}
});
});
Also, this particular solution doesn't work for my use case, but I'm sure will work for others:
var item = element(by.cssContainingText('.item-name', 'apple'));
//do something with item

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>

how to make ngReact work with ng-directives (ngClick, ngStyle, etc.)

[UPDATE July 23 2015]
I want to create a list of buttons, one for each marker in ctrl.getMarkers(). Let's assume a marker is something like
marker: {'title':' '}.
Using the classic ng-repeat, I have the following
<div ng-repeat="marker in ctrl.getMarkers()" >
<button class="btn btn-default"
ng-click = "doSomething(marker)"
ng-style="ctrl.isRed() ? {color:red} : {color:black}">
<b> {{::marker.title}}</b>
</button>
</div>
I have about 100 buttons to show, but the update is not fast at all. So I want to try the ng-react library. Unfortunately I haven't well understood how to use it.
So far, I wrote this:
HTML
<poi-list list="ctrl.getMarkers()" />
JS
/** #jsx React.DOM */
.value( "PoiList", React.createClass( {
propTypes : {
list: React.PropTypes.object.isRequired
},
getDefaultProps: function() {
return { list: [] };
},
render: function()
{
var markers = this.props.list.map( function( marker, i )
{
return React.DOM.button({className:'btn btn-default'
/*ng-click? ng-class?*/
}, marker.title) ;
} );
return React.DOM.div(null, markers);
}
}))
.directive( 'poiList', function( reactDirective ) {
return reactDirective( 'PoiList' );
} );
How could I use ng-click, ng-class etc with the React DOM element?
Here's the JSFiddle
[UPDATE 2]
I found a solution and I've answered my own question below. However I've got still a problem slowing down react: "Deprecated attempt to access property 'nodeType' on a non-Node object". Please, look at my answer below for further info about the error. Thank you
I figured out how to (almost) solve my problem. I pass the controller as attribute of the react element, so I can invoke its functions. Like this:
HTML
<poi-list list="ctrl.getMarkers()" ctrl="ctrl"/>
JS
.value( "PoiList", React.createClass( {
propTypes : {
list: React.PropTypes.object.isRequired,
ctrl: React.PropTypes.object.isRequired
},
render: function()
{
var ctrl = this.props.ctrl; // directive's Controller
var markers = this.props.list.map( function( marker, i )
{
return React.DOM.button( {
className:'btn btn-default',
onClick: function(){ctrl.doSomething(marker);},
onMouseOver: function(){ctrl.doSomethingElse(marker);},
}, marker.title
) ;
} );
return React.DOM.div(null, markers);
}
}))
.directive( 'poiList', function( reactDirective ) {
return reactDirective( 'PoiList' );
} );
It works!! JSFiddle
[UPDATE 1]
Anyway.... in my real application it's terrible slow... much slower then ng-repeat. I don't know why. Looking at the console, I have a bunch of error "Deprecated attempt to access property 'nodeType' on a non-Node object" from angular.js:328, and I guess this is the reason of the slow performance. I don't know what's the problem, and I don't have this error in the JSFiddle... any help?
This is the piece of Angular code that rises the error:
function forEach(obj, iterator, context) {
var key, length;
if (obj) {
if (isFunction(obj)) {
for (key in obj) {
// Need to check if hasOwnProperty exists,
// as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
iterator.call(context, obj[key], key, obj);
}
}
/*---->*/ } else if (isArray(obj) || isArrayLike(obj)) {// <---ERR
var isPrimitive = typeof obj !== 'object';
for (key = 0, length = obj.length; key < length; key++) {
if (isPrimitive || key in obj) {
iterator.call(context, obj[key], key, obj);
}
}
} else if (obj.forEach && obj.forEach !== forEach) {
obj.forEach(iterator, context, obj);
} else {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
iterator.call(context, obj[key], key, obj);
}
}
}
}
return obj;
}
[UPDATE 2]
I figured out that the problem was the array of complex objects passed to the React Element. It's impossible for React making comparison between old and new object, resulting in slowness and errors. This is the explanation of this problem and the solution.

kendo ui get id of checkbox when unchecked

i am using kendo ui tree view with check box
i want the check box's id when it is getting unchecked
this is kendo ui mine code
// var homogeneous contains data
$("#treeview").kendoTreeView({
checkboxes: {
checkChildren: false,
template:"# if(!item.hasChildren){# <input type='hidden' id='#=item.id#' parent_id='#=item.parent_id#' d_text='#=item.value#'/> <input type='checkbox' id_a='#= item.id #' name='c_#= item.id #' value='true' />#}else{# <div id='#=item.id#' style='display:none;' parent_id='#=item.parent_id#' d_text='#=item.value#'/> #}#",
},
dataSource: homogeneous,
dataBound: ondata,
dataTextField: "value"
});
function ondata() {
//alert("databound");
}
// function that gathers IDs of checked nodes
function checkedNodeIds(nodes, checkedNodes) {
//console.log(nodes);
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].checked) {
checkedNodes.push(nodes[i].id);
}
if (nodes[i].hasChildren) {
checkedNodeIds(nodes[i].children.view(), checkedNodes);
}
}
}
// show checked node IDs on datasource change
$("#treeview").data("kendoTreeView").dataSource.bind("change", function() {
var checkedNodes = [],
treeView = $("#treeview").data("kendoTreeView"),
message;
checkedNodeIds(treeView.dataSource.view(), checkedNodes);
if (checkedNodes.length > 0) {
message = "IDs of checked nodes: " + checkedNodes.join(",");
} else {
message = "No nodes checked.";
}
$("#result").html(message);
});
in this code i am not getting checkbox's id when it is unchecked so i have tried this
jquery code
$('input[type=checkbox]').click(function() {
if($(this).is(':checked')) {
alert('checked');
} else {
alert('not checked');
}
});
this code is only working in js fiddle but not in my case http://jsfiddle.net/NPUeL/
if i use this code then i can get the number of count but i dont know how to use it
var treeview = $("[data-role=treeview]").data("kendoTreeView");
treeview.dataSource.bind("change", function (e) {
if (e.field == "checked") {
console.log("Recorded Selected: " + $("[data-role=treeview] :checked").length);
}
});
what changed i need to do in data source so i can get id
thanks in adavance
If you want to get the id you might do:
$('input[type=checkbox]').click(function (e) {
var li = $(e.target).closest("li");
var id = $("input:hidden", li).attr("id");
var node = treeView.dataSource.get(id);
if (node.checked) {
console.log('checked');
} else {
console.log('not checked');
}
});
What I do in the event handler is:
find the closest li element that is the node of the tree that has been clicked.
the id is in an HTML input element that is hidden (this is the way that I understood that you have stored it).
Get item from dataSource using dataSource.get method.
See your code modified and running here
i made the small change and its working now
function ondata() {
$('input[type=checkbox]').click(function() {
if($(this).is(':checked')) {
alert('checked');
} else {
alert('not checked');
}
});
}

Resources