how to create a collapsable/expandable open-locked sidenav with angular material - angularjs

I want to have a sidenav with angular material which has 2 states: collapsed (only icons of the items are shown) and expanded (labes + icons shown). The example behaviour is shown at the RDash Dashboard, which is unfortunetally done with bootstrap.
Since the default sidenav of angular material does not provide that feature, I wanted to do it myself.
I have 2 ideas on how to do it:
1) using 2 different side-navs: one for collapsed, one for expanded. Then switching open-locked between them or just hiding/showing always one at a time.
2) using only 1 sidenav. somehow programmatically change the width, and the items of the sidenav and keeping it open-locked.
My favourite approach would be 2, but I want to know if there are any better ways to achieve that kind of sidenav with angular material?

I think approach 2 is the best - CodePen
Markup
<div ng-controller="AppCtrl" layout="column" style="height:500px;" ng-cloak="" class="sidenavdemoBasicUsage" ng-app="MyApp">
<section layout="row" flex="">
<md-sidenav class="md-sidenav-left" md-component-id="left" md-whiteframe="4" id="leftSideNav">
<md-toolbar class="md-theme-indigo" layout="row">
<h1 class="md-toolbar-tools">Sidenav Left</h1>
<span flex></span>
<md-button ng-click="toggleExpand()">{{toggleExpandButtonLabel}}</md-button>
</md-toolbar>
<md-content layout-padding="">
<md-button ng-click="close()" class="md-primary">
Close Sidenav Left
</md-button>
</md-content>
</md-sidenav>
<md-content flex="" layout-padding="">
<div layout="column" layout-align="top center">
<div>
<md-button ng-click="toggleLeft()" class="md-primary">
Toggle left
</md-button>
</div>
</div>
</md-content>
</section>
</div>
JS
angular
.module('MyApp',['ngMaterial', 'ngMessages', 'material.svgAssetsCache'])
.controller('AppCtrl', function ($scope, $timeout, $mdSidenav, $log, $element) {
$scope.toggleLeft = buildToggler('left');
$scope.toggleExpandButtonLabel = "Expand";
var sideNav = angular.element($element[0].querySelector('#leftSideNav'));
$scope.toggleExpand = function () {
if ($scope.toggleExpandButtonLabel == "Expand") {
sideNav.css("width", "500px")
}
else {
sideNav.css("width", "320px")
}
$scope.toggleExpandButtonLabel = ($scope.toggleExpandButtonLabel == "Expand") ? "Collapse" : "Expand";
}
$scope.close = function () {
$mdSidenav('left').close();
$scope.toggleExpandButtonLabel = "Expand";
sideNav.css("width", "320px")
};
function buildToggler(navID) {
return function() {
$mdSidenav(navID)
.toggle()
.then(function () {
$log.debug("toggle " + navID + " is done");
});
}
}
});

Related

How to use ui-view on RenderBody() in angularjs

I have this mark-up html with the #RenderBody():
<div layout="column" class="relative" layout-fill role="main">
<md-toolbar class="md-hue-2">
<div class="md-toolbar-tools">
<md-button class="md-icon-button" aria-label="Settings" ng-disabled="true">
<md-icon md-svg-icon="navigation:menu"></md-icon>
</md-button>
<md-button ui-sref="dashboard">Dashboard</md-button>
<md-button ui-sref="home">Home</md-button>
</div>
</md-toolbar>
<md-content layout-fill md-padding>
<div id="main-content" ui-view="">
#RenderBody()
</div>
</md-content>
</div>
and the js here:
app.config(function ($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise('/Home/Index');
$stateProvider
.state('home', {
url: '/Home/Index',
templateUrl: '/Home/Index' // corresponds to an MVC partial route
})
.state('dashboard', {
url: 'Home/Dashboard',
templateUrl: '/Home/Dashboard' // corresponds to an MVC partial route
})
});
The result is its own view inside a view. I cant figure it out.
Here is the output:
I see this problem in few of my apps.
Your layout is loading twice.
All you need to do is "make your layout null" while rendering from uiroute as below.
Add this on top of your Index.cshtml page
#{
Layout = null;
}
Or load ui-view out side the content div
<md-content layout-fill md-padding>
<div id="main-content">
#RenderBody()
</div>
</md-content>
<ui-view></ui-view>
Or you could use viewContentLoaded that is triggered after DOM is loaded when using uirouter in angular as below:
//trigger events after DOM content load
$scope.$watch("$viewContentLoaded",
function() {
if (document.getElementsByTagName("header").length === 2) {
document.getElementsByTagName("header")[1].remove();
}
if (document.getElementsByTagName("footer").length === 2) {
document.getElementsByTagName("footer")[1].remove();
}
});

