angular: view not being updated when data changes in another controller - angularjs

Basically it's simple, but I can't make it work.
Note: using ionic.
I have:
a view and controller for pre-defined Playlists
a view and controller for UserPlaylists.
a ionic popover for assigning a song to a UserPlaylist.
It should work like this:
I look at songs in the pre-defined Playlists. I choose a song to be added to a UserPlaylist. The popover appears, allowing to choose the UserPlaylist to add the song to. I select it and the song gets added to the UserPlaylist. The app remains in the pre-defined playlist view.
It all works. Except the view with the UserPlaylist does not get updated when I switch back to it with the new song - I need to RELOAD the UserPlaylist view, and then I can see the new song added...
I could NOT use $apply(), is it would always tell me it's already in process. Is ionic or angular somehow caching things?
The playlist html
<div ng-hide="loading">
<h2>{{playlist.name}}
<button on-tap="addAllToUserPlaylist()" class="small add-to-playlist"></button>
<button on-tap="addAllToQueue()" class="small add-to-queue"></button>
<button on-tap="playAll()" class="small play"></button>
</h2>
<app-song can-like="true" can-add-to-queue="true" can-add-to-playlist="true" on-select="tappedSong(song)" item="song" ng-repeat="song in playlist.songs" />
</div>
</ion-content>
The add to playlist code in the song directive
/**
* AddToPlaylist button was tapped
*/
function addToPlaylist(e){
e.stopPropagation();
// Show add to playlist template as a popover.
// Note that we're passing this directive's scope
// as the popover's parent scope. That way the popover's
// controller will have access to the variables here.
// Also, we're putting a reference to the popover in
// scope.popover so we can access it later to destroy it.
$ionicPopover.fromTemplateUrl('templates/add.to.playlist.html',{
animation: 'slide-in-up',
scope: scope,
backdropClickToClose: false
}).then(function(popover){
scope.popover = popover;
popover.show();
});
}
the UserPlaylist code:
UserPlaylistDataSource.prototype.addSongs = function(songs, id, beginning, cbk){
if(beginning){
Array.prototype.unshift.apply(this.songs, songs);
}
else{
Array.prototype.push.apply(this.songs, songs);
}
this.save(id, cbk);
}
the popover add code:
$scope.add = function(playlist){
var toAdd;
var msg = 'Agregaste $1 a la lista ' + playlist.name;
// If the parent scope has an 'item' it is the song they want to add
if($scope.item){
toAdd = [$scope.item];
msg = msg.replace('$1', 'una canción');
}
// If the parent scope has a 'playlist' add all of it's songs
else if($scope.playlist){
toAdd = $scope.playlist.songs;
msg = msg.replace('$1', $scope.playlist.songs.length+' canciones');
}
// If the parent scope has a 'queue' add all of it's songs
else if($scope.queue){
toAdd = $scope.queue.songs;
msg = msg.replace('$1', $scope.queue.songs.length+' canciones');
}
$scope.loading = true;
playlist.addSongs(toAdd, playlist.id, false, function(err){
$scope.loading = false;
//HERE playlist has the correct number of songs! So it worked!
if($scope.item) $scope.unflip(); // if parent scope is a single song.
$scope.close();
if (err) {
return MessageService.show(err);
}
MessageService.show(msg);
});
};
the popover html:
<ion-scroll>
<h3 on-tap="newPlaylist()"><img src="/img/icon-round-add.png"/> <span>Crear Nueva Lista de Reproducción</h3>
<div class="spinner-container" ng-show="loading"><ion-spinner></ion-spinner></div>
<br/>
<h3 class="playlist" ng-repeat="playlist in playlists" on-tap="add(playlist)"><img ng-src="{{playlist.client_data.icon_url}}"/> <span>{{playlist.name}}</span></h3>
</ion-scroll>

I fixed by reading this:
http://ionicframework.com/docs/api/directive/ionView/
and putting
<ion-view cache-view="false">
at the UserPlaylist view

Related

Creating a "Like" button with Angular.js

