Create custom follow button for my application - javascript

I would like a directive that dynamically knows if I'm following the user in my App.
I have a resource to get the currentUser
this.following = function () {
var defer = $q.defer();
var user = $cookies.getObject('currentUser');
UserResource.get({id: user.id}).$promise.then(
function (data) {
defer.resolve(data.following);
});
return defer.promise;
};
This is in one of my services. It returns all users that I'm following.
When instantiating my controller I fetch the users I follow within my app:
UserService.following().then(
function (data) {
$scope.following = data;
});
I would like to move that into a directive so that I can easily reuse it somewhere else in my app.
This is the HTML I am using right now (and it's not really beautiful) :
<div class="item" ng-repeat="user in users">
<div class="right floated content">
<div ng-show="isFollowing(user)" class="ui animated flip button" tabindex="0"
ng-click='unFollow(user)'>
<div class='visible content'>
Following
</div>
<div class="hidden content">
Unfollow
</div>
</div>
<div ng-hide="isFollowing(user)" ng-click="follow(user)" class="ui button">Follow</div>
</div>
</div>
But instead something like :
<div class="item" ng-repeat="user in users">
<um-follow-button um-user="user"></um-follow-button>
</div>
then depending if I'm following the user or not then render one of the two options.
I don't know if I will have to use a controller in my directive.
I have looked at : https://gist.github.com/jhdavids8/6265398
But it looks like a mess.

Related

How to call multiple angularjs service calls from within nested ng-repeat

I am making a simple sports goods shopping app in AngularJs.
I am in a situation where I have three nested ng-repeats.
First loop: Get the brand name. I have written angularjs service that calls the rest endpoint to fetch the lists of brands (Adidas, Yonex, Stiga, etc). I am calling this service as soon as the page(controller) gets loaded.
Second loop: For each brand, I want to display the category of products they are offering. Inside this loop, I want to execute a function/service that will take the brand name as input and get all the categories for the brand. For this, I also have an angularjs service that calls the rest endpoint to fetch the list of categories for a given brand name.
Third loop: For each brand and category, I want to display the products in that category. Inside this loop, I want to execute a function that will take the brand name and category as input and get all the products in that category. I an angularjs service call which will call the rest endpoint to fetch the products given the brand name and category.
Sample data set:
Adidas
-----T-Shirts
----------V-Neck
----------RoundNeck
-----Shoes
----------Sports Shoes
----------LifeStyle Shoes
Yonex
-----Badminton Racquet
----------Cabonex
----------Nanospeed
-----Shuttlecocks
----------Plastic
----------Feather
Stiga
-----Paddle
----------Procarbon
----------Semi-carbon
-----Ping Pong Balls
----------Light Weight
----------Heavy Weight
Please note that because of some constraints I cannot have a domain object on the REST side to mimic the data structure shown above.
I want to display the above data in a tree-like fashion (something on the same lines as shown above possibly with expand/collapse options).
Below are the code snippets.
CONTROLLER:
(function () {
'use strict';
angular.module('SportsShoppingApp.controllers').controller('sportsController', ['sportsService', '$scope', function (sportsService, $scope) {
$scope.brands = [];
$scope.categories = [];
$scope.products = {};
$scope.getBrands = function () {
sportsService.getBrands()
.then(loadBrands, serviceError);
};
var loadBrands = function(response) {
$scope.brands= response.data;
};
$scope.getCategories = function(brand) {
sportsService.getCategories(brand)
.then(loadCategories, serviceError);
};
var loadCategories = function (response) {
$scope.categories = response.data;
};
$scope.getProducts = function(brand, category) {
sportsService.getProducts(brand, category)
.then(loadProducts, serviceError);
};
var loadProducts = function (response) {
$scope.products = response.data;
};
var serviceError = function (errorMsg) {
console.log(errorMsg);
};
$scope.getBrands();
}]);
}());
HTML:
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<div ng-repeat="brand in brands.data">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<div ng-repeat="category in categories.data" ng-init="getCategories(brand)">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-repeat="product in products.data" ng-init="getProducts(brand, category)">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
When I use the above HTML, only the brand names are displayed on the UI. The categories and their corresponding products are not displayed. I know that there is some overlapping that is happening. I am not sure if I am doing it the right way. I might be completely wrong with my approach. I am new to AngularJS. I want to know how to loop in nested ng-repeat so that each ng-repeat could call an angularjs service and also I want to display the data in the tree fashion as shown above. Can someone help me here?
I think that the ng-inits have to be placed on separate tags to the ng-repeats:
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<div ng-repeat="brand in brands.data">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<div ng-init="getCategories(brand)">
<div ng-repeat="category in categories.data">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-init="getProducts(brand, category)">
<div ng-repeat="product in products.data">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
You might have to juggle your bootstrap classes around also, moving ng-init is only to fix the angular part.
Move the ng-init directives outside of the ng-repeat to which they provide data.
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<!-- MOVE init of categories here -->
<div ng-repeat="brand in brands.data" ng-init="getCategories(brand)">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<!-- MOVE init of products here -->
<div ng-repeat="category in categories.data" ng-init="getProducts(brand, category)">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-repeat="product in products.data">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The ng-init directive has a priority of 450; the ng-repeat, priority 1000. This means that when they are on the same element ng-init executes after the ng-repeat directive. The ng-repeat for categories.data won't execute its ng-init until it has a category. Thus its ng-init can't be used to populate the categories array.
Quick question. Is my approach correct ?
The approach works but it violates the Zen of Angular and the principles of an MV* Model View Whatever framework.
The model is the Single Source of Truth
Because the view is just a projection of the model, the controller is completely separated from the view and unaware of it. This makes testing a snap because it is easy to test your controller in isolation without the view and the related DOM/browser dependency.
--AngularJS Developer Guide -- Data-Binding
Having the ng-repeat and ng-init directives build the model creates a dependency that makes testing and debugging difficult. (As witnessed by this question.)
My advice is to learn how to build the model by chaining promises and using $q.all.

Retain scroll position when a button is toggled

I am working on an angularJS application which has a page where I display around 30 items using ng-repeat. In front of each item, there is a toggle button (enabled/disabled). With the current code that I have, I can toggle these items. But the problem is if I scroll down and toggle lets say item 25, then automatically it scrolls to the top of the page. If I now scroll down, I can see that the toggle actually took place.
So the requirement now is to make sure that the scroll position is retained after the toggle button is clicked.
Please see below the code that I have.
HTML
<div id="eventTypes" class="panel-body">
<div ng-if="!spinner" ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
<div ng-if="spinner" class="spinner">
<div class="spinner-container container1">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container2">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container3">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
</div>
</div>
JS
(function () {
'use strict';
angular.module('myApp').controller('itemsController', function ($scope, itemsService) {
var serviceError = function (errorMsg) {
console.log(errorMsg);
$scope.turnOffSpinner();
};
$scope.items = [];
$scope.item = {};
$scope.spinner = true;
$scope.toggleEnabled = function (item) {
$scope.turnOnSpinner();
itemsService.toggleEnabled(item)
.then(function () {
$scope.loaditems();
});
};
$scope.loaditems = function () {
itemsService.getitems().then(function (response) {
$scope.items = response.data;
}, serviceError);
$scope.turnOffSpinner();
};
$scope.turnOnSpinner = function () {
$scope.spinner = true;
};
$scope.turnOffSpinner = function () {
$scope.spinner = false;
};
$scope.loaditems();
});
}());
How this works right now is, once I click the toggle button, a spinner is enabled. Meanwhile the controller will call the itemService.toggleEnabled() method which does an ajax call to the server to just change the status of the item(enabled to disabled or vice-versa) in the backend. On successful change of the status and when the ajax call returns, the $scope.loadItems() method is called in the controller. This method will then do another ajax call to fetch the items (now with the updated status of the item that was toggled). The spinner is disabled and the data is then displayed on the UI.
When all of this is done, the page is scrolled to the top. This is annoying when I want to toggle an item which is way down in the list.
I want the page to be present at the same position when I clicked the toggle button of the corresponding item and not scrolling up to the top.
I am new to AngularJS and any help in this regard would be really helpful.
It looks like your spinner scheme is what's causing you problems:
...
<div ng-if="!spinner" ng-repeat="item in items" class="panel-body">
...
<div ng-if="spinner" class="spinner">
...
Whenever you click your button, you are removing every single element in your ng-repeat from the DOM when you $scope.turnOnSpinner(). That's why it appears to jump to the top. It's not really jumping, there just aren't enough DOM elements to fill up the page, making the page so short that the scrollbar disappears (even if it's only for a second). Then when the spinner is done, your ng-repeat fills up the page with DOM elements again, resulting in your scroll position being lost.
So basically what you are trying to fix is a symptom of a less than ideal loading spinner implementation.
ng-if is a "brutal" way of hiding things in Angular. It's mostly meant to hide things for a longer period of time than "softer" directives like ng-show/ng-hide. One solution to your problem is to use ng-disabled on each one of your buttons to prevent the user from interacting with it while the spinner is active, rather than doing a hard removal of each element:
Before:
<div ng-if="!spinner" ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
After:
<div ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
ng-disabled="spinner"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
Another solution, which I really like and use myself is this Angular module: https://github.com/darthwade/angular-loading
You can attach it to any element in the page and it will put a loading spinner over it and prevent you from interacting with it until your ajax or whatever is done.
If you don't like either of those, try putting your ng-repeat into a container that you can use to prevent interaction with your elements when the spinner is up:
<div class="container" ng-class="{'you-cant-touch-this': spinner}">
<div ng-repeat="item in items" class="panel-body">
<div class="row">
<div class="col-md-9">{{item.itemName)}}</div>
<div class="col-md-3">
<input id="toggleEnabled"
type="button"
ng-class="{'btn-primary': item.enabled}"
value="{{item.enabled ? 'enabled' : 'disabled'}}"
ng-click="toggleEnabled(item)">
</div>
</div>
</div>
</div>
Now you can style it in some way to prevent interaction without having to remove all those items from the DOM:
.you-cant-touch-this {
pointer-events: none;
}