ng-click not responding when HTML added by JavaScript

ng-click is not working as it should. I tried looking at documentation and tutorials but couldn't find the reason.
I have a JavaScript variable storing html template. which by using
document.getElementById('someId').innerHTML = emptypage;
where emptypage is a JavaScript variable, is implemented inside a div with id=someId.
My Code:
var emptypage=` <md-toolbar `+ this.createID() +` class="md-accent
soopsObject" ng-app="vdApp">
<span class="example-spacer ng-controller="vdNav">
<md-sidenav class="md-sidenav-left" md-component-id="left" md-
disable-backdrop md-whiteframe="4">
<md-content layout-margin>
<p>
About
</p>
<p>
Contact
</p>
<md-button ng-click="toggleLeft()" class="md-accent">
Close
</md-button>
</md-content>
</md-sidenav>
<div>
<md-button ng-click="toggleLeft()">
<md-icon>menu</md-icon> Menu
</md-button>
</div>
</span>
</md-toolbar>`;
My Controller:
var nav=angular.module("vdApp", []);
nav.controller("vdNav", function($scope, $mdSidenav) {
alert("inside leftToggle");
$scope.toggleLeft = buildToggler('left');
function buildToggler(componentId){
return function(){
$mdSidenav(componentId).toggle();
};
}
});
Even alert isn't working.
What am I doing wrong? Any help or suggestion is appreciated.

How to use md-nav-bar correctly?

I'm using angular material md-nav-bar and i have those problems.
1) I want to add it to the middle of the page.
2) I want to know how to add pages to the nav-items
I have attached an image to show how it looks now
Is there any possible way to do it?
Any help highly appreciate. Thanks,
Here you go - CodePen
To centre the md-nav-bar use layout
To add a md-nav-item use ng-repeat with an array and add to the array
Markup
<div ng-controller="AppCtrl" ng-app="MyApp" layout-fill layout="column" ng-cloak>
<div layout="row" layout-align="center" flex="70">
<md-content class="md-padding">
<md-nav-bar md-selected-nav-item="currentNavItem" nav-bar-aria-label="navigation links">
<md-nav-item ng-repeat="item in navItems" md-nav-click="goto('{{item.value}}')" name="{{item.value}}">{{item.label}}</md-nav-item>
</md-nav-bar>
<span>{{currentNavItem}}</span>
</md-content>
</div>
<div>
<md-button class="md-raised md-primary" ng-click="addItem()">Add Item</md-button>
</div>
</div>
JS
(function() {
'use strict';
angular.module('MyApp',['ngMaterial'])
.controller('AppCtrl', AppCtrl);
function AppCtrl($scope, $element, $compile) {
$scope.currentNavItem = 'page1';
$scope.navItems = [
{value: "page1", label: "Page One"},
{value: "page2", label: "Page Two"},
{value: "page3", label: "Page Three"},
];
$scope.addItem = function () {
$scope.navItems.push(
{
value: "page" + ($scope.navItems.length + 1),
label: "Page" + ($scope.navItems.length + 1)
}
);
}
}
})();
To get the navbar elements to the center of the page you can add the <span flex></span> tags before the start of the first nav-item and the end of the last nav-item.
For your reference:
<md-nav-bar>
<span flex></span>
<md-nav-item>
</md-nav-item>
<md-nav-item>
</md-nav-item>
<span flex></span>
</md-nav-bar>

Angular: close ng-sidenav after a click on a element

Using angular & material, I have a sidenav on the left used to provide user's function (login, signup...).
Working fine, but I want the bar to be closed automatically once the user clicks on a menu. Currently, the bar stays open.
The sidebar code:
<md-sidenav md-component-id="user_sidenav" class="md-sidenav-left" flex>
<md-menu-content flex>
<md-menu-item>
<md-button ui-sref="app.user-state()">
<span>
User
</span>
</md-button>
</md-menu-item>
<md-menu-item>
<md-button ui-sref="vlg.user-other-state()">
<span>
Other
</span>
</md-button>
</md-menu-item>
...
Any idea?
What I have done is create a function in the controller for closing the sidenav and then on clicking of an element calling the function.
app.controller('AppController', ['$scope', '$mdSidenav',
function ($scope, $mdSidenav) {
$scope.close = function () {
$mdSidenav('left').close();
};
}]);
and then in html
<div ng-controller="AppController">
<!-- Add in code for sidenav-->
<md-button ng-href="#/path" ng-click="close()"></md-button>
</div>
You could also just add the below code to a function if your elements already have a ng-click attached.
.then(function () {
$mdSidenav('left').close();
});

