From 69415a2e3f5f703afd7960f51cc0ad1cf82ab51c Mon Sep 17 00:00:00 2001 From: Michael Benford Date: Wed, 4 Dec 2013 23:45:29 -0200 Subject: [PATCH] feat(tagsInput): Added addOnBlur option Added an option for the user to set if a tag should be created when the input field loses focus and there is some text left in it. This feature is important because it prevents a tag from being accidentally lost when it's not explicitly added. Closes #29. --- build/ng-tags-input.css | 4 ++ build/ng-tags-input.js | 69 ++++++++++++++++---------- build/ng-tags-input.min.zip | Bin 3841 -> 3928 bytes build/ng-tags-input.zip | Bin 6574 -> 6723 bytes css/tags-input.css | 4 ++ src/tags-input.js | 69 ++++++++++++++++---------- test/tags-input.spec.js | 96 +++++++++++++++++++++++++++++++++++- test/test-page.html | 5 +- 8 files changed, 190 insertions(+), 57 deletions(-) diff --git a/build/ng-tags-input.css b/build/ng-tags-input.css index d75b8dcb..6ae3d4ff 100644 --- a/build/ng-tags-input.css +++ b/build/ng-tags-input.css @@ -7,6 +7,10 @@ position: relative; } +.ngTagsInput:active { + outline: none; +} + .ngTagsInput .tags { -moz-appearance: textfield; -webkit-appearance: textfield; diff --git a/build/ng-tags-input.js b/build/ng-tags-input.js index b5f1ccb0..c4ccab26 100644 --- a/build/ng-tags-input.js +++ b/build/ng-tags-input.js @@ -31,6 +31,7 @@ angular.module('tags-input', []); * @param {boolean=} [addOnEnter=true] Flag indicating that a new tag will be added on pressing the ENTER key. * @param {boolean=} [addOnSpace=false] Flag indicating that a new tag will be added on pressing the SPACE key. * @param {boolean=} [addOnComma=true] Flag indicating that a new tag will be added on pressing the COMMA key. + * @param {boolean=} [addOnBlur=true] Flag indicating that a new tag will be added when the input field loses focus. * @param {boolean=} [replaceSpacesWithDashes=true] Flag indicating that spaces will be replaced with dashes. * @param {string=} [allowedTagsPattern=^[a-zA-Z0-9\s]+$*] Regular expression that determines whether a new tag is valid. * @param {boolean=} [enableEditingLastTag=false] Flag indicating that the last tag will be moved back into @@ -39,7 +40,7 @@ angular.module('tags-input', []); * @param {expression} onTagAdded Expression to evaluate upon adding a new tag. The new tag is available as $tag. * @param {expression} onTagRemoved Expression to evaluate upon removing an existing tag. The removed tag is available as $tag. */ -angular.module('tags-input').directive('tagsInput', ["configuration", function(configuration) { +angular.module('tags-input').directive('tagsInput', ["$timeout","$document","configuration", function($timeout, $document, configuration) { function SimplePubSub() { var events = {}; @@ -67,7 +68,7 @@ angular.module('tags-input').directive('tagsInput', ["configuration", function(c }, replace: false, transclude: true, - template: '
' + + template: '
' + '
' + '
    ' + '
  • ' + @@ -99,6 +100,7 @@ angular.module('tags-input').directive('tagsInput', ["configuration", function(c addOnEnter: { type: Boolean, defaultValue: true }, addOnSpace: { type: Boolean, defaultValue: false }, addOnComma: { type: Boolean, defaultValue: true }, + addOnBlur: { type: Boolean, defaultValue: true }, allowedTagsPattern: { type: RegExp, defaultValue: /^[a-zA-Z0-9\s]+$/ }, enableEditingLastTag: { type: Boolean, defaultValue: false } }); @@ -201,39 +203,52 @@ angular.module('tags-input').directive('tagsInput', ["configuration", function(c var hotkeys = [KEYS.enter, KEYS.comma, KEYS.space, KEYS.backspace]; var input = element.find('input'); - input.on('keydown', function(e) { - var key; + input + .on('keydown', function(e) { + var key; - // This hack is needed because jqLite doesn't implement stopImmediatePropagation properly. - // I've sent a PR to Angular addressing this issue and hopefully it'll be fixed soon. - // https://github.com/angular/angular.js/pull/4833 - if (e.isImmediatePropagationStopped && e.isImmediatePropagationStopped()) { - return; - } + // This hack is needed because jqLite doesn't implement stopImmediatePropagation properly. + // I've sent a PR to Angular addressing this issue and hopefully it'll be fixed soon. + // https://github.com/angular/angular.js/pull/4833 + if (e.isImmediatePropagationStopped && e.isImmediatePropagationStopped()) { + return; + } - if (hotkeys.indexOf(e.keyCode) === -1) { - return; - } + if (hotkeys.indexOf(e.keyCode) === -1) { + return; + } - key = e.keyCode; + key = e.keyCode; - if (key === KEYS.enter && scope.options.addOnEnter || - key === KEYS.comma && scope.options.addOnComma || - key === KEYS.space && scope.options.addOnSpace) { + if (key === KEYS.enter && scope.options.addOnEnter || + key === KEYS.comma && scope.options.addOnComma || + key === KEYS.space && scope.options.addOnSpace) { - if (scope.tryAdd()) { - scope.$apply(); + if (scope.tryAdd()) { + scope.$apply(); + } + e.preventDefault(); } - e.preventDefault(); - } - else if (key === KEYS.backspace && this.value.length === 0) { - if (scope.tryRemoveLast()) { - scope.$apply(); + else if (key === KEYS.backspace && this.value.length === 0) { + if (scope.tryRemoveLast()) { + scope.$apply(); - e.preventDefault(); + e.preventDefault(); + } } - } - }); + }) + .on('blur', function() { + if (!scope.options.addOnBlur) { + return; + } + + $timeout(function() { + var parentElement = angular.element($document[0].activeElement).parent(); + if (parentElement[0] !== element[0] && scope.tryAdd()) { + scope.$apply(); + } + }, 0); + }); element.find('div').on('click', function() { input[0].focus(); diff --git a/build/ng-tags-input.min.zip b/build/ng-tags-input.min.zip index 8e337b1a2121d6f15ce13c05a29b9a738ce30cfd..7af94720c0d1f30269da279ae0ea2333108f3477 100644 GIT binary patch delta 3810 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 Y00000000000IHMI4k!k_4gdfE0O2x0W&i*H delta 3732 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 j007Jo000pH000000000000000$&-W`BnGe<00000r+liH delta 6479 zcmY+JQ(Po~qlL4rsmZpvZMJQ5!`5bRP1c0Xwrxyqv$@%uHru%O|8j92&+j~c59b@; zspUpjQ-p=XgF-?=g3{3oPC%yw4H><=uFE8TZmV*ZX=G?ose5+mW@$^&8%S4p)a10Y zY?^G^wP<5;QZ*nzBS6tIx8_=sADcLUHgAJ6`k{)mH%TfT68r9s?(Xi~`FVQu0Znww z;OpF{C#&Y#lJYF3EZ!ogTp@@u)Ado)=NEvc5N>;I((0>k%H)2+lW!jAl4tBb;VI;W z4J@N0yZ4Ox-jqwr9P@Vac+!UO^E%}PVnP}P04f@6;mhuM%`7JALyYM9Zts^>$@-)< zCYOCQlvAxcrVqRqX>MOAm~~FSHM6tTJ#g$_f!T8-*$2Ibgjj8;rkId3ew1{iv6N7x z>%wt0V8)*bqy0^&sI~!Vw^BLIURng0=S%5;i?QgVlTrh13FN7n^otw!uqRX7*+e5d ztM#$;DHp28q9NtWRoeQzxRQ=3T4NeNX&eE!h-!YCrCb1dkDMv+zF=DZXfW!txwiZU z8|qv#m2&3(jv%wf8->;0SImWEwVXqFWAMVQ8|PmqsH!Z#d(az` zeQ1JP<+;MIcGq#SUc9leh3V}DG`&+A@S%SM+won8Asby ze@h{~@A>_vV-i&M`54b!&=b81_-9=90rNZp# zo3wvlSK{^Yuz%-3-K}ZR`-yU`0`k2*w2CyMUA@rjdja(}?DeRggjGk_HUTPYOnAe! zYL=7xg{@l=3Ston)O(eIlV+Un>(;JU6J!0KB43{dOi!!Wy?WbmF0H)g5|iX;Da@8} zngKXTy5_OA%yf^gS^N4Y>YmjyjgkVIx%(a({AL!;5@Q1OH^i-SmXcO8jIYjwMw*G2 z-uu$Fte}~FmDNAi(wn1jOFpoQw7O+Fu}6UJ;ZJZDzv)RxbJjB;qg`3{}Aq zt`Pe--r1K)#A;wUstWR~O)hha+KZrFv0hz$WBp*H&sSz#m)&U8&wwl|$jxNSiX+VB zLcvoU=_rTt5}qdO&S8@Q1j_8&noK;!&{wsX576%HNBi;*FZ=C%;a{y&q-QYvt3QqV zPm_nHd+J>R9|MD78nJJ+vTY``*|uxzW|o&pxCl`T^~ATR;K~^d^8MF^Dy*7nQDR3o zSQ@s8i~qtH-u392*}LfzC4Qsv|Hg0eMvn4Ii{2BBUKn⁣(`AA8Q>#ws1cS`3s}0 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 diff --git a/css/tags-input.css b/css/tags-input.css index 28cf9e73..40fd4bcc 100644 --- a/css/tags-input.css +++ b/css/tags-input.css @@ -7,6 +7,10 @@ position: relative; } +.ngTagsInput:active { + outline: none; +} + .ngTagsInput .tags { -moz-appearance: textfield; -webkit-appearance: textfield; diff --git a/src/tags-input.js b/src/tags-input.js index ccb03006..b61d9c6d 100644 --- a/src/tags-input.js +++ b/src/tags-input.js @@ -20,6 +20,7 @@ angular.module('tags-input', []); * @param {boolean=} [addOnEnter=true] Flag indicating that a new tag will be added on pressing the ENTER key. * @param {boolean=} [addOnSpace=false] Flag indicating that a new tag will be added on pressing the SPACE key. * @param {boolean=} [addOnComma=true] Flag indicating that a new tag will be added on pressing the COMMA key. + * @param {boolean=} [addOnBlur=true] Flag indicating that a new tag will be added when the input field loses focus. * @param {boolean=} [replaceSpacesWithDashes=true] Flag indicating that spaces will be replaced with dashes. * @param {string=} [allowedTagsPattern=^[a-zA-Z0-9\s]+$*] Regular expression that determines whether a new tag is valid. * @param {boolean=} [enableEditingLastTag=false] Flag indicating that the last tag will be moved back into @@ -28,7 +29,7 @@ angular.module('tags-input', []); * @param {expression} onTagAdded Expression to evaluate upon adding a new tag. The new tag is available as $tag. * @param {expression} onTagRemoved Expression to evaluate upon removing an existing tag. The removed tag is available as $tag. */ -angular.module('tags-input').directive('tagsInput', function(configuration) { +angular.module('tags-input').directive('tagsInput', function($timeout, $document, configuration) { function SimplePubSub() { var events = {}; @@ -56,7 +57,7 @@ angular.module('tags-input').directive('tagsInput', function(configuration) { }, replace: false, transclude: true, - template: '
    ' + + template: '
    ' + '
    ' + '
      ' + '
    • ' + @@ -88,6 +89,7 @@ angular.module('tags-input').directive('tagsInput', function(configuration) { addOnEnter: { type: Boolean, defaultValue: true }, addOnSpace: { type: Boolean, defaultValue: false }, addOnComma: { type: Boolean, defaultValue: true }, + addOnBlur: { type: Boolean, defaultValue: true }, allowedTagsPattern: { type: RegExp, defaultValue: /^[a-zA-Z0-9\s]+$/ }, enableEditingLastTag: { type: Boolean, defaultValue: false } }); @@ -190,39 +192,52 @@ angular.module('tags-input').directive('tagsInput', function(configuration) { var hotkeys = [KEYS.enter, KEYS.comma, KEYS.space, KEYS.backspace]; var input = element.find('input'); - input.on('keydown', function(e) { - var key; + input + .on('keydown', function(e) { + var key; - // This hack is needed because jqLite doesn't implement stopImmediatePropagation properly. - // I've sent a PR to Angular addressing this issue and hopefully it'll be fixed soon. - // https://github.com/angular/angular.js/pull/4833 - if (e.isImmediatePropagationStopped && e.isImmediatePropagationStopped()) { - return; - } + // This hack is needed because jqLite doesn't implement stopImmediatePropagation properly. + // I've sent a PR to Angular addressing this issue and hopefully it'll be fixed soon. + // https://github.com/angular/angular.js/pull/4833 + if (e.isImmediatePropagationStopped && e.isImmediatePropagationStopped()) { + return; + } - if (hotkeys.indexOf(e.keyCode) === -1) { - return; - } + if (hotkeys.indexOf(e.keyCode) === -1) { + return; + } - key = e.keyCode; + key = e.keyCode; - if (key === KEYS.enter && scope.options.addOnEnter || - key === KEYS.comma && scope.options.addOnComma || - key === KEYS.space && scope.options.addOnSpace) { + if (key === KEYS.enter && scope.options.addOnEnter || + key === KEYS.comma && scope.options.addOnComma || + key === KEYS.space && scope.options.addOnSpace) { - if (scope.tryAdd()) { - scope.$apply(); + if (scope.tryAdd()) { + scope.$apply(); + } + e.preventDefault(); } - e.preventDefault(); - } - else if (key === KEYS.backspace && this.value.length === 0) { - if (scope.tryRemoveLast()) { - scope.$apply(); + else if (key === KEYS.backspace && this.value.length === 0) { + if (scope.tryRemoveLast()) { + scope.$apply(); - e.preventDefault(); + e.preventDefault(); + } } - } - }); + }) + .on('blur', function() { + if (!scope.options.addOnBlur) { + return; + } + + $timeout(function() { + var parentElement = angular.element($document[0].activeElement).parent(); + if (parentElement[0] !== element[0] && scope.tryAdd()) { + scope.$apply(); + } + }, 0); + }); element.find('div').on('click', function() { input[0].focus(); diff --git a/test/tags-input.spec.js b/test/tags-input.spec.js index 65ba7e9f..8a96ac44 100644 --- a/test/tags-input.spec.js +++ b/test/tags-input.spec.js @@ -2,15 +2,17 @@ 'use strict'; describe('tags-input-directive', function() { - var $compile, $scope, + var $compile, $scope, $timeout, $document, isolateScope, element; beforeEach(function() { module('tags-input'); - inject(function(_$compile_, _$rootScope_) { + inject(function(_$compile_, _$rootScope_, _$document_, _$timeout_) { $compile = _$compile_; $scope = _$rootScope_; + $document = _$document_; + $timeout = _$timeout_; }); }); @@ -332,6 +334,96 @@ describe('tags-input-directive', function() { }); }); + describe('add-on-blur option', function() { + it('initializes the option to true', function() { + // Arrange/Act + compile(); + + // Assert + expect(isolateScope.options.addOnBlur).toBe(true); + }); + + it('sets the option given a static string', function() { + // Arrange/Act + compile('add-on-blur="false"'); + + // Assert + expect(isolateScope.options.addOnBlur).toBe(false); + }); + + it('sets the option given an interpolated string', function() { + // Arrange + $scope.value = false; + + // Act + compile('add-on-blur="{{ value }}"'); + + // Assert + expect(isolateScope.options.addOnBlur).toBe(false); + }); + + it('ensures the outermost div element has a tabindex attribute set to -1', function() { + // Arrange/Act + compile(); + + // Assert + expect(element.find('div').attr('tabindex')).toBe('-1'); + }); + + describe('option is true', function() { + var anotherElement; + + beforeEach(function() { + compile('add-on-blur="true"'); + anotherElement = angular.element('
      '); + + $document.find('body') + .append(element) + .append(anotherElement); + }); + + it('adds a tag when the input field loses focus to any element on the page but the directive itself', function() { + // Arrange + isolateScope.newTag = 'foo'; + anotherElement[0].focus(); + + // Act + getInput().trigger('blur'); + $timeout.flush(); + + // Assert + expect($scope.tags).toEqual(['foo']); + }); + + it('does not add a tag when the input field loses focus to the directive itself', function() { + // Arrange + isolateScope.newTag = 'foo'; + element.find('div')[0].focus(); + + // Act + getInput().trigger('blur'); + $timeout.flush(); + + // Assert + expect($scope.tags).toEqual([]); + }); + }); + + describe('option is off', function() { + it('does not add a new tag when the input field loses focus', function() { + // Arrange + compile('add-on-blur="false"'); + isolateScope.newTag = 'foo'; + + // Act + getInput().trigger('blur'); + + // Assert + expect($scope.tags).toEqual([]); + }); + }); + }); + describe('placeholder option', function() { it('sets the input field placeholder text', function() { // Arrange/Act diff --git a/test/test-page.html b/test/test-page.html index 16a9eef0..d07a3dc0 100644 --- a/test/test-page.html +++ b/test/test-page.html @@ -9,7 +9,8 @@ + replace-spaces-with-dashes="false" + add-on-blur="false"> + +