AngularJs navbar routing directive - angularjs

I have a two-level navbar that works quite well.
Top level displays main menu.
Second level displays menu for any subcontent. This is hidden when no sub items are found.
<div id="nav-wrap">
<nav id="nav" class="site-nav" data-ng-class="{'sub-nav': hasSubRoutes()}">
<div class="row">
<ul>
<li ng-repeat="route in routes">{{route.config.title}}
<ul data-ng-class="{'active': isCurrentRoute(route)}">
<li ng-repeat="subItem in route.subItems" data-ng-class="{'ui-tabs-selected ui-state-active': isCurrentSubRoute(subItem)}"><span>{{subItem.config.title}}</span>
</li>
</ul>
</li>
</ul>
</div>
</nav>
</div>
And this is what I'm replacing the above with:
<my-navbar />
And here is the code for everything else
(function () {
"use strict";
var newRoutes = {
routes:
[
{ "url": "/layout", "config": { "title": "Layout", "templateUrl": "pageContent/layout.html" } },
{ "url": "/commandments", "config": { "title": "Commandments", "templateUrl": "pageContent/commandments.html" } },
{ "url": "/release", "config": { "title": "Release Notes", "templateUrl": "pageContent/release.html" } },
{ "url": "/resources", "config": { "title": "Resources", "templateUrl": "pageContent/resources.html" } },
{
"url": "/whatever", "redirectTo": "/whatever/resources1", "config": { "title": "Whatever" },
"subItems":
[
{ "url": "/whatever/resources1", "config": { "title": "Resources", "templateUrl": "pageContent/resources.html" } },
{ "url": "/whatever/release1", "config": { "title": "Release", "templateUrl": "pageContent/release.html" } }
]
}
],
otherwise: "/layout"
};
angular.module("themeApp", ["ngRoute"])
.constant("routes", newRoutes.routes)
.constant("otherwise", newRoutes.otherwise)
.config(function ($routeProvider, routes, otherwise) {
routes.forEach(function (r) {
if (r.redirectTo) {
$routeProvider.when(r.url, {
redirectTo: r.redirectTo
});
}
if (r.subItems) {
r.subItems.forEach(function (sr) {
$routeProvider.when(sr.url, sr.config);
});
} else {
$routeProvider.when(r.url, r.config);
}
});
$routeProvider.otherwise({ redirectTo: otherwise });
})
.controller("themeController", ["$scope", "$location", "routes", function ($scope, $location, routes) {
//Public Parameters
$scope.routes = routes;
}])
.directive("myNavbar", [function () {
return {
restrict: "E",
template: '<div id="nav-wrap"><nav id="nav" class="site-nav" data-ng-class="{\'sub-nav\': hasSubRoutes()}"><div class="row"><ul><li ng-repeat="route in routes">{{route.config.title}}<ul data-ng-class="{\'active\': isCurrentRoute(route)}"><li ng-repeat="subItem in route.subItems" data-ng-class="{\'ui-tabs-selected ui-state-active\': isCurrentSubRoute(subItem)}"><span>{{subItem.config.title}}</span></li></ul></li></ul></div></nav></div>',
replace: true,
controller: function ($scope, $location) {
//COPIED FROM themeController
//Public Methods
$scope.isCurrentRoute = isCurrentRoute;
$scope.isCurrentSubRoute = isCurrentSubRoute;
$scope.hasSubRoutes = hasSubRoutes;
//Private Variables
var hasSubItems;
//Private Methods
function hasSubRoutes() {
return hasSubItems;
}
function isCurrentRoute(route) {
var current = getCurrent(route);
hasSubItems = false;
if (current) {
hasSubItems = route.subItems && route.subItems.length > 0;
}
return current;
}
function isCurrentSubRoute(route) {
return getCurrent(route);
}
function getCurrent(route) {
var path = route.url;
return $location.path().substr(0, path.length) === path;
}
}
};
}]);
}())
Here's the problem:
Right now, the menu bar shows up and I can click the menu commands, the code to manipulate the menu tabs works great! However, I get no content.
When I restore the HTML (use the 'nav-wrap' HTML from above, and copy the javascript from the directive controller into the themeController and remove '<my-navbar>'), it works perfectly. The menu works and I get the content.
Thanks,
Duane

