I'm making a fairly complex menu system where it should change depending on the authority that has logged in. Now my question is, have I nested it too much? How do I access for example a title in the items, in the admin within the menus? I'm gonna generate the menus using angularJS's ng-repeat so I need to be able to access it via "item in menus.admin.items.title" for example. I figured I should ask now before I add more in case this isn't a viable option.
This is my menu structure inside the angular controller:
$scope.menus = [{
admin: [{
title: 'Administration',
items: [{
title: 'Hantera utbildningar',
URL: 'mngprograms'
},
{
title: 'Hantera kurser',
URL: 'mngcourses'
},
{
title: 'Hantera lärare',
URL: 'mngteachers'
},
{
title: 'Hantera studenter',
URL: 'mngstudents'
}],
URL: 'administration',
id: 'administration'
}]
}]
Here's my failed attempt at accessing it:
EDIT:
To get a clearer view of the whole thing go here: http://jsfiddle.net/52evmfg9/
<ul id="main-menu">
<li data-ng-repeat="menu in menus" id="{{menu.id}}">{{menu.title}}
<ul data-ng-if="menu.admin">
<li data-ng-repeat="subitem in menu[3].admin.items">{{subitem.title}}</li>
</ul>
</li>
</ul>
How do I write this?
I recommend making your hierarchy structure more explicit. For each menu, you can have a key "submenus" where you can find menus further down in the hierarchy. A useful directive for hiding elements is ng-show.
I would only build one complete menu tree and then set permissions for your users or roles. Otherwise the tree will become unmanageable and there will be repeated data.
Here is an example jsBin:
http://jsbin.com/sixiw/2/edit?html,js,output
To answer the question in the comments:
<div data-ng-repeat="(k,v) in menus">
<div data-ng-repeat="(k,w) in v">
<div data-ng-repeat="(k,x) in w">
<ul data-ng-repeat="(k,y) in x" >
{{k}} : {{y}}
<li ng-if="z.title" data-ng-repeat="(k,z) in y track by $index">
Title: {{z.title}} URL: {{z.URL}}
</li>
</ul>
</div>
</div>
</div>
http://plnkr.co/edit/WLnXBQbbOqveJuHiUeEI?p=preview
Related
I am trying to build ng-template dynamically, In the Data structure, I have a Object that contains list of object of same type or other type of object.
[
{
type: "device",
id: 1,
allowedTypes: ["device","action"],
max: 5,
columns: [
{
type: "action",
id: "1"
},
{
type: "device",
id: 2,
allowedTypes: ["device","action"],
max: 5,
columns: [
{
type: "action",
id: "1"
},
{
type: "action",
id: "2"
}
]
}
]
}
]
My ng-template code:
<script type="text/ng-template" id="and.html">
<div class="container-element box box-red">
<h3>And {{item.id}}</h3>
<ul dnd-list="item.columns"
dnd-allowed-types="item.allowedTypes"
dnd-disable-if="item.columns.length >= item.max">
<li ng-repeat="element in item.columns"
dnd-draggable="element"
dnd-effect-allowed="move"
dnd-moved="item.columns.splice($index, 1)"
dnd-selected="models.selected = element"
ng-class="{selected: models.selected === element}"
ng-include="element.type + '.html'" onload="onloadAction()">
</li>
</ul>
<div class="clearfix"></div>
</div>
</script>
In the ng-template code the first level or different types of object can easily be added, however same type of object does not work properly when added in the 2nd level, it seems the its going in a recursive loop.
This is because the nested ng-template (child) ng-template use of root item.columns from the ng-repeat="element in item.columns".
<li ng-repeat="element in item.columns"
dnd-draggable="element"
ng-include="element.type + '.html'">
</li>
any advise how to resolve this issue.
I have managed to resolve this issue, ng-repeat create a new child scope, however the item from the parent scope is also visible to in the child scope, as this is recursive that mean child template and parent template both have the variable with the same name called item. However the child scope variable item is ignored:
<li ng-repeat="element in item.columns"
dnd-draggable="element"
ng-include="element.type + '.html'">
</li>
in the above snippet the element also has a variable called item, so in the child scope when we call item.columns the item variable of parent is invoked again, to ensure that the item variable of child is not ignored, we need to use ng-init with ng-include and set the current element as item in the child scope as shown below:
<li ng-repeat="element in item.columns"
dnd-draggable="element"
ng-include="element.type + '.html'" ng-init="item=element">
</li>
To read more on this please visit
http://benfoster.io/blog/angularjs-recursive-templates
I am learning inherited/isolated scopes in angular directives and struck with some basic concepts. please see the plunker below.
Example.
Scenario1:
I have 2 directives (book and details). I am displaying two "Book Details" containers and toggling book name by sending custom attributes like this.
<book bookdetails="book" collapsed="yes" list="list"></book>
<book bookdetails="book1" collapsed="no" list="list"></book>
Question: Is this the right way to handle displaying things in 2 different containers?
Scenario 2:
I want to hide the author details section in container 1 but show in container2 on load. How to accomplish that?
When I use this line below it will hide and show both author details section but I want to keep it separate.
<details collapsed="yes"></details>
I know I am lacking basic skills using inherited/isolated scopes. Can someone educate me?
It's OK to use nested directives like you've used so you can do everything related to the details pane in the details controller like removing items from the books list.
If you wouldn't do any logic in details controller and just include some html I would just use ng-include.
Some points I've detected during improving your code:
Template markups are partial html files, so no need to add header, body etc. Just add your markup that you need in your directive.
I've created one model array books that you can iterate with ng-repeat and not two separate scope variables. That's easier to add more books.
I wouldn't pass the collapsed state to directive isolated scope. I would add it to the book model then you can have independent states of the details panes.
You could also create a collapsed array scope variable separate from your model and use it like ng-hide='collapsed[$index]' if you don't like to add it to your model.
Don't compare to the string yes. It makes things more complicated. It's better to use true or false.
The list you're passing is OK if you'd like to use one list for every details pane. But I think you need them independent from each other so add it to your book model.
For toggeling a value you can use the js shorthand: collapsed = !collapsed;. It takes the value of collapsed and inverts it and re-asigns it to collapsed.
Details directive: You don't need to pass attributes to a directive that doesn't use isolated scope. Instead you can directly use the inherited scope of the parent.
Note: I think you should have a look at angular-ui-bootstrap and use an accordion instead of your manually created panes later. But for learning directives your code is OK.
Please have a look at your updated code below or in this plunker.
If something is not clear, feel free to add a comment and I'll try to help.
angular.module('plunker', [])
.controller('MainCtrl', function($scope) {
$scope.books = [{
id: 0,
name: 'Building modern ASP.NET 5',
author: {
name: 'name1',
age: 31,
country: 'USA'
},
collapsed: false,
list: [{
id: 0,
name: 'book1'
}, {
id: 1,
name: 'book2'
}, {
id: 2,
name: 'book3'
}]
}, {
id: 1,
name: 'AngularJS',
author: {
name: 'name2',
age: 27,
country: 'USA'
},
collapsed: true,
list: [{
id: 0,
name: 'book1'
}, {
id: 1,
name: 'book2'
}, {
id: 2,
name: 'book3'
}]
}];
//$scope.list = ["book1", "book2", "book3"];
}).directive('book', function() {
return {
restrict: 'E',
templateUrl: 'book.html',
scope: {
bkdet: "=bookdetails"
//list: "="
//collapsed: "#"
},
controller: function($scope) {
$scope.toggleDetails = function() {
$scope.bkdet.collapsed = !$scope.bkdet.collapsed;
updateCaption();
};
function updateCaption() {
$scope.hypshowhide = $scope.bkdet.collapsed ? 'show details' : 'hide details';
}
// first run
updateCaption();
/*if ($scope.collapsed == 'yes')
{
$scope.dethide = true;
}
else {
$scope.dethide = false;
} */
//$scope.hypshowhide = 'show details';
}
}
})
.directive('details', function() {
return {
restrict: 'E',
templateUrl: 'details.html',
controller: function($scope) {
/*console.log($scope.bkdet.collapsed);
if (!$scope.bkdet.collapsed) { //== 'yes') {
$scope.dethide = true;
}
else {
$scope.dethide = false;
}*/
$scope.removeItem = function(index) {
$scope.bkdet.list.splice(index, 1);
}
}
}
})
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="plunker">
<div ng-controller="MainCtrl">
<div class="container">
<book bookdetails="book" ng-repeat="book in books"></book>
</div>
</div>
<script type="text/ng-template" id="book.html">
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">
<h1>Book Details</h1>
</div>
<div class="panel-body">
<a class="pull-right" href="#" ng-click="toggleDetails(collapsed)">{{hypshowhide}}</a>
<div>
<!--ng-hide="dethide">-->
{{bkdet.name}}
</div>
<!--<details collapsed="no"></details>-->
<details></details>
</div>
</div>
</div>
</script>
<script type="text/ng-template" id="details.html">
<div class="container" ng-hide="bkdet.collapsed">
<div class="row">
<ul class="list-group list-unstyled">
<!--<li>
<h1>Author:</h1>
</li>
<li>
<ul>-->
<li>
<strong>Author</strong>
{{bkdet.author.name}}
</li>
<li>
<strong>Age</strong>
{{bkdet.author.age}}
</li>
<li>
<strong>Country</strong>
{{bkdet.author.country}}
</li>
<li>
<div ng-if="bkdet.list.length == 0">
<p>No books here!</p>
</div>
<div ng-repeat="c in bkdet.list">
<p>
{{c.name}}
<button class="btn btn-danger" ng-click="removeItem($index)">X</button>
</p>
</div>
</li>
<!--</ul>
</li>-->
</ul>
</div>
</div>
</script>
</div>
I have an extremely hierarchical JSON structure as a scope variable in my AngularJS controller. I want to loop around different sections of that variable. I thought about using ng-init to specify where in the structure I am. Here is some code:
my_app.js:
(function() {
var app = angular.module("my_app");
app.controller("MyController", [ "$scope", function($scope) {
$scope.things = {
name: "a",
children: [
{
name: "a.a",
children: [
{ name: "a.a.a" },
{ name: "a.a.b" }
]
},
{
name: "a.b",
children: [
{ name: "a.b.a" },
{ name: "a.b.b" }
]
}
]
}
}]);
});
my_template.html:
<div ng-app="my_app" ng-controller="MyController">
<ul>
<li ng-init="current_thing=things.children[0]" ng-repeat="thing in current_thing.children>
{{ thing.name }}
</li>
</ul>
</div>
I would expect this to display a list:
a.a.a
a.a.b
But it displays nothing.
Of course, if I specify the loop explicitly (ng-repeat="thing in things.children[0].children") it works just fine. But that little snippet of template code will have to be run at various points in my application at various levels of "things."
(Just to make life complicated, I can get the current thing level using standard JavaScript or else via Django cleverness.)
Any ideas?
ng-init runs at a lower priority (450) than ng-repeat (1000). As a result, when placed on the same element, ng-repeat is compiled first meaning that the scope property created by ng-init won't be defined until after ng-repeat is executed.
As a result, if you want to use it in this manner, you'd need to place it on the parent element instead.
<div ng-app="my_app" ng-controller="MyController">
<ul ng-init="current_thing=things.children[0]">
<li ng-repeat="thing in current_thing.children>
{{ thing.name }}
</li>
</ul>
</div>
<div>
<ul id="teachers">
<li ng-repeat></li>
</ul>
<ul id="students">
<li ng-repeat></li>
</ul>
</div>
I have two ul elements and dynamic data. For example:
[
{
name: 'Jack'
status: 'teacher'
},
{
name: 'Angelina'
status: 'teacher'
},
{
name: 'Maya'
status: 'student'
},
{
name: 'Kate'
status: 'teacher'
},
{
name: 'Margaret'
status: 'student'
}
]
I want to write some custom directive for ng-repeat, which will generates lists, for students and for teachers, for different ul's.
How can I write directive, with some condition, which will repeat li's in the right ul?
Yes, I can, filter My data and generate two Arrays, for students and teachers and than repeat those Independently.
But, I don't like this way. How it is possible to write one custom directive which will determines, where to repeat current Object?
UPDATE
Okey, I'm new in angular, so I've thought, that there will be something simple trick, something like this:
if(status === 'something')
use this template
else
use this template
So, with your answers I could write conditions which I wanted. Sorry about this, this was stupid decision.
So my actual case is:
I have Breadcrumbs data and main container, which width is equal to 500px.
I want to repeat li in this container and I want to my li's were always always inline.
If my data will be big, or some title will be big and my ul width will be more, than my container, some li elements will be dropped bellow.
because of this, I have two ul elements and lis which won't have there space I want to insert in second ul, which will be hidden and after click on something I will show this ul
Options:
Use in built angular filters. For example:
<ul id="teachers">
<li ng-repeat="person in people | filter: { status: 'teacher' }"></li>
</ul>
plnkr
Split the array in your controller. Both split arrays should still point to the original object (in the original array), so manipulation should be ok.
You can definitely create your own directive, but you will end up encapsulating one of the options above.
Better than write a directive, filter your array javascript with the built-in functions for array.
Example:
HTML
<div ng-controller="ClassroomController as classroom">
<ul id="teachers">
<li ng-repeat="teacher in classroom.teachers track by $index"></li>
</ul>
<ul id="students">
<li ng-repeat="student in classroom.students track by $index"></li>
</ul>
</div>
JAVASCRIPT
function Controller() {
var vm = this;
vm.data = [
{
name: 'Jack'
status: 'teacher'
},
{
name: 'Angelina'
status: 'teacher'
},
{
name: 'Maya'
status: 'student'
},
{
name: 'Kate'
status: 'teacher'
},
{
name: 'Margaret'
status: 'student'
}
];
vm.teachers = vm.data.filter(function(item){return item.status === 'teacher'});
vm.students = vm.data.filter(function(item){return item.status === 'student'});
}
I also think that filtering is the best as already answered. But according to your update you can do something like this in yuor directive controller:
$scope.getTemplateUrl = function() {
if (status == something) {
return '/partials/template1.html';
} else {
return '/partials/template2.html';
}
}
Then define your directive template as follows:
template: '<ng-include src="getTemplateUrl()"/>',
Of course status has to be defined before the directive is rendered.
directive('info', function()
{
return {
restrict : 'E',
template : '<ul> <li ng-repeat="l in list"><div ng-if="check(l)">{{l.name}}</div></li></ul></br><ul><li ng-repeat="l in list"><div ng-if="!check(l)">{{l.name}}</div></li></ul>',
controller : function($scope)
{
$scope.check = function(l)
{
if(l.status=="student")
return true;
else if(l.status=="teacher")
return false;
}
}
};
});
I'm new to Ember, and I'm following along with their Todo tutorial and making a basic app to create blog posts, adjust their code for my purposes. The app was working fine until I added an itemController to the template and a controller to handle the isCompleted event. Rather than showing the content, as it did before, it shows: <Posts.Post:ember257:1> which appears to be the model name rather than content. The Ember inspector says the model has the right attribute. It just doesn't display properly. Here's some code:
<script type="text/x-handlebars" data-template-name="posts">
<section id="postapp">
<section id="main">
<ul id="post-list">
// new code added
{{#each itemController="post"}}
<li {{bind-attr class="isCompleted:completed"}}>
{{input type="checkbox" checked=isCompleted class="toggle"}}
<label>{{title}}</label>
<p>{{content}}</p>
</li>
{{/each}}
</ul>
</section>
</section>
</script>
And the relevant JavaScript (see the bottom at PostController to see the only change after the code worked):
Posts.Post = DS.Model.extend({
title: DS.attr('string'),
content: DS.attr('string'),
isCompleted: DS.attr('boolean')
});
Posts.Post.FIXTURES = [
{
id: 1,
title: "JavaScript: The Dark Side",
content: "Here is a bunch of information on the dark side of " +
"Javascript. Welcome to hell!"
},
{
id: 2,
title: "The glory of underscore",
content: "Here, we're going to talk about the many uses of the " +
"underscore library. Read on!"
},
{
id: 3,
title: "Objectifying Objects",
content: "Objects are confusing, eh? Let's play around with objects " +
"a bit to see how to really use them."
}
];
// This is the only code that changed before the app was functioning properly
Posts.PostController = Ember.ObjectController.extend({
isCompleted: function(key, value){
var model = this.get('model');
if (value === undefined) {
// property being used as a getter
return model.get('isCompleted');
} else {
// property being used as a setter
model.set('isCompleted', value);
model.save();
return value;
}
}.property('model.isCompleted')
});
Any insight as to why the right content isn't displayed would be greatly appreciated.
I just figured out the problem. content is a property all Ember controllers, so my variable name for the post content was creating some confusion when Ember was rendering the page. When I changed the variable name in my model and other places to post_content, content was rendering properly in the page.
// template
{{#each itemController="post"}}
<li {{bind-attr class="isCompleted:completed"}}>
{{input type="checkbox" checked=isCompleted class="toggle"}}
<label>{{title}}</label>
<p>{{post_content}}</p>
</li>
{{/each}}
//model
Posts.Post = DS.Model.extend({
title: DS.attr('string'),
post_content: DS.attr('string'),
isCompleted: DS.attr('boolean')
});
And problem solved.