Im trying to create a like button with Angular.js.
(It is just a heart icon. default color is white = NOT liked. It is red when liked. Like/unlike is toggled by a click)
I get some data from my web service that has also an array of some ID's. These ID's are the ones that clicked the like button before.
Then i populate the DOM with the ng-repeat directive according to the data retrieved from the web service.
I attach the button a ng-class that sets the proper class and a ng-click directive that is supposed to somehow change the class too.
* I cant connect between the ng-class and the ng-click result.
some code:
<div ng-repeat="photo in photos track by photo._id">
<button ng-class="{carouselFooterButtonLikeActive : initLike(photo)}" ng-click="like(photo, this)">
<i class="icon ion-heart"></i>
</button>
</div>
Controller:
// Handle like button click
$scope.like = function(photo, photoScope){
HOW CAN I AFFECT THE NG-CLASS FROM HERE?
}
$scope.initLike = function(photo){
if(photo.likes.indexOf($localstorage.getObject('userInfo').id) > -1) {
$scope.liked = true;
return true;
}
$scope.liked = false;
return false;
}
Edit: added a possible data retrieved from the web service
{
photos: [
{
src: "src1.jpg",
likes:[111,222,333]
},
{
src: "src2.jpg",
likes:[]
}
]
}
You can use as a flag some additional property that will be initially undefined on each photo element - say photo.liked. When user clicks it, $scope.like function sets this property to true. Then ng-class evaluates photo.liked to true and adds carouselFooterButtonLikeActive class to button element.
The code is as follows:
In the template:
<button ng-class="{'carouselFooterButtonLikeActive' : photo.liked}" ng-click="like(photo, this)">
In the controller:
$scope.like = function(photo, photoScope){
photo.liked = true;
}
UPD
Say you have photos array:
[
{'src':'bla-bla.jpg', liked: true, id: 8347},
{'src':'foo-bar.jpg', id: 45},
{'src':'baz-baz.jpg', id: 47}
]
then only the first one will be shown with button.carouselFooterButtonLikeActive class, thanks to ng-class evaluation expression.
UPD2
If photo.likes is an array, you can use:
//template
ng-class="{'carouselFooterButtonLikeActive' : (photo.likes && photo.likes.length >0)}"
//controller
$scope.like = function(photo, photoScope){
photo.likes.push(someUserID);
}

angularjs : disable a button and show popup instead

I have this button :
html:
<button nav-direction="back" class="button yy" ui-sref="app.result" ui-sref-active="currentNav" ng-click="navResult()">
Board
</button>
I would like it to display a popup if a certain condition is, else I would like it to go to another page.
I need to keep the benefit of the class in ui-sref-active to show that this is the current page.
controller.js
$scope.navResult = function (){
console.log(sessionService.get('computed'));
if (sessionService.get('computed')) {
$scope.go('app.result');
} else {
//popup to user to tap on a board
//$scope.go('app.compute');
var popupConfig = {
title: 'Beware! ;)',
template: 'Tap on a board below'
};
var popup = $ionicPopup.show(popupConfig);
ClosePopupService.register(popup);
}
}
$scope.go = function ( state ) {
// console.log("go has been launched with : "+ state)
$state.go( state );
};
Simple. You just use an ng-click method instead of a ui-sref, and go to the state from there.
<button nav-direction="back" ng-class="{'your-class':classCondition}" class="button yy" ng-click="navResult()">
Board
</button>
Then in your controller....
$scope.navResult = function(){
if(something){
$scope.classCondition = false;
//code to display popup here
} else {
$state.go('app.result')
}
}
You can pass any valid state into $state.go, so if you ever want to check for a condition and perform some logic BEFORE you redirect to another page, use it inside a $scope method instead of just using the straight ui-sref.

UI AngularJS Sortable - Getting an undefined error when attaching to scope

I am using the AngularJS UI Sortable directive and I am trying to pull the data from my view into my controller and update/stop the sorting on every click. I am creating a blank array and then attaching the $scope.areas to the blank array. I am able to display the content through the ng-repeat. However, when I console.log(areas), I am getting an undefined.
VIEW
<div class="panel-body">
<ul ui-sortable="sortableOptions" ng-model="areas" class="uk-nestable">
<li data-item="{{area.label}}" data-item-id="{{area.order}}" ng-repeat="area in areas">
<div class="uk-nestable-item mainarea-blue">
<div class="uk-nestable-handle mainarea-text-white"></div>
<div data-nestable-action="toggle"></div>
<div class="list-white">{{area.label}}</div>
</div>
</li>
</ul>
</div>
CONTROLLER
//create a blank array
var tmpList = [];
//attaches ng-model scope to tmpList
$scope.areas = tmpList;
console.log(areas);
//changed old sort to new sort
$scope.sortingLog = [];
$scope.sortableOptions = {
//creates a log entry of the new update view
update: function(e, ui) {
var logEntry = tmpList.map(function(i){
return i.value;
}).join(', ');
//displays the update text and array
$scope.sortingLog.push('Update: ' + logEntry);
},
stop: function(e, ui) {
// this callback has the changed model
var logEntry = tmpList.map(function(i){
return i.value;
}).join(', ');
$scope.sortingLog.push('Stop: ' + logEntry);
}
};
My goal is to display the area.label in the correct sort order. For example; I have 3 unordered lists - floor, basement, kitchen and I want to change the order to basement, floor, kitchen. As I am changing the sort order it is updating and stop the sorts.
I am probably not doing the best job explaining myself so here is a similar codepen...http://codepen.io/thgreasi/pen/jlkhr.

