I have been continuing learning angular and have now used the angular ui bootstrap pagination successfully. I am able to display the list of items together with the correct number of pages. And also switch to the correct page whenever I click on the pagination.
Now my question is if a user wanted to bookmark a certain page, or to make sure the user stays on the same page whenever he refreshes the browser, how do I go about it. There are no links (href) being generated on the address bar of the browser. Do I also need to set routes? Can you please post some examples, as it would greatly help me. Thanks.
You need to set up routes, you can do it using routeProvider or ui router
In this example, I use route provider to demonstrate, but the idea is the same.
Here I set up a route with currentPage as parameter:
app.config(function($routeProvider) {
$routeProvider
.when('/page/:currentPage', {
templateUrl: "template.html",
controller: PaginationDemoCtrl
})
});
In your controller, you can retrieve the current page from $routeParam:
$scope.currentPage = $routeParams.currentPage || 1; //default to 1 if the parameter is missing
//load your paged data from server here.
You could just $watch current page for changes and update the location accordingly:
$scope.$watch("currentPage",function(value){
if (value){
$location.path("/page/" + value);
}
})
Source code
DEMO link
With routing, you also need to update your code to load paged data from server. We don't load data immediately when the currentPage changes (in this case is the $watch function). We load our paged data when we retrieve the $routeParam.currentPage parameter.
As requested by #Harry, here is another solution to generate href links by overwriting bootstrap html template:
app.config(function($routeProvider) {
$routeProvider
.when('/page/:currentPage?', {
templateUrl: "template.html",
controller: PaginationDemoCtrl
})
})
.run(["$templateCache","$rootScope","$location", function($templateCache,$rootScope,$location) {
$rootScope.createPagingLink = function(pageNumber){
return "#" + $location.path().replace(/([0-9])+/,pageNumber);
//Here is a sample function to build href paths. In your real app, you may need to improve this to deal with more case.
}
$templateCache.put("template/pagination/pagination.html",
"<ul class=\"pagination\">\n" +
" <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noPrevious()}\"><a ng-href=\"{{$root.createPagingLink(1)}}\">{{getText('first')}}</a></li>\n" +
" <li ng-if=\"directionLinks\" ng-class=\"{disabled: noPrevious()}\"><a ng-href=\"{{$root.createPagingLink(page - 1)}}\">{{getText('previous')}}</a></li>\n" +
" <li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active}\"><a ng-href=\"{{$root.createPagingLink(page.number)}}\">{{page.text}}</a></li>\n" +
" <li ng-if=\"directionLinks\" ng-class=\"{disabled: noNext()}\"><a ng-href=\"{{$root.createPagingLink(page + 1)}}\">{{getText('next')}}</a></li>\n" +
" <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noNext()}\"><a ng-href=\"{{$root.createPagingLink(totalPages)}}\">{{getText('last')}}</a></li>\n" +
"</ul>");
}]);
Source code
DEMO link
We can do this with the $location, without needing $route.
Here is an example of how to call the pagination from Angular UI Bootstrap:
<pagination ng-model="Controls.currentPage"
total-items="Controls.totalItems"
items-per-page="Controls.videosPerPage"
max-size="5"
rotate="false"
boundary-links="true"
ng-change="pageChanged()">
</pagination>
Inside the pageChanged() function, use the following to create a unique URL for your results page:
** My code is on Coffeescript, but the logic is simple and the code easy to adapt **
$location.search('page', $scope.Controls.currentPage)
And on your controller, use the following to check, when starting, if the URL parameter is there:
urlParams = $location.search()
if urlParams.page?
$scope.Controls.currentPage = urlParams.page
else
$scope.Controls.currentPage = 1
Yes, if you want your app to allow linking into certain states then you will have to have your app leverage the routeProvider.
The official docs don't have a great deal of information on routes but the tutorial has this page:
http://docs.angularjs.org/tutorial/step_07
Also John Lindquist's excellent short video tutorials are a must watch. One dealing with routes is:
http://www.egghead.io/video/gNtnxRzXj8s
Here the generic solution - the uibPagination directive decorator and the updated template:
angular.module('ui.bootstrap.pagination')
.config(function($provide) {
$provide.decorator('uibPaginationDirective', function($delegate, $templateCache) {
var directive = $delegate[0];
directive.scope.getPageHref = "&";
$templateCache.put("uib/template/pagination/pagination.html",
"<li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a ng-href=\"{{noPrevious() ? '' : getPageHref()(1)}}\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('first')}}</a></li>\n" +
"<li ng-if=\"::directionLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-prev\"><a ng-href=\"{{noPrevious() ? '' : getPageHref()(page - 1)}}\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('previous')}}</a></li>\n" +
"<li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active,disabled: ngDisabled&&!page.active}\" class=\"pagination-page\"><a ng-href=\"{{getPageHref()(page.number)}}\" ng-disabled=\"ngDisabled&&!page.active\" uib-tabindex-toggle>{{page.text}}</a></li>\n" +
"<li ng-if=\"::directionLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-next\"><a ng-href=\"{{noNext() ? '' : getPageHref()(page + 1)}}\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('next')}}</a></li>\n" +
"<li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a ng-href=\"{{noNext() ? '' : getPageHref()(totalPages)}}\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('last')}}</a></li>\n" +
"");
return $delegate;
});
});
We decorate the directive with the getPageHref function passed from the outer scope which is used to construct the page links.
Usage:
<div ng-controller="ProductController">
...
<ul uib-pagination
total-items="totalItems"
ng-model="page"
get-page-href="getPageHref">
</ul>
</div>
Now you have to define the getPageHref function in your outer scope:
angular.module('app')
.controller('ProductController', function($scope) {
...
$scope.getPageHref = function(page) {
return '#/products?page=' + page;
};
});
Just to make code look better you can use template-url attribute on uib-pagination element
to specify your new template url
I whould use ui-sref instead of using a function to generate the link .
and instead of reloading the controller for each page click pass a function for ng-click with $eventso you could prevent the href from been executed
with e.preventDefault() and calling your ajax before
<li ng-repeat="page in pages track by $index" ng-class="{active: page.active}"><a ui-sref="list({page: page.text})" ng-click="$root.paginationClick(page.number,$event)">{{page.text}}</a></li>
Related
I'm trying to use angular js ui router to route my page.
Here's the code that I've written:
<a ui-sref="transporterEditDetails({companyId: '{{d.companyId}}' )" style="cursor: pointer">{{d.companyName}}</a>
Here's the js code :
var routerApp = angular.module('routerApp', ['ui.router','ui.bootstrap','xeditable','google.places','angular-loading-bar','ui.select','angularUtils.directives.dirPagination','notificationsTruckway','ui.tinymce'])
routerApp.config(function($stateProvider, $urlRouterProvider,cfpLoadingBarProvider) {
$stateProvider
.state('transporterEditDetails', {
url: '/transporterEditDetails/:companyId',
controller:'TransporterEditDetailsController',
templateUrl: 'Transporter-editDetails.html'
})
routerApp.controller('TransporterEditDetailsController', function($scope,$location,$http,usersFactory,$stateParams) {
$scope.companyId = $stateParams.companyId;
}
I don't know what's wrong in my code, I'm unable to get the href attribute which should be generated automatically.
Did you try without quotes and parentheses
<a ui-sref="transporterEditDetails({companyId: d.companyId})" style="cursor: pointer">{{d.companyName}}</a>
You don't need to use {{}}(interpolation) inside ui-sref. Also remove '(wrapped single quote)
ui-sref="transporterEditDetails({companyId: d.companyId })"
Realized that to implement some front-end stuff in my project, it`s better to use a front-end framework, not just a pure JS. Choose AngularJS.
Trying to implement and generated code and stuck with it...
I`ve got this html code generated and and added to the page content on some action,
function Content(i){
var html =
'<div ng-controller="Hello">'+
'<button ng-click="click_btn()">Load Data!</button>'+
'<div class="panel panel-default">'+
'<div class="panel-heading">Items List</div>'+
'<div class="col-sm-8">'+
'<ul>'+
'<li ng-repeat="element in content"> {{ element }} '</li>'+
'</ul>'+
'</div>'+
'</div>'+
'</div>';
var ul = document.getElementById('FullList');
ul.insertAdjacentHTML('beforeend', html);
AddElementAngular(contentString);
}
And the problem is that AngularJS <div ng-controller="Hello"> won't work here. can't get why, because code is successfully added to the page. I'm also tried to add click_btn() action and test the controller call on this action, but still doesn't work.
Here is the controller's code
angular.module('MainPage', [])
.controller('Hello', function($scope, $http) {
alert('jijij');
$scope.click_btn = function() {
alert('fsdfsdfsd');
}
$http.get('/api/getlist?groupID=2').
success(function(data) {
$scope.content = data;
});
});
The page is big to paste it here, but it starts with <html ng-app="MainPage">, so there shouldn't be a problem.
I'm inserted alerts to check, does the code is running. with the dynamic controller load - no. If i just insert controller's code directly in page content - everything works ok.
Controller inserted to the page dynamically only once. If the function Content(i){} call made again - previous controller's code will be removed.
EDIT:
Found a way to inject generated controller to the scope, from here:
http://geevcookie.com/2014/01/compile-dynamic-html-with-angularjs/
function AddElementAngular(html){
var $div = $(html);
$(document.body).append($div);
angular.element(document).injector().invoke(function($compile) {
var scope = angular.element($div).scope();
$compile($div)(scope);
});
}
But still didn't work properly. Code in the controller is executed(
$http.get('/api/getlist?groupID=2').
success(function(data) {
alert(data);
$scope.content = data;
});
i get the [object][Object]... etc in alert. ), but the in-page <li ng-repeat="element in content"> {{ element }} </li> doesn`t work for unknown reason. I just getting '{{ element }}' in HTML content.
I've got some projects displayed in divs via ng-repeat.
Each div contains a link with the id of the project.
Basically I want to bind my project id on ng-click in order to update a factory. This will allow me to share the clicked project's id with another controller who will load the project details data only.
Here is my projects view:
<div ng-repeat="detail in projects.details">
<a ng-href="#!/project/project-details" ng-click="setId(detail.project_id)"><span></span></a>
</div>
Here is my factory:
app.factory('getGoodIdProjectDetails', function (){
var savedId = 1; //Default à 1
return {
getId : function () {
return savedId;
},
setId:function(idGet){
savedId = idGet;
return savedId;
}
}
});
Here are my controllers:
function ProjectCtrl($scope, $http, $timeout, getGoodIdProjectDetails) {
$scope.setId = function (idGet) {
$scope.idProjectDetails = getGoodIdProjectDetails.setId(idGet);
};
}
function ProjectDetailsCtrl($scope, $http, $timeout, getGoodIdProjectDetails) {
$scope.idProjectDetails = getGoodIdProjectDetails.getId();
}
In my view the console is displaying an error like I can't bind ng-click this way, but when I inspect the view it's sorting me the good thing, like ng-click="setId(8)"
I want the factory to update with the good id on click. Then I want to access this id in projectDetailsCtrl in order to load the project.
|| EDIT || : I changed the ng-click, that works fine, everything's good. Thx all
try this:
<div ng-repeat="detail in projects.details">
<a ng-href="#!/project/project-details" ng-click="setId(detail.project_id)"><span>Voir le projet ></span></a>
</div>
you don't need to use binding expression {{}} to pass values to functions
I have a simple angular controller in my app. And I wish to enhance a page containing static content, with angular. This will allow me to have a good, content rich page as fall back for those without JS (who?!) and web crawlers and readers.
So far I have identified a few ways to do this, possibly using custom directives, or by doubling up content in 'ng-bind-template' directive (which is obviously not ideal).
I am fairly new to the world of angular so would like to try and garner the best practice for this scenario.
What is the best approach for this?
Controller:
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this"
$scope.activate = function(){
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
};
});
Markup:
(The problem here is that 'Static content' is replaced by '' if angular initializes)
<div ng-controller="TestArticle">
<div ng-cloak ng-bind-template='{{eTitle}}'>Static content</div>
<div ng-cloak ng-bind-template='{{eContent}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
EDIT
I should be clear that even though angular is bootstrapping, The aim is to leave the default content intact, and not replace it with dynamic content. That only wants to be achieved once the activate button is clicked. I have tried the following but it involves doubling up the content which could get bulky if it is a whole article.
<div ng-controller="TestArticle">
<div ng-cloak ng-bind-template='{{eTitle || "Static Content"}}'>Static content</div>
<div ng-cloak ng-bind-template='{{eContent || "Some more content"}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div> <a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
You can create a simple directive to create the html structure that you want.
I propose this to be your new html:
<div ng-cloak sk-custom-bind="eTitle">Static content</div>
Notice how you don't have to specify the or clause with the static content.
Now you can create a directive that will build the html template for you:
app.directive('skCustomBind',function() {
return {
restrict: 'A',
template: function(elem, attrs) {
var templ = "<div ";
templ += "ng-bind-template='{{";
templ += attrs.skCustomBind;
templ += ' || "';
templ += elem.text();
templ += '"';
templ += "}}'></div>";
return templ;
},
replace: true
};
});
And as simple as that you have the functionality that you are looking for without the duplication.
See this plunker to see a working sample.
The thing here is that when you define the controller, angular will parse through the html to find all the variables and link them with the corresponding controller items using $scope.
So, when angular initializes, since eTitle and eContent are not having any values (since you initialize them in the function only), which is called on click of the last item(where on your application works fine).
So until you click the button, the static text is replaced by $scope.eTitle and $scope.eContent both of which are undefined. Hence no text inside the tags.
So the solution,
1. Either you initialize the variables inside the controller with the static text, like you did for 'This is angular injecting this'(reason only this came!):
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this";
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
$scope.activate = function(){
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
};
});
Or you can initialize the variables in the html using ng-init, it will initialize the variable in html only:
<div ng-controller="TestArticle">
<div ng-init="eTitle = 'Static Content'" ng-cloak ng-bind-template='{{eTitle}}'>Static content</div>
<div ng-init="eContent = 'Some more content'" ng-cloak ng-bind-template='{{eContent}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
The thing is when you start using angular, the html will get kind of hijacked and angular will start using its own variables.
Thanks to #JoseM for putting me on to right track and helping me find the following solution...
SOLUTION
My Markup:
NOTE: the custom directive and properties ('cg-dyn' and 'cg-content'). These will not initialise any values when angular bootstraps. And further down you will see how these map from controller scope via an isolated scope.
<div ng-controller="TestArticle">
<div ng-cloak cg-dyn cg-content="eTitle">Static content</div>
<div ng-cloak cg-dyn cg-content="eContent">Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="upDateTitle()" ng-cloak>Update title</a> | <a href ng-click="upDateContent()" ng-cloak>Update content</a>
</div>
My Controller:
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this"
$scope.upDateTitle = function(){
$scope.eTitle = "Dynamic title";
};
$scope.upDateContent = function(){
$scope.eContent = "Dynamic Content Lorem ipsum";
};
});
And finally the magic in My Directive
app.directive('cgDyn',function() {
return {
restrict: 'A',
scope:{
'content':'=cgContent'
// this is a two way binding to the property defined in 'cg-content
},
link: function(scope, elem, attrs) {
// below is a watcher keyed on a combination content
scope.$watch('content',function(){
if(scope.content){
elem.html(scope.content);
// and here is the bit, normally plumbed by default, but instead we only replace the html if the value has been set
}
});
},
controller: 'TestArticle',
replace: false
};
});
Thanks for everyone's help. I am sure there is an even better solution out there so keep the suggestions coming!
My index.html has the following div
<div ng-view></div>
And I have my app declared as follows :
angular.module('app', [])
.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/listen', {
templateUrl : 'partials/listen.html',
controller : PlaylistCtrl
})
.when('/settings', {
templateUrl : 'partials/settings.html',
controller : SettingsCtrl
})
.otherwise({
redirectTo : '/listen'
})
}
])
;
Now, when I'm at the homepage (i.e. /#/listen), and click on a link to /#/settings, it replaces the contents of the page with the contents from partials/settings.html. How can I modify it so that the contents aren't replaced, but just added on? My goal is to have settings show up in a modal dialog, so I don't want the previous contents of the page to get cleared out.
ng-view is not going to help you here.
Instead you should combine ng-include with ng-switch or ng-show.
<div><ng-include src="listen.html"/></div>
<div ng-show="isOnSettingsUrl()"><ng-include src="settings.html"/></div>
Or something along those lines.
In the parent controller you need to read the $routeParams so that you can implement isOnSettingsUrl().
That's not possible with ng-view. You need to create an own directive and include it in your index.html:
<modal></modal>
Angular-ui has an implementation. Maybe you should check it out.
Edit:
In the past I've made my own modal (when testing out angular). I just started to learn angular, so it has lots of room for improvement (read now i would do it differently). However, it could give you an example:
app.directive('modal', function($compile, $http) {
return {
restrict: 'E',
replace: true,
compile: function(elm, attrs) {
var htmlText =
'<div id="' + attrs.id + '" class="modal hide fade" aria-hidden="true">' +
'<div class="modal-header">' +
'<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>' +
'<p> </p>' +
'</div>' +
'<div class="modal-body">' +
'<div>to be replaced</div>' +
'</div>' +
'<div class="modal-footer">' +
'<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>' +
'</div>' +
'</div>';
elm.replaceWith(htmlText);
return function postLink(scope, elm, attrs) {
var modal = $('#' + attrs.id);
modal.css({
width: '60%',
left: '20%',
margin: 'auto auto auto auto',
top: '10%'
});
var modalBody = modal.children('.modal-body');
modalBody.css({
maxHeight: '800px'
});
var replaceDiv = modalBody.children();
$http.get(attrs.src).success(function(data) {
var childScope = scope.$new();
childScope.modalMode = true;
var element = $compile(data)(childScope);
angular.element(replaceDiv).replaceWith(element);
});
};
}
};
});
Html:
<a class="btn" data-target="#myId" data-toggle="modal" data-backdrop="static">Open modal</a>
<modal id="myId" src="path/to/partial" ></modal>
ng-view directly updates itself with the content came from the routeProvider, not only useful, but also increases performance as it unloads the controller and the watchers which you won't be using (you are on a different page).
Also a change in the url should change the view, not append anything. What would happen if I go directly to the page? That definitely won't be intuitive.
It is expected to have a index.html page which includes layout, ng-view which will be the changing page. For other things, you can use ng-include.
In your case, I assume that you want to show a partial page in which user can update their settings.
You don't need to this with routing, you can have the settings partial within the play template and use ng-show on the partial. You then just put a link to show the partial page.
If however, you want something like grooveshark; then you do need to use ng-view with routing to settings, then you should put the play template (and its controller) outside of the ng-view as you expect it to show up in every page.