While I didn't get any JavaScript errors, I did get a warning that I finally paid attention to.
The browser didn't like my self-closing <my-navbar />, though I swear I've done that before with other directives...but I could be wrong.
Once I changed it to:
<my-navbar></my-navbar>
everything worked.
Thank you Khanh for working with me!!!!

Related

Angular 1.6 typeError with binding

New with angular and getting this error.
Uncaught TypeError: Cannot use 'in' operator to search for '$ctrl' in subpackage
I have a parent and a child component where I have this function on the parent that I use to filter some JSON to get a certain value. I am getting stuck in what to do in the child controller so I can call this parent function and haven't even begun to figure out what I need to do to call this from the child template. Here is what I have.
var myApp = angular.module('subPackages', ['ngMaterial', 'ngMessages']);
(function (app) {
'use strict';
app.component('appComponent', {
templateUrl: '../subpackages/templates/app-template.html',
controller: subAppController
});
app.component('perfList', {
templateUrl: '../subpackages/templates/perf-list.templateV3.html',
controller: PerfListController,
bindings: {
contentJson: '<',
getGlobalContent: '&'
},
});
})(myApp);
Parent
function subAppController() {
this.currentStep = 1;
this.contentJson =
{
"id": "1",
"cart_method": "cartAdd",
"package": "69",
"page_title": "Subscriptions",
"page_header": "Choose a minimum of 3 performances\/events for guaranteed subscriber prices throughout the season.<\/br>\r\nStep 1: Choose your performances and price sections. Step 2: Indicate your seating preference.",
"add_btn_txt": "Add"
}
this.globalContentJson = [
{ "id": "1", "module": "subpackage", "item": "mobileNavText", "content": "Month Navigation" },
{ "id": "2", "module": "subpackage", "item": "allPerfText", "content": "All Performances" },
{ "id": "3", "module": "subpackage", "item": "pageTopText", "content": "BACK TO TOP" },
{ "id": "4", "module": "subpackage", "item": "cartSummaryText", "content": "Your Selections" },
{ "id": "5", "module": "subpackage", "item": "cartSummaryRemoveText", "content": "Delete" },
{ "id": "6", "module": "subpackage", "item": "continueBtnText", "content": "Continue" }
];
//Called from the template to get global content.
this.getGlobalContent = function (module, item) {
var globalContent = new contentProvider(this.globalContentJson);
var result = globalContent.get(module, item);
return result;
}
}
Parent Template
<div class="container-fluid">
<div class="cs-app-left row">
<div class="pull-left">
<label>{{$ctrl.contentJson.page_title}}</label>
</div>
<div class="cs-app-right pull-right">
<cart-summary
content-json="$ctrl.contentJson">
</cart-summary>
</div>
</div>
<div class="cs-app-main row">
<div>
<perf-list
ng-if="$ctrl.currentStep == 1"
content-json="$ctrl.contentJson"
get-global-content="$ctrl.getGlobalContent(module,item)"
>
</perf-list>
</div>
</div>
</div>
child controller
function PerfListController() {
this.$onInit = function () {
this.content = this.contentJson;
this.globalContent = this.getGlobalContent;
var cartAddEl = angular.element(document.querySelector('.cs-cartadd'));
var redirectEl = angular.element(document.querySelector('.cs-redirect'));
if (this.content.cart_method == "cartAdd") {
cartAddEl.removeClass('hidden');
redirectEl.addClass('hidden');
} else {
redirectEl.removeClass('hidden');
cartAddEl.addClass('hidden');
}
this.cart_method = this.content.cart_method;
this.test = this.globalContent("subpackage", "mobileNavText");
};
//Other Code Here
}
Got it solved. Was calling the function wrong in the child template. Ended up using
<span>
{{$ctrl.globalContent({module: "subpackage", item:"mobileNavText"})}}
</span>
Thanks for the advice georgeawg on the classes, I will modify them.

Angular 2 router System is undefined in ES5

