I'm having an issue with a very simple nested ng-repeat with AngularJS 1.2.0-rc3.
I have a JSON object inside my controller representing a menu structure, like this:
$scope.menus = [
{
title: "menu1",
action: "#",
menus: [
{
title: "submenu1_1",
action: "stuff"
},
{
title: "submenu1_2",
action: "moreStuff"
}
]
},
{
title: "menu2",
action: "#",
menus: [
{
title: "submenu2_1",
action: "awesomeStuff"
},
{
title: "submenu2_2",
action: "moreAwesomeStuff"
}
]
}
]
My view uses this information to create a Bootstrap nav menu structure, like this:
<div class="navbar">
<ul class="nav navbar-nav">
<li ng-repeat="menu in menus" ng-class="{dropdown: menu.menus}">
{{menu.title}}
<ul ng-if="menu.menus" class="dropdown-menu">
<li ng-repeat="submenu in menu.menus">
{{submenu.title}}
</li>
</ul>
</li>
</ul>
</div>
This should work fine, and it does... 50% of the time. Sometimes, almost randomly, the menu won't show up at all and I'll get this error in the console:
Error: 'undefined' is not an object (evaluating 'node.childNodes')
Is there something wrong with my markup? The error appears sometimes on page load, and always when I click on a submenu (in my code the actions are replaced simply by an "#").
Related
I'm kind of lost in which is the best way to proceed. After fetching the user data from an API, I store the user data in the sessionStorage. How can I display menu items based on roles (ie- Admin, Representative, Guest).
I have a basic Bootstrap Sidebar that looks like below.
<ul class="sidebar-nav">
<li><a>Home</a>
<ul class="sidebar-dropdown">
<li>Default</li>
<li>Reports</li>
</ul>
</li>
<li><a>Contact</a>
<ul class="sidebar-dropdown">
<li>Admin Contact</li>
<li>Contact 1</li>
</ul>
</li>
</ul>
Should I add classes to menu items based on roles to my menu items and with a JavaScript function that runs on load display or hide menu items??
Any guidance would be greatly appreciated.
JSFIDDLE
What you want is dynamically render a list based on data. Receiving data from session storage is not an issue because it can be done by
JSON.parse(sessionStorage.getItem('data'));
your issue is to render them. Try to use hardcoded data first to see if you are able to render what you want and then just plug in data from the storage. For an example
https://codesandbox.io/s/elated-fast-my3ud?file=/src/index.js
const routes = [
{
href: "/home",
title: "Home",
children: [
{
href: "/home/defaults",
title: "Defaults"
},
{
href: "/home/reports",
title: "Reports"
}
]
},
{
href: "/contact",
title: "Contact",
children: [
{
href: "/contact/admin-contact",
title: "Admin Contanct"
},
{
href: "/contact/contact-1",
title: "Contact 1"
}
]
}
];
function renderSingleRoute(route) {
const children = route.children ? renderRoutes(route.children) : "";
return `
<li>
${route.title}
${children}
</li>
`;
}
function renderRoutes(routes) {
return `
<ul>
${routes.map((route) => renderRoute(route)).join("")}
</ul>
`;
}
document.getElementById("app").innerHTML = renderRoutes(routes);
So the idea here is to build your routes however you want and then pass it to render function
I trying to add sub menu by clicking the button but it doesn't work. My data looks like:
$scope.menuItems = [
{ name: 'Menu1',
children: [
{ name: 'Sub1' },
{ name: 'Sub2'} ]
},
{ name: 'Menu1',
children: [
{ name: 'Sub1' } ]
}
];
$scope.addSubItem = function() {
$scope.menuItems.children.push({
name: 'Test Sub Item'
});
};
http://plnkr.co/edit/2R5kpY2iGhiE6FEy65Ji?p=preview
Plunker Solution here
You need to modify the submenu button markup to pass the reference to the menu item that the button resides in:
<ul class="sub-menu">
<li ng-repeat="menuItem in menuItem.children" ng-include="'sub-tree-renderer.html'"></li>
<button class="btn btn-warning" style="margin-top: 10px;" ng-click="addSubItem(menuItem)">Add Sub Menu Item</button>
</ul>
and then in your addSubItem function operate directly on the item like this:
$scope.addSubItem = function(item) {
item.children.push({
name: 'Sub' + (item.children.length + 1)
});
};
Also make sure that every time you create new item the children array is defined as empty array instead of being undefined:
$scope.addItem = function() {
$scope.menuItems.push({
name: 'Test Menu Item',
children: []
});
};
I would recommend using data value object that you can construct a new item with instead of using hand typed object literals as if you use them in many places it is easy to make mistake and cause bugs which happens only in some places and are time consuming to find.
You need to specify the index of the menuItems array that you wish to add the sub menu to.
This would add a sub menu to the first menu item:
$scope.menuItems[0].children.push({
name: 'Test Sub Item'
});
Also, if this is of n depth and can vary depending on the data that is driving the menu, you could build a controller for the menu item and have it recursively add a child/show in your template based on the node you are clicking on. Then you don't need to explicitly worry about indexes.
firstly you should determine sub menu by it index. here you can use $index for this. when you add new Item just add item name. when you need add children array also.
<ul class="sub-menu">
<li ng-repeat="menuItem in menuItem.children" ng-include="'sub-tree-renderer.html'"></li>
<button class="btn btn-warning" style="margin-top: 10px;" ng-click="addSubItem($index)">Add Sub Menu Item</button>
</ul>
and in controller
$scope.addSubItem = function(index) {
$scope.menuItems[index].children.push({
name: 'Test Sub Item'
});
};
$scope.addItem = function() {
var item = {
name: 'Test Menu Item',
children: []
};
$scope.menuItems.push(item);
};
I am trying to use Iron Router to call a page and filter results by the route. Effectively, the application allows you to create items. Items include an array that can have 0 to many tags:
Item: {
_id: <assigned by application>,
itemTags: []
}
The dropdown list on the navbar collects all tags from the items, does some cleaning, and then drops them into the dropdown menu:
// HTML
<template name="header">
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<ul class="nav navbar-nav">
<li class="dropdown">
Select List to View <span class="caret"></span>
<ul class="dropdown-menu" role="menu">
<li>All List Items</li>
<li class="divider"></li>
{{#each uniqueTags}}
<li>{{this}}</li>
{{/each}}
</ul>
</li>
</ul>
</div>
</div>
</nav>
</template>
// JS
Template.header.helpers({
uniqueTags: function() {
var allItems = Items.find({checked: false});
var uniqueTags = allItems.map((function(allItems) {return allItems.itemTags;}));
uniqueTags = _.flatten(uniqueTags);
uniqueTags = _.compact(uniqueTags);
uniqueTags = _.uniq(uniqueTags);
uniqueTags = _.sortBy(uniqueTags, function (name) {return name;});
return uniqueTags;
}
});
The router then sends you to "/list/". Everything up to this point works great. But then it falls apart. My problem is twofold:
1) How do I appropriately grab the data context of just the items that have the tag listed somewhere in the array?
2) How do I display those items on the "list" page? I have zero idea what should be in list.js to handle the returned data context
My current code is below, but it's hacked up and clearly not working :)
// JS - router.js
Router.route('/list/:tagName', {
name: 'list',
data: function() {
return Items.find({$and: [
{checked: false}, {listItem: true}, {itemTags: {$in: ['this.params.tagName']}}
]});
}
});
// HTML - list.html
<template name="list">
<h3><span class="label label-default view-list">List</span></h3>
{{#each listItems}}
{{> item}}
{{/each}}
</template>
// JS - list.js
Template.list.helpers({
listItems: function() {
<somehow use the returned data context>
}
});
Thanks for your help!
This would work
Router.route('/list/:tagName', {
name: 'list',
data: function() {
return { 'listItems' : Items.find({$and: [
{checked: false}, {listItem: true}, {itemTags: {$in: [this.params.tagName]}}
]}) };
}
});
// HTML - list.html
<template name="list">
<h3><span class="label label-default view-list">List</span></h3>
{{#each listItems}}
{{> item}}
{{/each}}
</template>
// You have to write a item template
// JS - list.js
Template.list.helpers({
//listItems: function() {
// this helper is not required
// you could access the data by this.data.listItems
//<somehow use the returned data context>
//}
});
I have a a collection of panels each with a simple list of items that needs to either be sorted by 'computedLoad' or 'Name'. I have the following objects and methods to accomplish this generically over all of the panels (only showing one panel among many).
scope.orderBy = {
name: {
displayName: "Name",
sort: "Name",
reverse: false
},
load: {
displayName: "Load",
sort: "-computedLoad",
reverse:false
}
};
scope.selectOrder = function (panel, order) {
timeout(function () {
panel.activeOrder = order;
});
};
scope.panels = {
methods: {
activeOrder: scope.orderBy.name
}
};
I have the following html:
<div>
<ul class="nav nav-pills">
<li class="list-label"><a>Order By:</a></li>
<li ng-repeat="order in orderBy">{{order.displayName}}</li>
</ul>
<ul class="nav nav-pills nav-stacked">
<li ng-repeat="item in suite.Methods | orderBy:panel.methods.activeOrder.sort"><span class="text">{{item.Name}}</span></li>
</ul>
</div>
The selectOrder method doesn't seem to work. Any ideas? Am I missing something?
Here is an example: http://jsbin.com/puxoxi/1/
Setting panel.activeOrder happens asynchronously, so it is outside of angulars so called "digest cycle".
To make angular re-evaluate your scope, use the $apply function:
It could look like this:
scope.$apply(function() {
panel.activeOrder = order;
});
I am new to angular and wanted advice on the best route to achieve something like this. This jsFiddle doesn't work but here is the idea.
I want tabs along the top with items available for selection. When you select the item, the data is populated below.
I wanted to have a ListController and an ItemController, so i can separate out the methods that act on the list vs that act on the item; since i wanted the items to be updatable directly. I am getting all the data on the page load, so i don't want to load each tab dynamically.
How can i do this and/or how can i fix the fiddle or new fiddle?
jsFiddle
plunker
<div ng-app="myApp">
<div ng-controller="ListController">
<ul class="nav nav-pills">
<li ng-repeat="artist in list">
<a show-tab="" ng-href="" ng-click="select(artist)">{{$index}} - {{artist.name}}</a>
</li>
</ul>
<div ng-controller="ItemController">
<p>{{name}} - {{selected.name}}</p>
<span>{{totalSongs}}</span>
<span>{{selected.songs.length}}</span>
<ul>
<li ng-repeat="song in selected.songs" ng-controller="ItemController">{{song}} - {{totalSongs}}</li>
</ul>
</div>
</div>
</div>
I would really like to keep the controllers separate and logic separate.
I created some functionality in the ItemController to illustrate how you could act on them separately:
http://jsfiddle.net/jdstein1/LbAcz/
Added some data to the list controller:
$scope.list = [{
name: "Beatles",
songs: [{
title: "Yellow Submarine",
time: "424"
}, {
title: "Helter Skelter",
time: "343"
}, {
title: "Lucy in the Sky with Diamonds",
time: "254"
}]
}, {
name: "Rolling Stones",
songs: [{
title: "Ruby Tuesday",
time: "327"
}, {
title: "Satisfaction",
time: "431"
}]
}];
And fleshed out the item controller:
app.controller('ItemController', ['$scope', function ($scope) {
$scope.selectItem = function (song) {
$scope.song.time++;
};
}]);