How can I make it so only an empty state or a drop target placeholder of ui-sortable show?

I have two connected ui-sortable lists. When one of the lists is empty, I need to show a message; when that empty list is hovered while dragging, I need to show a styled drop target and hide the empty list message. I was able to program the vast majority of this code and here is a simplifed Codepen of it working.
The bug is that when you drag from the populated list over the empty list and then out again, the empty list shows both the empty list placeholder and the styled drop target. Here is a screen capture:
The root of the problem appears to be in way I calculate if the list is empty for the sortableList directive:
scope.isEmpty = function() {
if (!scope.attachments) {
return true;
} else if (scope.dragDirection === 'drag-out' && !scope.hovered) {
return scope.attachments.length <= 1;
} else if (scope.hovered) {
return false;
} else {
return scope.attachments.length === 0;
}
};
Note that I am keeping track of the state on the scope and using $apply to ensure the DOM updates like so:
function onDragStart() {
scope.$apply(function() {
scope.dragDirection = 'drag-out';
});
}
function onDragStop() {
scope.$apply(function() {
scope.dragDirection = '';
});
}
function onDragOver() {
scope.$apply(function() {
scope.hovered = true;
});
}
function onDragOut() {
scope.$apply(function() {
scope.hovered = false;
});
}
Here is the html for the directives template:
<div class="drop-target" ui-sortable="sortOptions" ng-model="attachments">
<div ng-repeat="attachment in attachments" class="attachment-box">
<span class="fa fa-bars pull-left drag-handle"></span>
<div class="link-attachment">
<a href ng-href="{{ attachment.fileUrl }}" target="_blank" class="attachment-name">{{ attachment.name }}</a>
<div class="extra-info link-info">{{ attachment.fileType }}</div>
</div>
</div>
<attachment-empty-state ng-show="isEmpty()"></attachment-empty-state>
</div>
The dependency list is quite long for the codepen to work, I simplified the code from actual production code and eliminating the dependencies would have made the custom code quite substantial. Here is a list of the dependencies if you want to try to get it running yourself: jquery, jquery-ui, angular, bootstrap, lodash, and sortable from angular-ui. There is some font-awesome in there as well.
I think I solved the problem. Here is a codepen with the solution.
Basically, the problem was that the dragout event was being (correctly) fired when your cursor dragged the item out of a sortable-list, but the placeholder would stay in the sortable-list until you dragged it into another sortable-list. So in that in between time, both the attachment-empty-state element and the placeholder would be shown in the sortable-list.
Here are the lines that I edited in the code:
Less file:
attachment-empty-state {
...
// hide empty state when the placeholder is in this list
.placeholderShown & {
display:none;
}
}
JS:
//Inside sortable-list
// Helper function
function setPlaceholderShownClass(element) {
$(".drop-target").removeClass("placeholderShown");
$(element).addClass("placeholderShown");
}
...
function onPlaceholderUpdate(container, placeholder) {
setPlaceholderShownClass(container.element.context);
...
}
If you don't like using jQuery to add and remove classes globally, you could use $rootScope.$broadcast("placeholderShown") and $rootScope.$on("placeholderShown",function() { // scope logic }. I figured a little jQuery is less complex, even though it isn't pure Angular.

Execute different Event on First and second click angular js