I am trying to learn Angular 2 with ES5. But I am stuck at routing. I followed the tutorial in angular.io and added the following scripts in my index.html -
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
<script src="node_modules/rxjs/bundles/Rx.umd.js"></script>
<script src="node_modules/angular2/bundles/angular2-all.umd.js"></script>
It worked all fine until I tried adding
<script src="node_modules/angular2/bundles/router.dev.js"></script>
After adding this I get the following error
System is undefined
I can see that router.dev.js uses System variable which is not defined.
How can I solve this?
After adding
<script src="node_modules/systemjs/dist/system.src.js"></script>
I am getting the following error -
EXCEPTION: No provider for Route! (class3 -> Route)
main.js
(function (app) {
document.addEventListener('DOMContentLoaded', function () {
ng.platform.browser.bootstrap(app.AppComponent, [ng.router.ROUTER_PROVIDERS]);
});
})(window.app || (window.app = {}));
app.component.js
(function (app) {
//Heroes Detail Component
app.HeroDetailComponent = ng.core.Component({
selector: 'my-hero-detail',
templateUrl: 'hero-detail.html',
inputs: ['hero']
}).Class({
constructor: function () {
}
});
//Heroes Component
app.HeroesComponent = ng.core.Component({
selector: "my-heroes",
templateUrl: 'heroes.html',
styleUrls: ['style.css'],
directives: [app.HeroDetailComponent]
}).Class({
constructor: [app.HeroService, function (_heroService) {
this.title = 'Tour of Heroes';
this._heroService = _heroService;
}],
ngOnInit: function () {
this.getHeroes();
},
onSelect: function (hero) {
this.selectedHero = hero;
},
getHeroes: function () {
this._heroService.getHeroes().then(heroes => this.heroes = heroes);
}
});
//App Component
app.AppComponent = ng.core.Component({
selector: 'my-app',
templateUrl: 'app.html',
directives: [app.HeroesComponent, ng.router.ROUTER_DIRECTIVES],
providers: [app.HeroService]
}).Class({
constructor: [ng.router.Route, function (_router) {
this.title = 'Tour of Heroes';
this._router = _router;
}]
});
//Route config
app.AppComponent = ng.router.RouteConfig([
{
path: '/heroes',
name: 'Heroes',
component: app.HeroesComponent
}
])(app.AppComponent);
})(window.app || (window.app = {}));
app.service.js
(function (app) {
app.HeroService = ng.core.Class({
constructor: function () {
this.HEROES = [
{ "id": 11, "name": "Mr. Nice" },
{ "id": 12, "name": "Narco" },
{ "id": 13, "name": "Bombasto" },
{ "id": 14, "name": "Celeritas" },
{ "id": 15, "name": "Magneta" },
{ "id": 16, "name": "RubberMan" },
{ "id": 17, "name": "Dynama" },
{ "id": 18, "name": "Dr IQ" },
{ "id": 19, "name": "Magma" },
{ "id": 20, "name": "Tornado" }
];
},
getHeroes: function () {
return Promise.resolve(this.HEROES);
},
getHeroesSlowly: function () {
return new Promise(resolve =>
setTimeout(() => resolve(this.HEROES), 2000) // 2 seconds
);
}
});
})(window.app || (window.app = {}));
I am trying to convert the Heroes Tutorial in ES5 from angular.io.
TL;DR/Solution
ES5 or angular2-all.umd.js doesn't require router.dev.js
Error was caused by misspelling. ng.router.Route should be ng.router.Router
You should try to include SystemJS in your HTML entry file:
<script src="node_modules/systemjs/dist/system.src.js"></script>
That said, I used routing with ES5 (see this plunkr: https://plnkr.co/edit/w61Ecbmuj7EfDnsYEHOS?p=info) but I didn't need to include the router.dev.js. The latter is for Angular2 applications written TypeScript or ES6 applications...

Get Json array that equals the value of $stateParam

I'm having trouble in fetching specific array in details page. $stateParams needs to be the same with the JSON array id but I can't print that on its template. I really appreciate any help. Thank you.
Services
angular.module('demo', ['ui.router']);
.factory('BookService', ['$http', function($http){
return {list: $http.get('data.json')}
}])
Routes
.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/books');
$stateProvider
.state('books', {
url:'/books',
templateUrl: 'templates/books.html',
controller: 'BooksCtrl'
})
.state('books.detail', {
url: '/detail/:id',
templateUrl: 'templates/books-detail.html',
controller: 'BooksDetailCtrl'
});
}])
Controllers
.controller('BooksCtrl', ['$scope', 'BookService', function($scope, BookService){
BookService.list
.success(function(data){
$scope.books = data;
});
}])
.controller('BooksDetailCtrl', ['$scope', '$stateParams', 'BookService',
function($scope, $stateParams, BookService){
$scope.selectedBook = BookService.find(BookService.list, $stateParams.id);
}])
JSON File(data.json)
{ "demo_site": [{
"id": "1",
"title": "Demo 1",
"summary": "Summary 1",
"body": "test demo 1",
"image": "img/compress/placehold.jpg",
"source": "angular/source",
"demo_link": "http://github.com"
}, {
"id": "2",
"title": "Demo 2",
"summary": "Summary 2",
"body": "test demo 2",
"image": "img/compress/placehold.jpg",
"source": "angular/source",
"demo_link": "http://github.com"
}]
}
Templates
books.html
<ul>
<li ui-sref-action="selected" ng-repeat="book in books.demo_site">
<a ui-sref="books.detail({id: book.id})">{{ book.title }}</a>
</li>
</ul>
<div class="detail" ui-view></div>
books-detail.html
<div>
{{selectedBook.title}}
</div>
I've edited your code a bit and it works now. The most important change is mocking the BookService by bookServiceMock (I added it because I don't have access to your service).
app.js
var app = angular.module('demo', ['ui.router']);
app.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/books');
$stateProvider
.state('books', {
url:'/books',
templateUrl: 'books.html',
controller: 'BooksCtrl'
})
.state('books.detail', {
url: '/detail/:id',
templateUrl: 'books-detail.html',
controller: 'BooksDetailCtrl'
});
}]);
app.service('bookServiceMock', function() {
var books =[{
"id": "1",
"title": "Demo 1",
"summary": "Summary 1",
"body": "test demo 1",
"image": "img/compress/placehold.jpg",
"source": "angular/source",
"demo_link": "http://github.com"
}, {
"id": "2",
"title": "Demo 2",
"summary": "Summary 2",
"body": "test demo 2",
"image": "img/compress/placehold.jpg",
"source": "angular/source",
"demo_link": "http://github.com"
}];
this.getBooks = function() {
return books;
};
this.getBook = function(id) {
for (var i = 0; i < books.length; i++) {
if (books[i].id === id) {
return books[i];
}
}
return null;
}
})
app.controller('BooksCtrl', ['$scope', 'bookServiceMock', function($scope, bookServiceMock){
console.log('BooksCtrl');
$scope.books = bookServiceMock.getBooks();
}]);
app.controller('BooksDetailCtrl', ['$scope', '$stateParams', 'bookServiceMock',
function($scope, $stateParams, bookServiceMock){
$scope.selectedBook = bookServiceMock.getBook($stateParams.id);
}]);
books.html
<ul>
<li ui-sref-action="selected" ng-repeat="book in books">
<a ui-sref="books.detail({id: book.id})">{{ book.title }}</a>
</li>
</ul>
<div class="detail" ui-view></div>
Here is the plunkr: http://plnkr.co/edit/VJxlqguJZGrIutAFLCNc

