Load in new view without removing previous one - javascript

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.

Related

Why can`t I access scope via controller?

I am working on passion project. And I cant access scope to pass data to back-end frame work.
Here is my index file
<div id="main-menu" ng-controller="appCtrl">
//some other code
<div id="includedDocumentsFilter" style="float:right; display:none; padding-right: 10px;">
<my-documents validate-options="validateDialogOptions()" call-dialog="showDialog()"> </my-documents>
</div>
//some other code
</div>
My custom directive
'use strict';
dbApp
.directive('myDocuments', [
function () {
var documentTemplate =
' <div class="caption-row">' +
'<kendo-button style="width:62px" ng-click="changeDocument(true)"> Ok </kendo-button>'+
'<kendo-button style="width:62px" ng-click="changeDocument(false)" > Revert changes </kendo-button>'+
'</div>'
}
return {
scope: true,
template: documentTemplate
}
}]
)
My controller
$scope.changeDocument = function (applyFilter) {
if (applyFilter === true) {
//Here is where I cant access $scope
}
}
Firstly, I see a extra closing curly braces in your directive. Secondly in your html code there is display:none in div with id "includedDocumentsFilter". Just wondering if you are hiding the div, how will you be able to see the template defined in your directive. I have added a working jsfiddle link below using your above mentioned code
dbApp.directive('myDocuments', [
function () {
var documentTemplate =
' <div class="caption-row">' +
'<kendo-button style="width:62px" ng-click="changeDocument(true)"> Ok </kendo-button>'+
'<kendo-button style="width:62px" ng-click="changeDocument(false)" > Revert changes </kendo-button>'+
'</div>'
return {
scope: true,
template: documentTemplate
}
}]
)
JsFiddle link: https://jsfiddle.net/anilsarkar/gk2dfh1p/21/
Note: I have replaced kendo-button with span in jsfiddle

Use link function in directives to append different HTML elements

Fiddle
I have two buttons. When pressed it displays a modal, with som text. But I also want to add some html dynamically depending on which button is pressed.
I have tried both $observe and $watch methods, but I'm having problems making it work.
here is my code.
angular.module('TM', [])
.controller('protocolCtrl', function(){
this.text = 'Now looking at the protocol part';
this.modalId = 'protocolModal';
})
.controller('categoryCtrl', function(){
this.text = 'Now looking at the category part';
this.modalId = "categoryModal";
})
.directive('modalDirective', function(){
return {
restrict: 'E',
scope: {
ctrl: '=',
modalId: '#',
},
template: ['<div id="{{modalId}}" class="modal fade" role="dialog">',
'<div class="modal-dialog">',
'<div class="modal-content">',
'<div class="modal-header">',
'<h4 class="modal-title">Modal Header</h4>',
'</div>',
'<div class="modal-body">',
'<p> {{ ctrl.text }} </p>',
'</div>',
'<div class="modal-footer">',
'<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>',
'</div>',
'</div>',
'</div>',
'</div>'].join(''),
link: function(scope, element, attrs) {
element.$observe('modalId', function(){
var modal = element.find('#{{modalId}}');
if(modal == 'protocolModal'){
element.find('#{{modalId}}').append('<div>this is a protocol test...</div>');
} else {
element.find('#{{modalId}}').append('<div>this is a category test...</div>');
}
});
}
}
});
I don't think there is element.$observe - there is attrs.$observe and scope.$watch. You already have modelId on the scope, so let's use that.
Also, instead of the wonky .find by id, inject an element as a placeholder for the template and replaceWith it accordingly:
template: '<div id="{{modalId}}">\
...\
<div class="modal-body">\
<template-placeholder></template-placeholder>\
</div>\
</div>",
link: function(scope, element){
// ...
var unwatch = scope.$watch("modalId", function(val){
var placeholder = element.find('template-placeholder');
if(val == 'protocolModal'){
placeholder.replaceWith('<div>this is a protocol test...</div>');
} else {
placeholder.replaceWith('<div>this is a category test...</div>');
}
unwatch(); // seems like you don't really need to set it again
}
}
See i updated your Fiddle
Use attr in link function. because you already given attribute to
your html i.e: modal-id="{{pctrl.modalId}}
<modal-directive ctrl="pctrl" modal-id="{{pctrl.modalId}}"></modal-directive>
if(attrs.modalId == 'protocolModal'){
element.find('#{{modalId}}').append('<div>this is a protocol test...</div>');
} else {
element.find('#{{modalId}}').append('<div>this is a category test...</div>');
}
Edit :
use $timeout
$timeout(function () {
if (attrs.modalId == 'protocolModal') {
element.find('#' + attrs.modalId).append('<div>this is a protocol test...</div>');
} else {
element.find('#' + attrs.modalId).append('<div>this is a category test...</div>');
}
}, 1000)
Now why $timeout because you are applying template and and at same
time your link function append your div so it will not apply your div
.So first apply template and then in template append your div
And if you want to show that div in popup content the use this selector.
see this example: https://jsfiddle.net/kevalbhatt18/o76hxj69/6/
element.find('#'+attrs.modalId +' .modal-body').append('<div>this is a protocol test...</div>');
If your two DOM structure diverses, you can consider using two different templates depending on some parameter value.
templateUrl can be specified as a function, such as:
angular.module('Joy', [])
.controller('ProfileCtrl', ['$scope', function ($scope) {
$scope.user = {
name: 'Elit'
};
}])
.directive('profile', [function () {
return {
restrict: 'A',
templateUrl: function (elem, attrs) {
return 'style-' + attrs.color + '.html';
}
};
}]);
And use the directive as:
<div ng-app="Joy" id="play-ground" ng-controller="ProfileCtrl">
<div profile color="red"></div>
<div profile color="green"></div>
</div>
In this case, if the color is red, the directive will load template from style-red.html. Otherwise from style-green.html.
In your case, you might maintain a flag in the outside controller. Clicking either button will change this flag value and pass in to the directive. The directive will load different templates accordingly.

generated AngularJS controller usage

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.

How to generate links in Angular UI Bootstrap Pagination

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>

AngularJS : Customizing the template within a Directive that includes references to scoped attributes

I'm trying to customize a template within a directive and include references to attributes in the parent scope. I'm pretty new to Angular but I've done a fair bit of searching and I've based my attempts on Customizing the template within a Directive. However if I pass a reference to a parent scoped variable as an attribute to the directive it doesn't get resolved, possibly because it's still undefined at the time the compile function is called.
My directive definition looks like this:
app.directive('sectionHeader', function() {
return {
restrict: 'EC',
replace: true,
transclude: true,
scope: {sectionName:'#sectionName', imageUrl:'#imageUrl'},
compile: function(element, attrs) {
var imageHtml = attrs.hasOwnProperty('imageUrl') ? '<div style="float: left; padding-right: 5px;"><img class="float_left" src="' + attrs.imageUrl + '" alt=""/></div>' : '';
var htmlText =
'<div>' + imageHtml + '<h1 class="float-left">' + attrs.sectionName + '</h1>' +
'<div class="clear"></div>' +
'<div class="modal_hr"></div></div>';
element.replaceWith(htmlText);
},
};
});
And I'm using the directive like this:
<div class="section-header" section-name="{{currentFeatureName}}"></div>
The problem is the {{currentFeatureName}} variable from my controller doesn't appear to be defined when the compile function is called on the directive.
One way I've considered to get around this is within the compile function set up an observer function on the sectionName attribute that updates h1 element content when it sees a change. This seems a little clunky and I was wondering if there is a better or more elegant way of doing this.
Check out the $observe function in Directive docs.
But beside that, there actually seems to be no need to do what you were trying to do. See:
var app = angular.module('plunker', []);
app.controller('AppController',
[
'$scope',
function($scope) {
$scope.currentFeatureName = 'Current Feature Name';
$scope.imageUrl = "https://lh3.googleusercontent.com/GYSBZh5RpCFwTU6db0JlHfOr_f-RWvSQwP505d0ZjWfqoovT3SYxIUPOCbUZNhLeN9EDRK3b2g=s128-h128-e365";
}
]
);
app.directive('sectionHeader', function() {
return {
restrict: 'EC',
replace: true,
transclude: true,
scope: {
sectionName:'#',
imageUrl:'#'
},
template: '<div><div style="float: left; padding-right: 5px;" ng-show="imageUrl"><img class="float_left" ng-src="{{imageUrl}}" alt=""/></div><h1 class="float-left">{{sectionName}}</h1><div class="clear"></div><div class="modal_hr"></div></div>'
};
});
HTML:
<div ng-controller="AppController">
<div class="section-header" section-name="{{currentFeatureName}}" image-url="{{imageUrl}}"></div>
</div>
Plunker.
You are correct on why this isn't working. Interpolated attributes are not available when the compile and link functions run because no digest has occurred yet to resolve the interpolation to a value. You can read more about this here. You are also right about the solution: use attrs.$observe( 'sectionName', function ( val ) { ... });
However, it doesn't look like you need a dynamic template. If this was your template:
<div>
<div style="float: left; padding-right: 5px;" ng-show="{{imageUrl}}">
<img class="float_left" ng-src="{{imageUrl}}" alt="" />
</div>
<h1 class="float-left">{{sectionName}}</h1>
<div class="clear"></div>
<div class="modal_hr"></div>
</div>
Then you wouldn't need any logic in a compile or link function. Perhaps this pattern will also help you.

Categories

Resources