Get AngularJS app to display modal on click

I'm listing my schedule entries, now I'm trying to display a modal when clicking on edit for a specific entry. My goal is to be able to edit the values of my entries. But first, can't get the modal to even display
See my plunker or code below
html
<div class="container" ng-app="appUserSchedule">
<div ng-controller="CtrlUserSchedule" >
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">User Schedule</div>
<div class="panel-body">
<div ng-cloak style="max-width:400px;">
<header>
<h3>User schedule</h3>
</header>
<ul>
<li ng-repeat="x in userscheds">{{x.week_day}} {{x.time_start}}-{{x.time_end}}
<span ng-click="showModal($index)" style="cursor:pointer;">Edit</span>
<span ng-click="removeItem($index)" style="cursor:pointer;">Delete</span>
</li>
</ul>
<div>
<div>
<div>
<input placeholder="Add user schedule entry here" ng-model="addMe">
</div>
<div>
<button ng-click="addItem()">Add</button>
</div>
</div>
<p>{{errortext}}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="modalContent.html" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Edit</h4>
</div>
<div class="modal-body">
<timepicker ng-model="dt1" hour-step="1" minute-step="15" show-meridian="true"></timepicker>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div> <!-- ng-controller -->
</div> <!-- ng-app -->
js
var app = angular.module("appUserSchedule", []);
app.controller("CtrlUserSchedule", function($scope,$http,$location) {
$http.get('userschedule.json').success(function(data, status, headers, config) {
$scope.userscheds = data.userschedules;
console.log(data);
}).error(function(data, status, headers, config) {
console.log("No data found..");
});
$scope.addItem = function () {
$scope.errortext = "";
if (!$scope.addMe) {return;}
if ($scope.userscheds.indexOf($scope.addMe) == -1) {
$scope.userscheds.push($scope.addMe);
} else {
$scope.errortext = "The item is already in your list.";
}
}
$scope.removeItem = function (x) {
$scope.errortext = "";
$scope.userscheds.splice(x, 1);
}
$scope.showModal = function (action, x) {
var modalInstance;
modalInstance = $modal.open({
templateUrl: 'modalContent.html',
controller: 'CtrlUserSchedule',
scope: $scope
});
// This code for later
// Save User Schedule Entry after making a change, then close modal
saveUserScheduleEntry = function(event) {
$http.put('').success(function(eventsuccess){
}).error(function(err){
/* do something with errors */
});
modalInstance.close();
};
// This code for later
// Close modal
closeUserScheduleEntry = function(event) {
$http.put('').success(function(eventsuccess){
}).error(function(err){
/* do something with errors */
});
modalInstance.close();
};
}
});
ng-repeat creates its own scope, hence the scope of your controller and the scope inside of the ng-repeat is not the same.
therefore $scope.showModal will not be defined inside of the ng-repeat.
the main mistake you are doing here is the "there always has to be a dot in there" rule.
look here:
Why don't the AngularJS docs use a dot in the model directive?
i would recommend to take a little tutorial about the most made angularjs mistakes first, because indeed there is some stuff to know about angularjs before you stop running into little traps like this.
furthermore you did not inject $modal to your controller which should lead in an error like $modal is undefined or something like that.
also you didn't even create/add the corresponding html file you are about to open.
and last but not least you didn't add any dependencies to your angular module regarding bootstrap. so your app won't be able to use/inject $modal anyhow.
see a working plunkr here:
http://plnkr.co/edit/rOjXt1C4E6lHRXUqHdHq?p=preview
have a look at the line where i am "putting the dot in"
$scope.ctrl = this;
i replaced the templateUrl by a simple template because i think it's enough to give the idea.
in the end i have to say that there was so many things wrong about your code that you really should try to debug more on your own and try to accomplish things more step by step.
since there have been like 4-5 mistakes in it this code barely can be your own, but rather is some random copy&paste stuff from anywhere.
otherwise you could not have accomplished that many different lines of code which don't do anything, sorry.
First is you need to add ui.bootstrap module to you ar angular app and pass $modal to your controller like this
var app = angular.module("appUserSchedule", ['ui.bootstrap']);
app.controller("CtrlUserSchedule", function($scope,$http,$location, $modal) {
Then you need to add modalContent.html. This displays the html inside modal window
This may not be the answer, but make sure showModal is receiving the correct arguments. Appears you are only sending $index.
Would have simply made this a comment, but not quite there on the reputation yet. :-)

AngularJS dirPaginate Only Filtering current page

I have included dir paginate into my angularjs project to handle pagination easily, it works well up until the point where I want to filter all the pages results.
The controller that contains the data and filter looks like so:
app.controller('listCtrl', function ($scope, services) {
$scope.sort = function(keyname){
$scope.sortKey = keyname; //set the sortKey to the param passed
$scope.reverse = !$scope.reverse; //if true make it false and vice versa
}
$scope.currentPage = 1;
$scope.pageSize = 10;
services.getPosts().then(function(data){
$scope.posts = data.data;
});
});
The dir-paginate looks like so
<div dir-paginate="data in posts | itemsPerPage:5 | orderBy:sortKey:reverse" class="col-xs-12 post">
<div id="title" class="col-xs-3"></div>
<div id="title" class="col-xs-9"></div>
<div id="poster" class="col-xs-3">
<img src="pics/house.jpg" id="avatar">
<a ng-if="data.user_name == '<?php echo $currentUser?>'" href="edit-post/{{data.post_id}}"> Edit </a>
</div>
<div class="col-xs-9">
<div class="col-xs-12">
<p class="timestamp">{{ data.post_datetime }}</p>
</div>
<div id="rant">
<span style="word-wrap: break-word;">{{ data.post_content }}</span>
</div>
<div id="stats" class="col-xs-12"> <img src="pics/comment.png"><span>3</span>
</div>
</div>
</div>
<dir-pagination-controls
max-size="5"
direction-links="true"
boundary-links="true" >
</dir-pagination-controls>
I call the sort function like so:
<button id="changeLikes" ng-click="sort('post_id')">ID</button>
It only sorts the current page of Data, if i use the pagination and click page 2 it will then only filter page 2 and so on, any ideas here would be of great help getPosts() returns post data via php with no group or order by clauses in the sql that would affect the angular.
Have you tried flipping the order of the filters, i.e.
<div dir-paginate="data in posts | orderBy:sortKey:reverse | itemsPerPage:5" class="col-xs-12 post">
The dir-paginate docs state:
itemsPerPage: The expression must include this filter. It is required
by the pagination logic. The syntax is the same as any filter:
itemsPerPage: 10, or you can also bind it to a property of the $scope:
itemsPerPage: pageSize. Note: This filter should come after any other
filters in order to work as expected. A safe rule is to always put it
at the end of the expression.

Angular Image Conditions

Bascially what i am doing at the moment is calling a jsonp array which contains an image url for an icon which works fine and displays fine except the icons suck. What i want to do is replace the icons with my own but the problem is that they are dynamic as its a weather api so the icon link will change to a different icon when the weather changes. What i can get from the json is the status of the weather eg partially cloudy and use that to somehow call a specific img. How would i go about this is in angular js?
var app=angular.module('app');
app.controller('MainCtrl', function($scope, $http) {
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Melbourne.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentMelbourne=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Sydney.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentSydney=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Adelaide.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentAdelaide=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Darwin.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentDarwin=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Perth.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentPerth=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Cairns.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentCairns=data;
});
$http.jsonp('http://api.wunderground.com/api/5ad0204df4bdbeff/conditions/q/Australia/Brisbane.json?callback=JSON_CALLBACK').success(function(data){
$scope.currentBrisbane=data;
$scope.cityData=[
{ name:'Brisbane',
temp:$scope.currentBrisbane.current_observation.temp_c,
image:$scope.currentBrisbane.current_observation.icon
},
{ name:'Melbourne',
temp:$scope.currentMelbourne.current_observation.temp_c,
image:$scope.currentMelbourne.current_observation.icon
},
{
name:'Adelaide',
temp:$scope.currentAdelaide.current_observation.temp_c ,
image:$scope.currentAdelaide.current_observation.icon
},
{ name:'Darwin',
temp:$scope.currentDarwin.current_observation.temp_c ,
image:$scope.currentDarwin.current_observation.icon
},
{ name:'Perth',
temp:$scope.currentPerth.current_observation.temp_c ,
image:$scope.currentPerth.current_observation.icon
},
{ name:'Cairns',
temp:$scope.currentCairns.current_observation.temp_c,
image:$scope.currentCairns.current_observation.icon
},
]
});
});
Here is the html
<div id="weather-container">
<div id="current-weather">
<!--Angular JSON pull -->
<div id="title"><span id="current-title">Current Weather</span></div>
<div id="current-condition">{{currentSydney.current_observation.weather}}</div>
<!--Image thingo here-->
<img ng-src="{{currentSydney.current_observation.icon_url}}"></img>
<div id="current-temp"><span id="current-temp"> {{currentSydney.current_observation.temp_c}} </span></div>
<span id="current-city">{{currentSydney.current_observation.display_location.city}} </span>
</div>
<!--Angular JSON pull and iteration-->
<div id="other-city-container">
<div class="other-city-weather" ng-repeat="city in cityData" >
<!--Image thingo here-->
<img ng-src="{{city.image}}"></img>
<div class="current-city-temp">
<span>{{city.temp}}</span>
</div>
<div class="current-city-lower">
<span>{{city.name}}</span>
</div>
</div>
</div>
</div>
You can write an helper function which will be rewrite the url property in your json after each succeeded ajax request. Please provide more informations, so we can help you.

Categories