Skip to content
This repository has been archived by the owner on Nov 22, 2021. It is now read-only.

Commit

Permalink
feat(tagsInput): Add onTagAdding/onTagRemoving callbacks
Browse files Browse the repository at this point in the history
Add two new callbacks, onTagAdding and onTagRemoving, to be called before
a tag is added or removed, respectively. Those callbacks will allow users
to define their own custom rules for when a tag can be added or removed.

Closes #100
  • Loading branch information
jandritsch authored and mbenford committed Mar 9, 2015
1 parent f1f5124 commit c4ceed5
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 10 deletions.
42 changes: 32 additions & 10 deletions src/tags-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@
* When this flag is true, addOnEnter, addOnComma, addOnSpace, addOnBlur and
* allowLeftoverText values are ignored.
* @param {boolean=} [spellcheck=true] Flag indicating whether the browser's spellcheck is enabled for the input field or not.
* @param {expression} onTagAdding Expression to evaluate that will be invoked before adding a new tag. The new tag is available as $tag. This method must return either true or false. If false, the tag will not be added.
* @param {expression} onTagAdded Expression to evaluate upon adding a new tag. The new tag is available as $tag.
* @param {expression} onInvalidTag Expression to evaluate when a tag is invalid. The invalid tag is available as $tag.
* @param {expression} onTagRemoving Expression to evaluate that will be invoked before removing a tag. The tag is available as $tag. This method must return either true or false. If false, the tag will not be removed.
* @param {expression} onTagRemoved Expression to evaluate upon removing an existing tag. The removed tag is available as $tag.
*/
tagsInput.directive('tagsInput', function($timeout, $document, tagsInputConfig, tiUtil) {
function TagList(options, events) {

function TagList(options, events, additionalCallbacks) {

var self = {}, getTagText, setTagText, tagIsValid;

getTagText = function(tag) {
Expand All @@ -53,12 +57,18 @@ tagsInput.directive('tagsInput', function($timeout, $document, tagsInputConfig,

tagIsValid = function(tag) {
var tagText = getTagText(tag);
var okToAddTag = additionalCallbacks.onTagAdding({ $tag: tag });
var valid = tagText &&
tagText.length >= options.minLength &&
tagText.length <= options.maxLength &&
options.allowedTagsPattern.test(tagText) &&
!tiUtil.findInObjectArray(self.items, tag, options.displayProperty);

if (okToAddTag !== undefined) {
valid = valid && okToAddTag;
}

return tagText &&
tagText.length >= options.minLength &&
tagText.length <= options.maxLength &&
options.allowedTagsPattern.test(tagText) &&
!tiUtil.findInObjectArray(self.items, tag, options.displayProperty);
return valid;
};

self.items = [];
Expand Down Expand Up @@ -90,9 +100,14 @@ tagsInput.directive('tagsInput', function($timeout, $document, tagsInputConfig,
};

self.remove = function(index) {
var tag = self.items.splice(index, 1)[0];
events.trigger('tag-removed', { $tag: tag });
return tag;
var tag = self.items[index];
var okToRemoveTag = additionalCallbacks.onTagRemoving({ $tag: tag });

if (okToRemoveTag === undefined || okToRemoveTag) {
self.items.splice(index, 1);
events.trigger('tag-removed', { $tag: tag });
return tag;
}
};

self.removeLast = function() {
Expand Down Expand Up @@ -121,8 +136,10 @@ tagsInput.directive('tagsInput', function($timeout, $document, tagsInputConfig,
require: 'ngModel',
scope: {
tags: '=ngModel',
onTagAdding: '&',
onTagAdded: '&',
onInvalidTag: '&',
onTagRemoving: '&',
onTagRemoved: '&'
},
replace: false,
Expand Down Expand Up @@ -155,7 +172,12 @@ tagsInput.directive('tagsInput', function($timeout, $document, tagsInputConfig,
spellcheck: [Boolean, true]
});

$scope.tagList = new TagList($scope.options, $scope.events);
var additionalCallbacks = {
onTagAdding: $scope.onTagAdding,
onTagRemoving: $scope.onTagRemoving
};

$scope.tagList = new TagList($scope.options, $scope.events, additionalCallbacks);

this.registerAutocomplete = function() {
var input = $element.find('input');
Expand Down
36 changes: 36 additions & 0 deletions test/tags-input.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,42 @@ describe('tags-input directive', function() {
expect($scope.tags).toEqual([{ text: 'foo' }]);
});

it('will not add a tag based on custom logic specified by the on-tag-adding option', function() {
// Arrange
$scope.tagIsNotInvalid = function(newTag) {
return (newTag.text !== 'INVALID');
};

compile('on-tag-adding="tagIsNotInvalid($tag)"');

// Act
newTag('foo');
newTag('bar');
newTag('INVALID');

// Assert
expect($scope.tags).toEqual([{ text: 'foo' }, { text: 'bar' }]);
});

it('will not remove a tag based on custom logic specified by the on-tag-removing option', function() {
// Arrange
$scope.tagIsNotPermanent = function(newTag) {
return (newTag.text !== 'PERMANENT');
};

compile('on-tag-removing="tagIsNotPermanent($tag)"');

// Act
newTag('foo');
newTag('PERMANENT');
newTag('bar');

getRemoveButton(1).click();

// Assert
expect($scope.tags).toEqual([{ text: 'foo' }, { text: 'PERMANENT' }, { text: 'bar' }]);
});

it('makes the input field invalid when a duplicate tag is tried to be added', function() {
// Arrange
compile();
Expand Down

0 comments on commit c4ceed5

Please sign in to comment.