angular ui router directive dynamically ui-sref - angularjs

HTML
<li class="dropdown signals" signals="signals" data-nb-signals="" style="visibility: hidden;">
<a data-toggle="dropdown" href="#">
<i class="glyphicon glyphicon-inbox"></i><span class="badge">1</span>
<b class="caret"></b></a>
<ul class="dropdown-menu"></ul>
</li>
JS
.directive('nbSignals',function($compile,$interpolate) {
return {
restrict: 'A',
scope:{
signals:'='
},
link: function(scope, element) {
var signals = scope.signals,
num = signals.length,
$dropdown = element.find('ul.dropdown-menu'),
liTpl = [],
i18n = {add_post:'nuovo articolo'};
if(num > 0){
element.css('visibility', 'visible');
for(var i = 0; i < num; i++){
var current = signals[i];
var href = $interpolate('blog_details({id:{{_id}},slug:{{slug}}})')(current);
liTpl.push('<li><a data-ui-sref="'+href+'">'+i18n[current.label]+'</a></li>');
}
$dropdown.append($compile(liTpl.join(''))(scope));
}
}
};
});
Doing so i've got
Error: [$parse:syntax] Syntax Error: Token 'f9ccb520daa8c167b3431' is
unexpected, expecting [}] at column 8 of the expression
I also tried with $state.href but I don't find the way
to pass the value (I cannot use href beacuse of give me 404)
so what's the way ?

The exception here comes from the fact that the GUID is in fact a string. So we cannot handle it as a number, i.e. without apostrophe:'
so, because your current could look like this:
// the one of the $scope.signals
current {
_id: '016ab73979797971605013efc42942e8',
...
}
we must change the parameter passing similar way, instead of this:
// GUID is not a number
var href = $interpolate('blog_details({id:{{_id}},slug:{{slug}}})')(current);
we must use: '{{_id}}'
// GUID is a string here
var href = $interpolate('blog_details({id:\'{{_id}}\',slug:{{slug}}})')(current);

Related

Edit an object inside several ng-repeat

