2020-05-19 11:43:42 +03:00

462 lines
12 KiB
JavaScript

(function(){"use strict";angular
.module('material.components.expansionPanels')
.directive('mdExpansionPanel', expansionPanelDirective);
var ANIMATION_TIME = 180; //ms
/**
* @ngdoc directive
* @name mdExpansionPanel
* @module material.components.expansionPanels
*
* @restrict E
*
* @description
* `mdExpansionPanel` is the main container for panels
*
* @param {string=} md-component-id - add an id if you want to acces the panel via the `$mdExpansionPanel` service
**/
function expansionPanelDirective() {
var directive = {
restrict: 'E',
require: ['mdExpansionPanel', '?^^mdExpansionPanelGroup'],
scope: true,
compile: compile,
controller: ['$scope', '$element', '$attrs', '$window', '$$rAF', '$mdConstant', '$mdUtil', '$mdComponentRegistry', '$timeout', '$q', '$animate', '$parse', controller]
};
return directive;
function compile(tElement, tAttrs) {
var INVALID_PREFIX = 'Invalid HTML for md-expansion-panel: ';
tElement.attr('tabindex', tAttrs.tabindex || '0');
if (tElement[0].querySelector('md-expansion-panel-collapsed') === null) {
throw Error(INVALID_PREFIX + 'Expected a child element of `md-epxansion-panel-collapsed`');
}
if (tElement[0].querySelector('md-expansion-panel-expanded') === null) {
throw Error(INVALID_PREFIX + 'Expected a child element of `md-epxansion-panel-expanded`');
}
return function postLink(scope, element, attrs, ctrls) {
var epxansionPanelCtrl = ctrls[0];
var epxansionPanelGroupCtrl = ctrls[1];
epxansionPanelCtrl.epxansionPanelGroupCtrl = epxansionPanelGroupCtrl || undefined;
epxansionPanelCtrl.init();
};
}
function controller($scope, $element, $attrs, $window, $$rAF, $mdConstant, $mdUtil, $mdComponentRegistry, $timeout, $q, $animate, $parse) {
/* jshint validthis: true */
var vm = this;
var collapsedCtrl;
var expandedCtrl;
var headerCtrl;
var footerCtrl;
var deregister;
var scrollContainer;
var stickyContainer;
var topKiller;
var resizeKiller;
var onRemoveCallback;
var transformParent;
var backdrop;
var inited = false;
var registerOnInit = false;
var _isOpen = false;
var isDisabled = false;
var debouncedUpdateScroll = $$rAF.throttle(updateScroll);
var debouncedUpdateResize = $$rAF.throttle(updateResize);
vm.registerCollapsed = function (ctrl) { collapsedCtrl = ctrl; };
vm.registerExpanded = function (ctrl) { expandedCtrl = ctrl; };
vm.registerHeader = function (ctrl) { headerCtrl = ctrl; };
vm.registerFooter = function (ctrl) { footerCtrl = ctrl; };
if ($attrs.mdComponentId === undefined) {
$attrs.$set('mdComponentId', '_expansion_panel_id_' + $mdUtil.nextUid());
registerPanel();
} else {
$attrs.$observe('mdComponentId', function() {
registerPanel();
});
}
vm.$element = $element;
vm.expand = expand;
vm.collapse = collapse;
vm.remove = remove;
vm.destroy = destroy;
vm.onRemove = onRemove;
vm.init = init;
if ($attrs.ngDisabled !== undefined) {
$scope.$watch($attrs.ngDisabled, function(value) {
isDisabled = value;
$element.attr('tabindex', isDisabled ? -1 : 0);
});
} else if ($attrs.disabled !== undefined) {
isDisabled = ($attrs.disabled !== undefined && $attrs.disabled !== 'false' && $attrs.disabled !== false);
$element.attr('tabindex', isDisabled ? -1 : 0);
}
$element
.on('focus', function (ev) {
$element.on('keydown', handleKeypress);
})
.on('blur', function (ev) {
$element.off('keydown', handleKeypress);
});
function handleKeypress(ev) {
var keyCodes = $mdConstant.KEY_CODE;
switch (ev.keyCode) {
case keyCodes.ENTER:
expand();
break;
case keyCodes.ESCAPE:
collapse();
break;
}
}
$scope.$panel = {
collapse: collapse,
expand: expand,
remove: remove,
isOpen: isOpen
};
$scope.$on('$destroy', function () {
removeClickCatcher();
// remove component from registry
if (typeof deregister === 'function') {
deregister();
deregister = undefined;
}
killEvents();
});
function init() {
inited = true;
if (registerOnInit === true) {
registerPanel();
}
}
function registerPanel() {
if (inited === false) {
registerOnInit = true;
return;
}
// deregister if component was already registered
if (typeof deregister === 'function') {
deregister();
deregister = undefined;
}
// remove component from group ctrl if component was already added
if (vm.componentId && vm.epxansionPanelGroupCtrl) {
vm.epxansionPanelGroupCtrl.removePanel(vm.componentId);
}
// if componentId was removed then set one
if ($attrs.mdComponentId === undefined) {
$attrs.$set('mdComponentId', '_expansion_panel_id_' + $mdUtil.nextUid());
}
vm.componentId = $attrs.mdComponentId;
deregister = $mdComponentRegistry.register({
expand: expand,
collapse: collapse,
remove: remove,
onRemove: onRemove,
isOpen: isOpen,
addClickCatcher: addClickCatcher,
removeClickCatcher: removeClickCatcher,
componentId: $attrs.mdComponentId
}, $attrs.mdComponentId);
if (vm.epxansionPanelGroupCtrl) {
vm.epxansionPanelGroupCtrl.addPanel(vm.componentId, {
expand: expand,
collapse: collapse,
remove: remove,
onRemove: onRemove,
destroy: destroy,
isOpen: isOpen
});
}
}
function isOpen() {
return _isOpen;
}
function expand(options) {
if (_isOpen === true || isDisabled === true) { return; }
_isOpen = true;
options = options || {};
var deferred = $q.defer();
if (vm.epxansionPanelGroupCtrl) {
vm.epxansionPanelGroupCtrl.expandPanel(vm.componentId);
}
$element.removeClass('md-close');
$element.addClass('md-open');
if (options.animation === false) {
$element.addClass('md-no-animation');
} else {
$element.removeClass('md-no-animation');
}
initEvents();
collapsedCtrl.hide(options);
expandedCtrl.show(options);
if (headerCtrl) { headerCtrl.show(options); }
if (footerCtrl) { footerCtrl.show(options); }
$timeout(function () {
deferred.resolve();
}, options.animation === false ? 0 : ANIMATION_TIME);
return deferred.promise;
}
function collapse(options) {
if (_isOpen === false) { return; }
_isOpen = false;
options = options || {};
var deferred = $q.defer();
$element.addClass('md-close');
$element.removeClass('md-open');
if (options.animation === false) {
$element.addClass('md-no-animation');
} else {
$element.removeClass('md-no-animation');
}
killEvents();
collapsedCtrl.show(options);
expandedCtrl.hide(options);
if (headerCtrl) { headerCtrl.hide(options); }
if (footerCtrl) { footerCtrl.hide(options); }
$timeout(function () {
deferred.resolve();
}, options.animation === false ? 0 : ANIMATION_TIME);
return deferred.promise;
}
function remove(options) {
options = options || {};
var deferred = $q.defer();
if (vm.epxansionPanelGroupCtrl) {
vm.epxansionPanelGroupCtrl.removePanel(vm.componentId);
}
if (typeof deregister === 'function') {
deregister();
deregister = undefined;
}
if (options.animation === false || _isOpen === false) {
$scope.$destroy();
$element.remove();
deferred.resolve();
callbackRemove();
} else {
collapse();
$timeout(function () {
$scope.$destroy();
$element.remove();
deferred.resolve();
callbackRemove();
}, ANIMATION_TIME);
}
return deferred.promise;
}
function onRemove(callback) {
onRemoveCallback = callback;
}
function callbackRemove() {
if (typeof onRemoveCallback === 'function') {
onRemoveCallback();
onRemoveCallback = undefined;
}
}
function destroy() {
$scope.$destroy();
}
function initEvents() {
if ((!footerCtrl || footerCtrl.noSticky === true) && (!headerCtrl || headerCtrl.noSticky === true)) {
return;
}
// watch for panel position changes
topKiller = $scope.$watch(function () { return $element[0].offsetTop; }, debouncedUpdateScroll, true);
// watch for panel position changes
resizeKiller = $scope.$watch(function () { return $element[0].offsetWidth; }, debouncedUpdateResize, true);
// listen to md-content scroll events id we are nested in one
scrollContainer = $mdUtil.getNearestContentElement($element);
if (scrollContainer.nodeName === 'MD-CONTENT') {
transformParent = getTransformParent(scrollContainer);
angular.element(scrollContainer).on('scroll', debouncedUpdateScroll);
} else {
transformParent = undefined;
}
// listen to expanded content scroll if height is set
if (expandedCtrl.setHeight === true) {
expandedCtrl.$element.on('scroll', debouncedUpdateScroll);
}
// listen to window scroll events
angular.element($window)
.on('scroll', debouncedUpdateScroll)
.on('resize', debouncedUpdateScroll)
.on('resize', debouncedUpdateResize);
}
function killEvents() {
if (typeof topKiller === 'function') {
topKiller();
topKiller = undefined;
}
if (typeof resizeKiller === 'function') {
resizeKiller();
resizeKiller = undefined;
}
if (scrollContainer && scrollContainer.nodeName === 'MD-CONTENT') {
angular.element(scrollContainer).off('scroll', debouncedUpdateScroll);
}
if (expandedCtrl.setHeight === true) {
expandedCtrl.$element.off('scroll', debouncedUpdateScroll);
}
angular.element($window)
.off('scroll', debouncedUpdateScroll)
.off('resize', debouncedUpdateScroll)
.off('resize', debouncedUpdateResize);
}
function getTransformParent(el) {
var parent = el.parentNode;
while (parent && parent !== document) {
if (hasComputedStyle(parent, 'transform')) {
return parent;
}
parent = parent.parentNode;
}
return undefined;
}
function hasComputedStyle(target, key) {
var hasValue = false;
if (target) {
var computedStyles = $window.getComputedStyle(target);
hasValue = computedStyles[key] !== undefined && computedStyles[key] !== 'none';
}
return hasValue;
}
function updateScroll(e) {
var top;
var bottom;
var bounds;
if (expandedCtrl.setHeight === true) {
bounds = expandedCtrl.$element[0].getBoundingClientRect();
} else {
bounds = scrollContainer.getBoundingClientRect();
}
var transformTop = transformParent ? transformParent.getBoundingClientRect().top : 0;
// we never want the header going post the top of the page. to prevent this don't allow top to go below 0
top = Math.max(bounds.top, 0);
bottom = top + bounds.height;
if (footerCtrl && footerCtrl.noSticky === false) { footerCtrl.onScroll(top, bottom, transformTop); }
if (headerCtrl && headerCtrl.noSticky === false) { headerCtrl.onScroll(top, bottom, transformTop); }
}
function updateResize() {
var value = $element[0].offsetWidth;
if (footerCtrl && footerCtrl.noSticky === false) { footerCtrl.onResize(value); }
if (headerCtrl && headerCtrl.noSticky === false) { headerCtrl.onResize(value); }
}
function addClickCatcher(clickCallback) {
backdrop = $mdUtil.createBackdrop($scope);
backdrop[0].tabIndex = -1;
if (typeof clickCallback === 'function') {
backdrop.on('click', clickCallback);
}
$animate.enter(backdrop, $element.parent(), null, {duration: 0});
$element.css('z-index', 60);
}
function removeClickCatcher() {
if (backdrop) {
backdrop.remove();
backdrop.off('click');
backdrop = undefined;
$element.css('z-index', '');
}
}
}
}
}());