How to disable an AngularJs filter when checkboxes are false (disabled)?

I have a question about how to disable a AngularJS filter related to checkboxes.
I have a classic construction:
The object:
$scope.wines = [
{ "name": "Wine a", "type": "red", "country": "france", "style": "strong" },
{ "name": "Wine a", "type": "white", "country": "italie", "style": "medium" },
{ "name": "Wine a", "type": "white", "country": "france", "style": "light" },
{ "name": "Wine a", "type": "rose", "country": "usa", "style": "strong" },
{ "name": "Wine a", "type": "champagne", "country": "chili", "style": "medium" },
{ "name": "Wine a", "type": "red", "country": "brazil", "style": "strong" },
{ "name": "Wine a", "type": "red", "country": "france", "style": "strong" }
];
$scope.winetypes = {red : true,white : true,rose : true, champagne : true};
This filter is used to display only the related choice made with the checkboxes:
app.filter('winetypefilter', function () {
return function(input, filter) {
var result = [];
angular.forEach(input, function (wine) {
angular.forEach(filter, function (isfiltered, type) {
if (isfiltered && type === wine.type) {
result.push(wine);
}
});
});
return result;
};
The results are displayed with this html code:
<li ng-repeat="(type, value) in winetypes">
<input type="checkbox" ng-model="winetypes[type]" /> {{type}}
</li>
<ul>
<li ng-repeat="wine in wines | winetypefilter:winetypes">
{{wine.name}} is a {{wine.type}} with {{wine.style}} style from {{wine.country}}
</li>
</ul>
I would, when all the wines check-boxes are unchecked (false), to disable the winetypefilter, to display all the results on the page. I tried without success!!
Here the demo: http://plnkr.co/edit/nIQ2lkiJJY9MwJKHrqOk?p=preview
You can actually tweak your custom filter, winetypefilter, to check if all winetypes boolean values are disabled. if all values are disabled, then simply return the original array, in this case input, otherwise continue with your original filter.
Note: I also changed the way you filtered the winetypes, the extra layer of forEach wasn't necessary to determine if filtering should take place. just check if wine.type of filter was true in the winetypes json
Forked Plunker
e.g.
app.filter('winetypefilter', function () {
return function(input, filter) {
var result;
if(canFilter(filter)) {
result = [];
angular.forEach(input, function(wine) {
if(filter[wine.type])
result.push(wine);
});
} else
result = input;
return result;
};
function canFilter(filter) {
var hasFilter = false;
angular.forEach(filter, function(isFiltered) {
hasFilter = hasFilter || isFiltered;
});
return hasFilter;
}
});
I used ng-if to show depending on whether all the checkboxes are checked...
<ul>
<li ng-if="check()" ng-repeat="wine in wines | winetypefilter:winetypes">
{{wine.name}} is a {{wine.type}} with {{wine.style}} style from {{wine.country}}
</li>
<li ng-if="!check()" ng-repeat="wine in wines ">
{{wine.name}} is a {{wine.type}} with {{wine.style}} style from {{wine.country}}
</li>
</ul>
and the controller code..
$scope.check=function()
{
if($scope.winetypes.red===false&&$scope.winetypes.white===false&&$scope.winetypes.rose===false&&$scope.winetypes.champagne===false)
{
return false;
}
return true;
};
http://plnkr.co/edit/MWL727Yfq8MLWodmaBDk?p=preview
http://plnkr.co/edit/w4aCjOW5UnnFQcAKYBT9?p=preview
app.filter('winetypefilter', function () {
return function(input, filter) {
var result = [];
var alt = [];
var flag = false;
angular.forEach(input, function (wine) {
flag = false;
angular.forEach(filter, function (isfiltered, type) {
if (isfiltered && type === wine.type) {
result.push(wine);
flag = true;
}
});
if(!flag)
alt.push(wine);
});
if(!angular.equals(result.length,0))
return result;
else
return alt;
};
});
As per one of the other comments, you can forgo the extra forEach
Updated Plunker: http://plnkr.co/edit/w4aCjOW5UnnFQcAKYBT9?p=preview
app.filter('winetypefilter', function () {
return function(input, filter) {
var result = [];
var alt = [];
angular.forEach(input, function (wine) {
if(filter[wine.type])
result.push(wine);
else
alt.push(wine);
});
if(!angular.equals(result.length,0))
return result;
else
return alt;
};
});

scope of event arguments in angular directive

I have the following angular app to create a menu of sections/products.
at present when rendered and hitting the 'add' button that is rendered within each li I want to add a section/product as a sub of that section however multiple new children are created.
ultimately I wish to display a form which when submitted will create the child but that is the next step. Right now I need to limit the scope to the current section and not have multiple bound clicks.
If you need more information please state and I will post in an edit.
Some sample data data.
{
"sections":[
{
"name":"Flags",
"sections":[
{
"name":"Europe",
"sections":[],
"products":[
{ "name": "France" },
{ "name": "Germany" },
{ "name": "Ireland" },
{ "name": "England" }
]
},
{
"name": "Africa",
"sections":[],
"products":[
{ "name": "Egypt" },
{ "name": "Nigeria" },
{ "name": "Chad" }
]
},
{
"name": "South America",
"sections":[],
"products": [
{ "name": "Brasil" },
{ "name": "Argentina" },
{ "name": "Peru" }
]
}
],
"products":[]
},
{
"name": "Maps",
"sections":[
{
"name": "Africa",
"sections":[],
"products":[
{ "name": "Egypt" },
{ "name": "Nigeria" },
{ "name": "Chad" }
]
},
{
"name": "South America",
"sections":[],
"products": [
{ "name": "Brasil" },
{ "name": "Argentina" },
{ "name": "Peru" }
]
}
],
"products":[]
}
],
"products":[]
}
The app.
'use strict';
var menuApp = angular.module('menuApp', []);
menuApp
.directive('sections', function () {
return {
restrict: "E",
replace: true,
scope: {
sections: '='
},
template: '<ul><section ng-repeat="section in sections" section="section" /></ul>'
};
})
.directive('section', function ($compile) {
return {
restrict: "E",
replace: true,
scope: {
section: '=section'
},
template: '<li class="section">{{section.name}} <button ng-click="addSub(section)">Add</button></li>',
link: function (scope, element, attrs, controller) {
if (angular.isArray(scope.section.sections)) {
element.append("<sections sections='section.sections'></sections>");
$compile(element.contents())(scope);
}
if(angular.isArray(scope.section.products)){
element.append("<products products='section.products'></products>");
$compile(element.contents())(scope);
};
},
controller : function($scope){
console.log($scope);
$scope.addSub = function (section){
//console.log(section,'Adding Sub');
section.sections.push({"name":"Section","sections":[],"products":[]});
};
}
};
})
.directive('products', function () {
return {
restrict: "E",
replace: true,
scope: {
products: '='
},
template: '<ul><product ng-repeat="product in products" product="product"></product></ul>'
};
})
.directive('product', function ($compile) {
return {
restrict: "E",
replace: true,
scope: {
product: '='
},
template: '<li class="product">{{product.name}}</li>'
};
});
menuApp.controller('menuCtrl', function menuCtrl($scope,$http) {
$http.get('/ajax/getvenuesmenu?venueID='+venueMenu.venueId).success(function(resp) {
$scope.sections = resp;
});
$scope.add = function(data){
data.push({"name":"Section","sections":[]});
};
});
Took me a bit to figure it out but here's the basic problem, you are compiling the full contents of section 2 extra times and each compile seems to add a new event handler.
Instead of compiling the contents of element each time you make an append of new template, compile the template itself (outside of the DOM) and then append the compiled template. This way the ng-click handler doesn't get compiled again other than initial scope creation
Here's an abbreviated version with one template appended:
link: function (scope, element, attrs, controller) {
if (angular.isArray(scope.section.sections)) {
/* compile outside of the DOM*/
var subsections = $compile("<sections sections='section.sections'></sections>")(scope);
/* append compilation*/
element.append(subsections);
}
DEMO
Another approach would be to create a complete template string in link by checking for subsections and products, then compiling everything all at once....instead of using template option
Code for alternate approach compiling complete section at once:
.directive('section', function ($compile, $timeout) {
return {
restrict: "E",
scope: {
section: '=section'
},
link: function (scope, element, attrs, controller) {
var template = '<li class="section">{{section.name}} <button ng-click="addSub(section)">Add</button>';
if (angular.isArray(scope.section.sections)) {
template += "<sections sections='section.sections'></sections>";
}
if (angular.isArray(scope.section.products)) {
template += "<products products='section.products'></products>";
};
template += '</li>';
var compiledTemplate = $compile(template)(scope);
element.replaceWith(compiledTemplate);
scope.addSub = function (section) {
section.sections.push({ "name": "Section", "sections": [], "products": []
});
};
}
};
})
DEMO-Alt

Resources