I got two ng-repeat who display objects call 'post', and I have a button for edit the text and update it.
Everything works fine but I still got a little problem here's the html code :
<li ng-repeat="post in posts | filter: { etat: 'aTraiter' } ">
<p ng-show="!editing[$index]" ng-model href="#/{{post._id}}">{{ post.corps }}</p>
<input ng-show="editing[$index]" type="text" ng-model="post.corps">
<a ng-show="!editing[$index" ng-click="edit(post)">Editer</a>
<a ng-show="editing[$index]" ng-click="update(post)">Confirmer</a>
<a ng-show="editing[$index]" ng-click="cancel(post)">Annuler</a>
</li>
<li ng-repeat="post in posts | filter: { etat: 'enCours' } ">
<p ng-show="!editing[$index]" ng-model href="#/{{post._id}}">{{ post.corps }}</p>
<input ng-show="editing[$index]" type="text" ng-model="post.corps">
<a ng-show="!editing[$index]" ng-click="edit(post)">Editer</a>
<a ng-show="editing[$index]" ng-click="update(post)">Confirmer</a>
<a ng-show="editing[$index]" ng-click="cancel(post)">Annuler</a>
</li>
the controller :
$scope.editing = [];
$scope.posts= Posts.query();
$scope.edit = function(post){
var idx = $scope.posts.indexOf(post);
$scope.editing[idx] = angular.copy($scope.posts[idx]);
}
$scope.update = function(post){
var idx = $scope.posts.indexOf(post);
Posts.update({id: post._id}, post);
$scope.editing[idx] = false;
}
$scope.cancel = function(post){
var idx = $scope.posts.indexOf(post);
post = angular.copy(post);
$scope.editing[idx] = false;
}
If I got just one post I can edit it and all is ok.
But when I got one post in the both ng-repeat I got some bugs, if I click on edit, buttons change in the both ng-repeat and the both post can be edit.
I'm not really sure but I thinks it's a problem with my :
ng-show="editing[$index]"
I try to put the index of the post like this
ng-show="editing[posts.indexOf(post)]"
But this is not working, can somebody help me ?
(The jsfiddle link)
EDIT the post query :
Posts.query();: Array[0]
0: d
__v: 0
_id: "569563a96a81e64409623179"
corps: "asdad"
etat: "enCours"
nomReseau: "Google+"
section: "evolution"
__proto__: d
1: d
__v: 0
_id: "56954e676a81e6440962316b"
corps: "sdfsdfsf"
etat: "enCours"
nomReseau: "Google+"
section: "evolution"
__proto__: d
Using $index in a filtered repeat to access information in your array.
The problem with using $index, passing it to your controller and then trying to use that index to search for a 'post' in your array of posts, is that $index references your view index and not the true index of the item in the array.
This is traditionally not a problem unless you are filtering your array with ng-repeat. Why? Because $index does not reflex the index of the item, but the index of how the item is appearing in the DOM. So although the first rendered post could have index 5 in your posts array, it will still have $index of 0 because it is the first rendered item in the ng-repeat.
Solution: Separate your data first into two separate arrays and then repeat through them individually.
Controller:
$scope.posts = Posts.query();
$scope.postsATraiter = $scope.posts.filter(function(item, index) {
return item.etat === 'aTraiter';
});
$scope.postsEnCours = $scope.posts.filter(function(item, index) {
return item.etat === 'enCours';
})
$scope.edit = function(post, postType){
var idx = getPostsByType(postType).indexOf(post);
$scope.editing[idx] = angular.copy($scope.posts[idx]);
}
$scope.update = function(post, postType){
var idx = getPostsByType(postType).indexOf(post);
Posts.update({id: post._id}, post);
$scope.editing[idx] = false;
}
$scope.cancel = function(post, postType){
var idx = getPostsByType(postType).indexOf(post);
post = angular.copy(post);
$scope.editing[idx] = false;
}
function getPostsByType(postTypeString) {
if (postTypeString === 'aTraiter') {
return $scope.postsATraiter;
} else {
return $scope.postsEnCours;
}
}
Now that the data is separate you are free to use $index because we know that the $index will respect the true index of the item in the array because it is not being filtered.
<li ng-repeat="post in postsATraiter">
<p ng-show="!editing[$index]" ng-model href="#/{{post._id}}">{{ post.corps }}</p>
<input ng-show="editing[$index]" type="text" ng-model="post.corps">
<a ng-show="!editing[$index" ng-click="edit(post, 'aTraiter')">Editer</a>
<a ng-show="editing[$index]" ng-click="update(post, 'aTraiter')">Confirmer</a>
<a ng-show="editing[$index]" ng-click="cancel(post,'aTraiter')">Annuler</a>
</li>
<li ng-repeat="post in postsEnCours">
<p ng-show="!editing[$index]" ng-model href="#/{{post._id}}">{{ post.corps }}</p>
<input ng-show="editing[$index]" type="text" ng-model="post.corps">
<a ng-show="!editing[$index]" ng-click="edit(post, 'enCours')">Editer</a>
<a ng-show="editing[$index]" ng-click="update(post, 'enCours')">Confirmer</a>
<a ng-show="editing[$index]" ng-click="cancel(post, 'enCours')">Annuler</a>
</li>
You may have to fiddle around with your implementation more, but it appears that $index and the way it is being used may be the root of the problems you are having.
You're using $index, which is just ... well... an index. Both collections can have a value at index==1, right? So, $index isn't unique across the entire set of posts.
Luckily, it would appear that you have an ID for each post that seems to be unique: post._id. How about using that instead?
One little side-note - I'm using jquery's grep method below to find a post by Id. It's fine, but I like underscore.js better. Take a look at both ...
So, here's your controller code:
//This is me willfully and wantonly changing your variables ... sorry.
$scope.selectedPost = undefined;
$scope.selectedPost_unchanged = undefined;
//This is the same, though, so you should feel good ;-)
$scope.posts= Posts.query();
$scope.edit = function(postId){
var result = $.grep($scope.posts, function(p){ return p._id == postId; });
if(result.length==0) { return; }
//just store the one we are editing. that should be cool, right?
$scope.selectedPost = results[0];
//ok... im changing this too... see if you like it better?
//we're gonna use it in the CANCEL method (below).
$scope.selectedPost_unchanged = angular.copy(results[0]);
}
$scope.update = function(post){
Posts.update({id: post._id}, post);
$scope.selectedPost = undefined;
$scope.selectedPost_unchanged = undefined;
}
$scope.cancel = function(post){
post = angular.extend({}, $scope.selectedPost_unchanged);
$scope.selectedPost = undefined;
$scope.selectedPost_unchanged = undefined;
}
//This is new too ... just adding it so that the html is clearer.
$scope.isEditing = function(post) {
if($scope.selectedPost==undefined) { return false; }
return post._id == $scope.selectedPost._id;
}
The HTML changes a bit too
All of your editing[$index] code becomes just isEditing(post)
Voilà! Except maybe use css+ng-class...
Not for nothing, but I would add the while editing/not-editing show/hide using css. Then, add an ng-class to the li element instead (eg - ng-class="{editing: isEditing(post)}"). Then, take care of all your show/hides with css. This way, you only have to put isEditing(post) in ONE location in your html (instead of adding it to every element). ng tags are not expensive, but they REALLY add up inside of ng-repeat tags.

