Total Complexity | 849 |
Complexity/F | 2.81 |
Lines of Code | 3830 |
Function Count | 302 |
Duplicated Lines | 3830 |
Ratio | 100 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like public/lib/semantic/components/dropdown.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | /*! |
||
11 | View Code Duplication | ;(function ($, window, document, undefined) { |
|
|
|||
12 | |||
13 | "use strict"; |
||
14 | |||
15 | window = (typeof window != 'undefined' && window.Math == Math) |
||
16 | ? window |
||
17 | : (typeof self != 'undefined' && self.Math == Math) |
||
18 | ? self |
||
19 | : Function('return this')() |
||
20 | ; |
||
21 | |||
22 | $.fn.dropdown = function(parameters) { |
||
23 | var |
||
24 | $allModules = $(this), |
||
25 | $document = $(document), |
||
26 | |||
27 | moduleSelector = $allModules.selector || '', |
||
28 | |||
29 | hasTouch = ('ontouchstart' in document.documentElement), |
||
30 | time = new Date().getTime(), |
||
31 | performance = [], |
||
32 | |||
33 | query = arguments[0], |
||
34 | methodInvoked = (typeof query == 'string'), |
||
35 | queryArguments = [].slice.call(arguments, 1), |
||
36 | returnedValue |
||
37 | ; |
||
38 | |||
39 | $allModules |
||
40 | .each(function(elementIndex) { |
||
41 | var |
||
42 | settings = ( $.isPlainObject(parameters) ) |
||
43 | ? $.extend(true, {}, $.fn.dropdown.settings, parameters) |
||
44 | : $.extend({}, $.fn.dropdown.settings), |
||
45 | |||
46 | className = settings.className, |
||
47 | message = settings.message, |
||
48 | fields = settings.fields, |
||
49 | keys = settings.keys, |
||
50 | metadata = settings.metadata, |
||
51 | namespace = settings.namespace, |
||
52 | regExp = settings.regExp, |
||
53 | selector = settings.selector, |
||
54 | error = settings.error, |
||
55 | templates = settings.templates, |
||
56 | |||
57 | eventNamespace = '.' + namespace, |
||
58 | moduleNamespace = 'module-' + namespace, |
||
59 | |||
60 | $module = $(this), |
||
61 | $context = $(settings.context), |
||
62 | $text = $module.find(selector.text), |
||
63 | $search = $module.find(selector.search), |
||
64 | $sizer = $module.find(selector.sizer), |
||
65 | $input = $module.find(selector.input), |
||
66 | $icon = $module.find(selector.icon), |
||
67 | |||
68 | $combo = ($module.prev().find(selector.text).length > 0) |
||
69 | ? $module.prev().find(selector.text) |
||
70 | : $module.prev(), |
||
71 | |||
72 | $menu = $module.children(selector.menu), |
||
73 | $item = $menu.find(selector.item), |
||
74 | |||
75 | activated = false, |
||
76 | itemActivated = false, |
||
77 | internalChange = false, |
||
78 | element = this, |
||
79 | instance = $module.data(moduleNamespace), |
||
80 | |||
81 | initialLoad, |
||
82 | pageLostFocus, |
||
83 | willRefocus, |
||
84 | elementNamespace, |
||
85 | id, |
||
86 | selectObserver, |
||
87 | menuObserver, |
||
88 | module |
||
89 | ; |
||
90 | |||
91 | module = { |
||
92 | |||
93 | initialize: function() { |
||
94 | module.debug('Initializing dropdown', settings); |
||
95 | |||
96 | if( module.is.alreadySetup() ) { |
||
97 | module.setup.reference(); |
||
98 | } |
||
99 | else { |
||
100 | module.setup.layout(); |
||
101 | module.refreshData(); |
||
102 | |||
103 | module.save.defaults(); |
||
104 | module.restore.selected(); |
||
105 | |||
106 | module.create.id(); |
||
107 | module.bind.events(); |
||
108 | |||
109 | module.observeChanges(); |
||
110 | module.instantiate(); |
||
111 | } |
||
112 | |||
113 | }, |
||
114 | |||
115 | instantiate: function() { |
||
116 | module.verbose('Storing instance of dropdown', module); |
||
117 | instance = module; |
||
118 | $module |
||
119 | .data(moduleNamespace, module) |
||
120 | ; |
||
121 | }, |
||
122 | |||
123 | destroy: function() { |
||
124 | module.verbose('Destroying previous dropdown', $module); |
||
125 | module.remove.tabbable(); |
||
126 | $module |
||
127 | .off(eventNamespace) |
||
128 | .removeData(moduleNamespace) |
||
129 | ; |
||
130 | $menu |
||
131 | .off(eventNamespace) |
||
132 | ; |
||
133 | $document |
||
134 | .off(elementNamespace) |
||
135 | ; |
||
136 | module.disconnect.menuObserver(); |
||
137 | module.disconnect.selectObserver(); |
||
138 | }, |
||
139 | |||
140 | observeChanges: function() { |
||
141 | if('MutationObserver' in window) { |
||
142 | selectObserver = new MutationObserver(module.event.select.mutation); |
||
143 | menuObserver = new MutationObserver(module.event.menu.mutation); |
||
144 | module.debug('Setting up mutation observer', selectObserver, menuObserver); |
||
145 | module.observe.select(); |
||
146 | module.observe.menu(); |
||
147 | } |
||
148 | }, |
||
149 | |||
150 | disconnect: { |
||
151 | menuObserver: function() { |
||
152 | if(menuObserver) { |
||
153 | menuObserver.disconnect(); |
||
154 | } |
||
155 | }, |
||
156 | selectObserver: function() { |
||
157 | if(selectObserver) { |
||
158 | selectObserver.disconnect(); |
||
159 | } |
||
160 | } |
||
161 | }, |
||
162 | observe: { |
||
163 | select: function() { |
||
164 | if(module.has.input()) { |
||
165 | selectObserver.observe($input[0], { |
||
166 | childList : true, |
||
167 | subtree : true |
||
168 | }); |
||
169 | } |
||
170 | }, |
||
171 | menu: function() { |
||
172 | if(module.has.menu()) { |
||
173 | menuObserver.observe($menu[0], { |
||
174 | childList : true, |
||
175 | subtree : true |
||
176 | }); |
||
177 | } |
||
178 | } |
||
179 | }, |
||
180 | |||
181 | create: { |
||
182 | id: function() { |
||
183 | id = (Math.random().toString(16) + '000000000').substr(2, 8); |
||
184 | elementNamespace = '.' + id; |
||
185 | module.verbose('Creating unique id for element', id); |
||
186 | }, |
||
187 | userChoice: function(values) { |
||
188 | var |
||
189 | $userChoices, |
||
190 | $userChoice, |
||
191 | isUserValue, |
||
192 | html |
||
193 | ; |
||
194 | values = values || module.get.userValues(); |
||
195 | if(!values) { |
||
196 | return false; |
||
197 | } |
||
198 | values = $.isArray(values) |
||
199 | ? values |
||
200 | : [values] |
||
201 | ; |
||
202 | $.each(values, function(index, value) { |
||
203 | if(module.get.item(value) === false) { |
||
204 | html = settings.templates.addition( module.add.variables(message.addResult, value) ); |
||
205 | $userChoice = $('<div />') |
||
206 | .html(html) |
||
207 | .attr('data-' + metadata.value, value) |
||
208 | .attr('data-' + metadata.text, value) |
||
209 | .addClass(className.addition) |
||
210 | .addClass(className.item) |
||
211 | ; |
||
212 | if(settings.hideAdditions) { |
||
213 | $userChoice.addClass(className.hidden); |
||
214 | } |
||
215 | $userChoices = ($userChoices === undefined) |
||
216 | ? $userChoice |
||
217 | : $userChoices.add($userChoice) |
||
218 | ; |
||
219 | module.verbose('Creating user choices for value', value, $userChoice); |
||
220 | } |
||
221 | }); |
||
222 | return $userChoices; |
||
223 | }, |
||
224 | userLabels: function(value) { |
||
225 | var |
||
226 | userValues = module.get.userValues() |
||
227 | ; |
||
228 | if(userValues) { |
||
229 | module.debug('Adding user labels', userValues); |
||
230 | $.each(userValues, function(index, value) { |
||
231 | module.verbose('Adding custom user value'); |
||
232 | module.add.label(value, value); |
||
233 | }); |
||
234 | } |
||
235 | }, |
||
236 | menu: function() { |
||
237 | $menu = $('<div />') |
||
238 | .addClass(className.menu) |
||
239 | .appendTo($module) |
||
240 | ; |
||
241 | }, |
||
242 | sizer: function() { |
||
243 | $sizer = $('<span />') |
||
244 | .addClass(className.sizer) |
||
245 | .insertAfter($search) |
||
246 | ; |
||
247 | } |
||
248 | }, |
||
249 | |||
250 | search: function(query) { |
||
251 | query = (query !== undefined) |
||
252 | ? query |
||
253 | : module.get.query() |
||
254 | ; |
||
255 | module.verbose('Searching for query', query); |
||
256 | if(module.has.minCharacters(query)) { |
||
257 | module.filter(query); |
||
258 | } |
||
259 | else { |
||
260 | module.hide(); |
||
261 | } |
||
262 | }, |
||
263 | |||
264 | select: { |
||
265 | firstUnfiltered: function() { |
||
266 | module.verbose('Selecting first non-filtered element'); |
||
267 | module.remove.selectedItem(); |
||
268 | $item |
||
269 | .not(selector.unselectable) |
||
270 | .not(selector.addition + selector.hidden) |
||
271 | .eq(0) |
||
272 | .addClass(className.selected) |
||
273 | ; |
||
274 | }, |
||
275 | nextAvailable: function($selected) { |
||
276 | $selected = $selected.eq(0); |
||
277 | var |
||
278 | $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0), |
||
279 | $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0), |
||
280 | hasNext = ($nextAvailable.length > 0) |
||
281 | ; |
||
282 | if(hasNext) { |
||
283 | module.verbose('Moving selection to', $nextAvailable); |
||
284 | $nextAvailable.addClass(className.selected); |
||
285 | } |
||
286 | else { |
||
287 | module.verbose('Moving selection to', $prevAvailable); |
||
288 | $prevAvailable.addClass(className.selected); |
||
289 | } |
||
290 | } |
||
291 | }, |
||
292 | |||
293 | setup: { |
||
294 | api: function() { |
||
295 | var |
||
296 | apiSettings = { |
||
297 | debug : settings.debug, |
||
298 | urlData : { |
||
299 | value : module.get.value(), |
||
300 | query : module.get.query() |
||
301 | }, |
||
302 | on : false |
||
303 | } |
||
304 | ; |
||
305 | module.verbose('First request, initializing API'); |
||
306 | $module |
||
307 | .api(apiSettings) |
||
308 | ; |
||
309 | }, |
||
310 | layout: function() { |
||
311 | if( $module.is('select') ) { |
||
312 | module.setup.select(); |
||
313 | module.setup.returnedObject(); |
||
314 | } |
||
315 | if( !module.has.menu() ) { |
||
316 | module.create.menu(); |
||
317 | } |
||
318 | if( module.is.search() && !module.has.search() ) { |
||
319 | module.verbose('Adding search input'); |
||
320 | $search = $('<input />') |
||
321 | .addClass(className.search) |
||
322 | .prop('autocomplete', 'off') |
||
323 | .insertBefore($text) |
||
324 | ; |
||
325 | } |
||
326 | if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) { |
||
327 | module.create.sizer(); |
||
328 | } |
||
329 | if(settings.allowTab) { |
||
330 | module.set.tabbable(); |
||
331 | } |
||
332 | }, |
||
333 | select: function() { |
||
334 | var |
||
335 | selectValues = module.get.selectValues() |
||
336 | ; |
||
337 | module.debug('Dropdown initialized on a select', selectValues); |
||
338 | if( $module.is('select') ) { |
||
339 | $input = $module; |
||
340 | } |
||
341 | // see if select is placed correctly already |
||
342 | if($input.parent(selector.dropdown).length > 0) { |
||
343 | module.debug('UI dropdown already exists. Creating dropdown menu only'); |
||
344 | $module = $input.closest(selector.dropdown); |
||
345 | if( !module.has.menu() ) { |
||
346 | module.create.menu(); |
||
347 | } |
||
348 | $menu = $module.children(selector.menu); |
||
349 | module.setup.menu(selectValues); |
||
350 | } |
||
351 | else { |
||
352 | module.debug('Creating entire dropdown from select'); |
||
353 | $module = $('<div />') |
||
354 | .attr('class', $input.attr('class') ) |
||
355 | .addClass(className.selection) |
||
356 | .addClass(className.dropdown) |
||
357 | .html( templates.dropdown(selectValues) ) |
||
358 | .insertBefore($input) |
||
359 | ; |
||
360 | if($input.hasClass(className.multiple) && $input.prop('multiple') === false) { |
||
361 | module.error(error.missingMultiple); |
||
362 | $input.prop('multiple', true); |
||
363 | } |
||
364 | if($input.is('[multiple]')) { |
||
365 | module.set.multiple(); |
||
366 | } |
||
367 | if ($input.prop('disabled')) { |
||
368 | module.debug('Disabling dropdown'); |
||
369 | $module.addClass(className.disabled); |
||
370 | } |
||
371 | $input |
||
372 | .removeAttr('class') |
||
373 | .detach() |
||
374 | .prependTo($module) |
||
375 | ; |
||
376 | } |
||
377 | module.refresh(); |
||
378 | }, |
||
379 | menu: function(values) { |
||
380 | $menu.html( templates.menu(values, fields)); |
||
381 | $item = $menu.find(selector.item); |
||
382 | }, |
||
383 | reference: function() { |
||
384 | module.debug('Dropdown behavior was called on select, replacing with closest dropdown'); |
||
385 | // replace module reference |
||
386 | $module = $module.parent(selector.dropdown); |
||
387 | module.refresh(); |
||
388 | module.setup.returnedObject(); |
||
389 | // invoke method in context of current instance |
||
390 | if(methodInvoked) { |
||
391 | instance = module; |
||
392 | module.invoke(query); |
||
393 | } |
||
394 | }, |
||
395 | returnedObject: function() { |
||
396 | var |
||
397 | $firstModules = $allModules.slice(0, elementIndex), |
||
398 | $lastModules = $allModules.slice(elementIndex + 1) |
||
399 | ; |
||
400 | // adjust all modules to use correct reference |
||
401 | $allModules = $firstModules.add($module).add($lastModules); |
||
402 | } |
||
403 | }, |
||
404 | |||
405 | refresh: function() { |
||
406 | module.refreshSelectors(); |
||
407 | module.refreshData(); |
||
408 | }, |
||
409 | |||
410 | refreshItems: function() { |
||
411 | $item = $menu.find(selector.item); |
||
412 | }, |
||
413 | |||
414 | refreshSelectors: function() { |
||
415 | module.verbose('Refreshing selector cache'); |
||
416 | $text = $module.find(selector.text); |
||
417 | $search = $module.find(selector.search); |
||
418 | $input = $module.find(selector.input); |
||
419 | $icon = $module.find(selector.icon); |
||
420 | $combo = ($module.prev().find(selector.text).length > 0) |
||
421 | ? $module.prev().find(selector.text) |
||
422 | : $module.prev() |
||
423 | ; |
||
424 | $menu = $module.children(selector.menu); |
||
425 | $item = $menu.find(selector.item); |
||
426 | }, |
||
427 | |||
428 | refreshData: function() { |
||
429 | module.verbose('Refreshing cached metadata'); |
||
430 | $item |
||
431 | .removeData(metadata.text) |
||
432 | .removeData(metadata.value) |
||
433 | ; |
||
434 | }, |
||
435 | |||
436 | clearData: function() { |
||
437 | module.verbose('Clearing metadata'); |
||
438 | $item |
||
439 | .removeData(metadata.text) |
||
440 | .removeData(metadata.value) |
||
441 | ; |
||
442 | $module |
||
443 | .removeData(metadata.defaultText) |
||
444 | .removeData(metadata.defaultValue) |
||
445 | .removeData(metadata.placeholderText) |
||
446 | ; |
||
447 | }, |
||
448 | |||
449 | toggle: function() { |
||
450 | module.verbose('Toggling menu visibility'); |
||
451 | if( !module.is.active() ) { |
||
452 | module.show(); |
||
453 | } |
||
454 | else { |
||
455 | module.hide(); |
||
456 | } |
||
457 | }, |
||
458 | |||
459 | show: function(callback) { |
||
460 | callback = $.isFunction(callback) |
||
461 | ? callback |
||
462 | : function(){} |
||
463 | ; |
||
464 | if(!module.can.show() && module.is.remote()) { |
||
465 | module.debug('No API results retrieved, searching before show'); |
||
466 | module.queryRemote(module.get.query(), module.show); |
||
467 | } |
||
468 | if( module.can.show() && !module.is.active() ) { |
||
469 | module.debug('Showing dropdown'); |
||
470 | if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) { |
||
471 | module.remove.message(); |
||
472 | } |
||
473 | if(module.is.allFiltered()) { |
||
474 | return true; |
||
475 | } |
||
476 | if(settings.onShow.call(element) !== false) { |
||
477 | module.animate.show(function() { |
||
478 | if( module.can.click() ) { |
||
479 | module.bind.intent(); |
||
480 | } |
||
481 | if(module.has.menuSearch()) { |
||
482 | module.focusSearch(); |
||
483 | } |
||
484 | module.set.visible(); |
||
485 | callback.call(element); |
||
486 | }); |
||
487 | } |
||
488 | } |
||
489 | }, |
||
490 | |||
491 | hide: function(callback) { |
||
492 | callback = $.isFunction(callback) |
||
493 | ? callback |
||
494 | : function(){} |
||
495 | ; |
||
496 | if( module.is.active() ) { |
||
497 | module.debug('Hiding dropdown'); |
||
498 | if(settings.onHide.call(element) !== false) { |
||
499 | module.animate.hide(function() { |
||
500 | module.remove.visible(); |
||
501 | callback.call(element); |
||
502 | }); |
||
503 | } |
||
504 | } |
||
505 | }, |
||
506 | |||
507 | hideOthers: function() { |
||
508 | module.verbose('Finding other dropdowns to hide'); |
||
509 | $allModules |
||
510 | .not($module) |
||
511 | .has(selector.menu + '.' + className.visible) |
||
512 | .dropdown('hide') |
||
513 | ; |
||
514 | }, |
||
515 | |||
516 | hideMenu: function() { |
||
517 | module.verbose('Hiding menu instantaneously'); |
||
518 | module.remove.active(); |
||
519 | module.remove.visible(); |
||
520 | $menu.transition('hide'); |
||
521 | }, |
||
522 | |||
523 | hideSubMenus: function() { |
||
524 | var |
||
525 | $subMenus = $menu.children(selector.item).find(selector.menu) |
||
526 | ; |
||
527 | module.verbose('Hiding sub menus', $subMenus); |
||
528 | $subMenus.transition('hide'); |
||
529 | }, |
||
530 | |||
531 | bind: { |
||
532 | events: function() { |
||
533 | if(hasTouch) { |
||
534 | module.bind.touchEvents(); |
||
535 | } |
||
536 | module.bind.keyboardEvents(); |
||
537 | module.bind.inputEvents(); |
||
538 | module.bind.mouseEvents(); |
||
539 | }, |
||
540 | touchEvents: function() { |
||
541 | module.debug('Touch device detected binding additional touch events'); |
||
542 | if( module.is.searchSelection() ) { |
||
543 | // do nothing special yet |
||
544 | } |
||
545 | else if( module.is.single() ) { |
||
546 | $module |
||
547 | .on('touchstart' + eventNamespace, module.event.test.toggle) |
||
548 | ; |
||
549 | } |
||
550 | $menu |
||
551 | .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter) |
||
552 | ; |
||
553 | }, |
||
554 | keyboardEvents: function() { |
||
555 | module.verbose('Binding keyboard events'); |
||
556 | $module |
||
557 | .on('keydown' + eventNamespace, module.event.keydown) |
||
558 | ; |
||
559 | if( module.has.search() ) { |
||
560 | $module |
||
561 | .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input) |
||
562 | ; |
||
563 | } |
||
564 | if( module.is.multiple() ) { |
||
565 | $document |
||
566 | .on('keydown' + elementNamespace, module.event.document.keydown) |
||
567 | ; |
||
568 | } |
||
569 | }, |
||
570 | inputEvents: function() { |
||
571 | module.verbose('Binding input change events'); |
||
572 | $module |
||
573 | .on('change' + eventNamespace, selector.input, module.event.change) |
||
574 | ; |
||
575 | }, |
||
576 | mouseEvents: function() { |
||
577 | module.verbose('Binding mouse events'); |
||
578 | if(module.is.multiple()) { |
||
579 | $module |
||
580 | .on('click' + eventNamespace, selector.label, module.event.label.click) |
||
581 | .on('click' + eventNamespace, selector.remove, module.event.remove.click) |
||
582 | ; |
||
583 | } |
||
584 | if( module.is.searchSelection() ) { |
||
585 | $module |
||
586 | .on('mousedown' + eventNamespace, module.event.mousedown) |
||
587 | .on('mouseup' + eventNamespace, module.event.mouseup) |
||
588 | .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown) |
||
589 | .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup) |
||
590 | .on('click' + eventNamespace, selector.icon, module.event.icon.click) |
||
591 | .on('focus' + eventNamespace, selector.search, module.event.search.focus) |
||
592 | .on('click' + eventNamespace, selector.search, module.event.search.focus) |
||
593 | .on('blur' + eventNamespace, selector.search, module.event.search.blur) |
||
594 | .on('click' + eventNamespace, selector.text, module.event.text.focus) |
||
595 | ; |
||
596 | if(module.is.multiple()) { |
||
597 | $module |
||
598 | .on('click' + eventNamespace, module.event.click) |
||
599 | ; |
||
600 | } |
||
601 | } |
||
602 | else { |
||
603 | if(settings.on == 'click') { |
||
604 | $module |
||
605 | .on('click' + eventNamespace, selector.icon, module.event.icon.click) |
||
606 | .on('click' + eventNamespace, module.event.test.toggle) |
||
607 | ; |
||
608 | } |
||
609 | else if(settings.on == 'hover') { |
||
610 | $module |
||
611 | .on('mouseenter' + eventNamespace, module.delay.show) |
||
612 | .on('mouseleave' + eventNamespace, module.delay.hide) |
||
613 | ; |
||
614 | } |
||
615 | else { |
||
616 | $module |
||
617 | .on(settings.on + eventNamespace, module.toggle) |
||
618 | ; |
||
619 | } |
||
620 | $module |
||
621 | .on('mousedown' + eventNamespace, module.event.mousedown) |
||
622 | .on('mouseup' + eventNamespace, module.event.mouseup) |
||
623 | .on('focus' + eventNamespace, module.event.focus) |
||
624 | ; |
||
625 | if(module.has.menuSearch() ) { |
||
626 | $module |
||
627 | .on('blur' + eventNamespace, selector.search, module.event.search.blur) |
||
628 | ; |
||
629 | } |
||
630 | else { |
||
631 | $module |
||
632 | .on('blur' + eventNamespace, module.event.blur) |
||
633 | ; |
||
634 | } |
||
635 | } |
||
636 | $menu |
||
637 | .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter) |
||
638 | .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave) |
||
639 | .on('click' + eventNamespace, selector.item, module.event.item.click) |
||
640 | ; |
||
641 | }, |
||
642 | intent: function() { |
||
643 | module.verbose('Binding hide intent event to document'); |
||
644 | if(hasTouch) { |
||
645 | $document |
||
646 | .on('touchstart' + elementNamespace, module.event.test.touch) |
||
647 | .on('touchmove' + elementNamespace, module.event.test.touch) |
||
648 | ; |
||
649 | } |
||
650 | $document |
||
651 | .on('click' + elementNamespace, module.event.test.hide) |
||
652 | ; |
||
653 | } |
||
654 | }, |
||
655 | |||
656 | unbind: { |
||
657 | intent: function() { |
||
658 | module.verbose('Removing hide intent event from document'); |
||
659 | if(hasTouch) { |
||
660 | $document |
||
661 | .off('touchstart' + elementNamespace) |
||
662 | .off('touchmove' + elementNamespace) |
||
663 | ; |
||
664 | } |
||
665 | $document |
||
666 | .off('click' + elementNamespace) |
||
667 | ; |
||
668 | } |
||
669 | }, |
||
670 | |||
671 | filter: function(query) { |
||
672 | var |
||
673 | searchTerm = (query !== undefined) |
||
674 | ? query |
||
675 | : module.get.query(), |
||
676 | afterFiltered = function() { |
||
677 | if(module.is.multiple()) { |
||
678 | module.filterActive(); |
||
679 | } |
||
680 | if(query || (!query && module.get.activeItem().length == 0)) { |
||
681 | module.select.firstUnfiltered(); |
||
682 | } |
||
683 | if( module.has.allResultsFiltered() ) { |
||
684 | if( settings.onNoResults.call(element, searchTerm) ) { |
||
685 | if(settings.allowAdditions) { |
||
686 | if(settings.hideAdditions) { |
||
687 | module.verbose('User addition with no menu, setting empty style'); |
||
688 | module.set.empty(); |
||
689 | module.hideMenu(); |
||
690 | } |
||
691 | } |
||
692 | else { |
||
693 | module.verbose('All items filtered, showing message', searchTerm); |
||
694 | module.add.message(message.noResults); |
||
695 | } |
||
696 | } |
||
697 | else { |
||
698 | module.verbose('All items filtered, hiding dropdown', searchTerm); |
||
699 | module.hideMenu(); |
||
700 | } |
||
701 | } |
||
702 | else { |
||
703 | module.remove.empty(); |
||
704 | module.remove.message(); |
||
705 | } |
||
706 | if(settings.allowAdditions) { |
||
707 | module.add.userSuggestion(query); |
||
708 | } |
||
709 | if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) { |
||
710 | module.show(); |
||
711 | } |
||
712 | } |
||
713 | ; |
||
714 | if(settings.useLabels && module.has.maxSelections()) { |
||
715 | return; |
||
716 | } |
||
717 | if(settings.apiSettings) { |
||
718 | if( module.can.useAPI() ) { |
||
719 | module.queryRemote(searchTerm, function() { |
||
720 | if(settings.filterRemoteData) { |
||
721 | module.filterItems(searchTerm); |
||
722 | } |
||
723 | afterFiltered(); |
||
724 | }); |
||
725 | } |
||
726 | else { |
||
727 | module.error(error.noAPI); |
||
728 | } |
||
729 | } |
||
730 | else { |
||
731 | module.filterItems(searchTerm); |
||
732 | afterFiltered(); |
||
733 | } |
||
734 | }, |
||
735 | |||
736 | queryRemote: function(query, callback) { |
||
737 | var |
||
738 | apiSettings = { |
||
739 | errorDuration : false, |
||
740 | cache : 'local', |
||
741 | throttle : settings.throttle, |
||
742 | urlData : { |
||
743 | query: query |
||
744 | }, |
||
745 | onError: function() { |
||
746 | module.add.message(message.serverError); |
||
747 | callback(); |
||
748 | }, |
||
749 | onFailure: function() { |
||
750 | module.add.message(message.serverError); |
||
751 | callback(); |
||
752 | }, |
||
753 | onSuccess : function(response) { |
||
754 | module.remove.message(); |
||
755 | module.setup.menu({ |
||
756 | values: response[fields.remoteValues] |
||
757 | }); |
||
758 | callback(); |
||
759 | } |
||
760 | } |
||
761 | ; |
||
762 | if( !$module.api('get request') ) { |
||
763 | module.setup.api(); |
||
764 | } |
||
765 | apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings); |
||
766 | $module |
||
767 | .api('setting', apiSettings) |
||
768 | .api('query') |
||
769 | ; |
||
770 | }, |
||
771 | |||
772 | filterItems: function(query) { |
||
773 | var |
||
774 | searchTerm = (query !== undefined) |
||
775 | ? query |
||
776 | : module.get.query(), |
||
777 | results = null, |
||
778 | escapedTerm = module.escape.string(searchTerm), |
||
779 | beginsWithRegExp = new RegExp('^' + escapedTerm, 'igm') |
||
780 | ; |
||
781 | // avoid loop if we're matching nothing |
||
782 | if( module.has.query() ) { |
||
783 | results = []; |
||
784 | |||
785 | module.verbose('Searching for matching values', searchTerm); |
||
786 | $item |
||
787 | .each(function(){ |
||
788 | var |
||
789 | $choice = $(this), |
||
790 | text, |
||
791 | value |
||
792 | ; |
||
793 | if(settings.match == 'both' || settings.match == 'text') { |
||
794 | text = String(module.get.choiceText($choice, false)); |
||
795 | if(text.search(beginsWithRegExp) !== -1) { |
||
796 | results.push(this); |
||
797 | return true; |
||
798 | } |
||
799 | else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) { |
||
800 | results.push(this); |
||
801 | return true; |
||
802 | } |
||
803 | else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) { |
||
804 | results.push(this); |
||
805 | return true; |
||
806 | } |
||
807 | } |
||
808 | if(settings.match == 'both' || settings.match == 'value') { |
||
809 | value = String(module.get.choiceValue($choice, text)); |
||
810 | if(value.search(beginsWithRegExp) !== -1) { |
||
811 | results.push(this); |
||
812 | return true; |
||
813 | } |
||
814 | else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) { |
||
815 | results.push(this); |
||
816 | return true; |
||
817 | } |
||
818 | else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) { |
||
819 | results.push(this); |
||
820 | return true; |
||
821 | } |
||
822 | } |
||
823 | }) |
||
824 | ; |
||
825 | } |
||
826 | module.debug('Showing only matched items', searchTerm); |
||
827 | module.remove.filteredItem(); |
||
828 | if(results) { |
||
829 | $item |
||
830 | .not(results) |
||
831 | .addClass(className.filtered) |
||
832 | ; |
||
833 | } |
||
834 | }, |
||
835 | |||
836 | fuzzySearch: function(query, term) { |
||
837 | var |
||
838 | termLength = term.length, |
||
839 | queryLength = query.length |
||
840 | ; |
||
841 | query = query.toLowerCase(); |
||
842 | term = term.toLowerCase(); |
||
843 | if(queryLength > termLength) { |
||
844 | return false; |
||
845 | } |
||
846 | if(queryLength === termLength) { |
||
847 | return (query === term); |
||
848 | } |
||
849 | search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) { |
||
850 | var |
||
851 | queryCharacter = query.charCodeAt(characterIndex) |
||
852 | ; |
||
853 | while(nextCharacterIndex < termLength) { |
||
854 | if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) { |
||
855 | continue search; |
||
856 | } |
||
857 | } |
||
858 | return false; |
||
859 | } |
||
860 | return true; |
||
861 | }, |
||
862 | exactSearch: function (query, term) { |
||
863 | query = query.toLowerCase(); |
||
864 | term = term.toLowerCase(); |
||
865 | if(term.indexOf(query) > -1) { |
||
866 | return true; |
||
867 | } |
||
868 | return false; |
||
869 | }, |
||
870 | filterActive: function() { |
||
871 | if(settings.useLabels) { |
||
872 | $item.filter('.' + className.active) |
||
873 | .addClass(className.filtered) |
||
874 | ; |
||
875 | } |
||
876 | }, |
||
877 | |||
878 | focusSearch: function(skipHandler) { |
||
879 | if( module.has.search() && !module.is.focusedOnSearch() ) { |
||
880 | if(skipHandler) { |
||
881 | $module.off('focus' + eventNamespace, selector.search); |
||
882 | $search.focus(); |
||
883 | $module.on('focus' + eventNamespace, selector.search, module.event.search.focus); |
||
884 | } |
||
885 | else { |
||
886 | $search.focus(); |
||
887 | } |
||
888 | } |
||
889 | }, |
||
890 | |||
891 | forceSelection: function() { |
||
892 | var |
||
893 | $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), |
||
894 | $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0), |
||
895 | $selectedItem = ($currentlySelected.length > 0) |
||
896 | ? $currentlySelected |
||
897 | : $activeItem, |
||
898 | hasSelected = ($selectedItem.length > 0) |
||
899 | ; |
||
900 | if(hasSelected && !module.is.multiple()) { |
||
901 | module.debug('Forcing partial selection to selected item', $selectedItem); |
||
902 | module.event.item.click.call($selectedItem, {}, true); |
||
903 | return; |
||
904 | } |
||
905 | else { |
||
906 | if(settings.allowAdditions) { |
||
907 | module.set.selected(module.get.query()); |
||
908 | module.remove.searchTerm(); |
||
909 | } |
||
910 | else { |
||
911 | module.remove.searchTerm(); |
||
912 | } |
||
913 | } |
||
914 | }, |
||
915 | |||
916 | event: { |
||
917 | change: function() { |
||
918 | if(!internalChange) { |
||
919 | module.debug('Input changed, updating selection'); |
||
920 | module.set.selected(); |
||
921 | } |
||
922 | }, |
||
923 | focus: function() { |
||
924 | if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) { |
||
925 | module.show(); |
||
926 | } |
||
927 | }, |
||
928 | blur: function(event) { |
||
929 | pageLostFocus = (document.activeElement === this); |
||
930 | if(!activated && !pageLostFocus) { |
||
931 | module.remove.activeLabel(); |
||
932 | module.hide(); |
||
933 | } |
||
934 | }, |
||
935 | mousedown: function() { |
||
936 | if(module.is.searchSelection()) { |
||
937 | // prevent menu hiding on immediate re-focus |
||
938 | willRefocus = true; |
||
939 | } |
||
940 | else { |
||
941 | // prevents focus callback from occurring on mousedown |
||
942 | activated = true; |
||
943 | } |
||
944 | }, |
||
945 | mouseup: function() { |
||
946 | if(module.is.searchSelection()) { |
||
947 | // prevent menu hiding on immediate re-focus |
||
948 | willRefocus = false; |
||
949 | } |
||
950 | else { |
||
951 | activated = false; |
||
952 | } |
||
953 | }, |
||
954 | click: function(event) { |
||
955 | var |
||
956 | $target = $(event.target) |
||
957 | ; |
||
958 | // focus search |
||
959 | if($target.is($module)) { |
||
960 | if(!module.is.focusedOnSearch()) { |
||
961 | module.focusSearch(); |
||
962 | } |
||
963 | else { |
||
964 | module.show(); |
||
965 | } |
||
966 | } |
||
967 | }, |
||
968 | search: { |
||
969 | focus: function() { |
||
970 | activated = true; |
||
971 | if(module.is.multiple()) { |
||
972 | module.remove.activeLabel(); |
||
973 | } |
||
974 | if(settings.showOnFocus) { |
||
975 | module.search(); |
||
976 | } |
||
977 | }, |
||
978 | blur: function(event) { |
||
979 | pageLostFocus = (document.activeElement === this); |
||
980 | if(module.is.searchSelection() && !willRefocus) { |
||
981 | if(!itemActivated && !pageLostFocus) { |
||
982 | if(settings.forceSelection) { |
||
983 | module.forceSelection(); |
||
984 | } |
||
985 | module.hide(); |
||
986 | } |
||
987 | } |
||
988 | willRefocus = false; |
||
989 | } |
||
990 | }, |
||
991 | icon: { |
||
992 | click: function(event) { |
||
993 | module.toggle(); |
||
994 | } |
||
995 | }, |
||
996 | text: { |
||
997 | focus: function(event) { |
||
998 | activated = true; |
||
999 | module.focusSearch(); |
||
1000 | } |
||
1001 | }, |
||
1002 | input: function(event) { |
||
1003 | if(module.is.multiple() || module.is.searchSelection()) { |
||
1004 | module.set.filtered(); |
||
1005 | } |
||
1006 | clearTimeout(module.timer); |
||
1007 | module.timer = setTimeout(module.search, settings.delay.search); |
||
1008 | }, |
||
1009 | label: { |
||
1010 | click: function(event) { |
||
1011 | var |
||
1012 | $label = $(this), |
||
1013 | $labels = $module.find(selector.label), |
||
1014 | $activeLabels = $labels.filter('.' + className.active), |
||
1015 | $nextActive = $label.nextAll('.' + className.active), |
||
1016 | $prevActive = $label.prevAll('.' + className.active), |
||
1017 | $range = ($nextActive.length > 0) |
||
1018 | ? $label.nextUntil($nextActive).add($activeLabels).add($label) |
||
1019 | : $label.prevUntil($prevActive).add($activeLabels).add($label) |
||
1020 | ; |
||
1021 | if(event.shiftKey) { |
||
1022 | $activeLabels.removeClass(className.active); |
||
1023 | $range.addClass(className.active); |
||
1024 | } |
||
1025 | else if(event.ctrlKey) { |
||
1026 | $label.toggleClass(className.active); |
||
1027 | } |
||
1028 | else { |
||
1029 | $activeLabels.removeClass(className.active); |
||
1030 | $label.addClass(className.active); |
||
1031 | } |
||
1032 | settings.onLabelSelect.apply(this, $labels.filter('.' + className.active)); |
||
1033 | } |
||
1034 | }, |
||
1035 | remove: { |
||
1036 | click: function() { |
||
1037 | var |
||
1038 | $label = $(this).parent() |
||
1039 | ; |
||
1040 | if( $label.hasClass(className.active) ) { |
||
1041 | // remove all selected labels |
||
1042 | module.remove.activeLabels(); |
||
1043 | } |
||
1044 | else { |
||
1045 | // remove this label only |
||
1046 | module.remove.activeLabels( $label ); |
||
1047 | } |
||
1048 | } |
||
1049 | }, |
||
1050 | test: { |
||
1051 | toggle: function(event) { |
||
1052 | var |
||
1053 | toggleBehavior = (module.is.multiple()) |
||
1054 | ? module.show |
||
1055 | : module.toggle |
||
1056 | ; |
||
1057 | if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) { |
||
1058 | return; |
||
1059 | } |
||
1060 | if( module.determine.eventOnElement(event, toggleBehavior) ) { |
||
1061 | event.preventDefault(); |
||
1062 | } |
||
1063 | }, |
||
1064 | touch: function(event) { |
||
1065 | module.determine.eventOnElement(event, function() { |
||
1066 | if(event.type == 'touchstart') { |
||
1067 | module.timer = setTimeout(function() { |
||
1068 | module.hide(); |
||
1069 | }, settings.delay.touch); |
||
1070 | } |
||
1071 | else if(event.type == 'touchmove') { |
||
1072 | clearTimeout(module.timer); |
||
1073 | } |
||
1074 | }); |
||
1075 | event.stopPropagation(); |
||
1076 | }, |
||
1077 | hide: function(event) { |
||
1078 | module.determine.eventInModule(event, module.hide); |
||
1079 | } |
||
1080 | }, |
||
1081 | select: { |
||
1082 | mutation: function(mutations) { |
||
1083 | module.debug('<select> modified, recreating menu'); |
||
1084 | module.setup.select(); |
||
1085 | } |
||
1086 | }, |
||
1087 | menu: { |
||
1088 | mutation: function(mutations) { |
||
1089 | var |
||
1090 | mutation = mutations[0], |
||
1091 | $addedNode = mutation.addedNodes |
||
1092 | ? $(mutation.addedNodes[0]) |
||
1093 | : $(false), |
||
1094 | $removedNode = mutation.removedNodes |
||
1095 | ? $(mutation.removedNodes[0]) |
||
1096 | : $(false), |
||
1097 | $changedNodes = $addedNode.add($removedNode), |
||
1098 | isUserAddition = $changedNodes.is(selector.addition) || $changedNodes.closest(selector.addition).length > 0, |
||
1099 | isMessage = $changedNodes.is(selector.message) || $changedNodes.closest(selector.message).length > 0 |
||
1100 | ; |
||
1101 | if(isUserAddition || isMessage) { |
||
1102 | module.debug('Updating item selector cache'); |
||
1103 | module.refreshItems(); |
||
1104 | } |
||
1105 | else { |
||
1106 | module.debug('Menu modified, updating selector cache'); |
||
1107 | module.refresh(); |
||
1108 | } |
||
1109 | }, |
||
1110 | mousedown: function() { |
||
1111 | itemActivated = true; |
||
1112 | }, |
||
1113 | mouseup: function() { |
||
1114 | itemActivated = false; |
||
1115 | } |
||
1116 | }, |
||
1117 | item: { |
||
1118 | mouseenter: function(event) { |
||
1119 | var |
||
1120 | $target = $(event.target), |
||
1121 | $item = $(this), |
||
1122 | $subMenu = $item.children(selector.menu), |
||
1123 | $otherMenus = $item.siblings(selector.item).children(selector.menu), |
||
1124 | hasSubMenu = ($subMenu.length > 0), |
||
1125 | isBubbledEvent = ($subMenu.find($target).length > 0) |
||
1126 | ; |
||
1127 | if( !isBubbledEvent && hasSubMenu ) { |
||
1128 | clearTimeout(module.itemTimer); |
||
1129 | module.itemTimer = setTimeout(function() { |
||
1130 | module.verbose('Showing sub-menu', $subMenu); |
||
1131 | $.each($otherMenus, function() { |
||
1132 | module.animate.hide(false, $(this)); |
||
1133 | }); |
||
1134 | module.animate.show(false, $subMenu); |
||
1135 | }, settings.delay.show); |
||
1136 | event.preventDefault(); |
||
1137 | } |
||
1138 | }, |
||
1139 | mouseleave: function(event) { |
||
1140 | var |
||
1141 | $subMenu = $(this).children(selector.menu) |
||
1142 | ; |
||
1143 | if($subMenu.length > 0) { |
||
1144 | clearTimeout(module.itemTimer); |
||
1145 | module.itemTimer = setTimeout(function() { |
||
1146 | module.verbose('Hiding sub-menu', $subMenu); |
||
1147 | module.animate.hide(false, $subMenu); |
||
1148 | }, settings.delay.hide); |
||
1149 | } |
||
1150 | }, |
||
1151 | click: function (event, skipRefocus) { |
||
1152 | var |
||
1153 | $choice = $(this), |
||
1154 | $target = (event) |
||
1155 | ? $(event.target) |
||
1156 | : $(''), |
||
1157 | $subMenu = $choice.find(selector.menu), |
||
1158 | text = module.get.choiceText($choice), |
||
1159 | value = module.get.choiceValue($choice, text), |
||
1160 | hasSubMenu = ($subMenu.length > 0), |
||
1161 | isBubbledEvent = ($subMenu.find($target).length > 0) |
||
1162 | ; |
||
1163 | // prevents IE11 bug where menu receives focus even though `tabindex=-1` |
||
1164 | if(module.has.menuSearch()) { |
||
1165 | $(document.activeElement).blur(); |
||
1166 | } |
||
1167 | if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) { |
||
1168 | if(module.is.searchSelection()) { |
||
1169 | if(settings.allowAdditions) { |
||
1170 | module.remove.userAddition(); |
||
1171 | } |
||
1172 | module.remove.searchTerm(); |
||
1173 | if(!module.is.focusedOnSearch() && !(skipRefocus == true)) { |
||
1174 | module.focusSearch(true); |
||
1175 | } |
||
1176 | } |
||
1177 | if(!settings.useLabels) { |
||
1178 | module.remove.filteredItem(); |
||
1179 | module.set.scrollPosition($choice); |
||
1180 | } |
||
1181 | module.determine.selectAction.call(this, text, value); |
||
1182 | } |
||
1183 | } |
||
1184 | }, |
||
1185 | |||
1186 | document: { |
||
1187 | // label selection should occur even when element has no focus |
||
1188 | keydown: function(event) { |
||
1189 | var |
||
1190 | pressedKey = event.which, |
||
1191 | isShortcutKey = module.is.inObject(pressedKey, keys) |
||
1192 | ; |
||
1193 | if(isShortcutKey) { |
||
1194 | var |
||
1195 | $label = $module.find(selector.label), |
||
1196 | $activeLabel = $label.filter('.' + className.active), |
||
1197 | activeValue = $activeLabel.data(metadata.value), |
||
1198 | labelIndex = $label.index($activeLabel), |
||
1199 | labelCount = $label.length, |
||
1200 | hasActiveLabel = ($activeLabel.length > 0), |
||
1201 | hasMultipleActive = ($activeLabel.length > 1), |
||
1202 | isFirstLabel = (labelIndex === 0), |
||
1203 | isLastLabel = (labelIndex + 1 == labelCount), |
||
1204 | isSearch = module.is.searchSelection(), |
||
1205 | isFocusedOnSearch = module.is.focusedOnSearch(), |
||
1206 | isFocused = module.is.focused(), |
||
1207 | caretAtStart = (isFocusedOnSearch && module.get.caretPosition() === 0), |
||
1208 | $nextLabel |
||
1209 | ; |
||
1210 | if(isSearch && !hasActiveLabel && !isFocusedOnSearch) { |
||
1211 | return; |
||
1212 | } |
||
1213 | |||
1214 | if(pressedKey == keys.leftArrow) { |
||
1215 | // activate previous label |
||
1216 | if((isFocused || caretAtStart) && !hasActiveLabel) { |
||
1217 | module.verbose('Selecting previous label'); |
||
1218 | $label.last().addClass(className.active); |
||
1219 | } |
||
1220 | else if(hasActiveLabel) { |
||
1221 | if(!event.shiftKey) { |
||
1222 | module.verbose('Selecting previous label'); |
||
1223 | $label.removeClass(className.active); |
||
1224 | } |
||
1225 | else { |
||
1226 | module.verbose('Adding previous label to selection'); |
||
1227 | } |
||
1228 | if(isFirstLabel && !hasMultipleActive) { |
||
1229 | $activeLabel.addClass(className.active); |
||
1230 | } |
||
1231 | else { |
||
1232 | $activeLabel.prev(selector.siblingLabel) |
||
1233 | .addClass(className.active) |
||
1234 | .end() |
||
1235 | ; |
||
1236 | } |
||
1237 | event.preventDefault(); |
||
1238 | } |
||
1239 | } |
||
1240 | else if(pressedKey == keys.rightArrow) { |
||
1241 | // activate first label |
||
1242 | if(isFocused && !hasActiveLabel) { |
||
1243 | $label.first().addClass(className.active); |
||
1244 | } |
||
1245 | // activate next label |
||
1246 | if(hasActiveLabel) { |
||
1247 | if(!event.shiftKey) { |
||
1248 | module.verbose('Selecting next label'); |
||
1249 | $label.removeClass(className.active); |
||
1250 | } |
||
1251 | else { |
||
1252 | module.verbose('Adding next label to selection'); |
||
1253 | } |
||
1254 | if(isLastLabel) { |
||
1255 | if(isSearch) { |
||
1256 | if(!isFocusedOnSearch) { |
||
1257 | module.focusSearch(); |
||
1258 | } |
||
1259 | else { |
||
1260 | $label.removeClass(className.active); |
||
1261 | } |
||
1262 | } |
||
1263 | else if(hasMultipleActive) { |
||
1264 | $activeLabel.next(selector.siblingLabel).addClass(className.active); |
||
1265 | } |
||
1266 | else { |
||
1267 | $activeLabel.addClass(className.active); |
||
1268 | } |
||
1269 | } |
||
1270 | else { |
||
1271 | $activeLabel.next(selector.siblingLabel).addClass(className.active); |
||
1272 | } |
||
1273 | event.preventDefault(); |
||
1274 | } |
||
1275 | } |
||
1276 | else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) { |
||
1277 | if(hasActiveLabel) { |
||
1278 | module.verbose('Removing active labels'); |
||
1279 | if(isLastLabel) { |
||
1280 | if(isSearch && !isFocusedOnSearch) { |
||
1281 | module.focusSearch(); |
||
1282 | } |
||
1283 | } |
||
1284 | $activeLabel.last().next(selector.siblingLabel).addClass(className.active); |
||
1285 | module.remove.activeLabels($activeLabel); |
||
1286 | event.preventDefault(); |
||
1287 | } |
||
1288 | else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) { |
||
1289 | module.verbose('Removing last label on input backspace'); |
||
1290 | $activeLabel = $label.last().addClass(className.active); |
||
1291 | module.remove.activeLabels($activeLabel); |
||
1292 | } |
||
1293 | } |
||
1294 | else { |
||
1295 | $activeLabel.removeClass(className.active); |
||
1296 | } |
||
1297 | } |
||
1298 | } |
||
1299 | }, |
||
1300 | |||
1301 | keydown: function(event) { |
||
1302 | var |
||
1303 | pressedKey = event.which, |
||
1304 | isShortcutKey = module.is.inObject(pressedKey, keys) |
||
1305 | ; |
||
1306 | if(isShortcutKey) { |
||
1307 | var |
||
1308 | $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0), |
||
1309 | $activeItem = $menu.children('.' + className.active).eq(0), |
||
1310 | $selectedItem = ($currentlySelected.length > 0) |
||
1311 | ? $currentlySelected |
||
1312 | : $activeItem, |
||
1313 | $visibleItems = ($selectedItem.length > 0) |
||
1314 | ? $selectedItem.siblings(':not(.' + className.filtered +')').addBack() |
||
1315 | : $menu.children(':not(.' + className.filtered +')'), |
||
1316 | $subMenu = $selectedItem.children(selector.menu), |
||
1317 | $parentMenu = $selectedItem.closest(selector.menu), |
||
1318 | inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0), |
||
1319 | hasSubMenu = ($subMenu.length> 0), |
||
1320 | hasSelectedItem = ($selectedItem.length > 0), |
||
1321 | selectedIsSelectable = ($selectedItem.not(selector.unselectable).length > 0), |
||
1322 | delimiterPressed = (pressedKey == keys.delimiter && settings.allowAdditions && module.is.multiple()), |
||
1323 | isAdditionWithoutMenu = (settings.allowAdditions && settings.hideAdditions && (pressedKey == keys.enter || delimiterPressed) && selectedIsSelectable), |
||
1324 | $nextItem, |
||
1325 | isSubMenuItem, |
||
1326 | newIndex |
||
1327 | ; |
||
1328 | // allow selection with menu closed |
||
1329 | if(isAdditionWithoutMenu) { |
||
1330 | module.verbose('Selecting item from keyboard shortcut', $selectedItem); |
||
1331 | module.event.item.click.call($selectedItem, event); |
||
1332 | if(module.is.searchSelection()) { |
||
1333 | module.remove.searchTerm(); |
||
1334 | } |
||
1335 | } |
||
1336 | |||
1337 | // visible menu keyboard shortcuts |
||
1338 | if( module.is.visible() ) { |
||
1339 | |||
1340 | // enter (select or open sub-menu) |
||
1341 | if(pressedKey == keys.enter || delimiterPressed) { |
||
1342 | if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) { |
||
1343 | module.verbose('Pressed enter on unselectable category, opening sub menu'); |
||
1344 | pressedKey = keys.rightArrow; |
||
1345 | } |
||
1346 | else if(selectedIsSelectable) { |
||
1347 | module.verbose('Selecting item from keyboard shortcut', $selectedItem); |
||
1348 | module.event.item.click.call($selectedItem, event); |
||
1349 | if(module.is.searchSelection()) { |
||
1350 | module.remove.searchTerm(); |
||
1351 | } |
||
1352 | } |
||
1353 | event.preventDefault(); |
||
1354 | } |
||
1355 | |||
1356 | // sub-menu actions |
||
1357 | if(hasSelectedItem) { |
||
1358 | |||
1359 | if(pressedKey == keys.leftArrow) { |
||
1360 | |||
1361 | isSubMenuItem = ($parentMenu[0] !== $menu[0]); |
||
1362 | |||
1363 | if(isSubMenuItem) { |
||
1364 | module.verbose('Left key pressed, closing sub-menu'); |
||
1365 | module.animate.hide(false, $parentMenu); |
||
1366 | $selectedItem |
||
1367 | .removeClass(className.selected) |
||
1368 | ; |
||
1369 | $parentMenu |
||
1370 | .closest(selector.item) |
||
1371 | .addClass(className.selected) |
||
1372 | ; |
||
1373 | event.preventDefault(); |
||
1374 | } |
||
1375 | } |
||
1376 | |||
1377 | // right arrow (show sub-menu) |
||
1378 | if(pressedKey == keys.rightArrow) { |
||
1379 | if(hasSubMenu) { |
||
1380 | module.verbose('Right key pressed, opening sub-menu'); |
||
1381 | module.animate.show(false, $subMenu); |
||
1382 | $selectedItem |
||
1383 | .removeClass(className.selected) |
||
1384 | ; |
||
1385 | $subMenu |
||
1386 | .find(selector.item).eq(0) |
||
1387 | .addClass(className.selected) |
||
1388 | ; |
||
1389 | event.preventDefault(); |
||
1390 | } |
||
1391 | } |
||
1392 | } |
||
1393 | |||
1394 | // up arrow (traverse menu up) |
||
1395 | if(pressedKey == keys.upArrow) { |
||
1396 | $nextItem = (hasSelectedItem && inVisibleMenu) |
||
1397 | ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0) |
||
1398 | : $item.eq(0) |
||
1399 | ; |
||
1400 | if($visibleItems.index( $nextItem ) < 0) { |
||
1401 | module.verbose('Up key pressed but reached top of current menu'); |
||
1402 | event.preventDefault(); |
||
1403 | return; |
||
1404 | } |
||
1405 | else { |
||
1406 | module.verbose('Up key pressed, changing active item'); |
||
1407 | $selectedItem |
||
1408 | .removeClass(className.selected) |
||
1409 | ; |
||
1410 | $nextItem |
||
1411 | .addClass(className.selected) |
||
1412 | ; |
||
1413 | module.set.scrollPosition($nextItem); |
||
1414 | if(settings.selectOnKeydown && module.is.single()) { |
||
1415 | module.set.selectedItem($nextItem); |
||
1416 | } |
||
1417 | } |
||
1418 | event.preventDefault(); |
||
1419 | } |
||
1420 | |||
1421 | // down arrow (traverse menu down) |
||
1422 | if(pressedKey == keys.downArrow) { |
||
1423 | $nextItem = (hasSelectedItem && inVisibleMenu) |
||
1424 | ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0) |
||
1425 | : $item.eq(0) |
||
1426 | ; |
||
1427 | if($nextItem.length === 0) { |
||
1428 | module.verbose('Down key pressed but reached bottom of current menu'); |
||
1429 | event.preventDefault(); |
||
1430 | return; |
||
1431 | } |
||
1432 | else { |
||
1433 | module.verbose('Down key pressed, changing active item'); |
||
1434 | $item |
||
1435 | .removeClass(className.selected) |
||
1436 | ; |
||
1437 | $nextItem |
||
1438 | .addClass(className.selected) |
||
1439 | ; |
||
1440 | module.set.scrollPosition($nextItem); |
||
1441 | if(settings.selectOnKeydown && module.is.single()) { |
||
1442 | module.set.selectedItem($nextItem); |
||
1443 | } |
||
1444 | } |
||
1445 | event.preventDefault(); |
||
1446 | } |
||
1447 | |||
1448 | // page down (show next page) |
||
1449 | if(pressedKey == keys.pageUp) { |
||
1450 | module.scrollPage('up'); |
||
1451 | event.preventDefault(); |
||
1452 | } |
||
1453 | if(pressedKey == keys.pageDown) { |
||
1454 | module.scrollPage('down'); |
||
1455 | event.preventDefault(); |
||
1456 | } |
||
1457 | |||
1458 | // escape (close menu) |
||
1459 | if(pressedKey == keys.escape) { |
||
1460 | module.verbose('Escape key pressed, closing dropdown'); |
||
1461 | module.hide(); |
||
1462 | } |
||
1463 | |||
1464 | } |
||
1465 | else { |
||
1466 | // delimiter key |
||
1467 | if(delimiterPressed) { |
||
1468 | event.preventDefault(); |
||
1469 | } |
||
1470 | // down arrow (open menu) |
||
1471 | if(pressedKey == keys.downArrow && !module.is.visible()) { |
||
1472 | module.verbose('Down key pressed, showing dropdown'); |
||
1473 | module.show(); |
||
1474 | event.preventDefault(); |
||
1475 | } |
||
1476 | } |
||
1477 | } |
||
1478 | else { |
||
1479 | if( !module.has.search() ) { |
||
1480 | module.set.selectedLetter( String.fromCharCode(pressedKey) ); |
||
1481 | } |
||
1482 | } |
||
1483 | } |
||
1484 | }, |
||
1485 | |||
1486 | trigger: { |
||
1487 | change: function() { |
||
1488 | var |
||
1489 | events = document.createEvent('HTMLEvents'), |
||
1490 | inputElement = $input[0] |
||
1491 | ; |
||
1492 | if(inputElement) { |
||
1493 | module.verbose('Triggering native change event'); |
||
1494 | events.initEvent('change', true, false); |
||
1495 | inputElement.dispatchEvent(events); |
||
1496 | } |
||
1497 | } |
||
1498 | }, |
||
1499 | |||
1500 | determine: { |
||
1501 | selectAction: function(text, value) { |
||
1502 | module.verbose('Determining action', settings.action); |
||
1503 | if( $.isFunction( module.action[settings.action] ) ) { |
||
1504 | module.verbose('Triggering preset action', settings.action, text, value); |
||
1505 | module.action[ settings.action ].call(element, text, value, this); |
||
1506 | } |
||
1507 | else if( $.isFunction(settings.action) ) { |
||
1508 | module.verbose('Triggering user action', settings.action, text, value); |
||
1509 | settings.action.call(element, text, value, this); |
||
1510 | } |
||
1511 | else { |
||
1512 | module.error(error.action, settings.action); |
||
1513 | } |
||
1514 | }, |
||
1515 | eventInModule: function(event, callback) { |
||
1516 | var |
||
1517 | $target = $(event.target), |
||
1518 | inDocument = ($target.closest(document.documentElement).length > 0), |
||
1519 | inModule = ($target.closest($module).length > 0) |
||
1520 | ; |
||
1521 | callback = $.isFunction(callback) |
||
1522 | ? callback |
||
1523 | : function(){} |
||
1524 | ; |
||
1525 | if(inDocument && !inModule) { |
||
1526 | module.verbose('Triggering event', callback); |
||
1527 | callback(); |
||
1528 | return true; |
||
1529 | } |
||
1530 | else { |
||
1531 | module.verbose('Event occurred in dropdown, canceling callback'); |
||
1532 | return false; |
||
1533 | } |
||
1534 | }, |
||
1535 | eventOnElement: function(event, callback) { |
||
1536 | var |
||
1537 | $target = $(event.target), |
||
1538 | $label = $target.closest(selector.siblingLabel), |
||
1539 | inVisibleDOM = document.body.contains(event.target), |
||
1540 | notOnLabel = ($module.find($label).length === 0), |
||
1541 | notInMenu = ($target.closest($menu).length === 0) |
||
1542 | ; |
||
1543 | callback = $.isFunction(callback) |
||
1544 | ? callback |
||
1545 | : function(){} |
||
1546 | ; |
||
1547 | if(inVisibleDOM && notOnLabel && notInMenu) { |
||
1548 | module.verbose('Triggering event', callback); |
||
1549 | callback(); |
||
1550 | return true; |
||
1551 | } |
||
1552 | else { |
||
1553 | module.verbose('Event occurred in dropdown menu, canceling callback'); |
||
1554 | return false; |
||
1555 | } |
||
1556 | } |
||
1557 | }, |
||
1558 | |||
1559 | action: { |
||
1560 | |||
1561 | nothing: function() {}, |
||
1562 | |||
1563 | activate: function(text, value, element) { |
||
1564 | value = (value !== undefined) |
||
1565 | ? value |
||
1566 | : text |
||
1567 | ; |
||
1568 | if( module.can.activate( $(element) ) ) { |
||
1569 | module.set.selected(value, $(element)); |
||
1570 | if(module.is.multiple() && !module.is.allFiltered()) { |
||
1571 | return; |
||
1572 | } |
||
1573 | else { |
||
1574 | module.hideAndClear(); |
||
1575 | } |
||
1576 | } |
||
1577 | }, |
||
1578 | |||
1579 | select: function(text, value, element) { |
||
1580 | value = (value !== undefined) |
||
1581 | ? value |
||
1582 | : text |
||
1583 | ; |
||
1584 | if( module.can.activate( $(element) ) ) { |
||
1585 | module.set.value(value, $(element)); |
||
1586 | if(module.is.multiple() && !module.is.allFiltered()) { |
||
1587 | return; |
||
1588 | } |
||
1589 | else { |
||
1590 | module.hideAndClear(); |
||
1591 | } |
||
1592 | } |
||
1593 | }, |
||
1594 | |||
1595 | combo: function(text, value, element) { |
||
1596 | value = (value !== undefined) |
||
1597 | ? value |
||
1598 | : text |
||
1599 | ; |
||
1600 | module.set.selected(value, $(element)); |
||
1601 | module.hideAndClear(); |
||
1602 | }, |
||
1603 | |||
1604 | hide: function(text, value, element) { |
||
1605 | module.set.value(value, text); |
||
1606 | module.hideAndClear(); |
||
1607 | } |
||
1608 | |||
1609 | }, |
||
1610 | |||
1611 | get: { |
||
1612 | id: function() { |
||
1613 | return id; |
||
1614 | }, |
||
1615 | defaultText: function() { |
||
1616 | return $module.data(metadata.defaultText); |
||
1617 | }, |
||
1618 | defaultValue: function() { |
||
1619 | return $module.data(metadata.defaultValue); |
||
1620 | }, |
||
1621 | placeholderText: function() { |
||
1622 | return $module.data(metadata.placeholderText) || ''; |
||
1623 | }, |
||
1624 | text: function() { |
||
1625 | return $text.text(); |
||
1626 | }, |
||
1627 | query: function() { |
||
1628 | return $.trim($search.val()); |
||
1629 | }, |
||
1630 | searchWidth: function(value) { |
||
1631 | value = (value !== undefined) |
||
1632 | ? value |
||
1633 | : $search.val() |
||
1634 | ; |
||
1635 | $sizer.text(value); |
||
1636 | // prevent rounding issues |
||
1637 | return Math.ceil( $sizer.width() + 1); |
||
1638 | }, |
||
1639 | selectionCount: function() { |
||
1640 | var |
||
1641 | values = module.get.values(), |
||
1642 | count |
||
1643 | ; |
||
1644 | count = ( module.is.multiple() ) |
||
1645 | ? $.isArray(values) |
||
1646 | ? values.length |
||
1647 | : 0 |
||
1648 | : (module.get.value() !== '') |
||
1649 | ? 1 |
||
1650 | : 0 |
||
1651 | ; |
||
1652 | return count; |
||
1653 | }, |
||
1654 | transition: function($subMenu) { |
||
1655 | return (settings.transition == 'auto') |
||
1656 | ? module.is.upward($subMenu) |
||
1657 | ? 'slide up' |
||
1658 | : 'slide down' |
||
1659 | : settings.transition |
||
1660 | ; |
||
1661 | }, |
||
1662 | userValues: function() { |
||
1663 | var |
||
1664 | values = module.get.values() |
||
1665 | ; |
||
1666 | if(!values) { |
||
1667 | return false; |
||
1668 | } |
||
1669 | values = $.isArray(values) |
||
1670 | ? values |
||
1671 | : [values] |
||
1672 | ; |
||
1673 | return $.grep(values, function(value) { |
||
1674 | return (module.get.item(value) === false); |
||
1675 | }); |
||
1676 | }, |
||
1677 | uniqueArray: function(array) { |
||
1678 | return $.grep(array, function (value, index) { |
||
1679 | return $.inArray(value, array) === index; |
||
1680 | }); |
||
1681 | }, |
||
1682 | caretPosition: function() { |
||
1683 | var |
||
1684 | input = $search.get(0), |
||
1685 | range, |
||
1686 | rangeLength |
||
1687 | ; |
||
1688 | if('selectionStart' in input) { |
||
1689 | return input.selectionStart; |
||
1690 | } |
||
1691 | else if (document.selection) { |
||
1692 | input.focus(); |
||
1693 | range = document.selection.createRange(); |
||
1694 | rangeLength = range.text.length; |
||
1695 | range.moveStart('character', -input.value.length); |
||
1696 | return range.text.length - rangeLength; |
||
1697 | } |
||
1698 | }, |
||
1699 | value: function() { |
||
1700 | var |
||
1701 | value = ($input.length > 0) |
||
1702 | ? $input.val() |
||
1703 | : $module.data(metadata.value), |
||
1704 | isEmptyMultiselect = ($.isArray(value) && value.length === 1 && value[0] === '') |
||
1705 | ; |
||
1706 | // prevents placeholder element from being selected when multiple |
||
1707 | return (value === undefined || isEmptyMultiselect) |
||
1708 | ? '' |
||
1709 | : value |
||
1710 | ; |
||
1711 | }, |
||
1712 | values: function() { |
||
1713 | var |
||
1714 | value = module.get.value() |
||
1715 | ; |
||
1716 | if(value === '') { |
||
1717 | return ''; |
||
1718 | } |
||
1719 | return ( !module.has.selectInput() && module.is.multiple() ) |
||
1720 | ? (typeof value == 'string') // delimited string |
||
1721 | ? value.split(settings.delimiter) |
||
1722 | : '' |
||
1723 | : value |
||
1724 | ; |
||
1725 | }, |
||
1726 | remoteValues: function() { |
||
1727 | var |
||
1728 | values = module.get.values(), |
||
1729 | remoteValues = false |
||
1730 | ; |
||
1731 | if(values) { |
||
1732 | if(typeof values == 'string') { |
||
1733 | values = [values]; |
||
1734 | } |
||
1735 | $.each(values, function(index, value) { |
||
1736 | var |
||
1737 | name = module.read.remoteData(value) |
||
1738 | ; |
||
1739 | module.verbose('Restoring value from session data', name, value); |
||
1740 | if(name) { |
||
1741 | if(!remoteValues) { |
||
1742 | remoteValues = {}; |
||
1743 | } |
||
1744 | remoteValues[value] = name; |
||
1745 | } |
||
1746 | }); |
||
1747 | } |
||
1748 | return remoteValues; |
||
1749 | }, |
||
1750 | choiceText: function($choice, preserveHTML) { |
||
1751 | preserveHTML = (preserveHTML !== undefined) |
||
1752 | ? preserveHTML |
||
1753 | : settings.preserveHTML |
||
1754 | ; |
||
1755 | if($choice) { |
||
1756 | if($choice.find(selector.menu).length > 0) { |
||
1757 | module.verbose('Retrieving text of element with sub-menu'); |
||
1758 | $choice = $choice.clone(); |
||
1759 | $choice.find(selector.menu).remove(); |
||
1760 | $choice.find(selector.menuIcon).remove(); |
||
1761 | } |
||
1762 | return ($choice.data(metadata.text) !== undefined) |
||
1763 | ? $choice.data(metadata.text) |
||
1764 | : (preserveHTML) |
||
1765 | ? $.trim($choice.html()) |
||
1766 | : $.trim($choice.text()) |
||
1767 | ; |
||
1768 | } |
||
1769 | }, |
||
1770 | choiceValue: function($choice, choiceText) { |
||
1771 | choiceText = choiceText || module.get.choiceText($choice); |
||
1772 | if(!$choice) { |
||
1773 | return false; |
||
1774 | } |
||
1775 | return ($choice.data(metadata.value) !== undefined) |
||
1776 | ? String( $choice.data(metadata.value) ) |
||
1777 | : (typeof choiceText === 'string') |
||
1778 | ? $.trim(choiceText.toLowerCase()) |
||
1779 | : String(choiceText) |
||
1780 | ; |
||
1781 | }, |
||
1782 | inputEvent: function() { |
||
1783 | var |
||
1784 | input = $search[0] |
||
1785 | ; |
||
1786 | if(input) { |
||
1787 | return (input.oninput !== undefined) |
||
1788 | ? 'input' |
||
1789 | : (input.onpropertychange !== undefined) |
||
1790 | ? 'propertychange' |
||
1791 | : 'keyup' |
||
1792 | ; |
||
1793 | } |
||
1794 | return false; |
||
1795 | }, |
||
1796 | selectValues: function() { |
||
1797 | var |
||
1798 | select = {} |
||
1799 | ; |
||
1800 | select.values = []; |
||
1801 | $module |
||
1802 | .find('option') |
||
1803 | .each(function() { |
||
1804 | var |
||
1805 | $option = $(this), |
||
1806 | name = $option.html(), |
||
1807 | disabled = $option.attr('disabled'), |
||
1808 | value = ( $option.attr('value') !== undefined ) |
||
1809 | ? $option.attr('value') |
||
1810 | : name |
||
1811 | ; |
||
1812 | if(settings.placeholder === 'auto' && value === '') { |
||
1813 | select.placeholder = name; |
||
1814 | } |
||
1815 | else { |
||
1816 | select.values.push({ |
||
1817 | name : name, |
||
1818 | value : value, |
||
1819 | disabled : disabled |
||
1820 | }); |
||
1821 | } |
||
1822 | }) |
||
1823 | ; |
||
1824 | if(settings.placeholder && settings.placeholder !== 'auto') { |
||
1825 | module.debug('Setting placeholder value to', settings.placeholder); |
||
1826 | select.placeholder = settings.placeholder; |
||
1827 | } |
||
1828 | if(settings.sortSelect) { |
||
1829 | select.values.sort(function(a, b) { |
||
1830 | return (a.name > b.name) |
||
1831 | ? 1 |
||
1832 | : -1 |
||
1833 | ; |
||
1834 | }); |
||
1835 | module.debug('Retrieved and sorted values from select', select); |
||
1836 | } |
||
1837 | else { |
||
1838 | module.debug('Retrieved values from select', select); |
||
1839 | } |
||
1840 | return select; |
||
1841 | }, |
||
1842 | activeItem: function() { |
||
1843 | return $item.filter('.' + className.active); |
||
1844 | }, |
||
1845 | selectedItem: function() { |
||
1846 | var |
||
1847 | $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected) |
||
1848 | ; |
||
1849 | return ($selectedItem.length > 0) |
||
1850 | ? $selectedItem |
||
1851 | : $item.eq(0) |
||
1852 | ; |
||
1853 | }, |
||
1854 | itemWithAdditions: function(value) { |
||
1855 | var |
||
1856 | $items = module.get.item(value), |
||
1857 | $userItems = module.create.userChoice(value), |
||
1858 | hasUserItems = ($userItems && $userItems.length > 0) |
||
1859 | ; |
||
1860 | if(hasUserItems) { |
||
1861 | $items = ($items.length > 0) |
||
1862 | ? $items.add($userItems) |
||
1863 | : $userItems |
||
1864 | ; |
||
1865 | } |
||
1866 | return $items; |
||
1867 | }, |
||
1868 | item: function(value, strict) { |
||
1869 | var |
||
1870 | $selectedItem = false, |
||
1871 | shouldSearch, |
||
1872 | isMultiple |
||
1873 | ; |
||
1874 | value = (value !== undefined) |
||
1875 | ? value |
||
1876 | : ( module.get.values() !== undefined) |
||
1877 | ? module.get.values() |
||
1878 | : module.get.text() |
||
1879 | ; |
||
1880 | shouldSearch = (isMultiple) |
||
1881 | ? (value.length > 0) |
||
1882 | : (value !== undefined && value !== null) |
||
1883 | ; |
||
1884 | isMultiple = (module.is.multiple() && $.isArray(value)); |
||
1885 | strict = (value === '' || value === 0) |
||
1886 | ? true |
||
1887 | : strict || false |
||
1888 | ; |
||
1889 | if(shouldSearch) { |
||
1890 | $item |
||
1891 | .each(function() { |
||
1892 | var |
||
1893 | $choice = $(this), |
||
1894 | optionText = module.get.choiceText($choice), |
||
1895 | optionValue = module.get.choiceValue($choice, optionText) |
||
1896 | ; |
||
1897 | // safe early exit |
||
1898 | if(optionValue === null || optionValue === undefined) { |
||
1899 | return; |
||
1900 | } |
||
1901 | if(isMultiple) { |
||
1902 | if($.inArray( String(optionValue), value) !== -1 || $.inArray(optionText, value) !== -1) { |
||
1903 | $selectedItem = ($selectedItem) |
||
1904 | ? $selectedItem.add($choice) |
||
1905 | : $choice |
||
1906 | ; |
||
1907 | } |
||
1908 | } |
||
1909 | else if(strict) { |
||
1910 | module.verbose('Ambiguous dropdown value using strict type check', $choice, value); |
||
1911 | if( optionValue === value || optionText === value) { |
||
1912 | $selectedItem = $choice; |
||
1913 | return true; |
||
1914 | } |
||
1915 | } |
||
1916 | else { |
||
1917 | if( String(optionValue) == String(value) || optionText == value) { |
||
1918 | module.verbose('Found select item by value', optionValue, value); |
||
1919 | $selectedItem = $choice; |
||
1920 | return true; |
||
1921 | } |
||
1922 | } |
||
1923 | }) |
||
1924 | ; |
||
1925 | } |
||
1926 | return $selectedItem; |
||
1927 | } |
||
1928 | }, |
||
1929 | |||
1930 | check: { |
||
1931 | maxSelections: function(selectionCount) { |
||
1932 | if(settings.maxSelections) { |
||
1933 | selectionCount = (selectionCount !== undefined) |
||
1934 | ? selectionCount |
||
1935 | : module.get.selectionCount() |
||
1936 | ; |
||
1937 | if(selectionCount >= settings.maxSelections) { |
||
1938 | module.debug('Maximum selection count reached'); |
||
1939 | if(settings.useLabels) { |
||
1940 | $item.addClass(className.filtered); |
||
1941 | module.add.message(message.maxSelections); |
||
1942 | } |
||
1943 | return true; |
||
1944 | } |
||
1945 | else { |
||
1946 | module.verbose('No longer at maximum selection count'); |
||
1947 | module.remove.message(); |
||
1948 | module.remove.filteredItem(); |
||
1949 | if(module.is.searchSelection()) { |
||
1950 | module.filterItems(); |
||
1951 | } |
||
1952 | return false; |
||
1953 | } |
||
1954 | } |
||
1955 | return true; |
||
1956 | } |
||
1957 | }, |
||
1958 | |||
1959 | restore: { |
||
1960 | defaults: function() { |
||
1961 | module.clear(); |
||
1962 | module.restore.defaultText(); |
||
1963 | module.restore.defaultValue(); |
||
1964 | }, |
||
1965 | defaultText: function() { |
||
1966 | var |
||
1967 | defaultText = module.get.defaultText(), |
||
1968 | placeholderText = module.get.placeholderText |
||
1969 | ; |
||
1970 | if(defaultText === placeholderText) { |
||
1971 | module.debug('Restoring default placeholder text', defaultText); |
||
1972 | module.set.placeholderText(defaultText); |
||
1973 | } |
||
1974 | else { |
||
1975 | module.debug('Restoring default text', defaultText); |
||
1976 | module.set.text(defaultText); |
||
1977 | } |
||
1978 | }, |
||
1979 | placeholderText: function() { |
||
1980 | module.set.placeholderText(); |
||
1981 | }, |
||
1982 | defaultValue: function() { |
||
1983 | var |
||
1984 | defaultValue = module.get.defaultValue() |
||
1985 | ; |
||
1986 | if(defaultValue !== undefined) { |
||
1987 | module.debug('Restoring default value', defaultValue); |
||
1988 | if(defaultValue !== '') { |
||
1989 | module.set.value(defaultValue); |
||
1990 | module.set.selected(); |
||
1991 | } |
||
1992 | else { |
||
1993 | module.remove.activeItem(); |
||
1994 | module.remove.selectedItem(); |
||
1995 | } |
||
1996 | } |
||
1997 | }, |
||
1998 | labels: function() { |
||
1999 | if(settings.allowAdditions) { |
||
2000 | if(!settings.useLabels) { |
||
2001 | module.error(error.labels); |
||
2002 | settings.useLabels = true; |
||
2003 | } |
||
2004 | module.debug('Restoring selected values'); |
||
2005 | module.create.userLabels(); |
||
2006 | } |
||
2007 | module.check.maxSelections(); |
||
2008 | }, |
||
2009 | selected: function() { |
||
2010 | module.restore.values(); |
||
2011 | if(module.is.multiple()) { |
||
2012 | module.debug('Restoring previously selected values and labels'); |
||
2013 | module.restore.labels(); |
||
2014 | } |
||
2015 | else { |
||
2016 | module.debug('Restoring previously selected values'); |
||
2017 | } |
||
2018 | }, |
||
2019 | values: function() { |
||
2020 | // prevents callbacks from occurring on initial load |
||
2021 | module.set.initialLoad(); |
||
2022 | if(settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) { |
||
2023 | module.restore.remoteValues(); |
||
2024 | } |
||
2025 | else { |
||
2026 | module.set.selected(); |
||
2027 | } |
||
2028 | module.remove.initialLoad(); |
||
2029 | }, |
||
2030 | remoteValues: function() { |
||
2031 | var |
||
2032 | values = module.get.remoteValues() |
||
2033 | ; |
||
2034 | module.debug('Recreating selected from session data', values); |
||
2035 | if(values) { |
||
2036 | if( module.is.single() ) { |
||
2037 | $.each(values, function(value, name) { |
||
2038 | module.set.text(name); |
||
2039 | }); |
||
2040 | } |
||
2041 | else { |
||
2042 | $.each(values, function(value, name) { |
||
2043 | module.add.label(value, name); |
||
2044 | }); |
||
2045 | } |
||
2046 | } |
||
2047 | } |
||
2048 | }, |
||
2049 | |||
2050 | read: { |
||
2051 | remoteData: function(value) { |
||
2052 | var |
||
2053 | name |
||
2054 | ; |
||
2055 | if(window.Storage === undefined) { |
||
2056 | module.error(error.noStorage); |
||
2057 | return; |
||
2058 | } |
||
2059 | name = sessionStorage.getItem(value); |
||
2060 | return (name !== undefined) |
||
2061 | ? name |
||
2062 | : false |
||
2063 | ; |
||
2064 | } |
||
2065 | }, |
||
2066 | |||
2067 | save: { |
||
2068 | defaults: function() { |
||
2069 | module.save.defaultText(); |
||
2070 | module.save.placeholderText(); |
||
2071 | module.save.defaultValue(); |
||
2072 | }, |
||
2073 | defaultValue: function() { |
||
2074 | var |
||
2075 | value = module.get.value() |
||
2076 | ; |
||
2077 | module.verbose('Saving default value as', value); |
||
2078 | $module.data(metadata.defaultValue, value); |
||
2079 | }, |
||
2080 | defaultText: function() { |
||
2081 | var |
||
2082 | text = module.get.text() |
||
2083 | ; |
||
2084 | module.verbose('Saving default text as', text); |
||
2085 | $module.data(metadata.defaultText, text); |
||
2086 | }, |
||
2087 | placeholderText: function() { |
||
2088 | var |
||
2089 | text |
||
2090 | ; |
||
2091 | if(settings.placeholder !== false && $text.hasClass(className.placeholder)) { |
||
2092 | text = module.get.text(); |
||
2093 | module.verbose('Saving placeholder text as', text); |
||
2094 | $module.data(metadata.placeholderText, text); |
||
2095 | } |
||
2096 | }, |
||
2097 | remoteData: function(name, value) { |
||
2098 | if(window.Storage === undefined) { |
||
2099 | module.error(error.noStorage); |
||
2100 | return; |
||
2101 | } |
||
2102 | module.verbose('Saving remote data to session storage', value, name); |
||
2103 | sessionStorage.setItem(value, name); |
||
2104 | } |
||
2105 | }, |
||
2106 | |||
2107 | clear: function() { |
||
2108 | if(module.is.multiple() && settings.useLabels) { |
||
2109 | module.remove.labels(); |
||
2110 | } |
||
2111 | else { |
||
2112 | module.remove.activeItem(); |
||
2113 | module.remove.selectedItem(); |
||
2114 | } |
||
2115 | module.set.placeholderText(); |
||
2116 | module.clearValue(); |
||
2117 | }, |
||
2118 | |||
2119 | clearValue: function() { |
||
2120 | module.set.value(''); |
||
2121 | }, |
||
2122 | |||
2123 | scrollPage: function(direction, $selectedItem) { |
||
2124 | var |
||
2125 | $currentItem = $selectedItem || module.get.selectedItem(), |
||
2126 | $menu = $currentItem.closest(selector.menu), |
||
2127 | menuHeight = $menu.outerHeight(), |
||
2128 | currentScroll = $menu.scrollTop(), |
||
2129 | itemHeight = $item.eq(0).outerHeight(), |
||
2130 | itemsPerPage = Math.floor(menuHeight / itemHeight), |
||
2131 | maxScroll = $menu.prop('scrollHeight'), |
||
2132 | newScroll = (direction == 'up') |
||
2133 | ? currentScroll - (itemHeight * itemsPerPage) |
||
2134 | : currentScroll + (itemHeight * itemsPerPage), |
||
2135 | $selectableItem = $item.not(selector.unselectable), |
||
2136 | isWithinRange, |
||
2137 | $nextSelectedItem, |
||
2138 | elementIndex |
||
2139 | ; |
||
2140 | elementIndex = (direction == 'up') |
||
2141 | ? $selectableItem.index($currentItem) - itemsPerPage |
||
2142 | : $selectableItem.index($currentItem) + itemsPerPage |
||
2143 | ; |
||
2144 | isWithinRange = (direction == 'up') |
||
2145 | ? (elementIndex >= 0) |
||
2146 | : (elementIndex < $selectableItem.length) |
||
2147 | ; |
||
2148 | $nextSelectedItem = (isWithinRange) |
||
2149 | ? $selectableItem.eq(elementIndex) |
||
2150 | : (direction == 'up') |
||
2151 | ? $selectableItem.first() |
||
2152 | : $selectableItem.last() |
||
2153 | ; |
||
2154 | if($nextSelectedItem.length > 0) { |
||
2155 | module.debug('Scrolling page', direction, $nextSelectedItem); |
||
2156 | $currentItem |
||
2157 | .removeClass(className.selected) |
||
2158 | ; |
||
2159 | $nextSelectedItem |
||
2160 | .addClass(className.selected) |
||
2161 | ; |
||
2162 | if(settings.selectOnKeydown && module.is.single()) { |
||
2163 | module.set.selectedItem($nextSelectedItem); |
||
2164 | } |
||
2165 | $menu |
||
2166 | .scrollTop(newScroll) |
||
2167 | ; |
||
2168 | } |
||
2169 | }, |
||
2170 | |||
2171 | set: { |
||
2172 | filtered: function() { |
||
2173 | var |
||
2174 | isMultiple = module.is.multiple(), |
||
2175 | isSearch = module.is.searchSelection(), |
||
2176 | isSearchMultiple = (isMultiple && isSearch), |
||
2177 | searchValue = (isSearch) |
||
2178 | ? module.get.query() |
||
2179 | : '', |
||
2180 | hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0), |
||
2181 | searchWidth = module.get.searchWidth(), |
||
2182 | valueIsSet = searchValue !== '' |
||
2183 | ; |
||
2184 | if(isMultiple && hasSearchValue) { |
||
2185 | module.verbose('Adjusting input width', searchWidth, settings.glyphWidth); |
||
2186 | $search.css('width', searchWidth); |
||
2187 | } |
||
2188 | if(hasSearchValue || (isSearchMultiple && valueIsSet)) { |
||
2189 | module.verbose('Hiding placeholder text'); |
||
2190 | $text.addClass(className.filtered); |
||
2191 | } |
||
2192 | else if(!isMultiple || (isSearchMultiple && !valueIsSet)) { |
||
2193 | module.verbose('Showing placeholder text'); |
||
2194 | $text.removeClass(className.filtered); |
||
2195 | } |
||
2196 | }, |
||
2197 | empty: function() { |
||
2198 | $module.addClass(className.empty); |
||
2199 | }, |
||
2200 | loading: function() { |
||
2201 | $module.addClass(className.loading); |
||
2202 | }, |
||
2203 | placeholderText: function(text) { |
||
2204 | text = text || module.get.placeholderText(); |
||
2205 | module.debug('Setting placeholder text', text); |
||
2206 | module.set.text(text); |
||
2207 | $text.addClass(className.placeholder); |
||
2208 | }, |
||
2209 | tabbable: function() { |
||
2210 | if( module.is.searchSelection() ) { |
||
2211 | module.debug('Added tabindex to searchable dropdown'); |
||
2212 | $search |
||
2213 | .val('') |
||
2214 | .attr('tabindex', 0) |
||
2215 | ; |
||
2216 | $menu |
||
2217 | .attr('tabindex', -1) |
||
2218 | ; |
||
2219 | } |
||
2220 | else { |
||
2221 | module.debug('Added tabindex to dropdown'); |
||
2222 | if( $module.attr('tabindex') === undefined) { |
||
2223 | $module |
||
2224 | .attr('tabindex', 0) |
||
2225 | ; |
||
2226 | $menu |
||
2227 | .attr('tabindex', -1) |
||
2228 | ; |
||
2229 | } |
||
2230 | } |
||
2231 | }, |
||
2232 | initialLoad: function() { |
||
2233 | module.verbose('Setting initial load'); |
||
2234 | initialLoad = true; |
||
2235 | }, |
||
2236 | activeItem: function($item) { |
||
2237 | if( settings.allowAdditions && $item.filter(selector.addition).length > 0 ) { |
||
2238 | $item.addClass(className.filtered); |
||
2239 | } |
||
2240 | else { |
||
2241 | $item.addClass(className.active); |
||
2242 | } |
||
2243 | }, |
||
2244 | partialSearch: function(text) { |
||
2245 | var |
||
2246 | length = module.get.query().length |
||
2247 | ; |
||
2248 | $search.val( text.substr(0 , length)); |
||
2249 | }, |
||
2250 | scrollPosition: function($item, forceScroll) { |
||
2251 | var |
||
2252 | edgeTolerance = 5, |
||
2253 | $menu, |
||
2254 | hasActive, |
||
2255 | offset, |
||
2256 | itemHeight, |
||
2257 | itemOffset, |
||
2258 | menuOffset, |
||
2259 | menuScroll, |
||
2260 | menuHeight, |
||
2261 | abovePage, |
||
2262 | belowPage |
||
2263 | ; |
||
2264 | |||
2265 | $item = $item || module.get.selectedItem(); |
||
2266 | $menu = $item.closest(selector.menu); |
||
2267 | hasActive = ($item && $item.length > 0); |
||
2268 | forceScroll = (forceScroll !== undefined) |
||
2269 | ? forceScroll |
||
2270 | : false |
||
2271 | ; |
||
2272 | if($item && $menu.length > 0 && hasActive) { |
||
2273 | itemOffset = $item.position().top; |
||
2274 | |||
2275 | $menu.addClass(className.loading); |
||
2276 | menuScroll = $menu.scrollTop(); |
||
2277 | menuOffset = $menu.offset().top; |
||
2278 | itemOffset = $item.offset().top; |
||
2279 | offset = menuScroll - menuOffset + itemOffset; |
||
2280 | if(!forceScroll) { |
||
2281 | menuHeight = $menu.height(); |
||
2282 | belowPage = menuScroll + menuHeight < (offset + edgeTolerance); |
||
2283 | abovePage = ((offset - edgeTolerance) < menuScroll); |
||
2284 | } |
||
2285 | module.debug('Scrolling to active item', offset); |
||
2286 | if(forceScroll || abovePage || belowPage) { |
||
2287 | $menu.scrollTop(offset); |
||
2288 | } |
||
2289 | $menu.removeClass(className.loading); |
||
2290 | } |
||
2291 | }, |
||
2292 | text: function(text) { |
||
2293 | if(settings.action !== 'select') { |
||
2294 | if(settings.action == 'combo') { |
||
2295 | module.debug('Changing combo button text', text, $combo); |
||
2296 | if(settings.preserveHTML) { |
||
2297 | $combo.html(text); |
||
2298 | } |
||
2299 | else { |
||
2300 | $combo.text(text); |
||
2301 | } |
||
2302 | } |
||
2303 | else { |
||
2304 | if(text !== module.get.placeholderText()) { |
||
2305 | $text.removeClass(className.placeholder); |
||
2306 | } |
||
2307 | module.debug('Changing text', text, $text); |
||
2308 | $text |
||
2309 | .removeClass(className.filtered) |
||
2310 | ; |
||
2311 | if(settings.preserveHTML) { |
||
2312 | $text.html(text); |
||
2313 | } |
||
2314 | else { |
||
2315 | $text.text(text); |
||
2316 | } |
||
2317 | } |
||
2318 | } |
||
2319 | }, |
||
2320 | selectedItem: function($item) { |
||
2321 | var |
||
2322 | value = module.get.choiceValue($item), |
||
2323 | searchText = module.get.choiceText($item, false), |
||
2324 | text = module.get.choiceText($item, true) |
||
2325 | ; |
||
2326 | module.debug('Setting user selection to item', $item); |
||
2327 | module.remove.activeItem(); |
||
2328 | module.set.partialSearch(searchText); |
||
2329 | module.set.activeItem($item); |
||
2330 | module.set.selected(value, $item); |
||
2331 | module.set.text(text); |
||
2332 | }, |
||
2333 | selectedLetter: function(letter) { |
||
2334 | var |
||
2335 | $selectedItem = $item.filter('.' + className.selected), |
||
2336 | alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter), |
||
2337 | $nextValue = false, |
||
2338 | $nextItem |
||
2339 | ; |
||
2340 | // check next of same letter |
||
2341 | if(alreadySelectedLetter) { |
||
2342 | $nextItem = $selectedItem.nextAll($item).eq(0); |
||
2343 | if( module.has.firstLetter($nextItem, letter) ) { |
||
2344 | $nextValue = $nextItem; |
||
2345 | } |
||
2346 | } |
||
2347 | // check all values |
||
2348 | if(!$nextValue) { |
||
2349 | $item |
||
2350 | .each(function(){ |
||
2351 | if(module.has.firstLetter($(this), letter)) { |
||
2352 | $nextValue = $(this); |
||
2353 | return false; |
||
2354 | } |
||
2355 | }) |
||
2356 | ; |
||
2357 | } |
||
2358 | // set next value |
||
2359 | if($nextValue) { |
||
2360 | module.verbose('Scrolling to next value with letter', letter); |
||
2361 | module.set.scrollPosition($nextValue); |
||
2362 | $selectedItem.removeClass(className.selected); |
||
2363 | $nextValue.addClass(className.selected); |
||
2364 | if(settings.selectOnKeydown && module.is.single()) { |
||
2365 | module.set.selectedItem($nextValue); |
||
2366 | } |
||
2367 | } |
||
2368 | }, |
||
2369 | direction: function($menu) { |
||
2370 | if(settings.direction == 'auto') { |
||
2371 | // reset position |
||
2372 | module.remove.upward(); |
||
2373 | |||
2374 | if(module.can.openDownward($menu)) { |
||
2375 | module.remove.upward($menu); |
||
2376 | } |
||
2377 | else { |
||
2378 | module.set.upward($menu); |
||
2379 | } |
||
2380 | if(!module.is.leftward($menu) && !module.can.openRightward($menu)) { |
||
2381 | module.set.leftward($menu); |
||
2382 | } |
||
2383 | } |
||
2384 | else if(settings.direction == 'upward') { |
||
2385 | module.set.upward($menu); |
||
2386 | } |
||
2387 | }, |
||
2388 | upward: function($currentMenu) { |
||
2389 | var $element = $currentMenu || $module; |
||
2390 | $element.addClass(className.upward); |
||
2391 | }, |
||
2392 | leftward: function($currentMenu) { |
||
2393 | var $element = $currentMenu || $menu; |
||
2394 | $element.addClass(className.leftward); |
||
2395 | }, |
||
2396 | value: function(value, text, $selected) { |
||
2397 | var |
||
2398 | escapedValue = module.escape.value(value), |
||
2399 | hasInput = ($input.length > 0), |
||
2400 | isAddition = !module.has.value(value), |
||
2401 | currentValue = module.get.values(), |
||
2402 | stringValue = (value !== undefined) |
||
2403 | ? String(value) |
||
2404 | : value, |
||
2405 | newValue |
||
2406 | ; |
||
2407 | if(hasInput) { |
||
2408 | if(!settings.allowReselection && stringValue == currentValue) { |
||
2409 | module.verbose('Skipping value update already same value', value, currentValue); |
||
2410 | if(!module.is.initialLoad()) { |
||
2411 | return; |
||
2412 | } |
||
2413 | } |
||
2414 | |||
2415 | if( module.is.single() && module.has.selectInput() && module.can.extendSelect() ) { |
||
2416 | module.debug('Adding user option', value); |
||
2417 | module.add.optionValue(value); |
||
2418 | } |
||
2419 | module.debug('Updating input value', escapedValue, currentValue); |
||
2420 | internalChange = true; |
||
2421 | $input |
||
2422 | .val(escapedValue) |
||
2423 | ; |
||
2424 | if(settings.fireOnInit === false && module.is.initialLoad()) { |
||
2425 | module.debug('Input native change event ignored on initial load'); |
||
2426 | } |
||
2427 | else { |
||
2428 | module.trigger.change(); |
||
2429 | } |
||
2430 | internalChange = false; |
||
2431 | } |
||
2432 | else { |
||
2433 | module.verbose('Storing value in metadata', escapedValue, $input); |
||
2434 | if(escapedValue !== currentValue) { |
||
2435 | $module.data(metadata.value, stringValue); |
||
2436 | } |
||
2437 | } |
||
2438 | if(settings.fireOnInit === false && module.is.initialLoad()) { |
||
2439 | module.verbose('No callback on initial load', settings.onChange); |
||
2440 | } |
||
2441 | else { |
||
2442 | settings.onChange.call(element, value, text, $selected); |
||
2443 | } |
||
2444 | }, |
||
2445 | active: function() { |
||
2446 | $module |
||
2447 | .addClass(className.active) |
||
2448 | ; |
||
2449 | }, |
||
2450 | multiple: function() { |
||
2451 | $module.addClass(className.multiple); |
||
2452 | }, |
||
2453 | visible: function() { |
||
2454 | $module.addClass(className.visible); |
||
2455 | }, |
||
2456 | exactly: function(value, $selectedItem) { |
||
2457 | module.debug('Setting selected to exact values'); |
||
2458 | module.clear(); |
||
2459 | module.set.selected(value, $selectedItem); |
||
2460 | }, |
||
2461 | selected: function(value, $selectedItem) { |
||
2462 | var |
||
2463 | isMultiple = module.is.multiple(), |
||
2464 | $userSelectedItem |
||
2465 | ; |
||
2466 | $selectedItem = (settings.allowAdditions) |
||
2467 | ? $selectedItem || module.get.itemWithAdditions(value) |
||
2468 | : $selectedItem || module.get.item(value) |
||
2469 | ; |
||
2470 | if(!$selectedItem) { |
||
2471 | return; |
||
2472 | } |
||
2473 | module.debug('Setting selected menu item to', $selectedItem); |
||
2474 | if(module.is.multiple()) { |
||
2475 | module.remove.searchWidth(); |
||
2476 | } |
||
2477 | if(module.is.single()) { |
||
2478 | module.remove.activeItem(); |
||
2479 | module.remove.selectedItem(); |
||
2480 | } |
||
2481 | else if(settings.useLabels) { |
||
2482 | module.remove.selectedItem(); |
||
2483 | } |
||
2484 | // select each item |
||
2485 | $selectedItem |
||
2486 | .each(function() { |
||
2487 | var |
||
2488 | $selected = $(this), |
||
2489 | selectedText = module.get.choiceText($selected), |
||
2490 | selectedValue = module.get.choiceValue($selected, selectedText), |
||
2491 | |||
2492 | isFiltered = $selected.hasClass(className.filtered), |
||
2493 | isActive = $selected.hasClass(className.active), |
||
2494 | isUserValue = $selected.hasClass(className.addition), |
||
2495 | shouldAnimate = (isMultiple && $selectedItem.length == 1) |
||
2496 | ; |
||
2497 | if(isMultiple) { |
||
2498 | if(!isActive || isUserValue) { |
||
2499 | if(settings.apiSettings && settings.saveRemoteData) { |
||
2500 | module.save.remoteData(selectedText, selectedValue); |
||
2501 | } |
||
2502 | if(settings.useLabels) { |
||
2503 | module.add.value(selectedValue, selectedText, $selected); |
||
2504 | module.add.label(selectedValue, selectedText, shouldAnimate); |
||
2505 | module.set.activeItem($selected); |
||
2506 | module.filterActive(); |
||
2507 | module.select.nextAvailable($selectedItem); |
||
2508 | } |
||
2509 | else { |
||
2510 | module.add.value(selectedValue, selectedText, $selected); |
||
2511 | module.set.text(module.add.variables(message.count)); |
||
2512 | module.set.activeItem($selected); |
||
2513 | } |
||
2514 | } |
||
2515 | else if(!isFiltered) { |
||
2516 | module.debug('Selected active value, removing label'); |
||
2517 | module.remove.selected(selectedValue); |
||
2518 | } |
||
2519 | } |
||
2520 | else { |
||
2521 | if(settings.apiSettings && settings.saveRemoteData) { |
||
2522 | module.save.remoteData(selectedText, selectedValue); |
||
2523 | } |
||
2524 | module.set.text(selectedText); |
||
2525 | module.set.value(selectedValue, selectedText, $selected); |
||
2526 | $selected |
||
2527 | .addClass(className.active) |
||
2528 | .addClass(className.selected) |
||
2529 | ; |
||
2530 | } |
||
2531 | }) |
||
2532 | ; |
||
2533 | } |
||
2534 | }, |
||
2535 | |||
2536 | add: { |
||
2537 | label: function(value, text, shouldAnimate) { |
||
2538 | var |
||
2539 | $next = module.is.searchSelection() |
||
2540 | ? $search |
||
2541 | : $text, |
||
2542 | escapedValue = module.escape.value(value), |
||
2543 | $label |
||
2544 | ; |
||
2545 | $label = $('<a />') |
||
2546 | .addClass(className.label) |
||
2547 | .attr('data-' + metadata.value, escapedValue) |
||
2548 | .html(templates.label(escapedValue, text)) |
||
2549 | ; |
||
2550 | $label = settings.onLabelCreate.call($label, escapedValue, text); |
||
2551 | |||
2552 | if(module.has.label(value)) { |
||
2553 | module.debug('Label already exists, skipping', escapedValue); |
||
2554 | return; |
||
2555 | } |
||
2556 | if(settings.label.variation) { |
||
2557 | $label.addClass(settings.label.variation); |
||
2558 | } |
||
2559 | if(shouldAnimate === true) { |
||
2560 | module.debug('Animating in label', $label); |
||
2561 | $label |
||
2562 | .addClass(className.hidden) |
||
2563 | .insertBefore($next) |
||
2564 | .transition(settings.label.transition, settings.label.duration) |
||
2565 | ; |
||
2566 | } |
||
2567 | else { |
||
2568 | module.debug('Adding selection label', $label); |
||
2569 | $label |
||
2570 | .insertBefore($next) |
||
2571 | ; |
||
2572 | } |
||
2573 | }, |
||
2574 | message: function(message) { |
||
2575 | var |
||
2576 | $message = $menu.children(selector.message), |
||
2577 | html = settings.templates.message(module.add.variables(message)) |
||
2578 | ; |
||
2579 | if($message.length > 0) { |
||
2580 | $message |
||
2581 | .html(html) |
||
2582 | ; |
||
2583 | } |
||
2584 | else { |
||
2585 | $message = $('<div/>') |
||
2586 | .html(html) |
||
2587 | .addClass(className.message) |
||
2588 | .appendTo($menu) |
||
2589 | ; |
||
2590 | } |
||
2591 | }, |
||
2592 | optionValue: function(value) { |
||
2593 | var |
||
2594 | escapedValue = module.escape.value(value), |
||
2595 | $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'), |
||
2596 | hasOption = ($option.length > 0) |
||
2597 | ; |
||
2598 | if(hasOption) { |
||
2599 | return; |
||
2600 | } |
||
2601 | // temporarily disconnect observer |
||
2602 | module.disconnect.selectObserver(); |
||
2603 | if( module.is.single() ) { |
||
2604 | module.verbose('Removing previous user addition'); |
||
2605 | $input.find('option.' + className.addition).remove(); |
||
2606 | } |
||
2607 | $('<option/>') |
||
2608 | .prop('value', escapedValue) |
||
2609 | .addClass(className.addition) |
||
2610 | .html(value) |
||
2611 | .appendTo($input) |
||
2612 | ; |
||
2613 | module.verbose('Adding user addition as an <option>', value); |
||
2614 | module.observe.select(); |
||
2615 | }, |
||
2616 | userSuggestion: function(value) { |
||
2617 | var |
||
2618 | $addition = $menu.children(selector.addition), |
||
2619 | $existingItem = module.get.item(value), |
||
2620 | alreadyHasValue = $existingItem && $existingItem.not(selector.addition).length, |
||
2621 | hasUserSuggestion = $addition.length > 0, |
||
2622 | html |
||
2623 | ; |
||
2624 | if(settings.useLabels && module.has.maxSelections()) { |
||
2625 | return; |
||
2626 | } |
||
2627 | if(value === '' || alreadyHasValue) { |
||
2628 | $addition.remove(); |
||
2629 | return; |
||
2630 | } |
||
2631 | if(hasUserSuggestion) { |
||
2632 | $addition |
||
2633 | .data(metadata.value, value) |
||
2634 | .data(metadata.text, value) |
||
2635 | .attr('data-' + metadata.value, value) |
||
2636 | .attr('data-' + metadata.text, value) |
||
2637 | .removeClass(className.filtered) |
||
2638 | ; |
||
2639 | if(!settings.hideAdditions) { |
||
2640 | html = settings.templates.addition( module.add.variables(message.addResult, value) ); |
||
2641 | $addition |
||
2642 | .html(html) |
||
2643 | ; |
||
2644 | } |
||
2645 | module.verbose('Replacing user suggestion with new value', $addition); |
||
2646 | } |
||
2647 | else { |
||
2648 | $addition = module.create.userChoice(value); |
||
2649 | $addition |
||
2650 | .prependTo($menu) |
||
2651 | ; |
||
2652 | module.verbose('Adding item choice to menu corresponding with user choice addition', $addition); |
||
2653 | } |
||
2654 | if(!settings.hideAdditions || module.is.allFiltered()) { |
||
2655 | $addition |
||
2656 | .addClass(className.selected) |
||
2657 | .siblings() |
||
2658 | .removeClass(className.selected) |
||
2659 | ; |
||
2660 | } |
||
2661 | module.refreshItems(); |
||
2662 | }, |
||
2663 | variables: function(message, term) { |
||
2664 | var |
||
2665 | hasCount = (message.search('{count}') !== -1), |
||
2666 | hasMaxCount = (message.search('{maxCount}') !== -1), |
||
2667 | hasTerm = (message.search('{term}') !== -1), |
||
2668 | values, |
||
2669 | count, |
||
2670 | query |
||
2671 | ; |
||
2672 | module.verbose('Adding templated variables to message', message); |
||
2673 | if(hasCount) { |
||
2674 | count = module.get.selectionCount(); |
||
2675 | message = message.replace('{count}', count); |
||
2676 | } |
||
2677 | if(hasMaxCount) { |
||
2678 | count = module.get.selectionCount(); |
||
2679 | message = message.replace('{maxCount}', settings.maxSelections); |
||
2680 | } |
||
2681 | if(hasTerm) { |
||
2682 | query = term || module.get.query(); |
||
2683 | message = message.replace('{term}', query); |
||
2684 | } |
||
2685 | return message; |
||
2686 | }, |
||
2687 | value: function(addedValue, addedText, $selectedItem) { |
||
2688 | var |
||
2689 | currentValue = module.get.values(), |
||
2690 | newValue |
||
2691 | ; |
||
2692 | if(addedValue === '') { |
||
2693 | module.debug('Cannot select blank values from multiselect'); |
||
2694 | return; |
||
2695 | } |
||
2696 | // extend current array |
||
2697 | if($.isArray(currentValue)) { |
||
2698 | newValue = currentValue.concat([addedValue]); |
||
2699 | newValue = module.get.uniqueArray(newValue); |
||
2700 | } |
||
2701 | else { |
||
2702 | newValue = [addedValue]; |
||
2703 | } |
||
2704 | // add values |
||
2705 | if( module.has.selectInput() ) { |
||
2706 | if(module.can.extendSelect()) { |
||
2707 | module.debug('Adding value to select', addedValue, newValue, $input); |
||
2708 | module.add.optionValue(addedValue); |
||
2709 | } |
||
2710 | } |
||
2711 | else { |
||
2712 | newValue = newValue.join(settings.delimiter); |
||
2713 | module.debug('Setting hidden input to delimited value', newValue, $input); |
||
2714 | } |
||
2715 | |||
2716 | if(settings.fireOnInit === false && module.is.initialLoad()) { |
||
2717 | module.verbose('Skipping onadd callback on initial load', settings.onAdd); |
||
2718 | } |
||
2719 | else { |
||
2720 | settings.onAdd.call(element, addedValue, addedText, $selectedItem); |
||
2721 | } |
||
2722 | module.set.value(newValue, addedValue, addedText, $selectedItem); |
||
2723 | module.check.maxSelections(); |
||
2724 | } |
||
2725 | }, |
||
2726 | |||
2727 | remove: { |
||
2728 | active: function() { |
||
2729 | $module.removeClass(className.active); |
||
2730 | }, |
||
2731 | activeLabel: function() { |
||
2732 | $module.find(selector.label).removeClass(className.active); |
||
2733 | }, |
||
2734 | empty: function() { |
||
2735 | $module.removeClass(className.empty); |
||
2736 | }, |
||
2737 | loading: function() { |
||
2738 | $module.removeClass(className.loading); |
||
2739 | }, |
||
2740 | initialLoad: function() { |
||
2741 | initialLoad = false; |
||
2742 | }, |
||
2743 | upward: function($currentMenu) { |
||
2744 | var $element = $currentMenu || $module; |
||
2745 | $element.removeClass(className.upward); |
||
2746 | }, |
||
2747 | leftward: function($currentMenu) { |
||
2748 | var $element = $currentMenu || $menu; |
||
2749 | $element.removeClass(className.leftward); |
||
2750 | }, |
||
2751 | visible: function() { |
||
2752 | $module.removeClass(className.visible); |
||
2753 | }, |
||
2754 | activeItem: function() { |
||
2755 | $item.removeClass(className.active); |
||
2756 | }, |
||
2757 | filteredItem: function() { |
||
2758 | if(settings.useLabels && module.has.maxSelections() ) { |
||
2759 | return; |
||
2760 | } |
||
2761 | if(settings.useLabels && module.is.multiple()) { |
||
2762 | $item.not('.' + className.active).removeClass(className.filtered); |
||
2763 | } |
||
2764 | else { |
||
2765 | $item.removeClass(className.filtered); |
||
2766 | } |
||
2767 | module.remove.empty(); |
||
2768 | }, |
||
2769 | optionValue: function(value) { |
||
2770 | var |
||
2771 | escapedValue = module.escape.value(value), |
||
2772 | $option = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'), |
||
2773 | hasOption = ($option.length > 0) |
||
2774 | ; |
||
2775 | if(!hasOption || !$option.hasClass(className.addition)) { |
||
2776 | return; |
||
2777 | } |
||
2778 | // temporarily disconnect observer |
||
2779 | if(selectObserver) { |
||
2780 | selectObserver.disconnect(); |
||
2781 | module.verbose('Temporarily disconnecting mutation observer'); |
||
2782 | } |
||
2783 | $option.remove(); |
||
2784 | module.verbose('Removing user addition as an <option>', escapedValue); |
||
2785 | if(selectObserver) { |
||
2786 | selectObserver.observe($input[0], { |
||
2787 | childList : true, |
||
2788 | subtree : true |
||
2789 | }); |
||
2790 | } |
||
2791 | }, |
||
2792 | message: function() { |
||
2793 | $menu.children(selector.message).remove(); |
||
2794 | }, |
||
2795 | searchWidth: function() { |
||
2796 | $search.css('width', ''); |
||
2797 | }, |
||
2798 | searchTerm: function() { |
||
2799 | module.verbose('Cleared search term'); |
||
2800 | $search.val(''); |
||
2801 | module.set.filtered(); |
||
2802 | }, |
||
2803 | userAddition: function() { |
||
2804 | $item.filter(selector.addition).remove(); |
||
2805 | }, |
||
2806 | selected: function(value, $selectedItem) { |
||
2807 | $selectedItem = (settings.allowAdditions) |
||
2808 | ? $selectedItem || module.get.itemWithAdditions(value) |
||
2809 | : $selectedItem || module.get.item(value) |
||
2810 | ; |
||
2811 | |||
2812 | if(!$selectedItem) { |
||
2813 | return false; |
||
2814 | } |
||
2815 | |||
2816 | $selectedItem |
||
2817 | .each(function() { |
||
2818 | var |
||
2819 | $selected = $(this), |
||
2820 | selectedText = module.get.choiceText($selected), |
||
2821 | selectedValue = module.get.choiceValue($selected, selectedText) |
||
2822 | ; |
||
2823 | if(module.is.multiple()) { |
||
2824 | if(settings.useLabels) { |
||
2825 | module.remove.value(selectedValue, selectedText, $selected); |
||
2826 | module.remove.label(selectedValue); |
||
2827 | } |
||
2828 | else { |
||
2829 | module.remove.value(selectedValue, selectedText, $selected); |
||
2830 | if(module.get.selectionCount() === 0) { |
||
2831 | module.set.placeholderText(); |
||
2832 | } |
||
2833 | else { |
||
2834 | module.set.text(module.add.variables(message.count)); |
||
2835 | } |
||
2836 | } |
||
2837 | } |
||
2838 | else { |
||
2839 | module.remove.value(selectedValue, selectedText, $selected); |
||
2840 | } |
||
2841 | $selected |
||
2842 | .removeClass(className.filtered) |
||
2843 | .removeClass(className.active) |
||
2844 | ; |
||
2845 | if(settings.useLabels) { |
||
2846 | $selected.removeClass(className.selected); |
||
2847 | } |
||
2848 | }) |
||
2849 | ; |
||
2850 | }, |
||
2851 | selectedItem: function() { |
||
2852 | $item.removeClass(className.selected); |
||
2853 | }, |
||
2854 | value: function(removedValue, removedText, $removedItem) { |
||
2855 | var |
||
2856 | values = module.get.values(), |
||
2857 | newValue |
||
2858 | ; |
||
2859 | if( module.has.selectInput() ) { |
||
2860 | module.verbose('Input is <select> removing selected option', removedValue); |
||
2861 | newValue = module.remove.arrayValue(removedValue, values); |
||
2862 | module.remove.optionValue(removedValue); |
||
2863 | } |
||
2864 | else { |
||
2865 | module.verbose('Removing from delimited values', removedValue); |
||
2866 | newValue = module.remove.arrayValue(removedValue, values); |
||
2867 | newValue = newValue.join(settings.delimiter); |
||
2868 | } |
||
2869 | if(settings.fireOnInit === false && module.is.initialLoad()) { |
||
2870 | module.verbose('No callback on initial load', settings.onRemove); |
||
2871 | } |
||
2872 | else { |
||
2873 | settings.onRemove.call(element, removedValue, removedText, $removedItem); |
||
2874 | } |
||
2875 | module.set.value(newValue, removedText, $removedItem); |
||
2876 | module.check.maxSelections(); |
||
2877 | }, |
||
2878 | arrayValue: function(removedValue, values) { |
||
2879 | if( !$.isArray(values) ) { |
||
2880 | values = [values]; |
||
2881 | } |
||
2882 | values = $.grep(values, function(value){ |
||
2883 | return (removedValue != value); |
||
2884 | }); |
||
2885 | module.verbose('Removed value from delimited string', removedValue, values); |
||
2886 | return values; |
||
2887 | }, |
||
2888 | label: function(value, shouldAnimate) { |
||
2889 | var |
||
2890 | $labels = $module.find(selector.label), |
||
2891 | $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(value) +'"]') |
||
2892 | ; |
||
2893 | module.verbose('Removing label', $removedLabel); |
||
2894 | $removedLabel.remove(); |
||
2895 | }, |
||
2896 | activeLabels: function($activeLabels) { |
||
2897 | $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active); |
||
2898 | module.verbose('Removing active label selections', $activeLabels); |
||
2899 | module.remove.labels($activeLabels); |
||
2900 | }, |
||
2901 | labels: function($labels) { |
||
2902 | $labels = $labels || $module.find(selector.label); |
||
2903 | module.verbose('Removing labels', $labels); |
||
2904 | $labels |
||
2905 | .each(function(){ |
||
2906 | var |
||
2907 | $label = $(this), |
||
2908 | value = $label.data(metadata.value), |
||
2909 | stringValue = (value !== undefined) |
||
2910 | ? String(value) |
||
2911 | : value, |
||
2912 | isUserValue = module.is.userValue(stringValue) |
||
2913 | ; |
||
2914 | if(settings.onLabelRemove.call($label, value) === false) { |
||
2915 | module.debug('Label remove callback cancelled removal'); |
||
2916 | return; |
||
2917 | } |
||
2918 | module.remove.message(); |
||
2919 | if(isUserValue) { |
||
2920 | module.remove.value(stringValue); |
||
2921 | module.remove.label(stringValue); |
||
2922 | } |
||
2923 | else { |
||
2924 | // selected will also remove label |
||
2925 | module.remove.selected(stringValue); |
||
2926 | } |
||
2927 | }) |
||
2928 | ; |
||
2929 | }, |
||
2930 | tabbable: function() { |
||
2931 | if( module.is.searchSelection() ) { |
||
2932 | module.debug('Searchable dropdown initialized'); |
||
2933 | $search |
||
2934 | .removeAttr('tabindex') |
||
2935 | ; |
||
2936 | $menu |
||
2937 | .removeAttr('tabindex') |
||
2938 | ; |
||
2939 | } |
||
2940 | else { |
||
2941 | module.debug('Simple selection dropdown initialized'); |
||
2942 | $module |
||
2943 | .removeAttr('tabindex') |
||
2944 | ; |
||
2945 | $menu |
||
2946 | .removeAttr('tabindex') |
||
2947 | ; |
||
2948 | } |
||
2949 | } |
||
2950 | }, |
||
2951 | |||
2952 | has: { |
||
2953 | menuSearch: function() { |
||
2954 | return (module.has.search() && $search.closest($menu).length > 0); |
||
2955 | }, |
||
2956 | search: function() { |
||
2957 | return ($search.length > 0); |
||
2958 | }, |
||
2959 | sizer: function() { |
||
2960 | return ($sizer.length > 0); |
||
2961 | }, |
||
2962 | selectInput: function() { |
||
2963 | return ( $input.is('select') ); |
||
2964 | }, |
||
2965 | minCharacters: function(searchTerm) { |
||
2966 | if(settings.minCharacters) { |
||
2967 | searchTerm = (searchTerm !== undefined) |
||
2968 | ? String(searchTerm) |
||
2969 | : String(module.get.query()) |
||
2970 | ; |
||
2971 | return (searchTerm.length >= settings.minCharacters); |
||
2972 | } |
||
2973 | return true; |
||
2974 | }, |
||
2975 | firstLetter: function($item, letter) { |
||
2976 | var |
||
2977 | text, |
||
2978 | firstLetter |
||
2979 | ; |
||
2980 | if(!$item || $item.length === 0 || typeof letter !== 'string') { |
||
2981 | return false; |
||
2982 | } |
||
2983 | text = module.get.choiceText($item, false); |
||
2984 | letter = letter.toLowerCase(); |
||
2985 | firstLetter = String(text).charAt(0).toLowerCase(); |
||
2986 | return (letter == firstLetter); |
||
2987 | }, |
||
2988 | input: function() { |
||
2989 | return ($input.length > 0); |
||
2990 | }, |
||
2991 | items: function() { |
||
2992 | return ($item.length > 0); |
||
2993 | }, |
||
2994 | menu: function() { |
||
2995 | return ($menu.length > 0); |
||
2996 | }, |
||
2997 | message: function() { |
||
2998 | return ($menu.children(selector.message).length !== 0); |
||
2999 | }, |
||
3000 | label: function(value) { |
||
3001 | var |
||
3002 | escapedValue = module.escape.value(value), |
||
3003 | $labels = $module.find(selector.label) |
||
3004 | ; |
||
3005 | return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0); |
||
3006 | }, |
||
3007 | maxSelections: function() { |
||
3008 | return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections); |
||
3009 | }, |
||
3010 | allResultsFiltered: function() { |
||
3011 | var |
||
3012 | $normalResults = $item.not(selector.addition) |
||
3013 | ; |
||
3014 | return ($normalResults.filter(selector.unselectable).length === $normalResults.length); |
||
3015 | }, |
||
3016 | userSuggestion: function() { |
||
3017 | return ($menu.children(selector.addition).length > 0); |
||
3018 | }, |
||
3019 | query: function() { |
||
3020 | return (module.get.query() !== ''); |
||
3021 | }, |
||
3022 | value: function(value) { |
||
3023 | var |
||
3024 | values = module.get.values(), |
||
3025 | hasValue = $.isArray(values) |
||
3026 | ? values && ($.inArray(value, values) !== -1) |
||
3027 | : (values == value) |
||
3028 | ; |
||
3029 | return (hasValue) |
||
3030 | ? true |
||
3031 | : false |
||
3032 | ; |
||
3033 | } |
||
3034 | }, |
||
3035 | |||
3036 | is: { |
||
3037 | active: function() { |
||
3038 | return $module.hasClass(className.active); |
||
3039 | }, |
||
3040 | bubbledLabelClick: function(event) { |
||
3041 | return $(event.target).is('select, input') && $module.closest('label').length > 0; |
||
3042 | }, |
||
3043 | bubbledIconClick: function(event) { |
||
3044 | return $(event.target).closest($icon).length > 0; |
||
3045 | }, |
||
3046 | alreadySetup: function() { |
||
3047 | return ($module.is('select') && $module.parent(selector.dropdown).length > 0 && $module.prev().length === 0); |
||
3048 | }, |
||
3049 | animating: function($subMenu) { |
||
3050 | return ($subMenu) |
||
3051 | ? $subMenu.transition && $subMenu.transition('is animating') |
||
3052 | : $menu.transition && $menu.transition('is animating') |
||
3053 | ; |
||
3054 | }, |
||
3055 | leftward: function($subMenu) { |
||
3056 | var $selectedMenu = $subMenu || $menu; |
||
3057 | return $selectedMenu.hasClass(className.leftward); |
||
3058 | }, |
||
3059 | disabled: function() { |
||
3060 | return $module.hasClass(className.disabled); |
||
3061 | }, |
||
3062 | focused: function() { |
||
3063 | return (document.activeElement === $module[0]); |
||
3064 | }, |
||
3065 | focusedOnSearch: function() { |
||
3066 | return (document.activeElement === $search[0]); |
||
3067 | }, |
||
3068 | allFiltered: function() { |
||
3069 | return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() ); |
||
3070 | }, |
||
3071 | hidden: function($subMenu) { |
||
3072 | return !module.is.visible($subMenu); |
||
3073 | }, |
||
3074 | initialLoad: function() { |
||
3075 | return initialLoad; |
||
3076 | }, |
||
3077 | inObject: function(needle, object) { |
||
3078 | var |
||
3079 | found = false |
||
3080 | ; |
||
3081 | $.each(object, function(index, property) { |
||
3082 | if(property == needle) { |
||
3083 | found = true; |
||
3084 | return true; |
||
3085 | } |
||
3086 | }); |
||
3087 | return found; |
||
3088 | }, |
||
3089 | multiple: function() { |
||
3090 | return $module.hasClass(className.multiple); |
||
3091 | }, |
||
3092 | remote: function() { |
||
3093 | return settings.apiSettings && module.can.useAPI(); |
||
3094 | }, |
||
3095 | single: function() { |
||
3096 | return !module.is.multiple(); |
||
3097 | }, |
||
3098 | selectMutation: function(mutations) { |
||
3099 | var |
||
3100 | selectChanged = false |
||
3101 | ; |
||
3102 | $.each(mutations, function(index, mutation) { |
||
3103 | if(mutation.target && $(mutation.target).is('select')) { |
||
3104 | selectChanged = true; |
||
3105 | return true; |
||
3106 | } |
||
3107 | }); |
||
3108 | return selectChanged; |
||
3109 | }, |
||
3110 | search: function() { |
||
3111 | return $module.hasClass(className.search); |
||
3112 | }, |
||
3113 | searchSelection: function() { |
||
3114 | return ( module.has.search() && $search.parent(selector.dropdown).length === 1 ); |
||
3115 | }, |
||
3116 | selection: function() { |
||
3117 | return $module.hasClass(className.selection); |
||
3118 | }, |
||
3119 | userValue: function(value) { |
||
3120 | return ($.inArray(value, module.get.userValues()) !== -1); |
||
3121 | }, |
||
3122 | upward: function($menu) { |
||
3123 | var $element = $menu || $module; |
||
3124 | return $element.hasClass(className.upward); |
||
3125 | }, |
||
3126 | visible: function($subMenu) { |
||
3127 | return ($subMenu) |
||
3128 | ? $subMenu.hasClass(className.visible) |
||
3129 | : $menu.hasClass(className.visible) |
||
3130 | ; |
||
3131 | }, |
||
3132 | verticallyScrollableContext: function() { |
||
3133 | var |
||
3134 | overflowY = ($context.get(0) !== window) |
||
3135 | ? $context.css('overflow-y') |
||
3136 | : false |
||
3137 | ; |
||
3138 | return (overflowY == 'auto' || overflowY == 'scroll'); |
||
3139 | }, |
||
3140 | horizontallyScrollableContext: function() { |
||
3141 | var |
||
3142 | overflowX = ($context.get(0) !== window) |
||
3143 | ? $context.css('overflow-X') |
||
3144 | : false |
||
3145 | ; |
||
3146 | return (overflowX == 'auto' || overflowX == 'scroll'); |
||
3147 | } |
||
3148 | }, |
||
3149 | |||
3150 | can: { |
||
3151 | activate: function($item) { |
||
3152 | if(settings.useLabels) { |
||
3153 | return true; |
||
3154 | } |
||
3155 | if(!module.has.maxSelections()) { |
||
3156 | return true; |
||
3157 | } |
||
3158 | if(module.has.maxSelections() && $item.hasClass(className.active)) { |
||
3159 | return true; |
||
3160 | } |
||
3161 | return false; |
||
3162 | }, |
||
3163 | openDownward: function($subMenu) { |
||
3164 | var |
||
3165 | $currentMenu = $subMenu || $menu, |
||
3166 | canOpenDownward = true, |
||
3167 | onScreen = {}, |
||
3168 | calculations |
||
3169 | ; |
||
3170 | $currentMenu |
||
3171 | .addClass(className.loading) |
||
3172 | ; |
||
3173 | calculations = { |
||
3174 | context: { |
||
3175 | scrollTop : $context.scrollTop(), |
||
3176 | height : $context.outerHeight() |
||
3177 | }, |
||
3178 | menu : { |
||
3179 | offset: $currentMenu.offset(), |
||
3180 | height: $currentMenu.outerHeight() |
||
3181 | } |
||
3182 | }; |
||
3183 | if(module.is.verticallyScrollableContext()) { |
||
3184 | calculations.menu.offset.top += calculations.context.scrollTop; |
||
3185 | } |
||
3186 | onScreen = { |
||
3187 | above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.menu.height, |
||
3188 | below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top + calculations.menu.height |
||
3189 | }; |
||
3190 | if(onScreen.below) { |
||
3191 | module.verbose('Dropdown can fit in context downward', onScreen); |
||
3192 | canOpenDownward = true; |
||
3193 | } |
||
3194 | else if(!onScreen.below && !onScreen.above) { |
||
3195 | module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen); |
||
3196 | canOpenDownward = true; |
||
3197 | } |
||
3198 | else { |
||
3199 | module.verbose('Dropdown cannot fit below, opening upward', onScreen); |
||
3200 | canOpenDownward = false; |
||
3201 | } |
||
3202 | $currentMenu.removeClass(className.loading); |
||
3203 | return canOpenDownward; |
||
3204 | }, |
||
3205 | openRightward: function($subMenu) { |
||
3206 | var |
||
3207 | $currentMenu = $subMenu || $menu, |
||
3208 | canOpenRightward = true, |
||
3209 | isOffscreenRight = false, |
||
3210 | calculations |
||
3211 | ; |
||
3212 | $currentMenu |
||
3213 | .addClass(className.loading) |
||
3214 | ; |
||
3215 | calculations = { |
||
3216 | context: { |
||
3217 | scrollLeft : $context.scrollLeft(), |
||
3218 | width : $context.outerWidth() |
||
3219 | }, |
||
3220 | menu: { |
||
3221 | offset : $currentMenu.offset(), |
||
3222 | width : $currentMenu.outerWidth() |
||
3223 | } |
||
3224 | }; |
||
3225 | if(module.is.horizontallyScrollableContext()) { |
||
3226 | calculations.menu.offset.left += calculations.context.scrollLeft; |
||
3227 | } |
||
3228 | isOffscreenRight = (calculations.menu.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width); |
||
3229 | if(isOffscreenRight) { |
||
3230 | module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight); |
||
3231 | canOpenRightward = false; |
||
3232 | } |
||
3233 | $currentMenu.removeClass(className.loading); |
||
3234 | return canOpenRightward; |
||
3235 | }, |
||
3236 | click: function() { |
||
3237 | return (hasTouch || settings.on == 'click'); |
||
3238 | }, |
||
3239 | extendSelect: function() { |
||
3240 | return settings.allowAdditions || settings.apiSettings; |
||
3241 | }, |
||
3242 | show: function() { |
||
3243 | return !module.is.disabled() && (module.has.items() || module.has.message()); |
||
3244 | }, |
||
3245 | useAPI: function() { |
||
3246 | return $.fn.api !== undefined; |
||
3247 | } |
||
3248 | }, |
||
3249 | |||
3250 | animate: { |
||
3251 | show: function(callback, $subMenu) { |
||
3252 | var |
||
3253 | $currentMenu = $subMenu || $menu, |
||
3254 | start = ($subMenu) |
||
3255 | ? function() {} |
||
3256 | : function() { |
||
3257 | module.hideSubMenus(); |
||
3258 | module.hideOthers(); |
||
3259 | module.set.active(); |
||
3260 | }, |
||
3261 | transition |
||
3262 | ; |
||
3263 | callback = $.isFunction(callback) |
||
3264 | ? callback |
||
3265 | : function(){} |
||
3266 | ; |
||
3267 | module.verbose('Doing menu show animation', $currentMenu); |
||
3268 | module.set.direction($subMenu); |
||
3269 | transition = module.get.transition($subMenu); |
||
3270 | if( module.is.selection() ) { |
||
3271 | module.set.scrollPosition(module.get.selectedItem(), true); |
||
3272 | } |
||
3273 | if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) { |
||
3274 | if(transition == 'none') { |
||
3275 | start(); |
||
3276 | $currentMenu.transition('show'); |
||
3277 | callback.call(element); |
||
3278 | } |
||
3279 | else if($.fn.transition !== undefined && $module.transition('is supported')) { |
||
3280 | $currentMenu |
||
3281 | .transition({ |
||
3282 | animation : transition + ' in', |
||
3283 | debug : settings.debug, |
||
3284 | verbose : settings.verbose, |
||
3285 | duration : settings.duration, |
||
3286 | queue : true, |
||
3287 | onStart : start, |
||
3288 | onComplete : function() { |
||
3289 | callback.call(element); |
||
3290 | } |
||
3291 | }) |
||
3292 | ; |
||
3293 | } |
||
3294 | else { |
||
3295 | module.error(error.noTransition, transition); |
||
3296 | } |
||
3297 | } |
||
3298 | }, |
||
3299 | hide: function(callback, $subMenu) { |
||
3300 | var |
||
3301 | $currentMenu = $subMenu || $menu, |
||
3302 | duration = ($subMenu) |
||
3303 | ? (settings.duration * 0.9) |
||
3304 | : settings.duration, |
||
3305 | start = ($subMenu) |
||
3306 | ? function() {} |
||
3307 | : function() { |
||
3308 | if( module.can.click() ) { |
||
3309 | module.unbind.intent(); |
||
3310 | } |
||
3311 | module.remove.active(); |
||
3312 | }, |
||
3313 | transition = module.get.transition($subMenu) |
||
3314 | ; |
||
3315 | callback = $.isFunction(callback) |
||
3316 | ? callback |
||
3317 | : function(){} |
||
3318 | ; |
||
3319 | if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) { |
||
3320 | module.verbose('Doing menu hide animation', $currentMenu); |
||
3321 | |||
3322 | if(transition == 'none') { |
||
3323 | start(); |
||
3324 | $currentMenu.transition('hide'); |
||
3325 | callback.call(element); |
||
3326 | } |
||
3327 | else if($.fn.transition !== undefined && $module.transition('is supported')) { |
||
3328 | $currentMenu |
||
3329 | .transition({ |
||
3330 | animation : transition + ' out', |
||
3331 | duration : settings.duration, |
||
3332 | debug : settings.debug, |
||
3333 | verbose : settings.verbose, |
||
3334 | queue : true, |
||
3335 | onStart : start, |
||
3336 | onComplete : function() { |
||
3337 | callback.call(element); |
||
3338 | } |
||
3339 | }) |
||
3340 | ; |
||
3341 | } |
||
3342 | else { |
||
3343 | module.error(error.transition); |
||
3344 | } |
||
3345 | } |
||
3346 | } |
||
3347 | }, |
||
3348 | |||
3349 | hideAndClear: function() { |
||
3350 | module.remove.searchTerm(); |
||
3351 | if( module.has.maxSelections() ) { |
||
3352 | return; |
||
3353 | } |
||
3354 | if(module.has.search()) { |
||
3355 | module.hide(function() { |
||
3356 | module.remove.filteredItem(); |
||
3357 | }); |
||
3358 | } |
||
3359 | else { |
||
3360 | module.hide(); |
||
3361 | } |
||
3362 | }, |
||
3363 | |||
3364 | delay: { |
||
3365 | show: function() { |
||
3366 | module.verbose('Delaying show event to ensure user intent'); |
||
3367 | clearTimeout(module.timer); |
||
3368 | module.timer = setTimeout(module.show, settings.delay.show); |
||
3369 | }, |
||
3370 | hide: function() { |
||
3371 | module.verbose('Delaying hide event to ensure user intent'); |
||
3372 | clearTimeout(module.timer); |
||
3373 | module.timer = setTimeout(module.hide, settings.delay.hide); |
||
3374 | } |
||
3375 | }, |
||
3376 | |||
3377 | escape: { |
||
3378 | value: function(value) { |
||
3379 | var |
||
3380 | multipleValues = $.isArray(value), |
||
3381 | stringValue = (typeof value === 'string'), |
||
3382 | isUnparsable = (!stringValue && !multipleValues), |
||
3383 | hasQuotes = (stringValue && value.search(regExp.quote) !== -1), |
||
3384 | values = [] |
||
3385 | ; |
||
3386 | if(isUnparsable || !hasQuotes) { |
||
3387 | return value; |
||
3388 | } |
||
3389 | module.debug('Encoding quote values for use in select', value); |
||
3390 | if(multipleValues) { |
||
3391 | $.each(value, function(index, value){ |
||
3392 | values.push(value.replace(regExp.quote, '"')); |
||
3393 | }); |
||
3394 | return values; |
||
3395 | } |
||
3396 | return value.replace(regExp.quote, '"'); |
||
3397 | }, |
||
3398 | string: function(text) { |
||
3399 | text = String(text); |
||
3400 | return text.replace(regExp.escape, '\\$&'); |
||
3401 | } |
||
3402 | }, |
||
3403 | |||
3404 | setting: function(name, value) { |
||
3405 | module.debug('Changing setting', name, value); |
||
3406 | if( $.isPlainObject(name) ) { |
||
3407 | $.extend(true, settings, name); |
||
3408 | } |
||
3409 | else if(value !== undefined) { |
||
3410 | if($.isPlainObject(settings[name])) { |
||
3411 | $.extend(true, settings[name], value); |
||
3412 | } |
||
3413 | else { |
||
3414 | settings[name] = value; |
||
3415 | } |
||
3416 | } |
||
3417 | else { |
||
3418 | return settings[name]; |
||
3419 | } |
||
3420 | }, |
||
3421 | internal: function(name, value) { |
||
3422 | if( $.isPlainObject(name) ) { |
||
3423 | $.extend(true, module, name); |
||
3424 | } |
||
3425 | else if(value !== undefined) { |
||
3426 | module[name] = value; |
||
3427 | } |
||
3428 | else { |
||
3429 | return module[name]; |
||
3430 | } |
||
3431 | }, |
||
3432 | debug: function() { |
||
3433 | if(!settings.silent && settings.debug) { |
||
3434 | if(settings.performance) { |
||
3435 | module.performance.log(arguments); |
||
3436 | } |
||
3437 | else { |
||
3438 | module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
||
3439 | module.debug.apply(console, arguments); |
||
3440 | } |
||
3441 | } |
||
3442 | }, |
||
3443 | verbose: function() { |
||
3444 | if(!settings.silent && settings.verbose && settings.debug) { |
||
3445 | if(settings.performance) { |
||
3446 | module.performance.log(arguments); |
||
3447 | } |
||
3448 | else { |
||
3449 | module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
||
3450 | module.verbose.apply(console, arguments); |
||
3451 | } |
||
3452 | } |
||
3453 | }, |
||
3454 | error: function() { |
||
3455 | if(!settings.silent) { |
||
3456 | module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); |
||
3457 | module.error.apply(console, arguments); |
||
3458 | } |
||
3459 | }, |
||
3460 | performance: { |
||
3461 | log: function(message) { |
||
3462 | var |
||
3463 | currentTime, |
||
3464 | executionTime, |
||
3465 | previousTime |
||
3466 | ; |
||
3467 | if(settings.performance) { |
||
3468 | currentTime = new Date().getTime(); |
||
3469 | previousTime = time || currentTime; |
||
3470 | executionTime = currentTime - previousTime; |
||
3471 | time = currentTime; |
||
3472 | performance.push({ |
||
3473 | 'Name' : message[0], |
||
3474 | 'Arguments' : [].slice.call(message, 1) || '', |
||
3475 | 'Element' : element, |
||
3476 | 'Execution Time' : executionTime |
||
3477 | }); |
||
3478 | } |
||
3479 | clearTimeout(module.performance.timer); |
||
3480 | module.performance.timer = setTimeout(module.performance.display, 500); |
||
3481 | }, |
||
3482 | display: function() { |
||
3483 | var |
||
3484 | title = settings.name + ':', |
||
3485 | totalTime = 0 |
||
3486 | ; |
||
3487 | time = false; |
||
3488 | clearTimeout(module.performance.timer); |
||
3489 | $.each(performance, function(index, data) { |
||
3490 | totalTime += data['Execution Time']; |
||
3491 | }); |
||
3492 | title += ' ' + totalTime + 'ms'; |
||
3493 | if(moduleSelector) { |
||
3494 | title += ' \'' + moduleSelector + '\''; |
||
3495 | } |
||
3496 | if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { |
||
3497 | console.groupCollapsed(title); |
||
3498 | if(console.table) { |
||
3499 | console.table(performance); |
||
3500 | } |
||
3501 | else { |
||
3502 | $.each(performance, function(index, data) { |
||
3503 | console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); |
||
3504 | }); |
||
3505 | } |
||
3506 | console.groupEnd(); |
||
3507 | } |
||
3508 | performance = []; |
||
3509 | } |
||
3510 | }, |
||
3511 | invoke: function(query, passedArguments, context) { |
||
3512 | var |
||
3513 | object = instance, |
||
3514 | maxDepth, |
||
3515 | found, |
||
3516 | response |
||
3517 | ; |
||
3518 | passedArguments = passedArguments || queryArguments; |
||
3519 | context = element || context; |
||
3520 | if(typeof query == 'string' && object !== undefined) { |
||
3521 | query = query.split(/[\. ]/); |
||
3522 | maxDepth = query.length - 1; |
||
3523 | $.each(query, function(depth, value) { |
||
3524 | var camelCaseValue = (depth != maxDepth) |
||
3525 | ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) |
||
3526 | : query |
||
3527 | ; |
||
3528 | if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { |
||
3529 | object = object[camelCaseValue]; |
||
3530 | } |
||
3531 | else if( object[camelCaseValue] !== undefined ) { |
||
3532 | found = object[camelCaseValue]; |
||
3533 | return false; |
||
3534 | } |
||
3535 | else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { |
||
3536 | object = object[value]; |
||
3537 | } |
||
3538 | else if( object[value] !== undefined ) { |
||
3539 | found = object[value]; |
||
3540 | return false; |
||
3541 | } |
||
3542 | else { |
||
3543 | module.error(error.method, query); |
||
3544 | return false; |
||
3545 | } |
||
3546 | }); |
||
3547 | } |
||
3548 | if ( $.isFunction( found ) ) { |
||
3549 | response = found.apply(context, passedArguments); |
||
3550 | } |
||
3551 | else if(found !== undefined) { |
||
3552 | response = found; |
||
3553 | } |
||
3554 | if($.isArray(returnedValue)) { |
||
3555 | returnedValue.push(response); |
||
3556 | } |
||
3557 | else if(returnedValue !== undefined) { |
||
3558 | returnedValue = [returnedValue, response]; |
||
3559 | } |
||
3560 | else if(response !== undefined) { |
||
3561 | returnedValue = response; |
||
3562 | } |
||
3563 | return found; |
||
3564 | } |
||
3565 | }; |
||
3566 | |||
3567 | if(methodInvoked) { |
||
3568 | if(instance === undefined) { |
||
3569 | module.initialize(); |
||
3570 | } |
||
3571 | module.invoke(query); |
||
3572 | } |
||
3573 | else { |
||
3574 | if(instance !== undefined) { |
||
3575 | instance.invoke('destroy'); |
||
3576 | } |
||
3577 | module.initialize(); |
||
3578 | } |
||
3579 | }) |
||
3580 | ; |
||
3581 | return (returnedValue !== undefined) |
||
3582 | ? returnedValue |
||
3583 | : $allModules |
||
3584 | ; |
||
3585 | }; |
||
3586 | |||
3587 | $.fn.dropdown.settings = { |
||
3588 | |||
3589 | silent : false, |
||
3590 | debug : false, |
||
3591 | verbose : false, |
||
3592 | performance : true, |
||
3593 | |||
3594 | on : 'click', // what event should show menu action on item selection |
||
3595 | action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){}) |
||
3596 | |||
3597 | |||
3598 | apiSettings : false, |
||
3599 | selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used |
||
3600 | minCharacters : 0, // Minimum characters required to trigger API call |
||
3601 | |||
3602 | filterRemoteData : false, // Whether API results should be filtered after being returned for query term |
||
3603 | saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh |
||
3604 | |||
3605 | throttle : 200, // How long to wait after last user input to search remotely |
||
3606 | |||
3607 | context : window, // Context to use when determining if on screen |
||
3608 | direction : 'auto', // Whether dropdown should always open in one direction |
||
3609 | keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing |
||
3610 | |||
3611 | match : 'both', // what to match against with search selection (both, text, or label) |
||
3612 | fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches) |
||
3613 | |||
3614 | placeholder : 'auto', // whether to convert blank <select> values to placeholder text |
||
3615 | preserveHTML : true, // preserve html when selecting value |
||
3616 | sortSelect : false, // sort selection on init |
||
3617 | |||
3618 | forceSelection : true, // force a choice on blur with search selection |
||
3619 | |||
3620 | allowAdditions : false, // whether multiple select should allow user added values |
||
3621 | hideAdditions : true, // whether or not to hide special message prompting a user they can enter a value |
||
3622 | |||
3623 | maxSelections : false, // When set to a number limits the number of selections to this count |
||
3624 | useLabels : true, // whether multiple select should filter currently active selections from choices |
||
3625 | delimiter : ',', // when multiselect uses normal <input> the values will be delimited with this character |
||
3626 | |||
3627 | showOnFocus : true, // show menu on focus |
||
3628 | allowReselection : false, // whether current value should trigger callbacks when reselected |
||
3629 | allowTab : true, // add tabindex to element |
||
3630 | allowCategorySelection : false, // allow elements with sub-menus to be selected |
||
3631 | |||
3632 | fireOnInit : false, // Whether callbacks should fire when initializing dropdown values |
||
3633 | |||
3634 | transition : 'auto', // auto transition will slide down or up based on direction |
||
3635 | duration : 200, // duration of transition |
||
3636 | |||
3637 | glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width |
||
3638 | |||
3639 | // label settings on multi-select |
||
3640 | label: { |
||
3641 | transition : 'scale', |
||
3642 | duration : 200, |
||
3643 | variation : false |
||
3644 | }, |
||
3645 | |||
3646 | // delay before event |
||
3647 | delay : { |
||
3648 | hide : 300, |
||
3649 | show : 200, |
||
3650 | search : 20, |
||
3651 | touch : 50 |
||
3652 | }, |
||
3653 | |||
3654 | /* Callbacks */ |
||
3655 | onChange : function(value, text, $selected){}, |
||
3656 | onAdd : function(value, text, $selected){}, |
||
3657 | onRemove : function(value, text, $selected){}, |
||
3658 | |||
3659 | onLabelSelect : function($selectedLabels){}, |
||
3660 | onLabelCreate : function(value, text) { return $(this); }, |
||
3661 | onLabelRemove : function(value) { return true; }, |
||
3662 | onNoResults : function(searchTerm) { return true; }, |
||
3663 | onShow : function(){}, |
||
3664 | onHide : function(){}, |
||
3665 | |||
3666 | /* Component */ |
||
3667 | name : 'Dropdown', |
||
3668 | namespace : 'dropdown', |
||
3669 | |||
3670 | message: { |
||
3671 | addResult : 'Add <b>{term}</b>', |
||
3672 | count : '{count} selected', |
||
3673 | maxSelections : 'Max {maxCount} selections', |
||
3674 | noResults : 'No results found.', |
||
3675 | serverError : 'There was an error contacting the server' |
||
3676 | }, |
||
3677 | |||
3678 | error : { |
||
3679 | action : 'You called a dropdown action that was not defined', |
||
3680 | alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown', |
||
3681 | labels : 'Allowing user additions currently requires the use of labels.', |
||
3682 | missingMultiple : '<select> requires multiple property to be set to correctly preserve multiple values', |
||
3683 | method : 'The method you called is not defined.', |
||
3684 | noAPI : 'The API module is required to load resources remotely', |
||
3685 | noStorage : 'Saving remote data requires session storage', |
||
3686 | noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>' |
||
3687 | }, |
||
3688 | |||
3689 | regExp : { |
||
3690 | escape : /[-[\]{}()*+?.,\\^$|#\s]/g, |
||
3691 | quote : /"/g |
||
3692 | }, |
||
3693 | |||
3694 | metadata : { |
||
3695 | defaultText : 'defaultText', |
||
3696 | defaultValue : 'defaultValue', |
||
3697 | placeholderText : 'placeholder', |
||
3698 | text : 'text', |
||
3699 | value : 'value' |
||
3700 | }, |
||
3701 | |||
3702 | // property names for remote query |
||
3703 | fields: { |
||
3704 | remoteValues : 'results', // grouping for api results |
||
3705 | values : 'values', // grouping for all dropdown values |
||
3706 | disabled : 'disabled', // whether value should be disabled |
||
3707 | name : 'name', // displayed dropdown text |
||
3708 | value : 'value', // actual dropdown value |
||
3709 | text : 'text' // displayed text when selected |
||
3710 | }, |
||
3711 | |||
3712 | keys : { |
||
3713 | backspace : 8, |
||
3714 | delimiter : 188, // comma |
||
3715 | deleteKey : 46, |
||
3716 | enter : 13, |
||
3717 | escape : 27, |
||
3718 | pageUp : 33, |
||
3719 | pageDown : 34, |
||
3720 | leftArrow : 37, |
||
3721 | upArrow : 38, |
||
3722 | rightArrow : 39, |
||
3723 | downArrow : 40 |
||
3724 | }, |
||
3725 | |||
3726 | selector : { |
||
3727 | addition : '.addition', |
||
3728 | dropdown : '.ui.dropdown', |
||
3729 | hidden : '.hidden', |
||
3730 | icon : '> .dropdown.icon', |
||
3731 | input : '> input[type="hidden"], > select', |
||
3732 | item : '.item', |
||
3733 | label : '> .label', |
||
3734 | remove : '> .label > .delete.icon', |
||
3735 | siblingLabel : '.label', |
||
3736 | menu : '.menu', |
||
3737 | message : '.message', |
||
3738 | menuIcon : '.dropdown.icon', |
||
3739 | search : 'input.search, .menu > .search > input, .menu input.search', |
||
3740 | sizer : '> input.sizer', |
||
3741 | text : '> .text:not(.icon)', |
||
3742 | unselectable : '.disabled, .filtered' |
||
3743 | }, |
||
3744 | |||
3745 | className : { |
||
3746 | active : 'active', |
||
3747 | addition : 'addition', |
||
3748 | animating : 'animating', |
||
3749 | disabled : 'disabled', |
||
3750 | empty : 'empty', |
||
3751 | dropdown : 'ui dropdown', |
||
3752 | filtered : 'filtered', |
||
3753 | hidden : 'hidden transition', |
||
3754 | item : 'item', |
||
3755 | label : 'ui label', |
||
3756 | loading : 'loading', |
||
3757 | menu : 'menu', |
||
3758 | message : 'message', |
||
3759 | multiple : 'multiple', |
||
3760 | placeholder : 'default', |
||
3761 | sizer : 'sizer', |
||
3762 | search : 'search', |
||
3763 | selected : 'selected', |
||
3764 | selection : 'selection', |
||
3765 | upward : 'upward', |
||
3766 | leftward : 'left', |
||
3767 | visible : 'visible' |
||
3768 | } |
||
3769 | |||
3770 | }; |
||
3771 | |||
3772 | /* Templates */ |
||
3773 | $.fn.dropdown.settings.templates = { |
||
3774 | |||
3775 | // generates dropdown from select values |
||
3776 | dropdown: function(select) { |
||
3777 | var |
||
3778 | placeholder = select.placeholder || false, |
||
3779 | values = select.values || {}, |
||
3780 | html = '' |
||
3781 | ; |
||
3782 | html += '<i class="dropdown icon"></i>'; |
||
3783 | if(select.placeholder) { |
||
3784 | html += '<div class="default text">' + placeholder + '</div>'; |
||
3785 | } |
||
3786 | else { |
||
3787 | html += '<div class="text"></div>'; |
||
3788 | } |
||
3789 | html += '<div class="menu">'; |
||
3790 | $.each(select.values, function(index, option) { |
||
3791 | html += (option.disabled) |
||
3792 | ? '<div class="disabled item" data-value="' + option.value + '">' + option.name + '</div>' |
||
3793 | : '<div class="item" data-value="' + option.value + '">' + option.name + '</div>' |
||
3794 | ; |
||
3795 | }); |
||
3796 | html += '</div>'; |
||
3797 | return html; |
||
3798 | }, |
||
3799 | |||
3800 | // generates just menu from select |
||
3801 | menu: function(response, fields) { |
||
3802 | var |
||
3803 | values = response[fields.values] || {}, |
||
3804 | html = '' |
||
3805 | ; |
||
3806 | $.each(values, function(index, option) { |
||
3807 | var |
||
3808 | maybeText = (option[fields.text]) |
||
3809 | ? 'data-text="' + option[fields.text] + '"' |
||
3810 | : '', |
||
3811 | maybeDisabled = (option[fields.disabled]) |
||
3812 | ? 'disabled ' |
||
3813 | : '' |
||
3814 | ; |
||
3815 | html += '<div class="'+ maybeDisabled +'item" data-value="' + option[fields.value] + '"' + maybeText + '>' |
||
3816 | html += option[fields.name]; |
||
3817 | html += '</div>'; |
||
3818 | }); |
||
3819 | return html; |
||
3820 | }, |
||
3821 | |||
3822 | // generates label for multiselect |
||
3823 | label: function(value, text) { |
||
3824 | return text + '<i class="delete icon"></i>'; |
||
3825 | }, |
||
3826 | |||
3827 | |||
3828 | // generates messages like "No results" |
||
3829 | message: function(message) { |
||
3830 | return message; |
||
3831 | }, |
||
3832 | |||
3833 | // generates user addition to selection menu |
||
3834 | addition: function(choice) { |
||
3835 | return choice; |
||
3836 | } |
||
3837 | |||
3838 | }; |
||
3839 | |||
3840 | })( jQuery, window, document ); |
||
3841 |