From ef277697bc05d64a0350c0149d8557808b533185 Mon Sep 17 00:00:00 2001 From: Wesley Cho Date: Fri, 23 Oct 2015 14:51:44 -0700 Subject: [PATCH] feat(typeahead): remove deprecated code BREAKING CHANGE: Remove deprecated services/directives --- .../test/typeahead-highlight.spec.js | 29 - src/typeahead/test/typeahead-parser.spec.js | 4 +- src/typeahead/test/typeahead-popup.spec.js | 44 -- src/typeahead/test/typeahead.spec.js | 48 -- src/typeahead/typeahead.js | 527 ------------------ 5 files changed, 2 insertions(+), 650 deletions(-) diff --git a/src/typeahead/test/typeahead-highlight.spec.js b/src/typeahead/test/typeahead-highlight.spec.js index c696587751..4488c6d4ea 100644 --- a/src/typeahead/test/typeahead-highlight.spec.js +++ b/src/typeahead/test/typeahead-highlight.spec.js @@ -49,32 +49,3 @@ describe('typeaheadHighlight', function () { expect(logSpy).toHaveBeenCalled(); }); }); - -/* Deprecation tests below */ - -describe('typeahead highlightFilter deprecated', function(){ - var highlightFilter, $log, $sce, logSpy; - - beforeEach(module('ui.bootstrap.typeahead')); - - it('should supress the warning by default', function(){ - module(function($provide) { - $provide.value('$typeaheadSuppressWarning', true); - }); - - inject(function($compile, $log, $rootScope, typeaheadHighlightFilter, $sce){ - spyOn($log, 'warn'); - var highlightFilter = typeaheadHighlightFilter; - $sce.getTrustedHtml(highlightFilter('before match after', 'match')); - expect($log.warn.calls.count()).toBe(0); - }); - }); - - it('should decrecate typeaheadHighlightFilter', inject(function($compile, $log, $rootScope, typeaheadHighlightFilter, $sce){ - spyOn($log, 'warn'); - var highlightFilter = typeaheadHighlightFilter; - $sce.getTrustedHtml(highlightFilter('before match after', 'match')); - expect($log.warn.calls.count()).toBe(1); - expect($log.warn.calls.argsFor(0)).toEqual(['typeaheadHighlight is now deprecated. Use uibTypeaheadHighlight instead.']); - })); -}); diff --git a/src/typeahead/test/typeahead-parser.spec.js b/src/typeahead/test/typeahead-parser.spec.js index f19aec5cfb..d201b73644 100644 --- a/src/typeahead/test/typeahead-parser.spec.js +++ b/src/typeahead/test/typeahead-parser.spec.js @@ -2,8 +2,8 @@ describe('syntax parser', function() { var typeaheadParser, scope, filterFilter; beforeEach(module('ui.bootstrap.typeahead')); - beforeEach(inject(function(_$rootScope_, _filterFilter_, _typeaheadParser_) { - typeaheadParser = _typeaheadParser_; + beforeEach(inject(function(_$rootScope_, _filterFilter_, uibTypeaheadParser) { + typeaheadParser = uibTypeaheadParser; scope = _$rootScope_; filterFilter = _filterFilter_; })); diff --git a/src/typeahead/test/typeahead-popup.spec.js b/src/typeahead/test/typeahead-popup.spec.js index 63936609f0..1e3b3b39b3 100644 --- a/src/typeahead/test/typeahead-popup.spec.js +++ b/src/typeahead/test/typeahead-popup.spec.js @@ -55,47 +55,3 @@ describe('typeaheadPopup - result rendering', function() { expect($rootScope.select).toHaveBeenCalledWith(2); }); }); - -/* Deprecation tests below */ - -describe('typeaheadPopup deprecation', function() { - beforeEach(module('ui.bootstrap.typeahead')); - beforeEach(module('ngSanitize')); - beforeEach(module('template/typeahead/typeahead-popup.html')); - beforeEach(module('template/typeahead/typeahead-match.html')); - - it('should suppress warning', function() { - module(function($provide) { - $provide.value('$typeaheadSuppressWarning', true); - }); - - inject(function($compile, $log, $rootScope) { - var scope = $rootScope.$new(); - scope.matches = ['foo', 'bar', 'baz']; - scope.active = 1; - $rootScope.select = angular.noop; - spyOn($log, 'warn'); - - var element = '
'; - element = $compile(element)(scope); - $rootScope.$digest(); - expect($log.warn.calls.count()).toBe(0); - }); - }); - - it('should give warning by default', inject(function($compile, $log, $rootScope) { - var scope = $rootScope.$new(); - scope.matches = ['foo', 'bar', 'baz']; - scope.active = 1; - $rootScope.select = angular.noop; - spyOn($log, 'warn'); - - var element = '
'; - element = $compile(element)(scope); - - $rootScope.$digest(); - - expect($log.warn.calls.count()).toBe(1); - expect($log.warn.calls.argsFor(0)).toEqual(['typeahead-popup is now deprecated. Use uib-typeahead-popup instead.']); - })); -}); \ No newline at end of file diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index 040a2289e8..9e85ab3f88 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -1047,51 +1047,3 @@ describe('typeahead tests', function() { }); }); }); - -/* Deprecation tests below */ - -describe('typeahead deprecation', function() { - beforeEach(module('ui.bootstrap.typeahead')); - beforeEach(module('ngSanitize')); - beforeEach(module('template/typeahead/typeahead-popup.html')); - beforeEach(module('template/typeahead/typeahead-match.html')); - - it('should suppress warning', function() { - module(function($provide) { - $provide.value('$typeaheadSuppressWarning', true); - }); - - inject(function($compile, $log, $rootScope) { - spyOn($log, 'warn'); - - var element = '
'; - element = $compile(element)($rootScope); - $rootScope.$digest(); - expect($log.warn.calls.count()).toBe(0); - }); - }); - - it('should give warning by default', inject(function($compile, $log, $rootScope) { - spyOn($log, 'warn'); - - var element = '
'; - element = $compile(element)($rootScope); - $rootScope.$digest(); - - expect($log.warn.calls.count()).toBe(3); - expect($log.warn.calls.argsFor(0)).toEqual(['typeaheadParser is now deprecated. Use uibTypeaheadParser instead.']); - expect($log.warn.calls.argsFor(1)).toEqual(['typeahead is now deprecated. Use uib-typeahead instead.']); - expect($log.warn.calls.argsFor(2)).toEqual(['typeahead-popup is now deprecated. Use uib-typeahead-popup instead.']); - })); - - it('should deprecate typeaheadMatch', inject(function($compile, $log, $rootScope, $templateCache, $sniffer){ - spyOn($log, 'warn'); - - var element = '
'; - element = $compile(element)($rootScope); - $rootScope.$digest(); - - expect($log.warn.calls.count()).toBe(1); - expect($log.warn.calls.argsFor(0)).toEqual(['typeahead-match is now deprecated. Use uib-typeahead-match instead.']); - })); -}); diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index aa888429df..17c7d01764 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -531,530 +531,3 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position']) return matchItem; }; }]); - -/* Deprecated typeahead below */ - -angular.module('ui.bootstrap.typeahead') - .value('$typeaheadSuppressWarning', false) - .service('typeaheadParser', ['$parse', 'uibTypeaheadParser', '$log', '$typeaheadSuppressWarning', function($parse, uibTypeaheadParser, $log, $typeaheadSuppressWarning) { - if (!$typeaheadSuppressWarning) { - $log.warn('typeaheadParser is now deprecated. Use uibTypeaheadParser instead.'); - } - - return uibTypeaheadParser; - }]) - - .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$uibPosition', 'typeaheadParser', '$log', '$typeaheadSuppressWarning', - function($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser, $log, $typeaheadSuppressWarning) { - var HOT_KEYS = [9, 13, 27, 38, 40]; - var eventDebounceTime = 200; - return { - require: ['ngModel', '^?ngModelOptions'], - link: function(originalScope, element, attrs, ctrls) { - if (!$typeaheadSuppressWarning) { - $log.warn('typeahead is now deprecated. Use uib-typeahead instead.'); - } - var modelCtrl = ctrls[0]; - var ngModelOptions = ctrls[1]; - //SUPPORTED ATTRIBUTES (OPTIONS) - - //minimal no of characters that needs to be entered before typeahead kicks-in - var minLength = originalScope.$eval(attrs.typeaheadMinLength); - if (!minLength && minLength !== 0) { - minLength = 1; - } - - //minimal wait time after last character typed before typeahead kicks-in - var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; - - //should it restrict model values to the ones selected from the popup only? - var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; - - //binding to a variable that indicates if matches are being retrieved asynchronously - var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; - - //a callback executed when a match is selected - var onSelectCallback = $parse(attrs.typeaheadOnSelect); - - //should it select highlighted popup value when losing focus? - var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false; - - //binding to a variable that indicates if there were no results after the query is completed - var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop; - - var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; - - var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; - - var appendToElementId = attrs.typeaheadAppendToElementId || false; - - var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false; - - //If input matches an item of the list exactly, select it automatically - var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false; - - //INTERNAL VARIABLES - - //model setter executed upon match selection - var parsedModel = $parse(attrs.ngModel); - var invokeModelSetter = $parse(attrs.ngModel + '($$$p)'); - var $setModelValue = function(scope, newValue) { - if (angular.isFunction(parsedModel(originalScope)) && - ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) { - return invokeModelSetter(scope, {$$$p: newValue}); - } else { - return parsedModel.assign(scope, newValue); - } - }; - - //expressions used by typeahead - var parserResult = typeaheadParser.parse(attrs.typeahead); - - var hasFocus; - - //Used to avoid bug in iOS webview where iOS keyboard does not fire - //mousedown & mouseup events - //Issue #3699 - var selected; - - //create a child scope for the typeahead directive so we are not polluting original scope - //with typeahead-specific data (matches, query etc.) - var scope = originalScope.$new(); - var offDestroy = originalScope.$on('$destroy', function() { - scope.$destroy(); - }); - scope.$on('$destroy', offDestroy); - - // WAI-ARIA - var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); - element.attr({ - 'aria-autocomplete': 'list', - 'aria-expanded': false, - 'aria-owns': popupId - }); - - //pop-up element used to display matches - var popUpEl = angular.element('
'); - popUpEl.attr({ - id: popupId, - matches: 'matches', - active: 'activeIdx', - select: 'select(activeIdx)', - 'move-in-progress': 'moveInProgress', - query: 'query', - position: 'position' - }); - //custom item template - if (angular.isDefined(attrs.typeaheadTemplateUrl)) { - popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); - } - - if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) { - popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl); - } - - var resetMatches = function() { - scope.matches = []; - scope.activeIdx = -1; - element.attr('aria-expanded', false); - }; - - var getMatchId = function(index) { - return popupId + '-option-' + index; - }; - - // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. - // This attribute is added or removed automatically when the `activeIdx` changes. - scope.$watch('activeIdx', function(index) { - if (index < 0) { - element.removeAttr('aria-activedescendant'); - } else { - element.attr('aria-activedescendant', getMatchId(index)); - } - }); - - var inputIsExactMatch = function(inputValue, index) { - if (scope.matches.length > index && inputValue) { - return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase(); - } - - return false; - }; - - var getMatchesAsync = function(inputValue) { - var locals = {$viewValue: inputValue}; - isLoadingSetter(originalScope, true); - isNoResultsSetter(originalScope, false); - $q.when(parserResult.source(originalScope, locals)).then(function(matches) { - //it might happen that several async queries were in progress if a user were typing fast - //but we are interested only in responses that correspond to the current view value - var onCurrentRequest = (inputValue === modelCtrl.$viewValue); - if (onCurrentRequest && hasFocus) { - if (matches && matches.length > 0) { - scope.activeIdx = focusFirst ? 0 : -1; - isNoResultsSetter(originalScope, false); - scope.matches.length = 0; - - //transform labels - for (var i = 0; i < matches.length; i++) { - locals[parserResult.itemName] = matches[i]; - scope.matches.push({ - id: getMatchId(i), - label: parserResult.viewMapper(scope, locals), - model: matches[i] - }); - } - - scope.query = inputValue; - //position pop-up with matches - we need to re-calculate its position each time we are opening a window - //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page - //due to other elements being rendered - recalculatePosition(); - - element.attr('aria-expanded', true); - - //Select the single remaining option if user input matches - if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { - scope.select(0); - } - } else { - resetMatches(); - isNoResultsSetter(originalScope, true); - } - } - if (onCurrentRequest) { - isLoadingSetter(originalScope, false); - } - }, function() { - resetMatches(); - isLoadingSetter(originalScope, false); - isNoResultsSetter(originalScope, true); - }); - }; - - // bind events only if appendToBody params exist - performance feature - if (appendToBody) { - angular.element($window).bind('resize', fireRecalculating); - $document.find('body').bind('scroll', fireRecalculating); - } - - // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later - var timeoutEventPromise; - - // Default progress type - scope.moveInProgress = false; - - function fireRecalculating() { - if (!scope.moveInProgress) { - scope.moveInProgress = true; - scope.$digest(); - } - - // Cancel previous timeout - if (timeoutEventPromise) { - $timeout.cancel(timeoutEventPromise); - } - - // Debounced executing recalculate after events fired - timeoutEventPromise = $timeout(function() { - // if popup is visible - if (scope.matches.length) { - recalculatePosition(); - } - - scope.moveInProgress = false; - }, eventDebounceTime); - } - - // recalculate actual position and set new values to scope - // after digest loop is popup in right position - function recalculatePosition() { - scope.position = appendToBody ? $position.offset(element) : $position.position(element); - scope.position.top += element.prop('offsetHeight'); - } - - resetMatches(); - - //we need to propagate user's query so we can higlight matches - scope.query = undefined; - - //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later - var timeoutPromise; - - var scheduleSearchWithTimeout = function(inputValue) { - timeoutPromise = $timeout(function() { - getMatchesAsync(inputValue); - }, waitTime); - }; - - var cancelPreviousTimeout = function() { - if (timeoutPromise) { - $timeout.cancel(timeoutPromise); - } - }; - - //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM - //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue - modelCtrl.$parsers.unshift(function(inputValue) { - hasFocus = true; - - if (minLength === 0 || inputValue && inputValue.length >= minLength) { - if (waitTime > 0) { - cancelPreviousTimeout(); - scheduleSearchWithTimeout(inputValue); - } else { - getMatchesAsync(inputValue); - } - } else { - isLoadingSetter(originalScope, false); - cancelPreviousTimeout(); - resetMatches(); - } - - if (isEditable) { - return inputValue; - } else { - if (!inputValue) { - // Reset in case user had typed something previously. - modelCtrl.$setValidity('editable', true); - return null; - } else { - modelCtrl.$setValidity('editable', false); - return undefined; - } - } - }); - - modelCtrl.$formatters.push(function(modelValue) { - var candidateViewValue, emptyViewValue; - var locals = {}; - - // The validity may be set to false via $parsers (see above) if - // the model is restricted to selected values. If the model - // is set manually it is considered to be valid. - if (!isEditable) { - modelCtrl.$setValidity('editable', true); - } - - if (inputFormatter) { - locals.$model = modelValue; - return inputFormatter(originalScope, locals); - } else { - //it might happen that we don't have enough info to properly render input value - //we need to check for this situation and simply return model value if we can't apply custom formatting - locals[parserResult.itemName] = modelValue; - candidateViewValue = parserResult.viewMapper(originalScope, locals); - locals[parserResult.itemName] = undefined; - emptyViewValue = parserResult.viewMapper(originalScope, locals); - - return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue; - } - }); - - scope.select = function(activeIdx) { - //called from within the $digest() cycle - var locals = {}; - var model, item; - - selected = true; - locals[parserResult.itemName] = item = scope.matches[activeIdx].model; - model = parserResult.modelMapper(originalScope, locals); - $setModelValue(originalScope, model); - modelCtrl.$setValidity('editable', true); - modelCtrl.$setValidity('parse', true); - - onSelectCallback(originalScope, { - $item: item, - $model: model, - $label: parserResult.viewMapper(originalScope, locals) - }); - - resetMatches(); - - //return focus to the input element if a match was selected via a mouse click event - // use timeout to avoid $rootScope:inprog error - if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) { - $timeout(function() { element[0].focus(); }, 0, false); - } - }; - - //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) - element.bind('keydown', function(evt) { - //typeahead is open and an "interesting" key was pressed - if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { - return; - } - - // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results - if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) { - resetMatches(); - scope.$digest(); - return; - } - - evt.preventDefault(); - - if (evt.which === 40) { - scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; - scope.$digest(); - } else if (evt.which === 38) { - scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; - scope.$digest(); - } else if (evt.which === 13 || evt.which === 9) { - scope.$apply(function () { - scope.select(scope.activeIdx); - }); - } else if (evt.which === 27) { - evt.stopPropagation(); - - resetMatches(); - scope.$digest(); - } - }); - - element.bind('blur', function() { - if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) { - selected = true; - scope.$apply(function() { - scope.select(scope.activeIdx); - }); - } - hasFocus = false; - selected = false; - }); - - // Keep reference to click handler to unbind it. - var dismissClickHandler = function(evt) { - // Issue #3973 - // Firefox treats right click as a click on document - if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) { - resetMatches(); - if (!$rootScope.$$phase) { - scope.$digest(); - } - } - }; - - $document.bind('click', dismissClickHandler); - - originalScope.$on('$destroy', function() { - $document.unbind('click', dismissClickHandler); - if (appendToBody || appendToElementId) { - $popup.remove(); - } - - if (appendToBody) { - angular.element($window).unbind('resize', fireRecalculating); - $document.find('body').unbind('scroll', fireRecalculating); - } - // Prevent jQuery cache memory leak - popUpEl.remove(); - }); - - var $popup = $compile(popUpEl)(scope); - - if (appendToBody) { - $document.find('body').append($popup); - } else if (appendToElementId !== false) { - angular.element($document[0].getElementById(appendToElementId)).append($popup); - } else { - element.after($popup); - } - } - }; - }]) - - .directive('typeaheadPopup', ['$typeaheadSuppressWarning', '$log', function($typeaheadSuppressWarning, $log) { - return { - scope: { - matches: '=', - query: '=', - active: '=', - position: '&', - moveInProgress: '=', - select: '&' - }, - replace: true, - templateUrl: function(element, attrs) { - return attrs.popupTemplateUrl || 'template/typeahead/typeahead-popup.html'; - }, - link: function(scope, element, attrs) { - - if (!$typeaheadSuppressWarning) { - $log.warn('typeahead-popup is now deprecated. Use uib-typeahead-popup instead.'); - } - scope.templateUrl = attrs.templateUrl; - - scope.isOpen = function() { - return scope.matches.length > 0; - }; - - scope.isActive = function(matchIdx) { - return scope.active == matchIdx; - }; - - scope.selectActive = function(matchIdx) { - scope.active = matchIdx; - }; - - scope.selectMatch = function(activeIdx) { - scope.select({activeIdx:activeIdx}); - }; - } - }; - }]) - - .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', '$typeaheadSuppressWarning', '$log', function($templateRequest, $compile, $parse, $typeaheadSuppressWarning, $log) { - return { - restrict: 'EA', - scope: { - index: '=', - match: '=', - query: '=' - }, - link:function(scope, element, attrs) { - if (!$typeaheadSuppressWarning) { - $log.warn('typeahead-match is now deprecated. Use uib-typeahead-match instead.'); - } - - var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; - $templateRequest(tplUrl).then(function(tplContent) { - $compile(tplContent.trim())(scope, function(clonedElement) { - element.replaceWith(clonedElement); - }); - }); - } - }; - }]) - - .filter('typeaheadHighlight', ['$sce', '$injector', '$log', '$typeaheadSuppressWarning', function($sce, $injector, $log, $typeaheadSuppressWarning) { - var isSanitizePresent; - isSanitizePresent = $injector.has('$sanitize'); - - function escapeRegexp(queryToEscape) { - // Regex: capture the whole query string and replace it with the string that will be used to match - // the results, for example if the capture is "a" the result will be \a - return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); - } - - function containsHtml(matchItem) { - return /<.*>/g.test(matchItem); - } - - return function(matchItem, query) { - if (!$typeaheadSuppressWarning) { - $log.warn('typeaheadHighlight is now deprecated. Use uibTypeaheadHighlight instead.'); - } - - if (!isSanitizePresent && containsHtml(matchItem)) { - $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger - } - - matchItem = query? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag - if (!isSanitizePresent) { - matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive - } - - return matchItem; - }; - }]);