From e7c58791af6a63a6f1fbed991c94264773506978 Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Fri, 2 Oct 2015 20:44:44 -0700 Subject: [PATCH] fix(dropdown): restore deprecated directives - Adds uib- prefix to `dropdownConfig` - Restores functionality to deprecated directives Closes #4514 --- src/dropdown/docs/readme.md | 8 +- src/dropdown/dropdown.js | 131 +++++++++++++++++++++++++---- src/dropdown/test/dropdown.spec.js | 34 +++----- 3 files changed, 131 insertions(+), 42 deletions(-) diff --git a/src/dropdown/docs/readme.md b/src/dropdown/docs/readme.md index ff94a1cca1..b8571a175a 100644 --- a/src/dropdown/docs/readme.md +++ b/src/dropdown/docs/readme.md @@ -1,12 +1,12 @@ Dropdown is a simple directive which will toggle a dropdown menu on click or programmatically. -You can either use `is-open` to toggle or add inside a `` element to toggle it when is clicked. +You can either use `is-open` to toggle or add inside a `` element to toggle it when is clicked. There is also the `on-toggle(open)` optional expression fired when dropdown changes state. -Add `dropdown-append-to-body` to the `dropdown` element to append to the inner `dropdown-menu` to the body. +Add `dropdown-append-to-body` to the `uib-dropdown` element to append to the inner `dropdown-menu` to the body. This is useful when the dropdown button is inside a div with `overflow: hidden`, and the menu would otherwise be hidden. -Add `keyboard-nav` to the `dropdown` element to enable navigation of dropdown list elements with the arrow keys. +Add `uib-keyboard-nav` to the `uib-dropdown` element to enable navigation of dropdown list elements with the arrow keys. By default the dropdown will automatically close if any of its elements is clicked, you can change this behavior by setting the `auto-close` option as follows: @@ -16,4 +16,4 @@ By default the dropdown will automatically close if any of its elements is click Optionally, you may specify a template for the dropdown menu using the `template-url` attribute. This is especially useful when you have multiple similar dropdowns in a repeater and you want to keep your HTML output lean and your number of scopes to a minimum. The template has full access to the scope in which the dropdown lies. -Example: ``. +Example: ``. diff --git a/src/dropdown/dropdown.js b/src/dropdown/dropdown.js index 96702a402b..603237388e 100644 --- a/src/dropdown/dropdown.js +++ b/src/dropdown/dropdown.js @@ -1,6 +1,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) -.constant('dropdownConfig', { +.constant('uibDropdownConfig', { openClass: 'open' }) @@ -65,7 +65,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) }; }]) -.controller('UibDropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'uibDropdownService', '$animate', '$position', '$document', '$compile', '$templateRequest', function($scope, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) { +.controller('UibDropdownController', ['$scope', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) { var self = this, scope = $scope.$new(), // create a child scope so we are not polluting original one templateScope, @@ -253,10 +253,12 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) if (!dropdownCtrl) { return; } + var tplUrl = attrs.templateUrl; if (tplUrl) { dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; } + if (!dropdownCtrl.dropdownMenu) { dropdownCtrl.dropdownMenu = element; } @@ -340,17 +342,42 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) }; }); -/* Depreciated dropdown below */ +/* Deprecated dropdown below */ angular.module('ui.bootstrap.dropdown') + .value('$dropdownSuppressWarning', false) + +.service('dropdownService', ['$log', '$dropdownSuppressWarning', 'uibDropdownService', function($log, $dropdownSuppressWarning, uibDropdownService) { + if (!$dropdownSuppressWarning) { + $log.warn('dropdownService is now deprecated. Use uibDropdownService instead.'); + } + + angular.extend(this, uibDropdownService); +}]) + +.controller('DropdownController', ['$scope', '$attrs', '$log', '$dropdownSuppressWarning', '$controller', function($scope, $attrs, $log, $dropdownSuppressWarning, $controller) { + if (!$dropdownSuppressWarning) { + $log.warn('DropdownController is now deprecated. Use UibDropdownController instead.'); + } + + angular.extend(this, $controller('UibDropdownController', { + $scope: $scope, + $attrs: $attrs + })); +}]) + .directive('dropdown', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) { return { + controller: 'DropdownController', link: function(scope, element, attrs, dropdownCtrl) { - if (!$dropdownSuppressWarning) { - $log.warn('dropdown is now deprecated. Use uib-dropdown instead.'); - } + if (!$dropdownSuppressWarning) { + $log.warn('dropdown is now deprecated. Use uib-dropdown instead.'); } + + dropdownCtrl.init(element); + element.addClass('dropdown'); + } }; }]) @@ -359,10 +386,23 @@ angular.module('ui.bootstrap.dropdown') restrict: 'AC', require: '?^dropdown', link: function(scope, element, attrs, dropdownCtrl) { - if (!$dropdownSuppressWarning) { - $log.warn('dropdown-menu is now deprecated. Use uib-dropdown-menu instead.'); - } + if (!$dropdownSuppressWarning) { + $log.warn('dropdown-menu is now deprecated. Use uib-dropdown-menu instead.'); + } + + if (!dropdownCtrl) { + return; } + + var tplUrl = attrs.templateUrl; + if (tplUrl) { + dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; + } + + if (!dropdownCtrl.dropdownMenu) { + dropdownCtrl.dropdownMenu = element; + } + } }; }]) @@ -371,10 +411,41 @@ angular.module('ui.bootstrap.dropdown') restrict: 'A', require: '?^dropdown', link: function(scope, element, attrs, dropdownCtrl) { - if (!$dropdownSuppressWarning) { - $log.warn('keyboard-nav is now deprecated. Use uib-keyboard-nav instead.'); - } + if (!$dropdownSuppressWarning) { + $log.warn('keyboard-nav is now deprecated. Use uib-keyboard-nav instead.'); } + + element.bind('keydown', function(e) { + if ([38, 40].indexOf(e.which) !== -1) { + e.preventDefault(); + e.stopPropagation(); + + var elems = dropdownCtrl.dropdownMenu.find('a'); + + switch (e.which) { + case (40): { // Down + if (!angular.isNumber(dropdownCtrl.selectedOption)) { + dropdownCtrl.selectedOption = 0; + } else { + dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ? + dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1; + } + break; + } + case (38): { // Up + if (!angular.isNumber(dropdownCtrl.selectedOption)) { + dropdownCtrl.selectedOption = elems.length - 1; + } else { + dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ? + 0 : dropdownCtrl.selectedOption - 1; + } + break; + } + } + elems[dropdownCtrl.selectedOption].focus(); + } + }); + } }; }]) @@ -382,10 +453,40 @@ angular.module('ui.bootstrap.dropdown') return { require: '?^dropdown', link: function(scope, element, attrs, dropdownCtrl) { - if (!$dropdownSuppressWarning) { - $log.warn('dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead.'); + if (!$dropdownSuppressWarning) { + $log.warn('dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead.'); + } + + if (!dropdownCtrl) { + return; + } + + element.addClass('dropdown-toggle'); + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if (!element.hasClass('disabled') && !attrs.disabled) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); } - } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function(isOpen) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } }; }]); diff --git a/src/dropdown/test/dropdown.spec.js b/src/dropdown/test/dropdown.spec.js index c0c9d07c22..6d6a87d7d8 100644 --- a/src/dropdown/test/dropdown.spec.js +++ b/src/dropdown/test/dropdown.spec.js @@ -4,13 +4,13 @@ describe('dropdownToggle', function() { beforeEach(module('ngAnimateMock')); beforeEach(module('ui.bootstrap.dropdown')); - beforeEach(inject(function(_$animate_, _$compile_, _$rootScope_, _$document_, _$templateCache_, _dropdownConfig_, _$browser_, _$log_) { + beforeEach(inject(function(_$animate_, _$compile_, _$rootScope_, _$document_, _$templateCache_, uibDropdownConfig, _$browser_, _$log_) { $animate = _$animate_; $compile = _$compile_; $rootScope = _$rootScope_; $document = _$document_; $templateCache = _$templateCache_; - dropdownConfig = _dropdownConfig_; + dropdownConfig = uibDropdownConfig; $browser = _$browser_; $log = _$log_; })); @@ -707,33 +707,21 @@ describe('dropdown deprecation', function() { it('should give warning by default', inject(function($compile, $log, $rootScope) { spyOn($log, 'warn'); - var element = $compile('
  • ')($rootScope); + var element = $compile('
  • ')($rootScope); $rootScope.$digest(); - expect($log.warn.calls.count()).toBe(1); - expect($log.warn.calls.argsFor(0)).toEqual(['dropdown is now deprecated. Use uib-dropdown instead.']); - })); - - it('should give warning by default for dropdownToggle', inject(function($compile, $log, $rootScope) { - spyOn($log, 'warn'); - var element = $compile('
  • ')($rootScope); - $rootScope.$digest(); - expect($log.warn.calls.count()).toBe(2); - expect($log.warn.calls.argsFor(0)).toEqual(['dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead.']); + expect($log.warn.calls.count()).toBe(3); + expect($log.warn.calls.argsFor(0)).toEqual(['DropdownController is now deprecated. Use UibDropdownController instead.']); + expect($log.warn.calls.argsFor(1)).toEqual(['dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead.']); + expect($log.warn.calls.argsFor(2)).toEqual(['dropdown is now deprecated. Use uib-dropdown instead.']); })); it('should give warning by default for keyboardNav', inject(function($compile, $log, $rootScope) { spyOn($log, 'warn'); var element = $compile('
  • ')($rootScope); $rootScope.$digest(); - expect($log.warn.calls.count()).toBe(2); - expect($log.warn.calls.argsFor(0)).toEqual(['keyboard-nav is now deprecated. Use uib-keyboard-nav instead.']); + expect($log.warn.calls.count()).toBe(3); + expect($log.warn.calls.argsFor(0)).toEqual(['DropdownController is now deprecated. Use UibDropdownController instead.']); + expect($log.warn.calls.argsFor(1)).toEqual(['keyboard-nav is now deprecated. Use uib-keyboard-nav instead.']); + expect($log.warn.calls.argsFor(2)).toEqual(['dropdown is now deprecated. Use uib-dropdown instead.']); })); - - it('should give warning by default for dropdownMenu', inject(function($compile, $log, $rootScope) { - spyOn($log, 'warn'); - var element = $compile('
  • ')($rootScope); - $rootScope.$digest(); - expect($log.warn.calls.count()).toBe(2); - expect($log.warn.calls.argsFor(0)).toEqual(['dropdown-menu is now deprecated. Use uib-dropdown-menu instead.']); - })); });