Polymer iron-ajax: How can I send value to an argument of a function

First of all I want to say sorry for not-so-good title for this post. I could not think of anything better!
I am trying with iron-ajax. When I enter a value in my iron-input element and click on a button I get result fine. Now each record in the result has a link which is supposed to drill down more into the item itself.
To get first set of result I have done the following (which works fine):
<iron-ajax id="ajax"
handle-as="json"
on-response="hresponse"
last-response="{{data}}"
debounce-duration="300">
</iron-ajax>
<input is="iron-input" id="txtSearchItemsByName"/>
<button on-click="findItems">Search »</button>
<span id="waiting"></span>
<template is="dom-repeat" items="{{data}}">
<div>
<span>
<input id="itemId" value="{{item.id}}"/>
<a on-click="itemDetail( {{item.id}} )" href="javascript:;" title="{{item.plantid}}">{{item.title}}</a>
</span>
</div>
</template>
Following code block fetches all records which match the name entered by user
findPlants: function () {
var thisAjax = this.$.ajax;
var waitingContainer = this.$.waiting;
waitingContainer.innerHTML = "Please wait...";
var strEnc = encodeURIComponent(this.$.txtSearchItemByName.value);
this.$.ajax.url = "//domain.com/api/v0/items/search";
this.$.ajax.params = { "apikey": "xxxxxxxxxxx", "common_name": strEnc };
window.setTimeout(function () {
thisAjax.generateRequest();
waitingContainer.innerHTML = "";
}, 1000);
},
hresponse: function (request) {
data = this.$.ajax.lastResponse;
},
Up to this point everything works fine. Then I went on and created another function which is supposed to take an argument:
itemDetail: function (id) {
var thisAjax = this.$.ajax;
var waitingContainer = this.$.waiting;
waitingContainer.innerHTML = "Please wait...";
this.$.ajax.url = "//domain.com/api/v0/item/search";
this.$.ajax.params = { "apikey": "xxxxxxxxxx", "id": id };
window.setTimeout(function () {
thisAjax.generateRequest();
waitingContainer.innerHTML = "";
}, 1000);
},
And I expect the following line to do my job:
<a on-click="itemDetail( {{item.id}} )" href="javascript:;" title="{{item.plantid}}">{{item.title}}</a>
However, when I am clicking on the link I am getting the following error message and nothing is happening:
[iron-ajax-caller::_createEventHandler]: listener method itemDetail( {{item.id}} ) not defined
I have no idea what to do from here as I am still a newbie at Polymer.
Experts please help!
Thanks in advance,
Subrata
Use on-tap instead of on-click:
<a on-tap="_itemDetail" href="javascript:;" title="{{item.plantid}}">{{item.title}}</a>
Then in your function:
_itemDetail: function(e) {
var thisAjax = this.$.ajax, waitingContainer = this.$.waiting;
thisAjax.url = '//domain.com/api/v0/item/search';
thisAjax.params = { apikey: 'xxxxxx', id: e.model.item.id };
this.async(function() {
thisAjax.generateRequest();
waitingContainer.innerHTML = '';
}, 1000);
}
See documentation here for more info
As to why on-tap, check this out
I believe you made a simple mistake, if I'm not totally wrong. This:
<a on-click="itemDetail( {{item.id}} )" href="javascript:;" title="{{item.plantid}}">{{item.title}}</a>
should look like this:
<a on-click="{{itemDetail(item.id)}}" href="javascript:;" title="{{item.plantid}}">{{item.title}}</a>
I think it should work. Polymer 1.0 doesn't support string concatenation. In your case it can't put these together "itemDetail(" + item.id + ")". And therefore the syntax is as I described above.
<a on-click="itemDetail" data$="{{item.id}}" title="{{item.plantid}}">{{item.title}}</a>
itemDetail : function(e){
console.log(e.target.getAttribute("data"));
}

