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
596 lines (519 sloc) 19.4 KB
import * as angular from 'angular';
import { ContextualMenuDirective, ContextualMenuController } from './../contextualmenu/contextualMenu';
/**
* @ngdoc interface
* @name INavBarScope
* @module officeuifabric.components.navbar
*
* @description
* This is the scoped used by the `<uif-nav-bar>` directive.
*
* @property {string} overlay - Overlay name for the nav bar, see OverlayMode enum
* @property {function} openMenu - Open menu callback
* @property {function} closeMenu - Close menu callback
*/
interface INavBarScope extends angular.IScope {
overlay: string;
openMenu: () => void;
closeMenu: () => void;
}
/**
* @ngdoc controller
* @name NavBarController
* @module officeuifabric.components.navbar
*
* @description
* Controller used for the `<uif-nav-bar>` directive.
*/
export class NavBarController {
public static $inject: string[] = ['$scope', '$animate', '$element', '$log'];
constructor(
private $scope: INavBarScope,
private $animate: angular.animate.IAnimateService,
private $element: angular.IAugmentedJQuery,
public $log: angular.ILogService) {
}
public openMobileMenu(): void {
let menuVisible: boolean = this.$element.hasClass('is-open');
this.$animate[menuVisible ? 'removeClass' : 'addClass'](this.$element, 'is-open');
}
public closeMobileMenu(): void {
if (this.$element.hasClass('is-open')) {
this.$animate.removeClass(this.$element, 'is-open');
}
}
public closeAllContextMenus(): void {
let navBarItems: NodeListOf<Element> = this.$element[0].querySelectorAll('.ms-NavBar-item');
for (let i: number = 0; i < navBarItems.length; i++) {
let ngElement: angular.IAugmentedJQuery = angular.element(navBarItems[i]);
let navBarItemCtrl: NavBarItemController = ngElement.controller(NavBarItemDirective.directiveName);
if (navBarItemCtrl) {
navBarItemCtrl.closeContextualMenu();
navBarItemCtrl.deselectItem();
}
}
}
public hideSearchTextBox(): void {
let navBarItems: NodeListOf<Element> = this.$element[0].querySelectorAll('.ms-NavBar-item--search');
for (let i: number = 0; i < navBarItems.length; i++) {
let ngElement: angular.IAugmentedJQuery = angular.element(navBarItems[i]);
let navSearchCtrl: NavBarSearchController = ngElement.controller(NavBarSearch.directiveName);
if (navSearchCtrl) {
navSearchCtrl.closeSearch();
}
}
}
}
/**
* @ngdoc directive
* @name uifNavBar
* @module officeuifabric.components.navbar
*
* @restrict E
*
* @description
* `<uif-nav-bar>` is a nav bar directive.
*
* @see {link http://dev.office.com/fabric/components/navbar}
*
* @usage
*
* <uif-nav-bar uif-overlay="dark">
* <uif-nav-bar-search placeholder="search for smth" uif-on-search="onSearch(search)">
* </uif-nav-bar-search>
* <uif-nav-bar-item uif-text="'Home'" ng-click="logClick('Home item clicked')"></uif-nav-bar-item>
* <uif-nav-bar-item uif-text="'Contacts'"></uif-nav-bar-item>
* <uif-nav-bar-item>
* <uif-nav-item-content>
* <uif-icon uif-type="arrowRight"></uif-icon><b>Item in bold with icons</b>
* <uif-icon uif-type="arrowLeft"></uif-icon>
* </uif-nav-item-content>
* </uif-nav-bar-item>
* </uif-nav-bar>
*/
export class NavBarDirective implements angular.IDirective {
public static directiveName: string = 'uifNavBar';
public static overlayValues: string[] = ['light', 'dark'];
public replace: boolean = true;
public restrict: string = 'E';
public transclude: boolean = true;
public controller: any = NavBarController;
public controllerAs: string = 'nav';
public template: string = `
<div class=\"ms-NavBar\">
<div class="ms-NavBar-openMenu js-openMenu" ng-click="nav.openMobileMenu()">
<uif-icon uif-type="menu"></uif-icon>
</div>
<uif-overlay uif-mode="{{overlay}}" ng-click="nav.closeMobileMenu()"></uif-overlay>
<ul class=\"ms-NavBar-items\">
<div class='uif-nav-items'></div>
</ul>
</div>`;
public scope: {} = {
overlay: '@?uifOverlay'
};
public static factory(): angular.IDirectiveFactory {
const directive: angular.IDirectiveFactory = (
$log: angular.ILogService,
$animate: angular.animate.IAnimateService,
$document: angular.IDocumentService
) => new NavBarDirective($log, $animate, $document);
directive.$inject = ['$log', '$animate', '$document'];
return directive;
}
public link: angular.IDirectiveLinkFn = (
$scope: INavBarScope,
$element: angular.IAugmentedJQuery,
$attrs: angular.IAttributes,
navBarController: NavBarController,
$transclude: angular.ITranscludeFunction): void => {
this.$document.on('click', () => {
navBarController.closeAllContextMenus();
navBarController.hideSearchTextBox();
});
$transclude((clone: angular.IAugmentedJQuery) => {
let elementToReplace: angular.IAugmentedJQuery = angular.element($element[0].querySelector('.uif-nav-items'));
elementToReplace.replaceWith(clone);
});
}
constructor(
private $log: angular.ILogService, private $animate: angular.animate.IAnimateService, private $document: angular.IDocumentService) { }
}
/**
* @ngdoc interface
* @name INavBarItemScope
* @module officeuifabric.components.navbar
*
* @description
* This is the scope used by the `<uif-nav-bar-item>` directive.
*
* @property {boolean} hasChildMenu - Indicates if nav bar item has child menu (contexual menu)
* @property {object} contextMenuCtrl - Reference to contextual menu controller
* @property {string} text - Text used by the nav bar item
* @property {string} type - Menu type, string represents one of the values for the NavBarItemTypes enum
* @property {function} selectItem - Nav bar item click callback
* @property {boolean} isDisabled - Indicates that menu item is disabled
*/
interface INavBarItemScope extends angular.IScope {
hasChildMenu: boolean;
contextMenuCtrl: ContextualMenuController;
text: string;
type: string;
selectItem: ($event: JQueryEventObject) => void;
isDisabled: boolean;
}
/**
* @ngdoc interface
* @name INavBarItemAttributes
* @module officeuifabric.components.navbar
*
* @description
* Attributs for the `<uif-nav-bar-item>` directive.
*
* @property {string} uifType - The type of the nav bar menu item, based on `NavBarItemTypes` enum
*/
interface INavBarItemAttributes extends angular.IAttributes {
uifType: string;
}
/**
* @ngdoc enum
* @name NavBarItemTypes
* @module officeuifabric.components.navbar
*
* @description
* Determines which nav bar item type, default is `link`
*/
enum NavBarItemTypes {
link = 0,
menu = 1
}
/**
* @ngdoc controller
* @name NavBarItemController
* @module officeuifabric.components.navbar
*
* @description
* Controller used for the `<uif-nav-bar-item>` directive.
*/
export class NavBarItemController {
public static $inject: string[] = ['$scope', '$element'];
constructor(private $scope: INavBarItemScope, private $element: angular.IAugmentedJQuery) { }
public closeContextualMenu(): void {
if (this.$scope.hasChildMenu) {
this.$scope.contextMenuCtrl.closeMenu();
}
}
public deselectItem(): void {
this.$element.removeClass('is-selected');
}
}
/**
* @ngdoc directive
* @name uifNavBarItem
* @module officeuifabric.components.navbar
*
* @restrict E
*
* @description
* `<uif-nav-bar-item>` is a nav bar item directive.
*
* @see {link http://dev.office.com/fabric/components/navbar}
*
* @usage
*
* <uif-nav-bar-item uif-text="'Regular menu item'" ng-click="logClick('Menu item clicked')"></uif-nav-bar-item>
* <uif-nav-bar-item>
* <uif-nav-item-content>
* <uif-icon uif-type="arrowRight"></uif-icon><b>Item in bold with icons</b>
* <uif-icon uif-type="arrowLeft"></uif-icon>
* </uif-nav-item-content>
* </uif-nav-bar-item>
* <uif-nav-bar-item uif-type="menu">
* <uif-nav-item-content>Sub Menu</uif-nav-item-content>
* <uif-contextual-menu>
* <uif-contextual-menu-item uif-text="'Delete'"></uif-contextual-menu-item>
* <uif-contextual-menu-item uif-text="'Flag'"></uif-contextual-menu-item>
* </uif-contextual-menu-item>
* </uif-contextual-menu>
* </uif-nav-bar-item>
*/
export class NavBarItemDirective implements angular.IDirective {
public static directiveName: string = 'uifNavBarItem';
public replace: boolean = true;
public restrict: string = 'E';
public transclude: boolean = true;
public controller: any = NavBarItemController;
public require: string = `^${NavBarDirective.directiveName}`;
public scope: {} = {
isDisabled: '@?disabled',
position: '@?uifPosition',
text: '=?uifText',
type: '@?uifType'
};
private templateTypes: { [menuType: number]: string } = {};
public static factory(): angular.IDirectiveFactory {
const directive: angular.IDirectiveFactory = ($log: angular.ILogService) => new NavBarItemDirective($log);
directive.$inject = ['$log'];
return directive;
}
constructor(private $log: angular.ILogService) {
this.templateTypes[NavBarItemTypes.link] = `
<li class="ms-NavBar-item"
ng-class="{\'is-disabled\': isDisabled, 'ms-NavBar-item--right': position === 'right'}">
<a class="ms-NavBar-link" href=""><span class='uif-nav-item-content'></span></a>
</li>`;
this.templateTypes[NavBarItemTypes.menu] = `
<li class="ms-NavBar-item ms-NavBar-item--hasMenu" ng-class="{\'is-disabled\': isDisabled}">
<a class="ms-NavBar-link" href=""><span class='uif-nav-item-content'></span></a>
<i class="ms-NavBar-chevronDown ms-Icon ms-Icon--chevronDown"></i>
<div class='uif-submenu'></div>
</li>`;
}
public template: any = ($element?: angular.IAugmentedJQuery, $attrs?: INavBarItemAttributes): string => {
let type: string = $attrs.uifType;
if (angular.isUndefined(type)) {
return this.templateTypes[NavBarItemTypes.link];
}
if (NavBarItemTypes[type] === undefined) {
this.$log.error('Error [ngOfficeUiFabric] officeuifabric.components.navbar - unsupported nav bar item type:\n' +
'the type \'' + type + '\' is not supported by ng-Office UI Fabric as valid type for nav bar item.' +
'Supported types can be found under NavBarItemTypes enum here:\n' +
'https://github.com/ngOfficeUIFabric/ng-officeuifabric/blob/master/src/components/navbar/navbarDirective.ts');
return '<div></div>';
}
return this.templateTypes[NavBarItemTypes[type]];
}
public link: angular.IDirectiveLinkFn = (
$scope: INavBarItemScope,
$element: angular.IAugmentedJQuery,
$attrs: angular.IAttributes,
navBarController: NavBarController,
$transclude: angular.ITranscludeFunction): void => {
if ($scope.isDisabled) {
let navBarLinkEelement: JQuery = angular.element($element[0].querySelector('.ms-NavBar-link'));
navBarLinkEelement.removeAttr('href');
}
if (angular.isUndefined($scope.type)) {
$scope.type = NavBarItemTypes[NavBarItemTypes.link];
}
$scope.selectItem = ($event: JQueryEventObject) => {
$event.stopPropagation();
if ($element.hasClass('is-disabled')) {
return;
}
$element.parent().find('li').removeClass('is-selected');
navBarController.closeAllContextMenus();
navBarController.hideSearchTextBox();
$element.toggleClass('is-selected');
if ($scope.hasChildMenu && $scope.contextMenuCtrl.isMenuOpened()) {
$scope.contextMenuCtrl.closeMenu();
$element.removeClass('is-selected');
} else if ($scope.hasChildMenu && !$scope.contextMenuCtrl.isMenuOpened()) {
$scope.contextMenuCtrl.openMenu();
$element.addClass('is-selected');
} else if (!$scope.hasChildMenu) {
navBarController.closeMobileMenu();
}
$scope.$apply();
};
$element.on('click', $scope.selectItem);
this.transcludeChilds($scope, $element, $transclude);
let contextMenuCtrl: ContextualMenuController = angular.element($element[0].querySelector('.ms-ContextualMenu'))
.controller(ContextualMenuDirective.directiveName);
if (contextMenuCtrl) {
$scope.hasChildMenu = true;
$scope.contextMenuCtrl = contextMenuCtrl;
$scope.contextMenuCtrl.onRootMenuClosed.push(() => {
navBarController.closeMobileMenu();
$element.removeClass('is-selected');
});
}
}
private transcludeChilds($scope: INavBarItemScope, $element: angular.IAugmentedJQuery, $transclude: angular.ITranscludeFunction): void {
$transclude((clone: angular.IAugmentedJQuery) => {
let hasContent: boolean = this.hasItemContent(clone);
if (!hasContent && !$scope.text) {
this.$log.error('Error [ngOfficeUiFabric] officeuifabric.components.navbar - ' +
'you need to provide a text for a nav bar menu item.\n' +
'For <uif-nav-bar-item> you need to specify either \'uif-text\' as attribute or <uif-nav-item-content> as a child directive');
}
this.insertLink(clone, $scope, $element);
this.insertMenu(clone, $scope, $element);
});
}
private insertLink(clone: angular.IAugmentedJQuery, $scope: INavBarItemScope, $element: angular.IAugmentedJQuery): void {
let elementToReplace: JQuery = angular.element($element[0].querySelector('.uif-nav-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 insertMenu(clone: angular.IAugmentedJQuery, $scope: INavBarItemScope, $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-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 INavBarSearchScope
* @module officeuifabric.components.navbar
*
* @description
* This is the scope used by the `<uif-nav-bar-search>` directive.
*
* @property {string} searchText - Text being searched
* @property {function} onSearch - Search UI element click callback
* @property {string} placeholder - Placeholder for the html search input
* @property {function} onSearchCallback - User defined callback, firing by search event
* @property {function} skipOnClick - Helper search div click callback
*/
interface INavBarSearchScope extends angular.IScope {
searchText: string;
onSearch: ($event: KeyboardEvent | MouseEvent) => void;
placeholder: string;
onSearchCallback: (map: { search: string }) => void;
skipOnClick: ($event: MouseEvent) => void;
}
/**
* @ngdoc controller
* @name NavBarSearchController
* @module officeuifabric.components.navbar
*
* @description
* Controller used for the `<uif-nav-bar-search>` directive.
*/
export class NavBarSearchController {
public static $inject: string[] = ['$scope', '$element', '$document', '$animate', '$timeout'];
constructor(
private $scope: INavBarSearchScope,
private $element: angular.IAugmentedJQuery,
private $document: angular.IDocumentService,
private $animate: angular.animate.IAnimateService,
private $timeout: angular.ITimeoutService) {
}
public closeSearch(): void {
this.$timeout(() => {
if (!this.$scope.searchText) {
this.$animate.removeClass(this.$element, 'is-open');
}
this.$animate.removeClass(this.$element, 'is-selected');
});
}
}
/**
* @ngdoc directive
* @name uifNavBarSearch
* @module officeuifabric.components.navbar
*
* @restrict E
*
* @description
* `<uif-nav-bar-search>` is a nav bar search directive.
*
* @see {link http://dev.office.com/fabric/components/navbar}
*
* @usage
*
* <uif-nav-bar-search placeholder="search for smth" uif-on-search="onSearch(search)">
* </uif-nav-bar-search>
*/
export class NavBarSearch implements angular.IDirective {
public static directiveName: string = 'uifNavBarSearch';
public replace: boolean = true;
public restrict: string = 'E';
public controller: any = NavBarSearchController;
public require: string[] = [`^${NavBarDirective.directiveName}`, `${NavBarSearch.directiveName}`];
public transclude: boolean = true;
public scope: {} = {
onSearchCallback: '&?uifOnSearch',
placeholder: '@?placeholder'
};
public template: string = `
<li class="ms-NavBar-item ms-NavBar-item--search ms-u-hiddenSm" ng-click="onSearch($event)">
<div class="ms-TextField" ng-click="skipOnClick($event)">
<input placeholder={{placeholder}} class="ms-TextField-field" type="text" ng-keypress="onSearch($event)" ng-model="searchText">
</div>
</li>`;
public static factory(): angular.IDirectiveFactory {
const directive: angular.IDirectiveFactory = (
$document: angular.IDocumentService,
$animate: angular.animate.IAnimateService,
$timeout: angular.ITimeoutService) =>
new NavBarSearch($document, $animate, $timeout);
directive.$inject = ['$document', '$animate', '$timeout'];
return directive;
}
constructor(
private $document: angular.IDocumentService,
private $animate: angular.animate.IAnimateService,
private $timeout: angular.ITimeoutService) { }
public link: angular.IDirectiveLinkFn = (
$scope: INavBarSearchScope,
$element: angular.IAugmentedJQuery,
$attrs: angular.IAttributes,
ctrls: [NavBarController, NavBarSearchController],
$transclude: angular.ITranscludeFunction): void => {
this.$document.on('click', () => {
ctrls[1].closeSearch();
});
$scope.skipOnClick = ($event: MouseEvent) => {
this.applyCssClasses($element);
$event.stopPropagation();
};
$scope.onSearch = ($event: KeyboardEvent | MouseEvent) => {
ctrls[0].closeAllContextMenus();
if ($event instanceof KeyboardEvent && $event.which === 13 && $scope.onSearchCallback) {
$scope.onSearchCallback({ search: $scope.searchText });
} else if ($event instanceof MouseEvent && $element.hasClass('is-open') && $scope.onSearchCallback) {
$scope.onSearchCallback({ search: $scope.searchText });
}
this.applyCssClasses($element);
$event.stopPropagation();
};
}
private applyCssClasses($element: angular.IAugmentedJQuery): void {
if (!$element.hasClass('is-open')) {
this.$animate.addClass($element, 'is-open');
this.$timeout(
() => {
angular.element($element[0].querySelector('.ms-TextField-field'))[0].focus();
},
1);
}
$element.parent().find('li').removeClass('is-selected');
this.$animate.addClass($element, 'is-selected');
}
}
/**
* @ngdoc module
* @name officeuifabric.components.navbar
*
* @description
* NavBar Module
*
*/
export let module: angular.IModule = angular.module('officeuifabric.components.navbar', [
'officeuifabric.components'])
.directive(NavBarDirective.directiveName, NavBarDirective.factory())
.directive(NavBarItemDirective.directiveName, NavBarItemDirective.factory())
.directive(NavBarSearch.directiveName, NavBarSearch.factory());
You can’t perform that action at this time.