Skip to content

Instantly share code, notes, and snippets.

@JumpLink
Last active December 21, 2015 13:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JumpLink/cd2b4188a54ec3f4cc7a to your computer and use it in GitHub Desktop.
Save JumpLink/cd2b4188a54ec3f4cc7a to your computer and use it in GitHub Desktop.
Replace the orginal mdSidenav directive with a draggable Sidenav from this commit: https://github.com/angular/material/pull/6174
// take this sidenav and overwrite the original: https://github.com/angular/material/pull/6174
/* jshint ignore:start */
/**
* @ngdoc directive
* @name mdSidenav
* @module material.components.sidenav
* @restrict E
*
* @description
*
* A Sidenav component that can be opened and closed programatically.
*
* By default, upon opening it will slide out on top of the main content area.
*
* For keyboard and screen reader accessibility, focus is sent to the sidenav wrapper by default.
* It can be overridden with the `md-autofocus` directive on the child element you want focused.
*
* @usage
* <hljs lang="html">
* <div layout="row" ng-controller="MyController">
* <md-sidenav md-component-id="left" class="md-sidenav-left">
* Left Nav!
* </md-sidenav>
*
* <md-content>
* Center Content
* <md-button ng-click="openLeftMenu()">
* Open Left Menu
* </md-button>
* </md-content>
*
* <md-sidenav md-component-id="right"
* md-is-locked-open="$mdMedia('min-width: 333px')"
* class="md-sidenav-right">
* <form>
* <md-input-container>
* <label for="testInput">Test input</label>
* <input id="testInput" type="text"
* ng-model="data" md-autofocus>
* </md-input-container>
* </form>
* </md-sidenav>
* </div>
* </hljs>
*
* <hljs lang="js">
* var app = angular.module('myApp', ['ngMaterial']);
* app.controller('MyController', function($scope, $mdSidenav) {
* $scope.openLeftMenu = function() {
* $mdSidenav('left').toggle();
* };
* });
* </hljs>
*
* @param {expression=} md-is-open A model bound to whether the sidenav is opened.
* @param {string=} md-component-id componentId to use with $mdSidenav service.
* @param {boolean=} md-disable-drag Disables the abbility to drag the sidenav
* @param {expression=} md-is-locked-open When this expression evalutes to true,
* the sidenav 'locks open': it falls into the content's flow instead
* of appearing over it. This overrides the `is-open` attribute.
*
* The $mdMedia() service is exposed to the is-locked-open attribute, which
* can be given a media query or one of the `sm`, `gt-sm`, `md`, `gt-md`, `lg` or `gt-lg` presets.
* Examples:
*
* - `<md-sidenav md-is-locked-open="shouldLockOpen"></md-sidenav>`
* - `<md-sidenav md-is-locked-open="$mdMedia('min-width: 1000px')"></md-sidenav>`
* - `<md-sidenav md-is-locked-open="$mdMedia('sm')"></md-sidenav>` (locks open on small screens)
*/
function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $mdGesture, $parse, $log, $q, $document, $timeout) {
return {
restrict: 'E',
scope: {
isOpen: '=?mdIsOpen'
},
controller: '$mdSidenavController',
compile: function(element) {
element.addClass('md-closed');
element.attr('tabIndex', '-1');
return postLink;
}
};
/**
* Directive Post Link function...
*/
function postLink(scope, element, attr, sidenavCtrl) {
var lastParentOverFlow;
var triggeringElement = null;
var promise = $q.when(true);
var isLockedOpenParsed = $parse(attr.mdIsLockedOpen);
var isLocked = function() {
return isLockedOpenParsed(scope.$parent, {
$media: function(arg) {
$log.warn("$media is deprecated for is-locked-open. Use $mdMedia instead.");
return $mdMedia(arg);
},
$mdMedia: $mdMedia
});
};
var backdrop = $mdUtil.createBackdrop(scope, "md-sidenav-backdrop md-opaque ng-enter");
$mdTheming.inherit(backdrop, element);
element.on('$destroy', function() {
backdrop.remove();
sidenavCtrl.destroy();
});
scope.$on('$destroy', function(){
backdrop.remove()
});
scope.$watch(isLocked, updateIsLocked);
scope.$watch('isOpen', updateIsOpen);
// Enable dragging
if (!angular.isDefined(attr.mdDisableDrag) || !attr.mdDisableDrag) enableDragging();
// Publish special accessor for the Controller instance
sidenavCtrl.$toggleOpen = toggleOpen;
/**
* Toggle the DOM classes to indicate `locked`
* @param isLocked
*/
function updateIsLocked(isLocked, oldValue) {
scope.isLockedOpen = isLocked;
if (isLocked === oldValue) {
element.toggleClass('md-locked-open', !!isLocked);
} else {
$animate[isLocked ? 'addClass' : 'removeClass'](element, 'md-locked-open');
}
backdrop.toggleClass('md-locked-open', !!isLocked);
}
/**
* Toggle the SideNav view and attach/detach listeners
* @param isOpen
*/
function updateIsOpen(isOpen) {
// Support deprecated md-sidenav-focus attribute as fallback
var focusEl = $mdUtil.findFocusTarget(element) || $mdUtil.findFocusTarget(element,'[md-sidenav-focus]') || element;
var parent = element.parent();
parent[isOpen ? 'on' : 'off']('keydown', onKeyDown);
backdrop[isOpen ? 'on' : 'off']('click', close);
if ( isOpen ) {
// Capture upon opening..
triggeringElement = $document[0].activeElement;
}
disableParentScroll(isOpen);
return promise = $q.all([
isOpen ? $animate.enter(backdrop, parent) : $animate.leave(backdrop),
$animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed')
])
.then(function() {
// Perform focus when animations are ALL done...
if (scope.isOpen) {
focusEl && focusEl.focus();
}
});
}
/**
* Prevent parent scrolling (when the SideNav is open)
*/
function disableParentScroll(disabled) {
var parent = element.parent();
if ( disabled && !lastParentOverFlow ) {
lastParentOverFlow = parent.css('overflow');
parent.css('overflow', 'hidden');
} else if (angular.isDefined(lastParentOverFlow)) {
parent.css('overflow', lastParentOverFlow);
lastParentOverFlow = undefined;
}
}
function enableDragging() {
$mdGesture.register(element, 'drag', { horizontal: true });
element.on('$md.dragstart', onDragStart)
.on('$md.drag', onDrag)
.on('$md.dragend', onDragEnd);
var style = getComputedStyle(element[0]);
var sidenavWidth = parseInt(style.width);
var isRightSidenav = element.hasClass('md-sidenav-right');
var dragCancelled = false;
var dragPercentage;
var lastOpenState;
function onDragStart() {
if (element.hasClass('md-locked-open')) {
dragCancelled = true;
} else {
lastOpenState = scope.isOpen;
element.css($mdConstant.CSS.TRANSITION_DURATION, '0ms');
}
}
function onDrag(ev) {
if (dragCancelled) return;
// If the sidenav is aligned left, the sidenav is opened at zero x-axis
var startOffset = isRightSidenav ? 100 : 0;
dragPercentage = startOffset - Math.round((ev.pointer.distanceX / sidenavWidth) * 100);
if (dragPercentage > 100) dragPercentage = 100;
else if (dragPercentage < 0) dragPercentage = 0;
element.css($mdConstant.CSS.TRANSFORM, 'translate3d(-' + dragPercentage + '%,0,0)');
}
function onDragEnd() {
if (dragCancelled) {
dragCancelled = false;
return;
}
var remainingPercentage = dragPercentage > 50 ? 100 - dragPercentage : dragPercentage;
var animationTime = 4 * remainingPercentage;
var isOpen = dragPercentage > 50;
element.css($mdConstant.CSS.TRANSITION_DURATION, animationTime + "ms");
if (isOpen) {
element.css($mdConstant.CSS.TRANSFORM, 'translate3d(-100%,0,0)');
} else {
element.css($mdConstant.CSS.TRANSFORM, 'translate3d(-0%,0,0)');
}
$timeout(onAnimationDone, animationTime, true, (isRightSidenav ? isOpen : !isOpen));
}
function onAnimationDone(isOpen) {
scope.isOpen = isOpen;
element.css($mdConstant.CSS.TRANSFORM, '');
element.css($mdConstant.CSS.TRANSITION_DURATION, '');
if (isOpen && lastOpenState == false) {
$animate.enter(backdrop, element.parent());
} else if (!isOpen) {
$animate.leave(backdrop);
}
element[isOpen ? 'removeClass' : 'addClass']('md-closed')
}
}
/**
* Toggle the sideNav view and publish a promise to be resolved when
* the view animation finishes.
*
* @param isOpen
* @returns {*}
*/
function toggleOpen( isOpen ) {
if (scope.isOpen == isOpen ) {
return $q.when(true);
} else {
return $q(function(resolve){
// Toggle value to force an async `updateIsOpen()` to run
scope.isOpen = isOpen;
$mdUtil.nextTick(function() {
// When the current `updateIsOpen()` animation finishes
promise.then(function(result) {
if ( !scope.isOpen ) {
// reset focus to originating element (if available) upon close
triggeringElement && triggeringElement.focus();
triggeringElement = null;
}
resolve(result);
});
});
});
}
}
/**
* Auto-close sideNav when the `escape` key is pressed.
* @param evt
*/
function onKeyDown(ev) {
var isEscape = (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE);
return isEscape ? close(ev) : $q.when(true);
}
/**
* With backdrop `clicks` or `escape` key-press, immediately
* apply the CSS close transition... Then notify the controller
* to close() and perform its own actions.
*/
function close(ev) {
ev.preventDefault();
return sidenavCtrl.close();
}
}
}
/* jshint ignore:end */
// remove original mdSidenav: http://stackoverflow.com/questions/18421732/angularjs-how-to-override-directive-ngclick
angular.module('mdDraggableSidenav', ['ngMaterial']).config(function($provide){
$provide.decorator('mdSidenavDirective', ['$delegate', function($delegate) {
//$delegate is array of all ng-click directive
//in this case first one is angular buildin ng-click
//so we remove it.
$delegate.shift();
return $delegate;
}]);
});
/**
* Recreate mdSidenav
*/
angular.module('mdDraggableSidenav').directive('mdSidenav', SidenavDirective);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment