How to display menu items based on sessionStorage - javascript

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

Related

Vue Js binding 2 values to a tag href

I'm beginner on Vue js. I'm facing an issue where I have to update two values while rendering.
<ul class="category-list">
<li v-for="category in categories">
<a v-bind:href="location/category.slug/all" v-text="category.name"></a>
</li>
</ul>
My javascript file
new Vue({
el : '#example',
data : {
location: 'new-york',
categories: [],
},
mounted() {
axios.get('parent-categories').then((response) => {
this.categories = response.data;
});
},
});
Ajax Response
[{"name":"Default Category","slug":"default-category"},{"name":"Main Category","slug":"main-category"}]
Here I wanted to build url structure like location/category-slug/all ie,
http://myapp.com/new-york/default-category/all
How to achieve this?
Found out how to handle this situation after a long struggle. All I have to do is to create a method that generates URL for me.
JS
new Vue({
el : '#example',
data : {
location: 'new-york',
categories: [],
},
methods: {
slug: function (category) {
return this.location+'/'+category.slug+'/all';
}
},
mounted() {
axios.get('parent-categories').then((response) => {
this.categories = response.data;
});
},
});
Html
<div id="example">
<h3>Services</h3>
<ul class="category-list">
<li v-for="category in categories">
<a :href="slug(category)" v-text="category.name"></a>
</li>
</ul>
</div>

Toggle class on click in VueJS

I am learning Vuejs and I am constantly finding simple things like removing a class to be a pain. Please tell me how I can allow the .active class to be added or removed based on clicks between the 3 links.
In the example below the adding is working fine but it adds .active to all the links, and does not remove when clicked on again.
<div id="app">
<h2>App</h2>
<ul>
<li>Link text</li>
<li>Link text</li>
<li>Link text</li>
</ul>
</div>
JS
var app = new Vue({
el: '#app',
data: {
isActive: false
},
methods: {
activeLink() {
// the line below did not work
// document.getElementsByClassName("active").isActive = false,
this.isActive = true
}
}
})
JSfiddle is here, https://jsfiddle.net/s9r4q0gc/
You need to catch the event handler in the method and using that you can refer to the callee i.e. anchor object in this case.
See the fiddle : https://jsfiddle.net/s9r4q0gc/2/
activeLink(event) {
if(event.target.className == "noclass")
{
event.target.className = "link active";
}
else
{
event.target.className = "noclass";
}
}
UPDATED:
May be try this fiddle and see if it is hitting the bulls eye : https://jsfiddle.net/s9r4q0gc/4/
var app = new Vue({
el: '#app',
data: {
isActive: false
},
methods: {
activeLink(event) {
var checkboxes = document.getElementsByClassName ("noclass");
for (var i=0; i<checkboxes.length; i++) {
checkboxes[i].className = "link active";
//checkboxes[i].className = "link";
}
event.target.className = "noclass";
}
}
})
What you can do is use a data property to hold the currently active link. Then with that you'll be able to have any given link test if it is the active link and if the .active class should be applied.
Then it's just a matter of setting that property's new value when a link is clicked. If the same link that's currently active is clicked, then the property is cleared, thus removing the .active class from all links. Otherwise, the class is added to the link that was clicked.
This is a CodePen demonstrating what I mean in action, but your markup could look something like this:
<li><a href="#" class="link" :class="{ active: activeId == 'link-1' }"
#click.prevent="activeLink('link-1')">Link text</a></li>
<li><a href="#" class="link" :class="{ active: activeId == 'link-2' }"
#click.prevent="activeLink('link-2')">Link text</a></li>
<li><a href="#" class="link" :class="{ active: activeId == 'link-3' }"
#click.prevent="activeLink('link-3')">Link text</a></li>
and your JS could look something like this:
data: {
activeId: null
},
methods: {
activeLink(linkIdent) {
this.activeId = this.activeId === linkIdent ? null : linkIdent
}
}
It's obviously not as clean a solution as it could be, but I am sticking to your requirement that the solution fit the markup you provided.
Here is an alternative solution that may modify your code more than you want. I thought this might be useful since you are just learning and may be interested in an alternative. I started by declaring your links as an array of objects in vue so that we can assign an active attribute to each of them. Then we can just toggle the active value inline or use the toggleActive function (not currently in use.. just there for illustration if you prefer to use functions over inline logic).
Html:
<div id="app">
<h2>App</h2>
<ul>
<li v-for="l in links">
{{l.text}}
</li>
</ul>
</div>
Javascript:
var app = new Vue({
el: '#app',
data: {
links: [{
text: "Link text",
active: false
},{
text: "Second text",
active: false
},{
text: "Third text",
active: false
}]
},
methods: {
//To use this function make #click in html to:
//#click="toggleActive(l)"
toggleActive(x) {
x.active = !x.active;
}
}
})
https://jsfiddle.net/yrbt90v9/
Use V-for and an array of items so you have no need to statically type the links. This allows for the dynamic functionality you are looking for.
var app = new Vue({
el: '#app',
data: {
links: [
{text: "Link Text", active: false},
{text: "Link Text", active: false},
{text: "Link Text", active: false}
]
},
methods: {
activate(link) {
link.active = !link.active
}
}
})
.link{
text-decoration: none;
color: #555;
}
.active{
text-decoration: underline;
color: #42b983;
}
<div id="app">
<h2>App</h2>
<ul>
<li v-for="link in links">Link text</li>
</ul>
</div>

Angular: push data into nested array

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);
};

Nested ng-repeat undefined errors in AngularJS

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 "#").

angular js nested controllers list/item

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++;
};
}]);

Categories

Resources