angular-ui-select-rails 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a25b4179254a98771c81ff597172d13cdeff8ec9
4
+ data.tar.gz: fe542bb392e22e4bd82861758424512d8d2fc5e6
5
+ SHA512:
6
+ metadata.gz: 1b86b082650d629c685129cdbfea61dab310843b4949ecf98f4475ee99555de4dc29d952d1c4f5fc0ba24342cf9584c15f41b089190a2e3722361095d9ce4b76
7
+ data.tar.gz: 9a082e124574c402d17b6e4a74d36e81634f422decfb2f01bf0b6f9f1542260764aadcc767d738f9ad3040c13783be31fadc2e02d94a7fbf9cd5b685330fe33a
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Steve Ellis
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,36 @@
1
+ # Angular::Ui::Select::Rails
2
+
3
+ angular-ui-select-rails is a asset pipeline friendly version of [AngularJS ui-select](https://github.com/angular-ui/ui-select).
4
+
5
+ ## Installation
6
+
7
+ Add the gem in your Gemfile:
8
+
9
+ gem 'angular-ui-select-rails'
10
+
11
+ After bundling, add the directives to your manifest files.
12
+
13
+ JavaScripts (application.js):
14
+
15
+ //= require angular-ui-select
16
+
17
+ Stylesheets (application.css):
18
+
19
+ //= require angular-ui-select
20
+
21
+ Add 'ui.select' into your app declaration:
22
+
23
+ app = angular.module('MyApp', ["ui.select"])
24
+
25
+
26
+ ## Usage
27
+
28
+ See https://github.com/angular-ui/ui-select
29
+
30
+ ## Contributing
31
+
32
+ 1. Fork it ( http://github.com/<my-github-username>/angular-ui-select-rails/fork )
33
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
34
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
35
+ 4. Push to the branch (`git push origin my-new-feature`)
36
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'angular-ui-select-rails/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "angular-ui-select-rails"
8
+ spec.version = Angular::Ui::Select::Rails::VERSION
9
+ spec.authors = ["Steve Ellis"]
10
+ spec.email = ["email@steveell.is"]
11
+ spec.summary = %q{Packaged version of AngularJS ui-select.}
12
+ spec.description = %q{Asset pipeline compatible version of https://github.com/angular-ui/ui-select}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["{lib,vendor}/**/*"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,15 @@
1
+ require "angular-ui-select-rails/version"
2
+ require "jquery-rails"
3
+ require "select2-rails"
4
+
5
+ module Angular
6
+ module Ui
7
+ module Select
8
+ module Rails
9
+ class Engine < ::Rails::Engine
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,9 @@
1
+ module Angular
2
+ module Ui
3
+ module Select
4
+ module Rails
5
+ VERSION = "0.9.5"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,1327 @@
1
+ /*!
2
+ * ui-select
3
+ * http://github.com/angular-ui/ui-select
4
+ * Version: 0.9.5 - 2014-12-12T16:07:20.856Z
5
+ * License: MIT
6
+ */
7
+
8
+
9
+ (function () {
10
+ "use strict";
11
+
12
+ var KEY = {
13
+ TAB: 9,
14
+ ENTER: 13,
15
+ ESC: 27,
16
+ SPACE: 32,
17
+ LEFT: 37,
18
+ UP: 38,
19
+ RIGHT: 39,
20
+ DOWN: 40,
21
+ SHIFT: 16,
22
+ CTRL: 17,
23
+ ALT: 18,
24
+ PAGE_UP: 33,
25
+ PAGE_DOWN: 34,
26
+ HOME: 36,
27
+ END: 35,
28
+ BACKSPACE: 8,
29
+ DELETE: 46,
30
+ COMMAND: 91,
31
+
32
+ MAP: { 91 : "COMMAND", 8 : "BACKSPACE" , 9 : "TAB" , 13 : "ENTER" , 16 : "SHIFT" , 17 : "CTRL" , 18 : "ALT" , 19 : "PAUSEBREAK" , 20 : "CAPSLOCK" , 27 : "ESC" , 32 : "SPACE" , 33 : "PAGE_UP", 34 : "PAGE_DOWN" , 35 : "END" , 36 : "HOME" , 37 : "LEFT" , 38 : "UP" , 39 : "RIGHT" , 40 : "DOWN" , 43 : "+" , 44 : "PRINTSCREEN" , 45 : "INSERT" , 46 : "DELETE", 48 : "0" , 49 : "1" , 50 : "2" , 51 : "3" , 52 : "4" , 53 : "5" , 54 : "6" , 55 : "7" , 56 : "8" , 57 : "9" , 59 : ";", 61 : "=" , 65 : "A" , 66 : "B" , 67 : "C" , 68 : "D" , 69 : "E" , 70 : "F" , 71 : "G" , 72 : "H" , 73 : "I" , 74 : "J" , 75 : "K" , 76 : "L", 77 : "M" , 78 : "N" , 79 : "O" , 80 : "P" , 81 : "Q" , 82 : "R" , 83 : "S" , 84 : "T" , 85 : "U" , 86 : "V" , 87 : "W" , 88 : "X" , 89 : "Y" , 90 : "Z", 96 : "0" , 97 : "1" , 98 : "2" , 99 : "3" , 100 : "4" , 101 : "5" , 102 : "6" , 103 : "7" , 104 : "8" , 105 : "9", 106 : "*" , 107 : "+" , 109 : "-" , 110 : "." , 111 : "/", 112 : "F1" , 113 : "F2" , 114 : "F3" , 115 : "F4" , 116 : "F5" , 117 : "F6" , 118 : "F7" , 119 : "F8" , 120 : "F9" , 121 : "F10" , 122 : "F11" , 123 : "F12", 144 : "NUMLOCK" , 145 : "SCROLLLOCK" , 186 : ";" , 187 : "=" , 188 : "," , 189 : "-" , 190 : "." , 191 : "/" , 192 : "`" , 219 : "[" , 220 : "\\" , 221 : "]" , 222 : "'"
33
+ },
34
+
35
+ isControl: function (e) {
36
+ var k = e.which;
37
+ switch (k) {
38
+ case KEY.COMMAND:
39
+ case KEY.SHIFT:
40
+ case KEY.CTRL:
41
+ case KEY.ALT:
42
+ return true;
43
+ }
44
+
45
+ if (e.metaKey) return true;
46
+
47
+ return false;
48
+ },
49
+ isFunctionKey: function (k) {
50
+ k = k.which ? k.which : k;
51
+ return k >= 112 && k <= 123;
52
+ },
53
+ isVerticalMovement: function (k){
54
+ return ~[KEY.UP, KEY.DOWN].indexOf(k);
55
+ },
56
+ isHorizontalMovement: function (k){
57
+ return ~[KEY.LEFT,KEY.RIGHT,KEY.BACKSPACE,KEY.DELETE].indexOf(k);
58
+ }
59
+ };
60
+
61
+ /**
62
+ * Add querySelectorAll() to jqLite.
63
+ *
64
+ * jqLite find() is limited to lookups by tag name.
65
+ * TODO This will change with future versions of AngularJS, to be removed when this happens
66
+ *
67
+ * See jqLite.find - why not use querySelectorAll? https://github.com/angular/angular.js/issues/3586
68
+ * See feat(jqLite): use querySelectorAll instead of getElementsByTagName in jqLite.find https://github.com/angular/angular.js/pull/3598
69
+ */
70
+ if (angular.element.prototype.querySelectorAll === undefined) {
71
+ angular.element.prototype.querySelectorAll = function(selector) {
72
+ return angular.element(this[0].querySelectorAll(selector));
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Add closest() to jqLite.
78
+ */
79
+ if (angular.element.prototype.closest === undefined) {
80
+ angular.element.prototype.closest = function( selector) {
81
+ var elem = this[0];
82
+ var matchesSelector = elem.matches || elem.webkitMatchesSelector || elem.mozMatchesSelector || elem.msMatchesSelector;
83
+
84
+ while (elem) {
85
+ if (matchesSelector.bind(elem)(selector)) {
86
+ return elem;
87
+ } else {
88
+ elem = elem.parentElement;
89
+ }
90
+ }
91
+ return false;
92
+ };
93
+ }
94
+
95
+ angular.module('ui.select', [])
96
+
97
+ .constant('uiSelectConfig', {
98
+ theme: 'bootstrap',
99
+ searchEnabled: true,
100
+ placeholder: '', // Empty by default, like HTML tag <select>
101
+ refreshDelay: 1000, // In milliseconds
102
+ closeOnSelect: true
103
+ })
104
+
105
+ // See Rename minErr and make it accessible from outside https://github.com/angular/angular.js/issues/6913
106
+ .service('uiSelectMinErr', function() {
107
+ var minErr = angular.$$minErr('ui.select');
108
+ return function() {
109
+ var error = minErr.apply(this, arguments);
110
+ var message = error.message.replace(new RegExp('\nhttp://errors.angularjs.org/.*'), '');
111
+ return new Error(message);
112
+ };
113
+ })
114
+
115
+ /**
116
+ * Parses "repeat" attribute.
117
+ *
118
+ * Taken from AngularJS ngRepeat source code
119
+ * See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L211
120
+ *
121
+ * Original discussion about parsing "repeat" attribute instead of fully relying on ng-repeat:
122
+ * https://github.com/angular-ui/ui-select/commit/5dd63ad#commitcomment-5504697
123
+ */
124
+ .service('RepeatParser', ['uiSelectMinErr','$parse', function(uiSelectMinErr, $parse) {
125
+ var self = this;
126
+
127
+ /**
128
+ * Example:
129
+ * expression = "address in addresses | filter: {street: $select.search} track by $index"
130
+ * itemName = "address",
131
+ * source = "addresses | filter: {street: $select.search}",
132
+ * trackByExp = "$index",
133
+ */
134
+ self.parse = function(expression) {
135
+
136
+ var match = expression.match(/^\s*(?:([\s\S]+?)\s+as\s+)?([\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
137
+
138
+ if (!match) {
139
+ throw uiSelectMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
140
+ expression);
141
+ }
142
+
143
+ return {
144
+ itemName: match[2], // (lhs) Left-hand side,
145
+ source: $parse(match[3]),
146
+ trackByExp: match[4],
147
+ modelMapper: $parse(match[1] || match[2])
148
+ };
149
+
150
+ };
151
+
152
+ self.getGroupNgRepeatExpression = function() {
153
+ return '$group in $select.groups';
154
+ };
155
+
156
+ self.getNgRepeatExpression = function(itemName, source, trackByExp, grouped) {
157
+ var expression = itemName + ' in ' + (grouped ? '$group.items' : source);
158
+ if (trackByExp) {
159
+ expression += ' track by ' + trackByExp;
160
+ }
161
+ return expression;
162
+ };
163
+ }])
164
+
165
+ /**
166
+ * Contains ui-select "intelligence".
167
+ *
168
+ * The goal is to limit dependency on the DOM whenever possible and
169
+ * put as much logic in the controller (instead of the link functions) as possible so it can be easily tested.
170
+ */
171
+ .controller('uiSelectCtrl',
172
+ ['$scope', '$element', '$timeout', '$filter', 'RepeatParser', 'uiSelectMinErr', 'uiSelectConfig',
173
+ function($scope, $element, $timeout, $filter, RepeatParser, uiSelectMinErr, uiSelectConfig) {
174
+
175
+ var ctrl = this;
176
+
177
+ var EMPTY_SEARCH = '';
178
+
179
+ ctrl.placeholder = undefined;
180
+ ctrl.search = EMPTY_SEARCH;
181
+ ctrl.activeIndex = 0;
182
+ ctrl.activeMatchIndex = -1;
183
+ ctrl.items = [];
184
+ ctrl.selected = undefined;
185
+ ctrl.open = false;
186
+ ctrl.focus = false;
187
+ ctrl.focusser = undefined; //Reference to input element used to handle focus events
188
+ ctrl.disabled = undefined; // Initialized inside uiSelect directive link function
189
+ ctrl.searchEnabled = undefined; // Initialized inside uiSelect directive link function
190
+ ctrl.resetSearchInput = undefined; // Initialized inside uiSelect directive link function
191
+ ctrl.refreshDelay = undefined; // Initialized inside uiSelectChoices directive link function
192
+ ctrl.multiple = false; // Initialized inside uiSelect directive link function
193
+ ctrl.disableChoiceExpression = undefined; // Initialized inside uiSelect directive link function
194
+ ctrl.tagging = {isActivated: false, fct: undefined};
195
+ ctrl.taggingTokens = {isActivated: false, tokens: undefined};
196
+ ctrl.lockChoiceExpression = undefined; // Initialized inside uiSelect directive link function
197
+ ctrl.closeOnSelect = true; // Initialized inside uiSelect directive link function
198
+ ctrl.clickTriggeredSelect = false;
199
+ ctrl.$filter = $filter;
200
+
201
+ ctrl.isEmpty = function() {
202
+ return angular.isUndefined(ctrl.selected) || ctrl.selected === null || ctrl.selected === '';
203
+ };
204
+
205
+ var _searchInput = $element.querySelectorAll('input.ui-select-search');
206
+ if (_searchInput.length !== 1) {
207
+ throw uiSelectMinErr('searchInput', "Expected 1 input.ui-select-search but got '{0}'.", _searchInput.length);
208
+ }
209
+
210
+ // Most of the time the user does not want to empty the search input when in typeahead mode
211
+ function _resetSearchInput() {
212
+ if (ctrl.resetSearchInput || (ctrl.resetSearchInput === undefined && uiSelectConfig.resetSearchInput)) {
213
+ ctrl.search = EMPTY_SEARCH;
214
+ //reset activeIndex
215
+ if (ctrl.selected && ctrl.items.length && !ctrl.multiple) {
216
+ ctrl.activeIndex = ctrl.items.indexOf(ctrl.selected);
217
+ }
218
+ }
219
+ }
220
+
221
+ // When the user clicks on ui-select, displays the dropdown list
222
+ ctrl.activate = function(initSearchValue, avoidReset) {
223
+ if (!ctrl.disabled && !ctrl.open) {
224
+ if(!avoidReset) _resetSearchInput();
225
+ ctrl.focusser.prop('disabled', true); //Will reactivate it on .close()
226
+ ctrl.open = true;
227
+ ctrl.activeMatchIndex = -1;
228
+
229
+ ctrl.activeIndex = ctrl.activeIndex >= ctrl.items.length ? 0 : ctrl.activeIndex;
230
+
231
+ // ensure that the index is set to zero for tagging variants
232
+ // that where first option is auto-selected
233
+ if ( ctrl.activeIndex === -1 && ctrl.taggingLabel !== false ) {
234
+ ctrl.activeIndex = 0;
235
+ }
236
+
237
+ // Give it time to appear before focus
238
+ $timeout(function() {
239
+ ctrl.search = initSearchValue || ctrl.search;
240
+ _searchInput[0].focus();
241
+ });
242
+ }
243
+ };
244
+
245
+ ctrl.findGroupByName = function(name) {
246
+ return ctrl.groups && ctrl.groups.filter(function(group) {
247
+ return group.name === name;
248
+ })[0];
249
+ };
250
+
251
+ ctrl.parseRepeatAttr = function(repeatAttr, groupByExp) {
252
+ function updateGroups(items) {
253
+ ctrl.groups = [];
254
+ angular.forEach(items, function(item) {
255
+ var groupFn = $scope.$eval(groupByExp);
256
+ var groupName = angular.isFunction(groupFn) ? groupFn(item) : item[groupFn];
257
+ var group = ctrl.findGroupByName(groupName);
258
+ if(group) {
259
+ group.items.push(item);
260
+ }
261
+ else {
262
+ ctrl.groups.push({name: groupName, items: [item]});
263
+ }
264
+ });
265
+ ctrl.items = [];
266
+ ctrl.groups.forEach(function(group) {
267
+ ctrl.items = ctrl.items.concat(group.items);
268
+ });
269
+ }
270
+
271
+ function setPlainItems(items) {
272
+ ctrl.items = items;
273
+ }
274
+
275
+ var setItemsFn = groupByExp ? updateGroups : setPlainItems;
276
+
277
+ ctrl.parserResult = RepeatParser.parse(repeatAttr);
278
+
279
+ ctrl.isGrouped = !!groupByExp;
280
+ ctrl.itemProperty = ctrl.parserResult.itemName;
281
+
282
+ // See https://github.com/angular/angular.js/blob/v1.2.15/src/ng/directive/ngRepeat.js#L259
283
+ $scope.$watchCollection(ctrl.parserResult.source, function(items) {
284
+
285
+ if (items === undefined || items === null) {
286
+ // If the user specifies undefined or null => reset the collection
287
+ // Special case: items can be undefined if the user did not initialized the collection on the scope
288
+ // i.e $scope.addresses = [] is missing
289
+ ctrl.items = [];
290
+ } else {
291
+ if (!angular.isArray(items)) {
292
+ throw uiSelectMinErr('items', "Expected an array but got '{0}'.", items);
293
+ } else {
294
+ if (ctrl.multiple){
295
+ //Remove already selected items (ex: while searching)
296
+ var filteredItems = items.filter(function(i) {return ctrl.selected.indexOf(i) < 0;});
297
+ setItemsFn(filteredItems);
298
+ }else{
299
+ setItemsFn(items);
300
+ }
301
+ ctrl.ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters
302
+
303
+ }
304
+ }
305
+
306
+ });
307
+
308
+ if (ctrl.multiple){
309
+ //Remove already selected items
310
+ $scope.$watchCollection('$select.selected', function(selectedItems){
311
+ var data = ctrl.parserResult.source($scope);
312
+ if (!selectedItems.length) {
313
+ setItemsFn(data);
314
+ }else{
315
+ if ( data !== undefined ) {
316
+ var filteredItems = data.filter(function(i) {return selectedItems.indexOf(i) < 0;});
317
+ setItemsFn(filteredItems);
318
+ }
319
+ }
320
+ ctrl.sizeSearchInput();
321
+ });
322
+ }
323
+
324
+ };
325
+
326
+ var _refreshDelayPromise;
327
+
328
+ /**
329
+ * Typeahead mode: lets the user refresh the collection using his own function.
330
+ *
331
+ * See Expose $select.search for external / remote filtering https://github.com/angular-ui/ui-select/pull/31
332
+ */
333
+ ctrl.refresh = function(refreshAttr) {
334
+ if (refreshAttr !== undefined) {
335
+
336
+ // Debounce
337
+ // See https://github.com/angular-ui/bootstrap/blob/0.10.0/src/typeahead/typeahead.js#L155
338
+ // FYI AngularStrap typeahead does not have debouncing: https://github.com/mgcrea/angular-strap/blob/v2.0.0-rc.4/src/typeahead/typeahead.js#L177
339
+ if (_refreshDelayPromise) {
340
+ $timeout.cancel(_refreshDelayPromise);
341
+ }
342
+ _refreshDelayPromise = $timeout(function() {
343
+ $scope.$eval(refreshAttr);
344
+ }, ctrl.refreshDelay);
345
+ }
346
+ };
347
+
348
+ ctrl.setActiveItem = function(item) {
349
+ ctrl.activeIndex = ctrl.items.indexOf(item);
350
+ };
351
+
352
+ ctrl.isActive = function(itemScope) {
353
+ if ( !ctrl.open ) {
354
+ return false;
355
+ }
356
+ var itemIndex = ctrl.items.indexOf(itemScope[ctrl.itemProperty]);
357
+ var isActive = itemIndex === ctrl.activeIndex;
358
+
359
+ if ( !isActive || ( itemIndex < 0 && ctrl.taggingLabel !== false ) ||( itemIndex < 0 && ctrl.taggingLabel === false) ) {
360
+ return false;
361
+ }
362
+
363
+ if (isActive && !angular.isUndefined(ctrl.onHighlightCallback)) {
364
+ itemScope.$eval(ctrl.onHighlightCallback);
365
+ }
366
+
367
+ return isActive;
368
+ };
369
+
370
+ ctrl.isDisabled = function(itemScope) {
371
+
372
+ if (!ctrl.open) return;
373
+
374
+ var itemIndex = ctrl.items.indexOf(itemScope[ctrl.itemProperty]);
375
+ var isDisabled = false;
376
+ var item;
377
+
378
+ if (itemIndex >= 0 && !angular.isUndefined(ctrl.disableChoiceExpression)) {
379
+ item = ctrl.items[itemIndex];
380
+ isDisabled = !!(itemScope.$eval(ctrl.disableChoiceExpression)); // force the boolean value
381
+ item._uiSelectChoiceDisabled = isDisabled; // store this for later reference
382
+ }
383
+
384
+ return isDisabled;
385
+ };
386
+
387
+
388
+ // When the user selects an item with ENTER or clicks the dropdown
389
+ ctrl.select = function(item, skipFocusser, $event) {
390
+ if (item === undefined || !item._uiSelectChoiceDisabled) {
391
+
392
+ if ( ! ctrl.items && ! ctrl.search ) return;
393
+
394
+ if (!item || !item._uiSelectChoiceDisabled) {
395
+ if(ctrl.tagging.isActivated) {
396
+ // if taggingLabel is disabled, we pull from ctrl.search val
397
+ if ( ctrl.taggingLabel === false ) {
398
+ if ( ctrl.activeIndex < 0 ) {
399
+ item = ctrl.tagging.fct !== undefined ? ctrl.tagging.fct(ctrl.search) : ctrl.search;
400
+ if ( angular.equals( ctrl.items[0], item ) ) {
401
+ return;
402
+ }
403
+ } else {
404
+ // keyboard nav happened first, user selected from dropdown
405
+ item = ctrl.items[ctrl.activeIndex];
406
+ }
407
+ } else {
408
+ // tagging always operates at index zero, taggingLabel === false pushes
409
+ // the ctrl.search value without having it injected
410
+ if ( ctrl.activeIndex === 0 ) {
411
+ // ctrl.tagging pushes items to ctrl.items, so we only have empty val
412
+ // for `item` if it is a detected duplicate
413
+ if ( item === undefined ) return;
414
+
415
+ // create new item on the fly if we don't already have one;
416
+ // use tagging function if we have one
417
+ if ( ctrl.tagging.fct !== undefined && typeof item === 'string' ) {
418
+ item = ctrl.tagging.fct(ctrl.search);
419
+ // if item type is 'string', apply the tagging label
420
+ } else if ( typeof item === 'string' ) {
421
+ item = item.replace(ctrl.taggingLabel,'');
422
+ }
423
+ }
424
+ }
425
+ // search ctrl.selected for dupes potentially caused by tagging and return early if found
426
+ if ( ctrl.selected && ctrl.selected.filter( function (selection) { return angular.equals(selection, item); }).length > 0 ) {
427
+ ctrl.close(skipFocusser);
428
+ return;
429
+ }
430
+ }
431
+
432
+ var locals = {};
433
+ locals[ctrl.parserResult.itemName] = item;
434
+
435
+ if(ctrl.multiple) {
436
+ ctrl.selected.push(item);
437
+ ctrl.sizeSearchInput();
438
+ } else {
439
+ ctrl.selected = item;
440
+ }
441
+
442
+ ctrl.onSelectCallback($scope, {
443
+ $item: item,
444
+ $model: ctrl.parserResult.modelMapper($scope, locals)
445
+ });
446
+
447
+ if (!ctrl.multiple || ctrl.closeOnSelect) {
448
+ ctrl.close(skipFocusser);
449
+ }
450
+ if ($event && $event.type === 'click') {
451
+ ctrl.clickTriggeredSelect = true;
452
+ }
453
+ }
454
+ }
455
+ };
456
+
457
+ // Closes the dropdown
458
+ ctrl.close = function(skipFocusser) {
459
+ if (!ctrl.open) return;
460
+ _resetSearchInput();
461
+ ctrl.open = false;
462
+ if (!ctrl.multiple){
463
+ $timeout(function(){
464
+ ctrl.focusser.prop('disabled', false);
465
+ if (!skipFocusser) ctrl.focusser[0].focus();
466
+ },0,false);
467
+ }
468
+ };
469
+
470
+ // Toggle dropdown
471
+ ctrl.toggle = function(e) {
472
+ if (ctrl.open) {
473
+ ctrl.close();
474
+ e.preventDefault();
475
+ e.stopPropagation();
476
+ } else {
477
+ ctrl.activate();
478
+ }
479
+ };
480
+
481
+ ctrl.isLocked = function(itemScope, itemIndex) {
482
+ var isLocked, item = ctrl.selected[itemIndex];
483
+
484
+ if (item && !angular.isUndefined(ctrl.lockChoiceExpression)) {
485
+ isLocked = !!(itemScope.$eval(ctrl.lockChoiceExpression)); // force the boolean value
486
+ item._uiSelectChoiceLocked = isLocked; // store this for later reference
487
+ }
488
+
489
+ return isLocked;
490
+ };
491
+
492
+ // Remove item from multiple select
493
+ ctrl.removeChoice = function(index){
494
+ var removedChoice = ctrl.selected[index];
495
+
496
+ // if the choice is locked, can't remove it
497
+ if(removedChoice._uiSelectChoiceLocked) return;
498
+
499
+ var locals = {};
500
+ locals[ctrl.parserResult.itemName] = removedChoice;
501
+
502
+ ctrl.selected.splice(index, 1);
503
+ ctrl.activeMatchIndex = -1;
504
+ ctrl.sizeSearchInput();
505
+
506
+ ctrl.onRemoveCallback($scope, {
507
+ $item: removedChoice,
508
+ $model: ctrl.parserResult.modelMapper($scope, locals)
509
+ });
510
+ };
511
+
512
+ ctrl.getPlaceholder = function(){
513
+ //Refactor single?
514
+ if(ctrl.multiple && ctrl.selected.length) return;
515
+ return ctrl.placeholder;
516
+ };
517
+
518
+ var containerSizeWatch;
519
+ ctrl.sizeSearchInput = function(){
520
+ var input = _searchInput[0],
521
+ container = _searchInput.parent().parent()[0];
522
+ _searchInput.css('width','10px');
523
+ var calculate = function(){
524
+ var newWidth = container.clientWidth - input.offsetLeft - 10;
525
+ if(newWidth < 50) newWidth = container.clientWidth;
526
+ _searchInput.css('width',newWidth+'px');
527
+ };
528
+ $timeout(function(){ //Give tags time to render correctly
529
+ if (container.clientWidth === 0 && !containerSizeWatch){
530
+ containerSizeWatch = $scope.$watch(function(){ return container.clientWidth;}, function(newValue){
531
+ if (newValue !== 0){
532
+ calculate();
533
+ containerSizeWatch();
534
+ containerSizeWatch = null;
535
+ }
536
+ });
537
+ }else if (!containerSizeWatch) {
538
+ calculate();
539
+ }
540
+ }, 0, false);
541
+ };
542
+
543
+ function _handleDropDownSelection(key) {
544
+ var processed = true;
545
+ switch (key) {
546
+ case KEY.DOWN:
547
+ if (!ctrl.open && ctrl.multiple) ctrl.activate(false, true); //In case its the search input in 'multiple' mode
548
+ else if (ctrl.activeIndex < ctrl.items.length - 1) { ctrl.activeIndex++; }
549
+ break;
550
+ case KEY.UP:
551
+ if (!ctrl.open && ctrl.multiple) ctrl.activate(false, true); //In case its the search input in 'multiple' mode
552
+ else if (ctrl.activeIndex > 0 || (ctrl.search.length === 0 && ctrl.tagging.isActivated)) { ctrl.activeIndex--; }
553
+ break;
554
+ case KEY.TAB:
555
+ if (!ctrl.multiple || ctrl.open) ctrl.select(ctrl.items[ctrl.activeIndex], true);
556
+ break;
557
+ case KEY.ENTER:
558
+ if(ctrl.open){
559
+ ctrl.select(ctrl.items[ctrl.activeIndex]);
560
+ } else {
561
+ ctrl.activate(false, true); //In case its the search input in 'multiple' mode
562
+ }
563
+ break;
564
+ case KEY.ESC:
565
+ ctrl.close();
566
+ break;
567
+ default:
568
+ processed = false;
569
+ }
570
+ return processed;
571
+ }
572
+
573
+ // Handles selected options in "multiple" mode
574
+ function _handleMatchSelection(key){
575
+ var caretPosition = _getCaretPosition(_searchInput[0]),
576
+ length = ctrl.selected.length,
577
+ // none = -1,
578
+ first = 0,
579
+ last = length-1,
580
+ curr = ctrl.activeMatchIndex,
581
+ next = ctrl.activeMatchIndex+1,
582
+ prev = ctrl.activeMatchIndex-1,
583
+ newIndex = curr;
584
+
585
+ if(caretPosition > 0 || (ctrl.search.length && key == KEY.RIGHT)) return false;
586
+
587
+ ctrl.close();
588
+
589
+ function getNewActiveMatchIndex(){
590
+ switch(key){
591
+ case KEY.LEFT:
592
+ // Select previous/first item
593
+ if(~ctrl.activeMatchIndex) return prev;
594
+ // Select last item
595
+ else return last;
596
+ break;
597
+ case KEY.RIGHT:
598
+ // Open drop-down
599
+ if(!~ctrl.activeMatchIndex || curr === last){
600
+ ctrl.activate();
601
+ return false;
602
+ }
603
+ // Select next/last item
604
+ else return next;
605
+ break;
606
+ case KEY.BACKSPACE:
607
+ // Remove selected item and select previous/first
608
+ if(~ctrl.activeMatchIndex){
609
+ ctrl.removeChoice(curr);
610
+ return prev;
611
+ }
612
+ // Select last item
613
+ else return last;
614
+ break;
615
+ case KEY.DELETE:
616
+ // Remove selected item and select next item
617
+ if(~ctrl.activeMatchIndex){
618
+ ctrl.removeChoice(ctrl.activeMatchIndex);
619
+ return curr;
620
+ }
621
+ else return false;
622
+ }
623
+ }
624
+
625
+ newIndex = getNewActiveMatchIndex();
626
+
627
+ if(!ctrl.selected.length || newIndex === false) ctrl.activeMatchIndex = -1;
628
+ else ctrl.activeMatchIndex = Math.min(last,Math.max(first,newIndex));
629
+
630
+ return true;
631
+ }
632
+
633
+ // Bind to keyboard shortcuts
634
+ _searchInput.on('keydown', function(e) {
635
+
636
+ var key = e.which;
637
+
638
+ // if(~[KEY.ESC,KEY.TAB].indexOf(key)){
639
+ // //TODO: SEGURO?
640
+ // ctrl.close();
641
+ // }
642
+
643
+ $scope.$apply(function() {
644
+ var processed = false;
645
+ var tagged = false;
646
+
647
+ if(ctrl.multiple && KEY.isHorizontalMovement(key)){
648
+ processed = _handleMatchSelection(key);
649
+ }
650
+
651
+ if (!processed && (ctrl.items.length > 0 || ctrl.tagging.isActivated)) {
652
+ processed = _handleDropDownSelection(key);
653
+ if ( ctrl.taggingTokens.isActivated ) {
654
+ for (var i = 0; i < ctrl.taggingTokens.tokens.length; i++) {
655
+ if ( ctrl.taggingTokens.tokens[i] === KEY.MAP[e.keyCode] ) {
656
+ // make sure there is a new value to push via tagging
657
+ if ( ctrl.search.length > 0 ) {
658
+ tagged = true;
659
+ }
660
+ }
661
+ }
662
+ if ( tagged ) {
663
+ $timeout(function() {
664
+ _searchInput.triggerHandler('tagged');
665
+ var newItem = ctrl.search.replace(KEY.MAP[e.keyCode],'').trim();
666
+ if ( ctrl.tagging.fct ) {
667
+ newItem = ctrl.tagging.fct( newItem );
668
+ }
669
+ ctrl.select( newItem, true);
670
+ });
671
+ }
672
+ }
673
+ }
674
+
675
+ if (processed && key != KEY.TAB) {
676
+ //TODO Check si el tab selecciona aun correctamente
677
+ //Crear test
678
+ e.preventDefault();
679
+ e.stopPropagation();
680
+ }
681
+ });
682
+
683
+ if(KEY.isVerticalMovement(key) && ctrl.items.length > 0){
684
+ _ensureHighlightVisible();
685
+ }
686
+
687
+ });
688
+
689
+ _searchInput.on('keyup', function(e) {
690
+ if ( ! KEY.isVerticalMovement(e.which) ) {
691
+ $scope.$evalAsync( function () {
692
+ ctrl.activeIndex = ctrl.taggingLabel === false ? -1 : 0;
693
+ });
694
+ }
695
+ // Push a "create new" item into array if there is a search string
696
+ if ( ctrl.tagging.isActivated && ctrl.search.length > 0 ) {
697
+
698
+ // return early with these keys
699
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC || KEY.isVerticalMovement(e.which) ) {
700
+ return;
701
+ }
702
+ // always reset the activeIndex to the first item when tagging
703
+ ctrl.activeIndex = ctrl.taggingLabel === false ? -1 : 0;
704
+ // taggingLabel === false bypasses all of this
705
+ if (ctrl.taggingLabel === false) return;
706
+
707
+ var items = angular.copy( ctrl.items );
708
+ var stashArr = angular.copy( ctrl.items );
709
+ var newItem;
710
+ var item;
711
+ var hasTag = false;
712
+ var dupeIndex = -1;
713
+ var tagItems;
714
+ var tagItem;
715
+
716
+ // case for object tagging via transform `ctrl.tagging.fct` function
717
+ if ( ctrl.tagging.fct !== undefined) {
718
+ tagItems = ctrl.$filter('filter')(items,{'isTag': true});
719
+ if ( tagItems.length > 0 ) {
720
+ tagItem = tagItems[0];
721
+ }
722
+ // remove the first element, if it has the `isTag` prop we generate a new one with each keyup, shaving the previous
723
+ if ( items.length > 0 && tagItem ) {
724
+ hasTag = true;
725
+ items = items.slice(1,items.length);
726
+ stashArr = stashArr.slice(1,stashArr.length);
727
+ }
728
+ newItem = ctrl.tagging.fct(ctrl.search);
729
+ newItem.isTag = true;
730
+ // verify the the tag doesn't match the value of an existing item
731
+ if ( stashArr.filter( function (origItem) { return angular.equals( origItem, ctrl.tagging.fct(ctrl.search) ); } ).length > 0 ) {
732
+ return;
733
+ }
734
+ // handle newItem string and stripping dupes in tagging string context
735
+ } else {
736
+ // find any tagging items already in the ctrl.items array and store them
737
+ tagItems = ctrl.$filter('filter')(items,function (item) {
738
+ return item.match(ctrl.taggingLabel);
739
+ });
740
+ if ( tagItems.length > 0 ) {
741
+ tagItem = tagItems[0];
742
+ }
743
+ item = items[0];
744
+ // remove existing tag item if found (should only ever be one tag item)
745
+ if ( item !== undefined && items.length > 0 && tagItem ) {
746
+ hasTag = true;
747
+ items = items.slice(1,items.length);
748
+ stashArr = stashArr.slice(1,stashArr.length);
749
+ }
750
+ newItem = ctrl.search+' '+ctrl.taggingLabel;
751
+ if ( _findApproxDupe(ctrl.selected, ctrl.search) > -1 ) {
752
+ return;
753
+ }
754
+ // verify the the tag doesn't match the value of an existing item from
755
+ // the searched data set or the items already selected
756
+ if ( _findCaseInsensitiveDupe(stashArr.concat(ctrl.selected)) ) {
757
+ // if there is a tag from prev iteration, strip it / queue the change
758
+ // and return early
759
+ if ( hasTag ) {
760
+ items = stashArr;
761
+ $scope.$evalAsync( function () {
762
+ ctrl.activeIndex = 0;
763
+ ctrl.items = items;
764
+ });
765
+ }
766
+ return;
767
+ }
768
+ if ( _findCaseInsensitiveDupe(stashArr) ) {
769
+ // if there is a tag from prev iteration, strip it
770
+ if ( hasTag ) {
771
+ ctrl.items = stashArr.slice(1,stashArr.length);
772
+ }
773
+ return;
774
+ }
775
+ }
776
+ if ( hasTag ) dupeIndex = _findApproxDupe(ctrl.selected, newItem);
777
+ // dupe found, shave the first item
778
+ if ( dupeIndex > -1 ) {
779
+ items = items.slice(dupeIndex+1,items.length-1);
780
+ } else {
781
+ items = [];
782
+ items.push(newItem);
783
+ items = items.concat(stashArr);
784
+ }
785
+ $scope.$evalAsync( function () {
786
+ ctrl.activeIndex = 0;
787
+ ctrl.items = items;
788
+ });
789
+ }
790
+ });
791
+
792
+ _searchInput.on('tagged', function() {
793
+ $timeout(function() {
794
+ _resetSearchInput();
795
+ });
796
+ });
797
+
798
+ _searchInput.on('blur', function() {
799
+ $timeout(function() {
800
+ ctrl.activeMatchIndex = -1;
801
+ });
802
+ });
803
+
804
+ function _findCaseInsensitiveDupe(arr) {
805
+ if ( arr === undefined || ctrl.search === undefined ) {
806
+ return false;
807
+ }
808
+ var hasDupe = arr.filter( function (origItem) {
809
+ if ( ctrl.search.toUpperCase() === undefined ) {
810
+ return false;
811
+ }
812
+ return origItem.toUpperCase() === ctrl.search.toUpperCase();
813
+ }).length > 0;
814
+
815
+ return hasDupe;
816
+ }
817
+
818
+ function _findApproxDupe(haystack, needle) {
819
+ var tempArr = angular.copy(haystack);
820
+ var dupeIndex = -1;
821
+ for (var i = 0; i <tempArr.length; i++) {
822
+ // handle the simple string version of tagging
823
+ if ( ctrl.tagging.fct === undefined ) {
824
+ // search the array for the match
825
+ if ( tempArr[i]+' '+ctrl.taggingLabel === needle ) {
826
+ dupeIndex = i;
827
+ }
828
+ // handle the object tagging implementation
829
+ } else {
830
+ var mockObj = tempArr[i];
831
+ mockObj.isTag = true;
832
+ if ( angular.equals(mockObj, needle) ) {
833
+ dupeIndex = i;
834
+ }
835
+ }
836
+ }
837
+ return dupeIndex;
838
+ }
839
+
840
+ function _getCaretPosition(el) {
841
+ if(angular.isNumber(el.selectionStart)) return el.selectionStart;
842
+ // selectionStart is not supported in IE8 and we don't want hacky workarounds so we compromise
843
+ else return el.value.length;
844
+ }
845
+
846
+ // See https://github.com/ivaynberg/select2/blob/3.4.6/select2.js#L1431
847
+ function _ensureHighlightVisible() {
848
+ var container = $element.querySelectorAll('.ui-select-choices-content');
849
+ var choices = container.querySelectorAll('.ui-select-choices-row');
850
+ if (choices.length < 1) {
851
+ throw uiSelectMinErr('choices', "Expected multiple .ui-select-choices-row but got '{0}'.", choices.length);
852
+ }
853
+
854
+ var highlighted = choices[ctrl.activeIndex];
855
+ var posY = highlighted.offsetTop + highlighted.clientHeight - container[0].scrollTop;
856
+ var height = container[0].offsetHeight;
857
+
858
+ if (posY > height) {
859
+ container[0].scrollTop += posY - height;
860
+ } else if (posY < highlighted.clientHeight) {
861
+ if (ctrl.isGrouped && ctrl.activeIndex === 0)
862
+ container[0].scrollTop = 0; //To make group header visible when going all the way up
863
+ else
864
+ container[0].scrollTop -= highlighted.clientHeight - posY;
865
+ }
866
+ }
867
+
868
+ $scope.$on('$destroy', function() {
869
+ _searchInput.off('keyup keydown tagged blur');
870
+ });
871
+ }])
872
+
873
+ .directive('uiSelect',
874
+ ['$document', 'uiSelectConfig', 'uiSelectMinErr', '$compile', '$parse',
875
+ function($document, uiSelectConfig, uiSelectMinErr, $compile, $parse) {
876
+
877
+ return {
878
+ restrict: 'EA',
879
+ templateUrl: function(tElement, tAttrs) {
880
+ var theme = tAttrs.theme || uiSelectConfig.theme;
881
+ return theme + (angular.isDefined(tAttrs.multiple) ? '/select-multiple.tpl.html' : '/select.tpl.html');
882
+ },
883
+ replace: true,
884
+ transclude: true,
885
+ require: ['uiSelect', 'ngModel'],
886
+ scope: true,
887
+
888
+ controller: 'uiSelectCtrl',
889
+ controllerAs: '$select',
890
+
891
+ link: function(scope, element, attrs, ctrls, transcludeFn) {
892
+ var $select = ctrls[0];
893
+ var ngModel = ctrls[1];
894
+
895
+ var searchInput = element.querySelectorAll('input.ui-select-search');
896
+
897
+ $select.multiple = angular.isDefined(attrs.multiple) && (
898
+ attrs.multiple === '' ||
899
+ attrs.multiple.toLowerCase() === 'multiple' ||
900
+ attrs.multiple.toLowerCase() === 'true'
901
+ );
902
+
903
+ $select.closeOnSelect = function() {
904
+ if (angular.isDefined(attrs.closeOnSelect)) {
905
+ return $parse(attrs.closeOnSelect)();
906
+ } else {
907
+ return uiSelectConfig.closeOnSelect;
908
+ }
909
+ }();
910
+
911
+ $select.onSelectCallback = $parse(attrs.onSelect);
912
+ $select.onRemoveCallback = $parse(attrs.onRemove);
913
+
914
+ //From view --> model
915
+ ngModel.$parsers.unshift(function (inputValue) {
916
+ var locals = {},
917
+ result;
918
+ if ($select.multiple){
919
+ var resultMultiple = [];
920
+ for (var j = $select.selected.length - 1; j >= 0; j--) {
921
+ locals = {};
922
+ locals[$select.parserResult.itemName] = $select.selected[j];
923
+ result = $select.parserResult.modelMapper(scope, locals);
924
+ resultMultiple.unshift(result);
925
+ }
926
+ return resultMultiple;
927
+ }else{
928
+ locals = {};
929
+ locals[$select.parserResult.itemName] = inputValue;
930
+ result = $select.parserResult.modelMapper(scope, locals);
931
+ return result;
932
+ }
933
+ });
934
+
935
+ //From model --> view
936
+ ngModel.$formatters.unshift(function (inputValue) {
937
+ var data = $select.parserResult.source (scope, { $select : {search:''}}), //Overwrite $search
938
+ locals = {},
939
+ result;
940
+ if (data){
941
+ if ($select.multiple){
942
+ var resultMultiple = [];
943
+ var checkFnMultiple = function(list, value){
944
+ if (!list || !list.length) return;
945
+ for (var p = list.length - 1; p >= 0; p--) {
946
+ locals[$select.parserResult.itemName] = list[p];
947
+ result = $select.parserResult.modelMapper(scope, locals);
948
+ if (result == value){
949
+ resultMultiple.unshift(list[p]);
950
+ return true;
951
+ }
952
+ }
953
+ return false;
954
+ };
955
+ if (!inputValue) return resultMultiple; //If ngModel was undefined
956
+ for (var k = inputValue.length - 1; k >= 0; k--) {
957
+ if (!checkFnMultiple($select.selected, inputValue[k])){
958
+ checkFnMultiple(data, inputValue[k]);
959
+ }
960
+ }
961
+ return resultMultiple;
962
+ }else{
963
+ var checkFnSingle = function(d){
964
+ locals[$select.parserResult.itemName] = d;
965
+ result = $select.parserResult.modelMapper(scope, locals);
966
+ return result == inputValue;
967
+ };
968
+ //If possible pass same object stored in $select.selected
969
+ if ($select.selected && checkFnSingle($select.selected)) {
970
+ return $select.selected;
971
+ }
972
+ for (var i = data.length - 1; i >= 0; i--) {
973
+ if (checkFnSingle(data[i])) return data[i];
974
+ }
975
+ }
976
+ }
977
+ return inputValue;
978
+ });
979
+
980
+ //Set reference to ngModel from uiSelectCtrl
981
+ $select.ngModel = ngModel;
982
+
983
+ $select.choiceGrouped = function(group){
984
+ return $select.isGrouped && group && group.name;
985
+ };
986
+
987
+ //Idea from: https://github.com/ivaynberg/select2/blob/79b5bf6db918d7560bdd959109b7bcfb47edaf43/select2.js#L1954
988
+ var focusser = angular.element("<input ng-disabled='$select.disabled' class='ui-select-focusser ui-select-offscreen' type='text' aria-haspopup='true' role='button' />");
989
+
990
+ if(attrs.tabindex){
991
+ //tabindex might be an expression, wait until it contains the actual value before we set the focusser tabindex
992
+ attrs.$observe('tabindex', function(value) {
993
+ //If we are using multiple, add tabindex to the search input
994
+ if($select.multiple){
995
+ searchInput.attr("tabindex", value);
996
+ } else {
997
+ focusser.attr("tabindex", value);
998
+ }
999
+ //Remove the tabindex on the parent so that it is not focusable
1000
+ element.removeAttr("tabindex");
1001
+ });
1002
+ }
1003
+
1004
+ $compile(focusser)(scope);
1005
+ $select.focusser = focusser;
1006
+
1007
+ if (!$select.multiple){
1008
+
1009
+ element.append(focusser);
1010
+ focusser.bind("focus", function(){
1011
+ scope.$evalAsync(function(){
1012
+ $select.focus = true;
1013
+ });
1014
+ });
1015
+ focusser.bind("blur", function(){
1016
+ scope.$evalAsync(function(){
1017
+ $select.focus = false;
1018
+ });
1019
+ });
1020
+ focusser.bind("keydown", function(e){
1021
+
1022
+ if (e.which === KEY.BACKSPACE) {
1023
+ e.preventDefault();
1024
+ e.stopPropagation();
1025
+ $select.select(undefined);
1026
+ scope.$apply();
1027
+ return;
1028
+ }
1029
+
1030
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
1031
+ return;
1032
+ }
1033
+
1034
+ if (e.which == KEY.DOWN || e.which == KEY.UP || e.which == KEY.ENTER || e.which == KEY.SPACE){
1035
+ e.preventDefault();
1036
+ e.stopPropagation();
1037
+ $select.activate();
1038
+ }
1039
+
1040
+ scope.$digest();
1041
+ });
1042
+
1043
+ focusser.bind("keyup input", function(e){
1044
+
1045
+ if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC || e.which == KEY.ENTER || e.which === KEY.BACKSPACE) {
1046
+ return;
1047
+ }
1048
+
1049
+ $select.activate(focusser.val()); //User pressed some regular key, so we pass it to the search input
1050
+ focusser.val('');
1051
+ scope.$digest();
1052
+
1053
+ });
1054
+
1055
+ }
1056
+
1057
+
1058
+ scope.$watch('searchEnabled', function() {
1059
+ var searchEnabled = scope.$eval(attrs.searchEnabled);
1060
+ $select.searchEnabled = searchEnabled !== undefined ? searchEnabled : uiSelectConfig.searchEnabled;
1061
+ });
1062
+
1063
+ attrs.$observe('disabled', function() {
1064
+ // No need to use $eval() (thanks to ng-disabled) since we already get a boolean instead of a string
1065
+ $select.disabled = attrs.disabled !== undefined ? attrs.disabled : false;
1066
+ });
1067
+
1068
+ attrs.$observe('resetSearchInput', function() {
1069
+ // $eval() is needed otherwise we get a string instead of a boolean
1070
+ var resetSearchInput = scope.$eval(attrs.resetSearchInput);
1071
+ $select.resetSearchInput = resetSearchInput !== undefined ? resetSearchInput : true;
1072
+ });
1073
+
1074
+ attrs.$observe('tagging', function() {
1075
+ if(attrs.tagging !== undefined)
1076
+ {
1077
+ // $eval() is needed otherwise we get a string instead of a boolean
1078
+ var taggingEval = scope.$eval(attrs.tagging);
1079
+ $select.tagging = {isActivated: true, fct: taggingEval !== true ? taggingEval : undefined};
1080
+ }
1081
+ else
1082
+ {
1083
+ $select.tagging = {isActivated: false, fct: undefined};
1084
+ }
1085
+ });
1086
+
1087
+ attrs.$observe('taggingLabel', function() {
1088
+ if(attrs.tagging !== undefined )
1089
+ {
1090
+ // check eval for FALSE, in this case, we disable the labels
1091
+ // associated with tagging
1092
+ if ( attrs.taggingLabel === 'false' ) {
1093
+ $select.taggingLabel = false;
1094
+ }
1095
+ else
1096
+ {
1097
+ $select.taggingLabel = attrs.taggingLabel !== undefined ? attrs.taggingLabel : '(new)';
1098
+ }
1099
+ }
1100
+ });
1101
+
1102
+ attrs.$observe('taggingTokens', function() {
1103
+ if (attrs.tagging !== undefined) {
1104
+ var tokens = attrs.taggingTokens !== undefined ? attrs.taggingTokens.split('|') : [',','ENTER'];
1105
+ $select.taggingTokens = {isActivated: true, tokens: tokens };
1106
+ }
1107
+ });
1108
+
1109
+ if ($select.multiple){
1110
+ scope.$watchCollection(function(){ return ngModel.$modelValue; }, function(newValue, oldValue) {
1111
+ if (oldValue != newValue)
1112
+ ngModel.$modelValue = null; //Force scope model value and ngModel value to be out of sync to re-run formatters
1113
+ });
1114
+ scope.$watchCollection('$select.selected', function() {
1115
+ ngModel.$setViewValue(Date.now()); //Set timestamp as a unique string to force changes
1116
+ });
1117
+ focusser.prop('disabled', true); //Focusser isn't needed if multiple
1118
+ }else{
1119
+ scope.$watch('$select.selected', function(newValue) {
1120
+ if (ngModel.$viewValue !== newValue) {
1121
+ ngModel.$setViewValue(newValue);
1122
+ }
1123
+ });
1124
+ }
1125
+
1126
+ ngModel.$render = function() {
1127
+ if($select.multiple){
1128
+ // Make sure that model value is array
1129
+ if(!angular.isArray(ngModel.$viewValue)){
1130
+ // Have tolerance for null or undefined values
1131
+ if(angular.isUndefined(ngModel.$viewValue) || ngModel.$viewValue === null){
1132
+ $select.selected = [];
1133
+ } else {
1134
+ throw uiSelectMinErr('multiarr', "Expected model value to be array but got '{0}'", ngModel.$viewValue);
1135
+ }
1136
+ }
1137
+ }
1138
+ $select.selected = ngModel.$viewValue;
1139
+ };
1140
+
1141
+ function onDocumentClick(e) {
1142
+ var contains = false;
1143
+
1144
+ if (window.jQuery) {
1145
+ // Firefox 3.6 does not support element.contains()
1146
+ // See Node.contains https://developer.mozilla.org/en-US/docs/Web/API/Node.contains
1147
+ contains = window.jQuery.contains(element[0], e.target);
1148
+ } else {
1149
+ contains = element[0].contains(e.target);
1150
+ }
1151
+
1152
+ if (!contains && !$select.clickTriggeredSelect) {
1153
+ $select.close(angular.element(e.target).closest('.ui-select-container.open').length > 0); // Skip focusser if the target is another select
1154
+ scope.$digest();
1155
+ }
1156
+ $select.clickTriggeredSelect = false;
1157
+ }
1158
+
1159
+ // See Click everywhere but here event http://stackoverflow.com/questions/12931369
1160
+ $document.on('click', onDocumentClick);
1161
+
1162
+ scope.$on('$destroy', function() {
1163
+ $document.off('click', onDocumentClick);
1164
+ });
1165
+
1166
+ // Move transcluded elements to their correct position in main template
1167
+ transcludeFn(scope, function(clone) {
1168
+ // See Transclude in AngularJS http://blog.omkarpatil.com/2012/11/transclude-in-angularjs.html
1169
+
1170
+ // One day jqLite will be replaced by jQuery and we will be able to write:
1171
+ // var transcludedElement = clone.filter('.my-class')
1172
+ // instead of creating a hackish DOM element:
1173
+ var transcluded = angular.element('<div>').append(clone);
1174
+
1175
+ var transcludedMatch = transcluded.querySelectorAll('.ui-select-match');
1176
+ transcludedMatch.removeAttr('ui-select-match'); //To avoid loop in case directive as attr
1177
+ if (transcludedMatch.length !== 1) {
1178
+ throw uiSelectMinErr('transcluded', "Expected 1 .ui-select-match but got '{0}'.", transcludedMatch.length);
1179
+ }
1180
+ element.querySelectorAll('.ui-select-match').replaceWith(transcludedMatch);
1181
+
1182
+ var transcludedChoices = transcluded.querySelectorAll('.ui-select-choices');
1183
+ transcludedChoices.removeAttr('ui-select-choices'); //To avoid loop in case directive as attr
1184
+ if (transcludedChoices.length !== 1) {
1185
+ throw uiSelectMinErr('transcluded', "Expected 1 .ui-select-choices but got '{0}'.", transcludedChoices.length);
1186
+ }
1187
+ element.querySelectorAll('.ui-select-choices').replaceWith(transcludedChoices);
1188
+ });
1189
+ }
1190
+ };
1191
+ }])
1192
+
1193
+ .directive('uiSelectChoices',
1194
+ ['uiSelectConfig', 'RepeatParser', 'uiSelectMinErr', '$compile',
1195
+ function(uiSelectConfig, RepeatParser, uiSelectMinErr, $compile) {
1196
+
1197
+ return {
1198
+ restrict: 'EA',
1199
+ require: '^uiSelect',
1200
+ replace: true,
1201
+ transclude: true,
1202
+ templateUrl: function(tElement) {
1203
+ // Gets theme attribute from parent (ui-select)
1204
+ var theme = tElement.parent().attr('theme') || uiSelectConfig.theme;
1205
+ return theme + '/choices.tpl.html';
1206
+ },
1207
+
1208
+ compile: function(tElement, tAttrs) {
1209
+
1210
+ if (!tAttrs.repeat) throw uiSelectMinErr('repeat', "Expected 'repeat' expression.");
1211
+
1212
+ return function link(scope, element, attrs, $select, transcludeFn) {
1213
+
1214
+ // var repeat = RepeatParser.parse(attrs.repeat);
1215
+ var groupByExp = attrs.groupBy;
1216
+
1217
+ $select.parseRepeatAttr(attrs.repeat, groupByExp); //Result ready at $select.parserResult
1218
+
1219
+ $select.disableChoiceExpression = attrs.uiDisableChoice;
1220
+ $select.onHighlightCallback = attrs.onHighlight;
1221
+
1222
+ if(groupByExp) {
1223
+ var groups = element.querySelectorAll('.ui-select-choices-group');
1224
+ if (groups.length !== 1) throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-group but got '{0}'.", groups.length);
1225
+ groups.attr('ng-repeat', RepeatParser.getGroupNgRepeatExpression());
1226
+ }
1227
+
1228
+ var choices = element.querySelectorAll('.ui-select-choices-row');
1229
+ if (choices.length !== 1) {
1230
+ throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row but got '{0}'.", choices.length);
1231
+ }
1232
+
1233
+ choices.attr('ng-repeat', RepeatParser.getNgRepeatExpression($select.parserResult.itemName, '$select.items', $select.parserResult.trackByExp, groupByExp))
1234
+ .attr('ng-if', '$select.open') //Prevent unnecessary watches when dropdown is closed
1235
+ .attr('ng-mouseenter', '$select.setActiveItem('+$select.parserResult.itemName +')')
1236
+ .attr('ng-click', '$select.select(' + $select.parserResult.itemName + ',false,$event)');
1237
+
1238
+ var rowsInner = element.querySelectorAll('.ui-select-choices-row-inner');
1239
+ if (rowsInner.length !== 1) throw uiSelectMinErr('rows', "Expected 1 .ui-select-choices-row-inner but got '{0}'.", rowsInner.length);
1240
+ rowsInner.attr('uis-transclude-append', ''); //Adding uisTranscludeAppend directive to row element after choices element has ngRepeat
1241
+
1242
+ $compile(element, transcludeFn)(scope); //Passing current transcludeFn to be able to append elements correctly from uisTranscludeAppend
1243
+
1244
+ scope.$watch('$select.search', function(newValue) {
1245
+ if(newValue && !$select.open && $select.multiple) $select.activate(false, true);
1246
+ $select.activeIndex = $select.tagging.isActivated ? -1 : 0;
1247
+ $select.refresh(attrs.refresh);
1248
+ });
1249
+
1250
+ attrs.$observe('refreshDelay', function() {
1251
+ // $eval() is needed otherwise we get a string instead of a number
1252
+ var refreshDelay = scope.$eval(attrs.refreshDelay);
1253
+ $select.refreshDelay = refreshDelay !== undefined ? refreshDelay : uiSelectConfig.refreshDelay;
1254
+ });
1255
+ };
1256
+ }
1257
+ };
1258
+ }])
1259
+ // Recreates old behavior of ng-transclude. Used internally.
1260
+ .directive('uisTranscludeAppend', function () {
1261
+ return {
1262
+ link: function (scope, element, attrs, ctrl, transclude) {
1263
+ transclude(scope, function (clone) {
1264
+ element.append(clone);
1265
+ });
1266
+ }
1267
+ };
1268
+ })
1269
+ .directive('uiSelectMatch', ['uiSelectConfig', function(uiSelectConfig) {
1270
+ return {
1271
+ restrict: 'EA',
1272
+ require: '^uiSelect',
1273
+ replace: true,
1274
+ transclude: true,
1275
+ templateUrl: function(tElement) {
1276
+ // Gets theme attribute from parent (ui-select)
1277
+ var theme = tElement.parent().attr('theme') || uiSelectConfig.theme;
1278
+ var multi = tElement.parent().attr('multiple');
1279
+ return theme + (multi ? '/match-multiple.tpl.html' : '/match.tpl.html');
1280
+ },
1281
+ link: function(scope, element, attrs, $select) {
1282
+ $select.lockChoiceExpression = attrs.uiLockChoice;
1283
+ attrs.$observe('placeholder', function(placeholder) {
1284
+ $select.placeholder = placeholder !== undefined ? placeholder : uiSelectConfig.placeholder;
1285
+ });
1286
+
1287
+ $select.allowClear = (angular.isDefined(attrs.allowClear)) ? (attrs.allowClear === '') ? true : (attrs.allowClear.toLowerCase() === 'true') : false;
1288
+
1289
+ if($select.multiple){
1290
+ $select.sizeSearchInput();
1291
+ }
1292
+
1293
+ }
1294
+ };
1295
+ }])
1296
+
1297
+ /**
1298
+ * Highlights text that matches $select.search.
1299
+ *
1300
+ * Taken from AngularUI Bootstrap Typeahead
1301
+ * See https://github.com/angular-ui/bootstrap/blob/0.10.0/src/typeahead/typeahead.js#L340
1302
+ */
1303
+ .filter('highlight', function() {
1304
+ function escapeRegexp(queryToEscape) {
1305
+ return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
1306
+ }
1307
+
1308
+ return function(matchItem, query) {
1309
+ return query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<span class="ui-select-highlight">$&</span>') : matchItem;
1310
+ };
1311
+ });
1312
+ }());
1313
+
1314
+
1315
+ angular.module("ui.select").run(["$templateCache", function($templateCache) {$templateCache.put("bootstrap/choices.tpl.html","<ul class=\"ui-select-choices ui-select-choices-content dropdown-menu\" role=\"menu\" aria-labelledby=\"dLabel\" ng-show=\"$select.items.length > 0\"><li class=\"ui-select-choices-group\"><div class=\"divider\" ng-show=\"$select.isGrouped && $index > 0\"></div><div ng-show=\"$select.isGrouped\" class=\"ui-select-choices-group-label dropdown-header\" ng-bind-html=\"$group.name\"></div><div class=\"ui-select-choices-row\" ng-class=\"{active: $select.isActive(this), disabled: $select.isDisabled(this)}\"><a href=\"javascript:void(0)\" class=\"ui-select-choices-row-inner\"></a></div></li></ul>");
1316
+ $templateCache.put("bootstrap/match-multiple.tpl.html","<span class=\"ui-select-match\"><span ng-repeat=\"$item in $select.selected\"><span style=\"margin-right: 3px;\" class=\"ui-select-match-item btn btn-default btn-xs\" tabindex=\"-1\" type=\"button\" ng-disabled=\"$select.disabled\" ng-click=\"$select.activeMatchIndex = $index;\" ng-class=\"{\'btn-primary\':$select.activeMatchIndex === $index, \'select-locked\':$select.isLocked(this, $index)}\"><span class=\"close ui-select-match-close\" ng-hide=\"$select.disabled\" ng-click=\"$select.removeChoice($index)\">&nbsp;&times;</span> <span uis-transclude-append=\"\"></span></span></span></span>");
1317
+ $templateCache.put("bootstrap/match.tpl.html","<div class=\"btn-group ui-select-match btn-block\" ng-hide=\"$select.open\" ng-disabled=\"$select.disabled\" ng-class=\"{\'btn-default-focus\':$select.focus}\"><button type=\"button\" class=\"btn btn-default\" ng-class=\"{\'col-sm-8 col-md-10\': $select.allowClear && !$select.isEmpty(),\'col-sm-10 col-md-11\': !$select.allowClear || $select.isEmpty()}\" tabindex=\"-1\" ;=\"\" ng-click=\"$select.activate()\"><span ng-show=\"$select.isEmpty()\" class=\"text-muted\">{{$select.placeholder}}</span> <span ng-hide=\"$select.isEmpty()\" ng-transclude=\"\"></span></button> <button class=\"btn btn-default col-sm-2 col-md-1\" ng-if=\"$select.allowClear && !$select.isEmpty()\" ng-click=\"$select.select(undefined)\"><span class=\"glyphicon glyphicon-remove ui-select-toggle\"></span></button> <button class=\"btn btn-default col-sm-2 col-md-1\" ng-click=\"$select.activate()\"><span class=\"caret ui-select-toggle\" ng-click=\"$select.toggle($event)\"></span></button></div>");
1318
+ $templateCache.put("bootstrap/select-multiple.tpl.html","<div class=\"ui-select-container ui-select-multiple ui-select-bootstrap dropdown form-control\" ng-class=\"{open: $select.open}\"><div><div class=\"ui-select-match\"></div><input type=\"text\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" class=\"ui-select-search input-xs\" placeholder=\"{{$select.getPlaceholder()}}\" ng-disabled=\"$select.disabled\" ng-hide=\"$select.disabled\" ng-click=\"$select.activate()\" ng-model=\"$select.search\"></div><div class=\"ui-select-choices\"></div></div>");
1319
+ $templateCache.put("bootstrap/select.tpl.html","<div class=\"ui-select-container ui-select-bootstrap dropdown\" ng-class=\"{open: $select.open}\"><div class=\"ui-select-match\"></div><input type=\"text\" autocomplete=\"off\" tabindex=\"-1\" class=\"form-control ui-select-search\" placeholder=\"{{$select.placeholder}}\" ng-model=\"$select.search\" ng-show=\"$select.searchEnabled && $select.open\"><div class=\"ui-select-choices\"></div></div>");
1320
+ $templateCache.put("select2/choices.tpl.html","<ul class=\"ui-select-choices ui-select-choices-content select2-results\"><li class=\"ui-select-choices-group\" ng-class=\"{\'select2-result-with-children\': $select.choiceGrouped($group) }\"><div ng-show=\"$select.choiceGrouped($group)\" class=\"ui-select-choices-group-label select2-result-label\" ng-bind-html=\"$group.name\"></div><ul ng-class=\"{\'select2-result-sub\': $select.choiceGrouped($group), \'select2-result-single\': !$select.choiceGrouped($group) }\"><li class=\"ui-select-choices-row\" ng-class=\"{\'select2-highlighted\': $select.isActive(this), \'select2-disabled\': $select.isDisabled(this)}\"><div class=\"select2-result-label ui-select-choices-row-inner\"></div></li></ul></li></ul>");
1321
+ $templateCache.put("select2/match-multiple.tpl.html","<span class=\"ui-select-match\"><li class=\"ui-select-match-item select2-search-choice\" ng-repeat=\"$item in $select.selected\" ng-class=\"{\'select2-search-choice-focus\':$select.activeMatchIndex === $index, \'select2-locked\':$select.isLocked(this, $index)}\"><span uis-transclude-append=\"\"></span> <a href=\"javascript:;\" class=\"ui-select-match-close select2-search-choice-close\" ng-click=\"$select.removeChoice($index)\" tabindex=\"-1\"></a></li></span>");
1322
+ $templateCache.put("select2/match.tpl.html","<a class=\"select2-choice ui-select-match\" ng-class=\"{\'select2-default\': $select.isEmpty()}\" ng-click=\"$select.activate()\"><span ng-show=\"$select.isEmpty()\" class=\"select2-chosen\">{{$select.placeholder}}</span> <span ng-hide=\"$select.isEmpty()\" class=\"select2-chosen\" ng-transclude=\"\"></span> <abbr ng-if=\"$select.allowClear && !$select.isEmpty()\" class=\"select2-search-choice-close\" ng-click=\"$select.select(undefined)\"></abbr> <span class=\"select2-arrow ui-select-toggle\" ng-click=\"$select.toggle($event)\"><b></b></span></a>");
1323
+ $templateCache.put("select2/select-multiple.tpl.html","<div class=\"ui-select-container ui-select-multiple select2 select2-container select2-container-multi\" ng-class=\"{\'select2-container-active select2-dropdown-open open\': $select.open,\n \'select2-container-disabled\': $select.disabled}\"><ul class=\"select2-choices\"><span class=\"ui-select-match\"></span><li class=\"select2-search-field\"><input type=\"text\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" class=\"select2-input ui-select-search\" placeholder=\"{{$select.getPlaceholder()}}\" ng-disabled=\"$select.disabled\" ng-hide=\"$select.disabled\" ng-model=\"$select.search\" ng-click=\"$select.activate()\" style=\"width: 34px;\"></li></ul><div class=\"select2-drop select2-with-searchbox select2-drop-active\" ng-class=\"{\'select2-display-none\': !$select.open}\"><div class=\"ui-select-choices\"></div></div></div>");
1324
+ $templateCache.put("select2/select.tpl.html","<div class=\"ui-select-container select2 select2-container\" ng-class=\"{\'select2-container-active select2-dropdown-open open\': $select.open,\n \'select2-container-disabled\': $select.disabled,\n \'select2-container-active\': $select.focus, \n \'select2-allowclear\': $select.allowClear && !$select.isEmpty()}\"><div class=\"ui-select-match\"></div><div class=\"select2-drop select2-with-searchbox select2-drop-active\" ng-class=\"{\'select2-display-none\': !$select.open}\"><div class=\"select2-search\" ng-show=\"$select.searchEnabled\"><input type=\"text\" autocomplete=\"off\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\" class=\"ui-select-search select2-input\" ng-model=\"$select.search\"></div><div class=\"ui-select-choices\"></div></div></div>");
1325
+ $templateCache.put("selectize/choices.tpl.html","<div ng-show=\"$select.open\" class=\"ui-select-choices selectize-dropdown single\"><div class=\"ui-select-choices-content selectize-dropdown-content\"><div class=\"ui-select-choices-group optgroup\"><div ng-show=\"$select.isGrouped\" class=\"ui-select-choices-group-label optgroup-header\" ng-bind-html=\"$group.name\"></div><div class=\"ui-select-choices-row\" ng-class=\"{active: $select.isActive(this), disabled: $select.isDisabled(this)}\"><div class=\"option ui-select-choices-row-inner\" data-selectable=\"\"></div></div></div></div></div>");
1326
+ $templateCache.put("selectize/match.tpl.html","<div ng-hide=\"($select.open || $select.isEmpty())\" class=\"ui-select-match\" ng-transclude=\"\"></div>");
1327
+ $templateCache.put("selectize/select.tpl.html","<div class=\"ui-select-container selectize-control single\" ng-class=\"{\'open\': $select.open}\"><div class=\"selectize-input\" ng-class=\"{\'focus\': $select.open, \'disabled\': $select.disabled, \'selectize-focus\' : $select.focus}\" ng-click=\"$select.activate()\"><div class=\"ui-select-match\"></div><input type=\"text\" autocomplete=\"off\" tabindex=\"-1\" class=\"ui-select-search ui-select-toggle\" ng-click=\"$select.toggle($event)\" placeholder=\"{{$select.placeholder}}\" ng-model=\"$select.search\" ng-hide=\"!$select.searchEnabled || ($select.selected && !$select.open)\" ng-disabled=\"$select.disabled\"></div><div class=\"ui-select-choices\"></div></div>");}]);