Instantly share code, notes, and snippets.
Last active
December 21, 2015 13:12
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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