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

Commit 628433e

Browse files
committed
feat(tagsInput): Allow double click to edit and split tag by input-split-pattern
Add allowDblclickToEdit、inputSplitPattern options so that you can use double click to edit current tag and split tag by pattern Update general test spec
1 parent 87d0e6b commit 628433e

6 files changed

Lines changed: 314 additions & 47 deletions

File tree

src/init.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import AutocompleteDirective from './auto-complete';
66
import AutocompleteMatchDirective from './auto-complete-match';
77
import AutosizeDirective from './autosize';
88
import BindAttributesDirective from './bind-attrs';
9+
import SelectallDirective from './selectall';
910
import TranscludeAppendDirective from './transclude-append';
1011
import TagsInputConfigurationProvider from './configuration';
1112
import UtilService from './util';
@@ -19,6 +20,7 @@ angular.module('ngTagsInput', [])
1920
.directive('tiAutosize', AutosizeDirective)
2021
.directive('tiBindAttrs', BindAttributesDirective)
2122
.directive('tiTranscludeAppend', TranscludeAppendDirective)
23+
.directive('tiSelectall', SelectallDirective)
2224
.factory('tiUtil', UtilService)
2325
.constant('tiConstants', Constants)
2426
.provider('tagsInputConfig', TagsInputConfigurationProvider)

src/selectall.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @ngdoc directive
3+
* @name tiSelectall
4+
* @module ngTagsInput
5+
*
6+
* @description
7+
* Automatically select all and focus the input. Used internally by tagsInput directive.
8+
*/
9+
10+
export default function SelectallDirective($timeout, $parse) {
11+
'ngInject';
12+
return {
13+
scope: {},
14+
link(scope, element, attrs) {
15+
scope.selectAll = false;
16+
let model = $parse(attrs.tiSelectall);
17+
let selectAll = () => {
18+
$timeout(() => {
19+
element[0].focus();
20+
element[0].select();
21+
});
22+
};
23+
scope.$watch(model, (value) =>{
24+
if (value === true) {
25+
selectAll();
26+
}
27+
scope.selectAll = value;
28+
});
29+
}
30+
};
31+
}

