Skip to content
This repository was archived by the owner on Nov 22, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import AutocompleteDirective from './auto-complete';
import AutocompleteMatchDirective from './auto-complete-match';
import AutosizeDirective from './autosize';
import BindAttributesDirective from './bind-attrs';
import SelectallDirective from './selectall';
import TranscludeAppendDirective from './transclude-append';
import TagsInputConfigurationProvider from './configuration';
import UtilService from './util';
Expand All @@ -19,6 +20,7 @@ angular.module('ngTagsInput', [])
.directive('tiAutosize', AutosizeDirective)
.directive('tiBindAttrs', BindAttributesDirective)
.directive('tiTranscludeAppend', TranscludeAppendDirective)
.directive('tiSelectall', SelectallDirective)
.factory('tiUtil', UtilService)
.constant('tiConstants', Constants)
.provider('tagsInputConfig', TagsInputConfigurationProvider)
Expand Down
31 changes: 31 additions & 0 deletions src/selectall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @ngdoc directive
* @name tiSelectall
* @module ngTagsInput
*
* @description
* Automatically select all and focus the input. Used internally by tagsInput directive.
*/

export default function SelectallDirective($timeout, $parse) {
'ngInject';
return {
scope: {},
link(scope, element, attrs) {
scope.selectAll = false;
let model = $parse(attrs.tiSelectall);
let selectAll = () => {
$timeout(() => {
element[0].focus();
element[0].select();
});
};
scope.$watch(model, (value) =>{
if (value === true) {
selectAll();
}
scope.selectAll = value;
});
}
};
}
94 changes: 74 additions & 20 deletions src/tags-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,25 +52,27 @@
* promise is returned, the tag will not be removed.
* @param {expression=} [onTagRemoved=NA] Expression to evaluate upon removing an existing tag. The removed tag is available as $tag.
* @param {expression=} [onTagClicked=NA] Expression to evaluate upon clicking an existing tag. The clicked tag is available as $tag.
* @param {boolean=} [allowDblclickToEdit=false] Flag indicating that allow double click to edit current tag.
* @param {string=} [inputSplitPattern=null] Regular expression that split edit input tags.
*/
export default function TagsInputDirective($timeout, $document, $window, $q, tagsInputConfig, tiUtil, tiConstants) {
'ngInject';

function TagList(options, events, onTagAdding, onTagRemoving) {
let self = {};

let getTagText = tag =>tiUtil.safeToString(tag[options.displayProperty]);
let getTagText = tag => tiUtil.safeToString(tag[options.displayProperty]);
let setTagText = (tag, text) => {
tag[options.displayProperty] = text;
};

let canAddTag = tag => {
let tagText = getTagText(tag);
let valid = tagText &&
tagText.length >= options.minLength &&
tagText.length <= options.maxLength &&
options.allowedTagsPattern.test(tagText) &&
!tiUtil.findInObjectArray(self.items, tag, options.keyProperty || options.displayProperty);
tagText.length >= options.minLength &&
tagText.length <= options.maxLength &&
options.allowedTagsPattern.test(tagText) &&
!tiUtil.findInObjectArray(self.items, tag, options.keyProperty || options.displayProperty);

return $q.when(valid && onTagAdding({ $tag: tag })).then(tiUtil.promisifyValue);
};
Expand All @@ -85,6 +87,10 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
return self.add(tag);
};

self.addTextArr = textArr => {
textArr.forEach(text => self.addText(text));
}

self.add = tag => {
let tagText = getTagText(tag);

Expand All @@ -95,7 +101,7 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
setTagText(tag, tagText);

return canAddTag(tag)
.then(() =>{
.then(() => {
self.items.push(tag);
events.trigger('tag-added', { $tag: tag });
})
Expand Down Expand Up @@ -201,6 +207,8 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
allowLeftoverText: [Boolean, false],
addFromAutocompleteOnly: [Boolean, false],
spellcheck: [Boolean, true],
allowDblclickToEdit: [Boolean, false],
inputSplitPattern: [RegExp, null],
useStrings: [Boolean, false]
});

Expand All @@ -209,22 +217,22 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
tiUtil.handleUndefinedResult($scope.onTagRemoving, true));

this.registerAutocomplete = () => ({
addTag: function(tag) {
addTag: function (tag) {
return $scope.tagList.add(tag);
},
getTags: function() {
getTags: function () {
return $scope.tagList.items;
},
getCurrentTagText: function() {
getCurrentTagText: function () {
return $scope.newTag.text();
},
getOptions: function() {
getOptions: function () {
return $scope.options;
},
getTemplateScope: function() {
getTemplateScope: function () {
return $scope.templateScope;
},
on: function(name, handler) {
on: function (name, handler) {
$scope.events.on(name, handler, true);
return this;
}
Expand Down Expand Up @@ -263,6 +271,20 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag

ngModelCtrl.$isEmpty = value => !value || !value.length;

scope.isEditing = false;

scope.editingTag = {
text(value) {
if (angular.isDefined(value)) {
scope.editingText = value;
events.trigger('edit-input-change', value);
} else {
return scope.editingText || '';
}
},
invalid: null
};

scope.newTag = {
text(value) {
if (angular.isDefined(value)) {
Expand All @@ -281,8 +303,8 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
scope.getTagClass = (tag, index) => {
let selected = tag === tagList.selected;
return [
scope.tagClass({$tag: tag, $index: index, $selected: selected}),
{ selected: selected }
scope.tagClass({ $tag: tag, $index: index, $selected: selected }),
{ selected: selected }
];
};

Expand Down Expand Up @@ -337,6 +359,9 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
}
});
},
editBlur($event, tag) {
events.trigger('edit-input-blur', tag);
},
paste($event) {
$event.getTextData = () => {
let clipboardData = $event.clipboardData || ($event.originalEvent && $event.originalEvent.clipboardData);
Expand All @@ -356,6 +381,9 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
tag: {
click(tag) {
events.trigger('tag-clicked', { $tag: tag });
},
dblclick(tag) {
events.trigger('tag-dblclicked', tag);
}
}
};
Expand All @@ -365,6 +393,13 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
.on('invalid-tag', scope.onInvalidTag)
.on('tag-removed', scope.onTagRemoved)
.on('tag-clicked', scope.onTagClicked)
.on('tag-dblclicked', (tag) => {
if (options.allowDblclickToEdit) {
scope.editingTag.text(tag.text);
tag.editable = true;
scope.isEditing = true;
}
})
.on('tag-added', () => {
scope.newTag.text('');
})
Expand Down Expand Up @@ -394,11 +429,26 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
})
.on('input-blur', () => {
if (options.addOnBlur && !options.addFromAutocompleteOnly) {
tagList.addText(scope.newTag.text());
let tags = scope.newTag.text().split(options.inputSplitPattern);
tagList.addTextArr(tags);
}
element.triggerHandler('blur');
setElementValidity();
})
.on('edit-input-blur', tag => {
let editingText = scope.editingTag.text();
let tags = editingText.split(options.inputSplitPattern);
let firstTagText = tags.shift();
tag.text = firstTagText;
tagList.addTextArr(tags);
tag.editable = false;
scope.isEditing = false;
focusInput();
})
.on('edit-input-change', () => {
tagList.clearSelection();
scope.editingTag.invalid = null;
})
.on('input-keydown', event => {
let key = event.keyCode;

Expand All @@ -414,12 +464,17 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag

let shouldAdd = !options.addFromAutocompleteOnly && addKeys[key];
let shouldRemove = (key === tiConstants.KEYS.backspace || key === tiConstants.KEYS.delete) && tagList.selected;
let shouldEditLastTag = key === tiConstants.KEYS.backspace && scope.newTag.text().length === 0 && options.enableEditingLastTag;
let shouldEditLastTag = key === tiConstants.KEYS.backspace && scope.newTag.text().length === 0 && options.enableEditingLastTag && !scope.isEditing;
let shouldSelect = (key === tiConstants.KEYS.backspace || key === tiConstants.KEYS.left || key === tiConstants.KEYS.right) &&
scope.newTag.text().length === 0 && !options.enableEditingLastTag;

if (shouldAdd) {
tagList.addText(scope.newTag.text());
if (scope.isEditing) {
element.find('input')[0].blur();
return;
}
let tags = scope.newTag.text().split(options.inputSplitPattern);
tagList.addTextArr(tags);
}
else if (shouldEditLastTag) {
tagList.selectPrior();
Expand Down Expand Up @@ -451,13 +506,12 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
let tags = data.split(options.pasteSplitPattern);

if (tags.length > 1) {
tags.forEach(tag => {
tagList.addText(tag);
});
tagList.addTextArr(tags);
event.preventDefault();
}
}
});
}
};
}

23 changes: 18 additions & 5 deletions templates/tags-input.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
<div class="host" tabindex="-1" ng-click="eventHandlers.host.click()" ti-transclude-append>
<div class="tags" ng-class="{focused: hasFocus}">
<ul class="tag-list">
<li class="tag-item"
ng-repeat="tag in tagList.items track by track(tag)"
<li ng-repeat="tag in tagList.items track by track(tag)"
ng-class="getTagClass(tag, $index)"
ng-click="eventHandlers.tag.click(tag)">
<ti-tag-item scope="templateScope" data="::tag"></ti-tag-item>
ng-click="eventHandlers.tag.click(tag)"
ng-dblclick="eventHandlers.tag.dblclick(tag)">
<ti-tag-item class="tag-item" scope="templateScope" data="tag" ng-hide="tag.editable"></ti-tag-item>
<input class="input"
autocomplete="off"
ng-model="editingTag.text"
ng-model-options="{getterSetter: true}"
ng-click="$event.stopPropagation();"
ng-if="tag.editable"
ng-keydown="eventHandlers.input.keydown($event)"
ng-blur="eventHandlers.input.editBlur($event,tag)"
ng-paste="eventHandlers.input.paste($event)"
ng-disabled="disabled"
ng-class="{'invalid-tag': editingTag.invalid}"
ti-selectall='true'
ti-autosize="">
</li>
</ul>
<input class="input"
Expand All @@ -22,4 +35,4 @@
ti-bind-attrs="{type: options.type, placeholder: options.placeholder, tabindex: options.tabindex, spellcheck: options.spellcheck}"
ti-autosize>
</div>
</div>
</div>
44 changes: 44 additions & 0 deletions test/selectall.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

describe('selectall directive', () => {
var $scope, $compile, element, $timeout, isolateScope;
beforeEach(() => {
module('ngTagsInput');

inject(($rootScope, _$compile_, _$timeout_) => {
$scope = $scope = $rootScope.$new();
$compile = _$compile_;
$timeout = _$timeout_;
});

});

function compile() {
let attributes = $.makeArray(arguments).join(' ');

element = angular.element('<input class="input" ng-model="model" ' + attributes + '>');
$compile(element)($scope);
isolateScope = element.isolateScope();
$scope.$digest();
}

it('input select all and focus', () => {
// Act/Arrange
compile('ti-selectall="true"');
$timeout.flush();

//Assert
expect(isolateScope.selectAll).toBe(true);
});

it('input not select all and focus', () => {
// Act/Arrange
compile('ti-selectall="false"');
$timeout.flush();

//Assert
expect(isolateScope.selectAll).toBe(false);
});


});
Loading