Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
master
Go to file
 
 
Cannot retrieve contributors at this time
471 lines (399 sloc) 15.5 KB
import * as angular from 'angular';
/**
* @ngdoc enum
* @name MenuItemTypes
* @module officeuifabric.components.contextualmenu
*
* @description
* Determines which menu template to use, default is `link`
*/
enum MenuItemTypes {
link = 0,
divider = 1,
header = 2,
subMenu = 3
}
/**
* @ngdoc interface
* @name IContextualMenuItemAttributes
* @module officeuifabric.components.contextualmenu
*
* @description
* Attributs for the `<uif-contextual-menu-item>` directive.
*
* @property {string} uifType - The type of the menu item, based on `MenuItemTypes` enum
*/
interface IContextualMenuItemAttributes extends angular.IAttributes {
uifType: string;
}
/**
* @ngdoc directive
* @name uifContextualMenuItem
* @module officeuifabric.components.contextualmenu
*
* @restrict E
*
* @description
* `<uif-contextual-menu-item>` is a contextual menu item directive.
*
* @see {link http://dev.office.com/fabric/components/contextualmenu}
*
* @usage
*
* <uif-contextual-menu-item uif-text="'Item1'" uif-on-click="menuOnClick()"></uif-contextual-menu-item>
*/
export class ContextualMenuItemDirective implements angular.IDirective {
public static directiveName: string = 'uifContextualMenuItem';
public restrict: string = 'E';
public require: string = '^uifContextualMenu';
public transclude: boolean = true;
public controller: any = ContextualMenuItemController;
public replace: boolean = true;
public scope: {} = {
isSelected: '=?uifIsSelected',
onClick: '&ngClick',
text: '=?uifText',
type: '@uifType'
};
private templateTypes: { [menuType: number]: string } = {};
public static factory(): angular.IDirectiveFactory {
const directive: angular.IDirectiveFactory = ($log: angular.ILogService) => new ContextualMenuItemDirective($log);
directive.$inject = ['$log'];
return directive;
}
constructor(private $log: angular.ILogService) {
this.templateTypes[MenuItemTypes.subMenu] =
`<li class="ms-ContextualMenu-item">
<a class="ms-ContextualMenu-link ms-ContextualMenu-link--hasMenu"
ng-class="{\'is-selected\': isSelected, \'is-disabled\': isDisabled}" ng-click="selectItem($event)" href>
<span class='uif-item-content'></span></a>
<i class="ms-ContextualMenu-subMenuIcon ms-Icon ms-Icon--chevronRight"></i>
<div class="uif-context-submenu"></div>
</li>`;
this.templateTypes[MenuItemTypes.link] =
`<li class="ms-ContextualMenu-item">
<a class="ms-ContextualMenu-link" ng-class="{\'is-selected\': isSelected, \'is-disabled\': isDisabled}"
ng-click="selectItem($event)" href><span class='uif-item-content'></span></a>
</li>`;
this.templateTypes[MenuItemTypes.header] = `
<li class="ms-ContextualMenu-item ms-ContextualMenu-item--header">
<span class='uif-item-content'></span>
</li>`;
this.templateTypes[MenuItemTypes.divider] = `<li class="ms-ContextualMenu-item ms-ContextualMenu-item--divider"></li>`;
}
public template: any = ($element: angular.IAugmentedJQuery, $attrs: IContextualMenuItemAttributes) => {
let type: string = $attrs.uifType;
if (angular.isUndefined(type)) {
return this.templateTypes[MenuItemTypes.link];
}
if (MenuItemTypes[type] === undefined) {
this.$log.error('Error [ngOfficeUiFabric] officeuifabric.components.contextualmenu - unsupported menu type:\n' +
'the type \'' + type + '\' is not supported by ng-Office UI Fabric as valid type for context menu.' +
'Supported types can be found under MenuItemTypes enum here:\n' +
'https://github.com/ngOfficeUIFabric/ng-officeuifabric/blob/master/src/components/contextualmenu/contextualMenu.ts');
}
return this.templateTypes[MenuItemTypes[type]];
}
public link: angular.IDirectiveLinkFn = (
$scope: IContextualMenuItemScope,
$element: angular.IAugmentedJQuery,
$attrs: angular.IAttributes,
contextualMenuController: ContextualMenuController,
$transclude: angular.ITranscludeFunction): void => {
if (typeof $scope.isSelected !== 'boolean' && $scope.isSelected !== undefined) {
contextualMenuController.$log.error('Error [ngOfficeUiFabric] officeuifabric.components.contextualmenu - ' +
'invalid attribute type: \'uif-is-selected\'.\n' +
'The type \'' + typeof $scope.isSelected + '\' is not supported as valid type for \'uif-is-selected\' attribute for ' +
'<uif-contextual-menu-item />. The valid type is boolean.');
}
$attrs.$observe('disabled', (disabled) => {
$scope.isDisabled = !!disabled;
});
this.transcludeChilds($scope, $element, $transclude);
$scope.selectItem = ($event: MouseEvent) => {
if (!contextualMenuController.isMultiSelectionMenu()) {
contextualMenuController.deselectItems();
}
if (angular.isUndefined($scope.isSelected) && !$scope.isDisabled) {
$scope.isSelected = true;
} else {
$scope.isSelected = !$scope.isSelected;
}
/*close all menus if link is clicked, do not close if submenu clicked */
if (!$scope.hasChildMenu) {
contextualMenuController.closeSubMenus(null, true);
if (!contextualMenuController.isRootMenu()) {
contextualMenuController.deselectItems(true);
}
} else {
contextualMenuController.closeSubMenus($scope.$id);
}
if ($scope.hasChildMenu) {
$scope.childMenuCtrl.openMenu();
}
if (!angular.isUndefined($scope.onClick)) {
$scope.onClick();
}
$event.stopPropagation();
};
$scope.$on('uif-menu-deselect', () => {
$scope.isSelected = false;
});
$scope.$on('uif-menu-close', (event: angular.IAngularEvent, menuItemId: number) => {
if ($scope.hasChildMenu && $scope.$id !== menuItemId) {
$scope.childMenuCtrl.closeMenu();
}
});
}
private transcludeChilds(
$scope: IContextualMenuItemScope,
$element: angular.IAugmentedJQuery,
$transclude: angular.ITranscludeFunction): void {
$transclude((clone: angular.IAugmentedJQuery) => {
let hasContent: boolean = this.hasItemContent(clone);
if (!hasContent && !$scope.text && !$scope.hasChildMenu && $scope.type !== 'divider') {
this.$log.error('Error [ngOfficeUiFabric] officeuifabric.components.contextualmenu - ' +
'you need to provide a text for a contextual menu item.\n' +
'For <uif-contextual-menu-item> you need to specify either \'uif-text\' as attribute or <uif-content> as a child directive');
}
this.insertItemContent(clone, $scope, $element);
this.insertSubMenu(clone, $scope, $element);
});
}
private insertItemContent(clone: angular.IAugmentedJQuery, $scope: IContextualMenuItemScope, $element: angular.IAugmentedJQuery): void {
let elementToReplace: JQuery = angular.element($element[0].querySelector('.uif-item-content'));
if (this.hasItemContent(clone)) { /* element provided */
for (let i: number = 0; i < clone.length; i++) {
let element: angular.IAugmentedJQuery = angular.element(clone[i]);
if (element.hasClass('uif-content')) {
elementToReplace.replaceWith(element);
break;
}
}
} else { /* text attribute provided */
elementToReplace.replaceWith(angular.element('<span>' + $scope.text + '</span>'));
}
}
private insertSubMenu(clone: angular.IAugmentedJQuery, $scope: IContextualMenuItemScope, $element: angular.IAugmentedJQuery): void {
for (let i: number = 0; i < clone.length; i++) {
let element: angular.IAugmentedJQuery = angular.element(clone[i]);
if (element.hasClass('ms-ContextualMenu')) {
angular.element($element[0].querySelector('.uif-context-submenu')).replaceWith(element);
}
}
}
private hasItemContent(clone: angular.IAugmentedJQuery): boolean {
for (let i: number = 0; i < clone.length; i++) {
let element: angular.IAugmentedJQuery = angular.element(clone[i]);
if (element.hasClass('uif-content')) {
return true;
}
}
return false;
}
}
/**
* @ngdoc interface
* @name IContextualMenuItemScope
* @module officeuifabric.components.contextualmenu
*
* @description
* This is the scope used by the `<uif-contextual-menu-item>` directive.
*
* @property {boolean} isSelected - Indicates if particular item is selected
* @property {boolean} isDisabled - Indicates if particular item is disabled
* @property {boolean} hasChildMenu - Indicates if current menu item has child sub-menu
* @property {function} selectItem - Function which is called by clicking on menu item
* @property {function} onClick - On click callback for parent scope
* @property {object} childMenuCtrl - Reference to child controller (will be initialized only if `hasChildMenu` is true)
* @property {string} text - Represents text used by menu item
* @property {string} type - Menu type
*/
export interface IContextualMenuItemScope extends angular.IScope {
isSelected?: boolean;
isDisabled?: boolean;
hasChildMenu: boolean;
text: string;
type: string;
selectItem: ($event: MouseEvent) => void;
onClick: () => void;
childMenuCtrl: ContextualMenuController;
}
/**
* @ngdoc controller
* @name ContextualMenuItemController
* @module officeuifabric.components.contextualmenu
*
* @description
* Controller used for the `<uif-contextual-menu-item>` directive.
*/
export class ContextualMenuItemController {
public static $inject: string[] = ['$scope', '$element'];
constructor(private $scope: IContextualMenuItemScope, private $element: angular.IAugmentedJQuery) {
}
public setChildMenu(childMenuCtrl: ContextualMenuController): void {
this.$scope.hasChildMenu = true;
this.$scope.childMenuCtrl = childMenuCtrl;
}
}
/**
* @ngdoc directive
* @name uifContextualMenu
* @module officeuifabric.components.contextualmenu
*
* @restrict E
*
* @description
* `<uif-contextual-menu>` is a contextual menu directive.
*
* @see {link http://dev.office.com/fabric/components/contextualmenu}
*
* @usage
*
* <uif-contextual-menu uif-is-open="isOpen">
* <uif-contextual-menu-item uif-text="'Item1'" uif-on-click="menuOnClick()"></uif-contextual-menu-item>
* <uif-contextual-menu-item uif-text="'Item2'"></uif-contextual-menu-item>
* </uif-contextual-menu>
*/
export class ContextualMenuDirective implements angular.IDirective {
public static directiveName: string = 'uifContextualMenu';
public restrict: string = 'E';
public require: string = ContextualMenuDirective.directiveName;
public transclude: boolean = true;
public template: string = `<ul class="ms-ContextualMenu" ng-transclude></ul>`;
public replace: boolean = true;
public controller: any = ContextualMenuController;
public scope: {} = {
closeOnClick: '@uifCloseOnClick',
isOpen: '=?uifIsOpen',
multiselect: '@uifMultiselect'
};
public static factory(): angular.IDirectiveFactory {
const directive: angular.IDirectiveFactory = () => new ContextualMenuDirective();
return directive;
}
public link(
$scope: IContextualMenuScope,
$element: angular.IAugmentedJQuery,
$attrs: angular.IAttributes,
contextualMenuController: ContextualMenuController): void {
let setCloseOnClick: (value?: string | boolean) => void = (value: string | boolean) => {
if (angular.isUndefined(value)) {
$scope.closeOnClick = true;
} else {
$scope.closeOnClick = value.toString().toLowerCase() === 'true';
}
};
setCloseOnClick($scope.closeOnClick);
$attrs.$observe('uifCloseOnClick', setCloseOnClick);
let parentMenuItemCtrl: ContextualMenuItemController = $element.controller(ContextualMenuItemDirective.directiveName);
if (!angular.isUndefined(parentMenuItemCtrl)) {
parentMenuItemCtrl.setChildMenu(contextualMenuController);
}
if (!angular.isUndefined($scope.multiselect) && $scope.multiselect.toLowerCase() === 'true') {
$element.addClass('ms-ContextualMenu--multiselect');
}
}
}
/**
* @ngdoc interface
* @name IContextualMenuItemScope
* @module officeuifabric.components.contextualmenu
*
* @description
* This is the scope used by the `<uif-contextual-menu-item>` directive.
*
* @property {boolean} isOpen - Indicates if menu is open
* @property {boolean} isRootMenu - Indicates if this is root menu and not child
* @property {string} multiselect - Indicates if current menu is multiselection menu
* @property {boolean} closeOnClick - Indicates if root menu should be closed after clicking on menu item
*/
export interface IContextualMenuScope extends angular.IScope {
isOpen: boolean;
isRootMenu: boolean;
multiselect: string;
closeOnClick: boolean;
}
/**
* @ngdoc controller
* @name ContextualMenuController
* @module officeuifabric.components.contextualmenu
*
* @description
* Controller used for the `<uif-contextual-menu>` directive.
*/
export class ContextualMenuController {
public static $inject: string[] = ['$scope', '$animate', '$element', '$log'];
public onRootMenuClosed: (() => void)[] = [];
private isOpenClassName: string = 'is-open';
constructor(
private $scope: IContextualMenuScope,
private $animate: angular.animate.IAnimateService,
private $element: angular.IAugmentedJQuery,
public $log: angular.ILogService) {
if (angular.isUndefined($element.controller(ContextualMenuItemDirective.directiveName))) {
$scope.isRootMenu = true;
}
$scope.$watch('isOpen', (newValue: boolean) => {
if (typeof newValue !== 'boolean' && newValue !== undefined) {
this.$log.error('Error [ngOfficeUiFabric] officeuifabric.components.contextualmenu - invalid attribute type: \'uif-is-open\'.\n' +
'The type \'' + typeof newValue + '\' is not supported as valid type for \'uif-is-open\' attribute for ' +
'<uif-contextual-menu />. The valid type is boolean.');
}
$animate[newValue ? 'addClass' : 'removeClass']($element, this.isOpenClassName);
});
this.onRootMenuClosed.push(() => {
this.closeMenu();
this.deselectItems(true);
});
$scope.$on('uif-menu-close', () => {
if ($scope.isRootMenu && $scope.closeOnClick) {
this.onRootMenuClosed.forEach((callback: () => void) => {
callback();
});
}
});
}
public deselectItems(deselectParentMenus?: boolean): void {
this.$scope.$broadcast('uif-menu-deselect');
if (deselectParentMenus) {
this.$scope.$emit('uif-menu-deselect');
}
}
public closeSubMenus(menuItemToSkip?: number, closeRootMenu?: boolean): void {
this.$scope.$broadcast('uif-menu-close', menuItemToSkip);
if (closeRootMenu) {
this.$scope.$emit('uif-menu-close');
}
}
public openMenu(): void {
this.$scope.isOpen = true;
}
public closeMenu(): void {
this.$scope.isOpen = false;
}
public isRootMenu(): boolean {
return this.$scope.isRootMenu;
}
public isMultiSelectionMenu(): boolean {
if (angular.isUndefined(this.$scope.multiselect)) {
return false;
}
return this.$scope.multiselect.toLowerCase() === 'true';
}
public isMenuOpened(): boolean {
return this.$element.hasClass('is-open');
}
}
/**
* @ngdoc module
* @name officeuifabric.components.contextualmenu
*
* @description
* Contextual Menu Module
*
*/
export let module: angular.IModule = angular.module('officeuifabric.components.contextualmenu', [
'officeuifabric.components'])
.directive(ContextualMenuDirective.directiveName, ContextualMenuDirective.factory())
.directive(ContextualMenuItemDirective.directiveName, ContextualMenuItemDirective.factory());
You can’t perform that action at this time.