src/tags-input.js

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,25 +52,27 @@
5252
* promise is returned, the tag will not be removed.
5353
* @param {expression=} [onTagRemoved=NA] Expression to evaluate upon removing an existing tag. The removed tag is available as $tag.
5454
* @param {expression=} [onTagClicked=NA] Expression to evaluate upon clicking an existing tag. The clicked tag is available as $tag.
55+
* @param {boolean=} [allowDblclickToEdit=false] Flag indicating that allow double click to edit current tag.
56+
* @param {string=} [inputSplitPattern=null] Regular expression that split edit input tags.
5557
*/
5658
export default function TagsInputDirective($timeout, $document, $window, $q, tagsInputConfig, tiUtil, tiConstants) {
5759
'ngInject';
5860

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

62-
let getTagText = tag =>tiUtil.safeToString(tag[options.displayProperty]);
64+
let getTagText = tag => tiUtil.safeToString(tag[options.displayProperty]);
6365
let setTagText = (tag, text) => {
6466
tag[options.displayProperty] = text;
6567
};
6668

6769
let canAddTag = tag => {
6870
let tagText = getTagText(tag);
6971
let valid = tagText &&
70-
tagText.length >= options.minLength &&
71-
tagText.length <= options.maxLength &&
72-
options.allowedTagsPattern.test(tagText) &&
73-
!tiUtil.findInObjectArray(self.items, tag, options.keyProperty || options.displayProperty);
72+
tagText.length >= options.minLength &&
73+
tagText.length <= options.maxLength &&
74+
options.allowedTagsPattern.test(tagText) &&
75+
!tiUtil.findInObjectArray(self.items, tag, options.keyProperty || options.displayProperty);
7476

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

90+
self.addTextArr = textArr => {
91+
textArr.forEach(text => self.addText(text));
92+
}
93+
8894
self.add = tag => {
8995
let tagText = getTagText(tag);
9096

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

97103
return canAddTag(tag)
98-
.then(() =>{
104+
.then(() => {
99105
self.items.push(tag);
100106
events.trigger('tag-added', { $tag: tag });
101107
})
@@ -201,6 +207,8 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
201207
allowLeftoverText: [Boolean, false],
202208
addFromAutocompleteOnly: [Boolean, false],
203209
spellcheck: [Boolean, true],
210+
allowDblclickToEdit: [Boolean, false],
211+
inputSplitPattern: [RegExp, null],
204212
useStrings: [Boolean, false]
205213
});
206214

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

211219
this.registerAutocomplete = () => ({
212-
addTag: function(tag) {
220+
addTag: function (tag) {
213221
return $scope.tagList.add(tag);
214222
},
215-
getTags: function() {
223+
getTags: function () {
216224
return $scope.tagList.items;
217225
},
218-
getCurrentTagText: function() {
226+
getCurrentTagText: function () {
219227
return $scope.newTag.text();
220228
},
221-
getOptions: function() {
229+
getOptions: function () {
222230
return $scope.options;
223231
},
224-
getTemplateScope: function() {
232+
getTemplateScope: function () {
225233
return $scope.templateScope;
226234
},
227-
on: function(name, handler) {
235+
on: function (name, handler) {
228236
$scope.events.on(name, handler, true);
229237
return this;
230238
}
@@ -263,6 +271,20 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
263271

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

274+
scope.isEditing = false;
275+
276+
scope.editingTag = {
277+
text(value) {
278+
if (angular.isDefined(value)) {
279+
scope.editingText = value;
280+
events.trigger('edit-input-change', value);
281+
} else {
282+
return scope.editingText || '';
283+
}
284+
},
285+
invalid: null
286+
};
287+
266288
scope.newTag = {
267289
text(value) {
268290
if (angular.isDefined(value)) {
@@ -281,8 +303,8 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
281303
scope.getTagClass = (tag, index) => {
282304
let selected = tag === tagList.selected;
283305
return [
284-
scope.tagClass({$tag: tag, $index: index, $selected: selected}),
285-
{ selected: selected }
306+
scope.tagClass({ $tag: tag, $index: index, $selected: selected }),
307+
{ selected: selected }
286308
];
287309
};
288310

@@ -337,6 +359,9 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
337359
}
338360
});
339361
},
362+
editBlur($event, tag) {
363+
events.trigger('edit-input-blur', tag);
364+
},
340365
paste($event) {
341366
$event.getTextData = () => {
342367
let clipboardData = $event.clipboardData || ($event.originalEvent && $event.originalEvent.clipboardData);
@@ -356,6 +381,9 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
356381
tag: {
357382
click(tag) {
358383
events.trigger('tag-clicked', { $tag: tag });
384+
},
385+
dblclick(tag) {
386+
events.trigger('tag-dblclicked', tag);
359387
}
360388
}
361389
};
@@ -365,6 +393,13 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
365393
.on('invalid-tag', scope.onInvalidTag)
366394
.on('tag-removed', scope.onTagRemoved)
367395
.on('tag-clicked', scope.onTagClicked)
396+
.on('tag-dblclicked', (tag) => {
397+
if (options.allowDblclickToEdit) {
398+
scope.editingTag.text(tag.text);
399+
tag.editable = true;
400+
scope.isEditing = true;
401+
}
402+
})
368403
.on('tag-added', () => {
369404
scope.newTag.text('');
370405
})
@@ -394,11 +429,26 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
394429
})
395430
.on('input-blur', () => {
396431
if (options.addOnBlur && !options.addFromAutocompleteOnly) {
397-
tagList.addText(scope.newTag.text());
432+
let tags = scope.newTag.text().split(options.inputSplitPattern);
433+
tagList.addTextArr(tags);
398434
}
399435
element.triggerHandler('blur');
400436
setElementValidity();
401437
})
438+
.on('edit-input-blur', tag => {
439+
let editingText = scope.editingTag.text();
440+
let tags = editingText.split(options.inputSplitPattern);
441+
let firstTagText = tags.shift();
442+
tag.text = firstTagText;
443+
tagList.addTextArr(tags);
444+
tag.editable = false;
445+
scope.isEditing = false;
446+
focusInput();
447+
})
448+
.on('edit-input-change', () => {
449+
tagList.clearSelection();
450+
scope.editingTag.invalid = null;
451+
})
402452
.on('input-keydown', event => {
403453
let key = event.keyCode;
404454

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

415465
let shouldAdd = !options.addFromAutocompleteOnly && addKeys[key];
416466
let shouldRemove = (key === tiConstants.KEYS.backspace || key === tiConstants.KEYS.delete) && tagList.selected;
417-
let shouldEditLastTag = key === tiConstants.KEYS.backspace && scope.newTag.text().length === 0 && options.enableEditingLastTag;
467+
let shouldEditLastTag = key === tiConstants.KEYS.backspace && scope.newTag.text().length === 0 && options.enableEditingLastTag && !scope.isEditing;
418468
let shouldSelect = (key === tiConstants.KEYS.backspace || key === tiConstants.KEYS.left || key === tiConstants.KEYS.right) &&
419469
scope.newTag.text().length === 0 && !options.enableEditingLastTag;
420470

421471
if (shouldAdd) {
422-
tagList.addText(scope.newTag.text());
472+
if (scope.isEditing) {
473+
element.find('input')[0].blur();
474+
return;
475+
}
476+
let tags = scope.newTag.text().split(options.inputSplitPattern);
477+
tagList.addTextArr(tags);
423478
}
424479
else if (shouldEditLastTag) {
425480
tagList.selectPrior();
@@ -451,13 +506,12 @@ export default function TagsInputDirective($timeout, $document, $window, $q, tag
451506
let tags = data.split(options.pasteSplitPattern);
452507

453508
if (tags.length > 1) {
454-
tags.forEach(tag => {
455-
tagList.addText(tag);
456-
});
509+
tagList.addTextArr(tags);
457510
event.preventDefault();
458511
}
459512
}
460513
});
461514
}
462515
};
463516
}
517+

templates/tags-input.html

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
<div class="host" tabindex="-1" ng-click="eventHandlers.host.click()" ti-transclude-append>
22
<div class="tags" ng-class="{focused: hasFocus}">
33
<ul class="tag-list">
4-
<li class="tag-item"
5-
ng-repeat="tag in tagList.items track by track(tag)"
4+
<li ng-repeat="tag in tagList.items track by track(tag)"
65
ng-class="getTagClass(tag, $index)"
7-
ng-click="eventHandlers.tag.click(tag)">
8-
<ti-tag-item scope="templateScope" data="::tag"></ti-tag-item>
6+
ng-click="eventHandlers.tag.click(tag)"
7+
ng-dblclick="eventHandlers.tag.dblclick(tag)">
8+
<ti-tag-item class="tag-item" scope="templateScope" data="tag" ng-hide="tag.editable"></ti-tag-item>
9+
<input class="input"
10+
autocomplete="off"
11+
ng-model="editingTag.text"
12+
ng-model-options="{getterSetter: true}"
13+
ng-click="$event.stopPropagation();"
14+
ng-if="tag.editable"
15+
ng-keydown="eventHandlers.input.keydown($event)"
16+
ng-blur="eventHandlers.input.editBlur($event,tag)"
17+
ng-paste="eventHandlers.input.paste($event)"
18+
ng-disabled="disabled"
19+
ng-class="{'invalid-tag': editingTag.invalid}"
20+
ti-selectall='true'
21+
ti-autosize="">
922
</li>
1023
</ul>
1124
<input class="input"
@@ -22,4 +35,4 @@
2235
ti-bind-attrs="{type: options.type, placeholder: options.placeholder, tabindex: options.tabindex, spellcheck: options.spellcheck}"
2336
ti-autosize>
2437
</div>
25-
</div>
38+
</div>

test/selectall.spec.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
describe('selectall directive', () => {
4+
var $scope, $compile, element, $timeout, isolateScope;
5+
beforeEach(() => {
6+
module('ngTagsInput');
7+
8+
inject(($rootScope, _$compile_, _$timeout_) => {
9+
$scope = $scope = $rootScope.$new();
10+
$compile = _$compile_;
11+
$timeout = _$timeout_;
12+
});
13+
14+
});
15+
16+
function compile() {
17+
let attributes = $.makeArray(arguments).join(' ');
18+
19+
element = angular.element('<input class="input" ng-model="model" ' + attributes + '>');
20+
$compile(element)($scope);
21+
isolateScope = element.isolateScope();
22+
$scope.$digest();
23+
}
24+
25+
it('input select all and focus', () => {
26+
// Act/Arrange
27+
compile('ti-selectall="true"');
28+
$timeout.flush();
29+
30+
//Assert
31+
expect(isolateScope.selectAll).toBe(true);
32+
});
33+
34+
it('input not select all and focus', () => {
35+
// Act/Arrange
36+
compile('ti-selectall="false"');
37+
$timeout.flush();
38+
39+
//Assert
40+
expect(isolateScope.selectAll).toBe(false);
41+
});
42+
43+
44+
});

0 commit comments

Comments
 (0)