Angular Material - Dynamically add tab and change to that tab

Currently working on a chat app here https://playwithfire.firebaseapp.com/ and whenever a user adds a new room I want the new room tab to be entered. Currently you can add a room but need to click it afterwards to enter that room and display its content.
I tried changing the attribute md-selected="selectedIndex" but that makes no tab active so no content appears.
Is it possible to do what I'm asking for? What I've got so far:
index.html
<div layout="column" ng-controller="RoomController">
<!-- Tabs Container -->
<md-tabs md-stretch-tabs md-selected="selectedIndex">
<!-- Individual Tab -->
<md-tab ng-repeat="room in roomList"
label="{{room.roomName}}">
<div ng-controller="ChatController">
<!-- Display messages -->
<md-list>
<md-item ng-repeat="msg in messages">
<md-item-content>
<div class="md-tile-content">
<div class="bubble">
<strong>{{msg.from}}</strong><br>
{{msg.body}}
</div>
</div>
</md-item-content>
</md-item>
</md-list><!--/DisplayMessages-->
<!-- Chat controls -->
<div layout="row" layout-margin layout-wrap>
<div flex="33">
<!-- Assign username -->
<label for="nameInput">Username</label>
<input ng-model="name" type="text" id="nameInput" placeholder="Enter a username...">
</div>
<div flex="95">
<!-- Post a message -->
<label>Message</label>
<textarea class="form-control"
ng-model="msg"
ng-keydown="addMessage($event)"
id="messageInput"
placeholder="Type a message...">
</textarea>
</div>
<div layout="row" layout-sm="column" layout-margin layout-fill layout-align="start end">
<!-- Click to send message -->
<div flex>
<md-button class="md-raised md-primary pull-left" ng-click="sendMessage()">Send</md-button>
</div>
<!-- Modal to add or join room -->
<div flex ng-controller="ModalController">
<md-button class="md-raised md-primary pull-left" ng-click="open()">Add or Join Room</md-button>
</div>
<!-- Opens helper -->
<div flex ng-controller="HelpController">
<md-button class="pull-right" ng-click="open()" ng-href="">Need help?</md-button>
</div>
</div>
</div><!--/ChatController-->
</md-tab>
</md-tabs><!--/tabs container-->
</div><!--/RoomController-->
room.js
angular.module('myApp')
.controller('RoomController', function($scope, ShareFactory) {
$scope.roomList = ShareFactory.roomList;
// use this to default to index 0 in roomList
$scope.selectedIndex = 0;
$scope.$on('RoomChange', function(event, data) {
$scope.selectedIndex = data;
console.log('Heard the change!');
console.log('The data is: ' + data);
});
});
modal.js
angular.module('myApp')
.controller('ModalController', function($rootScope, $scope, $modal, ChatFactory, ShareFactory) {
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'views/modal.html',
controller: 'ModalInstanceController'
});
modalInstance.result.then(function (name) {
var found = false;
var length = ShareFactory.roomList.length;
var index = 0;
for(var i = 0; i < length; ++i) {
if(ShareFactory.roomList[i].roomName === name) {
found = true;
index = i;
console.log('index ' + index);
}
}
if(!found) {
ShareFactory.roomList.push({ roomName : name});
index = ShareFactory.roomList.length - 1;
}
else {
// don't care about disabled, not a feature i want to use
//ShareFactory.roomList[index].disabled = false;
}
// Broadcast event to all children of rootScope
// namely want RoomController to listen for this change
$rootScope.$broadcast('RoomChange', index);
}, function () {
console.log('cancel');
});
};
});
Ya know, this definitely came up in a Github issue a while ago, but it may have been kicked to the backlog due to other high priority issues.
I just added this feature to master:
https://github.com/angular/material/commit/8285e2d0bb6efbc72e311ee85b619cbbe8437072
And it should be viewable shortly on our site in the second demo:
https://material.angularjs.org/HEAD/#/demo/material.components.tabs
In the meantime, you should be able to solve this by adding the following code:
if(!found) {
ShareFactory.roomList.push({ roomName : name});
index = ShareFactory.roomList.length - 1;
$timeout(function () {
ShareFactory.selectedIndex = index;
});
}
The $timeout is necessary because you must wait until after the render is finished before you can update the selected index to the new one - otherwise, it will think that the valid is out of range.
Hope this helps!

Resources