angular-ui-select-rails 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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>");}]);