What is the right syntax for ng-show with scope variable

I am setting a ng-show for when I have certain state so my delete button should be shown.
But it's not working. I checked in the console and my method is passing true to the scope variable. Still not sure what is wrong.
application.html
<ul class="nav navbar-nav navbar-right">
<li ng-show="notesState" ><a ng-really-message="Are you sure you want to delete this note?" ng-really-click="deleteNote(note)" class="glyphicon glyphicon-trash " style="float:left;" >Delete note</a></li>
<li><a href="#/home" >+ New Note</a></li>
<li ng-hide="signedIn">Login</li>
<li ng-show="signedIn">{{user.email}}</li>
<li class="pointer" ng-show="signedIn"><a ng-click="logout()">Logout</a></li>
</ul>
in notesCtrl.js
$scope.notesState = $state.is('notes')
is notesState true when the controller is loading? you might need to $scope.$apply() if it something that is false when the controller loads but becomes true later.
sometimes returning a function works but i really do not know why you would need to do this.
$scope.notesState = function(){ return $state.is('notes') }
ng-show="notesState()"
here is $state.is and the function it sometimes returns
$state.is = function is(stateOrName, params, options) {
options = extend({ relative: $state.$current }, options || {});
var state = findState(stateOrName, options.relative);
if (!isDefined(state)) { return undefined; }
if ($state.$current !== state) { return false; }
return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
};
and
function equalForKeys(a, b, keys) {
if (!keys) {
keys = [];
for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
}
for (var i=0; i<keys.length; i++) {
var k = keys[i];
if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
}
return true;
}
if that helps anyone looking into this.
Try using this way.May be it works.
<li ng-show="notesState=='true'" >

ng-repeat on multiple arrays

Is there a way to loop through multiple arrays in an ng-repeat directive ?
I tried something like
<ul>
<li ng-repeat="sale in randomSales" ng-repeat="image in imageUrls">
<div display-sale></div>
</li>
</ul>
or
<ul>
<li ng-repeat="sale in randomSales, image in imageUrls">
<div display-sale></div>
</li>
</ul>
but it's not working.
I could solve this issue another way, but I'd like to know if this is possible !
EDIT :
Here is my controller & directive :
app.controller('RandomController', ['$rootScope', '$scope',function($rootScope, $scope) {
$scope.randomSales=[];
$scope.imageUrls = [];
for(var i= 0; i < displayrandomSalesNumber ; i++){
var randomNumber = Math.floor((Math.random() * $rootScope.sales.length-1) + 1);
$scope.randomSales.push($rootScope.sales[randomNumber]);
$scope.imageUrls.push($rootScope.sales[randomNumber].image_urls["300x280"][0].url);
}
console.log($scope.randomSales);
}])
.directive('displaySale', function() {
return {
template: '<div class="center"><a href="#/sale/{{sale.store}}/{{sale.sale_key}}">' +
'<header><h2>{{sale.name}}</h2><h4>in {{sale.store}}</h4></header>' +
'<article>' +
'<p>From : {{sale.begins}} To : {{sale.ends}}</p>' +
'<p class="center"><img ng-src="{{image}}"/></p>' +
'<p>{{sale.description}}</p>' +
'</article>' +
'</a></div>'
};
});
the image is already inside $scope.randomSales, I could access it with {{sale.image_urls["300x280"][0].url}} but i'd get a parse error.
to make it work you can try to do following:
- merge 2 arrays to one arrays of objects representing imageUrls and randomSales.
- iterate though them in your template.
For example:
var maxArrayLength = Math.max(randomSales.length, imageUrls.length);
var i = 0;
var result = []; //will put items {sale: ..., image}
for (i =0; i < maxArrayLength; i++){
var currentSale = randomSales[i] != null ? randomSales[i] : null;
var currentImage = imageUrls[i] != null ? imageUrls[i] : null;
result.push({sale: currentSale, image: currentImage})
}
return result;
And them you can use this array to iterate through on your angular template.
The First Solution proposed by you is wrong , we cant have two ng-repeat attributes in one Html tag, I am not sure about the second one . But you could try like this ..i think it would work.
<ul>
<li ng-repeat="sale in randomSales">
<span ng-repeat="image in imageUrls">
<div display-sale></div>
</span>
</li>
</ul>

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