I have a situation where i need to execute different event on first and second click on div.
On first click div will be expanded and in second click it will be redirected to it's detail page. Div contains details of post.
<div ng-click="select(comment._id);"
ng-dblclick="changeurl(user.username,data.type,comment._id)"
enter ng-repeat="user in data.user">
Till now i was expanding div on first click and then on double click i was redirecting it to it's detail page.
what would be the best way to do it.
right now my idea is to make a counter on ng-click and then execute event.
can we execute it on the DOM End.
<div ng-click="counter = counter + 1" ng-repeat="user in data.user" ng-init="counter = 0" >
is there any other directive where i can execute select(comment._id) changeurl(user.username,data.type,comment._id) conditionally or how can i call these functions conditionally ?
Update: on Diana advice here is updated status:
Here i have no of div's coming from ng-repeat, here i have two type of layouts which are displayed according to data.
<div ng-repeat="data in comments">
<div ng-if="data.type=story">
<div ng-click="clickEvent($index,comment._id, user.username,data.type);" enter ng-repeat="user in data.user">
//displaying data here
</div>
</div>
<div ng-if="data.type=news">
<div ng-click="clickEvent($index,comment._id, user.username,data.type);" enter ng-repeat="user in data.user">
//displaying data here
</div>
</div>
</div>
$scope.secondClick = [];
//scope data
myservice.data().then(function(response){
$scope.comments = response.data;
for (i=0; i< $scope.comments.length;i++){
$scope.secondClick[i] = false;
}
});
$scope.clickEvent = function(index,id, userName, type){
if(!$scope.secondClick[index]){
// Execute first-click logic (call select function with the id).
$scope.secondClick[index] = true;
}
else{
// Execute second-click logic (call changeurl function with the id, userName, and type).
}
}
I tried providing index to function and manipulate them. Because of nested ng-repeat i am getting index '0'. .
UPDATE2:
I tried to understand your setup from the question and came up with a full working demo accordingly. I hope this works for your case!
http://plnkr.co/edit/ARsDpRH7orzZQLU1pin1
Please let me know how this goes.
/////////////////////////////////////
You could have some boolean variable that is initially set to false, say $scope.secondClick - when you click the button the first time set it to true. When clicking it the next time the variable is true so you execute the "second click" logic.
Something like:
<div ng-click="clickEvent(comment._id, user.username,data.type);" enter ng-repeat="user in data.user">
In your controller, define:
$scope.secondClick = false;
$scope.clickEvent = function(id, userName, type){
if(!$scope.secondClick){
// Execute first-click logic (call select function with the id).
$scope.secondClick = true;
}
else{
// Execute second-click logic (call changeurl function with the id, userName, and type).
}
}
UPDATE:
Given that you have more than one div, we can tweak the solution a bit by having an array of boolean values in the scope and passing the index to know which is which:
<div ng-click="clickEvent($index, comment._id, user.username,data.type);" enter ng-repeat="user in data.user">
In the controller:
$scope.secondClicks = [];
//Initialize all values to false, based on the number of divs.
for(var i = 0; i<NoOfDivs; i++){
$scope.secondClicks[i] = false;
}
$scope.clickEvent = function(index, id, userName, type){
if(!$scope.secondClicks[index]){
// Execute first-click logic (call select function with the id).
$scope.secondClicks[index] = true;
}
else{
// Execute second-click logic (call changeurl function with the id, userName, and type).
}
}
Let us know if this works please. If someone has a neater solution, I'd like to check it out as well :)
I think this may be a simple way to tell JavaScript to do different things depending on the number of clicks. This example shows swapping of two different events. Basicly, I just limited the counting from 0 to 1 and made sequence circular. Ofc, you can make more than just two. I think the advantige of this is that is circular, so the user may navigate freely (first he expands something, than he shrinks it back, than he expands it again etc...). Maybe by following this principle you will solve your code.
<!DOCTYPE html>
<head>
<style>
#division {background-color: red;color: white;width: 10%;height: 20%;position: fixed;top: 10%;left: 10%;-webkit-transition: 1s;-moz-transition: 1s;-ms-transition: 1s;-o-transition: 1s;transition: 1s;}
#division:hover {background-color: blue;cursor: pointer;}
</style>
</head>
<body>
<script type="text/javascript">
var clicks = 0;
function onClick() {
clicks += 1;
if (clicks != 1) {
clicks = 0;
};
if (clicks == 1) {
document.getElementById("division").style.left = 30+'%';
}else {
document.getElementById("division").style.left = '10%';
};
};
</script>
<div onClick="onClick()"><a id="division">Click me Twice!</a></div>
</body>
</html>
I did this by putting post id in cookie. On first click that post's id is stored in cookie and if user click again it is founded in cookie store second click action is triggered. Hence we do action on first and second click.
$scope.clickEvent = function(id,name,type){
var favoriteCookie = $cookieStore.get('divonclick');
$cookieStore.put('divonclick',id);
if(favoriteCookie == id){
//second click action
}else{
//first click action
}
}
Try This:
In html div:
ng-init="count=0"
ng-click="select(count)(count=count+1);"
In JS:
$scope.select = function(count) {
if(count%2 == 0 ) {
here write your SECOND click functionality.
} else {
here write your FIRST click functionality.
}
}

Resources