You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3223 lines
110 KiB

  1. /*!
  2. * # Semantic UI 2.0.0 - Dropdown
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2015 Contributors
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. "use strict";
  13. $.fn.dropdown = function(parameters) {
  14. var
  15. $allModules = $(this),
  16. $document = $(document),
  17. moduleSelector = $allModules.selector || '',
  18. hasTouch = ('ontouchstart' in document.documentElement),
  19. time = new Date().getTime(),
  20. performance = [],
  21. query = arguments[0],
  22. methodInvoked = (typeof query == 'string'),
  23. queryArguments = [].slice.call(arguments, 1),
  24. returnedValue
  25. ;
  26. $allModules
  27. .each(function(elementIndex) {
  28. var
  29. settings = ( $.isPlainObject(parameters) )
  30. ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
  31. : $.extend({}, $.fn.dropdown.settings),
  32. className = settings.className,
  33. message = settings.message,
  34. metadata = settings.metadata,
  35. namespace = settings.namespace,
  36. regExp = settings.regExp,
  37. selector = settings.selector,
  38. error = settings.error,
  39. templates = settings.templates,
  40. eventNamespace = '.' + namespace,
  41. moduleNamespace = 'module-' + namespace,
  42. $module = $(this),
  43. $context = $(settings.context),
  44. $text = $module.find(selector.text),
  45. $search = $module.find(selector.search),
  46. $input = $module.find(selector.input),
  47. $icon = $module.find(selector.icon),
  48. $combo = ($module.prev().find(selector.text).length > 0)
  49. ? $module.prev().find(selector.text)
  50. : $module.prev(),
  51. $menu = $module.children(selector.menu),
  52. $item = $menu.find(selector.item),
  53. activated = false,
  54. itemActivated = false,
  55. element = this,
  56. instance = $module.data(moduleNamespace),
  57. initialLoad,
  58. pageLostFocus,
  59. elementNamespace,
  60. id,
  61. selectObserver,
  62. menuObserver,
  63. module
  64. ;
  65. module = {
  66. initialize: function() {
  67. module.debug('Initializing dropdown', settings);
  68. if( module.is.alreadySetup() ) {
  69. module.setup.reference();
  70. }
  71. else {
  72. module.setup.layout();
  73. module.refreshData();
  74. module.save.defaults();
  75. module.restore.selected();
  76. module.create.id();
  77. if(hasTouch) {
  78. module.bind.touchEvents();
  79. }
  80. module.bind.mouseEvents();
  81. module.bind.keyboardEvents();
  82. module.observeChanges();
  83. module.instantiate();
  84. }
  85. },
  86. instantiate: function() {
  87. module.verbose('Storing instance of dropdown', module);
  88. instance = module;
  89. $module
  90. .data(moduleNamespace, module)
  91. ;
  92. },
  93. destroy: function() {
  94. module.verbose('Destroying previous dropdown', $module);
  95. module.remove.tabbable();
  96. $module
  97. .off(eventNamespace)
  98. .removeData(moduleNamespace)
  99. ;
  100. $menu
  101. .off(eventNamespace)
  102. ;
  103. $document
  104. .off(elementNamespace)
  105. ;
  106. if(selectObserver) {
  107. selectObserver.disconnect();
  108. }
  109. if(menuObserver) {
  110. menuObserver.disconnect();
  111. }
  112. },
  113. observeChanges: function() {
  114. if('MutationObserver' in window) {
  115. selectObserver = new MutationObserver(function(mutations) {
  116. module.debug('<select> modified, recreating menu');
  117. module.setup.select();
  118. });
  119. menuObserver = new MutationObserver(function(mutations) {
  120. module.debug('Menu modified, updating selector cache');
  121. module.refresh();
  122. });
  123. if(module.has.input()) {
  124. selectObserver.observe($input[0], {
  125. childList : true,
  126. subtree : true
  127. });
  128. }
  129. if(module.has.menu()) {
  130. menuObserver.observe($menu[0], {
  131. childList : true,
  132. subtree : true
  133. });
  134. }
  135. module.debug('Setting up mutation observer', selectObserver, menuObserver);
  136. }
  137. },
  138. create: {
  139. id: function() {
  140. id = (Math.random().toString(16) + '000000000').substr(2, 8);
  141. elementNamespace = '.' + id;
  142. module.verbose('Creating unique id for element', id);
  143. },
  144. userChoice: function(values) {
  145. var
  146. $userChoices,
  147. $userChoice,
  148. isUserValue,
  149. html
  150. ;
  151. values = values || module.get.userValues();
  152. if(!values) {
  153. return false;
  154. }
  155. values = $.isArray(values)
  156. ? values
  157. : [values]
  158. ;
  159. $.each(values, function(index, value) {
  160. if(module.get.item(value) === false) {
  161. html = settings.templates.addition(value);
  162. $userChoice = $('<div />')
  163. .html(html)
  164. .data(metadata.value, value)
  165. .addClass(className.addition)
  166. .addClass(className.item)
  167. ;
  168. $userChoices = ($userChoices === undefined)
  169. ? $userChoice
  170. : $userChoices.add($userChoice)
  171. ;
  172. module.verbose('Creating user choices for value', value, $userChoice);
  173. }
  174. });
  175. return $userChoices;
  176. },
  177. userLabels: function(value) {
  178. var
  179. userValues = module.get.userValues()
  180. ;
  181. if(userValues) {
  182. module.debug('Adding user labels', userValues);
  183. $.each(userValues, function(index, value) {
  184. module.verbose('Adding custom user value');
  185. module.add.label(value, value);
  186. });
  187. }
  188. },
  189. },
  190. search: function(query) {
  191. query = (query !== undefined)
  192. ? query
  193. : module.get.query()
  194. ;
  195. module.verbose('Searching for query', query);
  196. module.filter(query);
  197. },
  198. select: {
  199. firstUnfiltered: function() {
  200. module.verbose('Selecting first non-filtered element');
  201. module.remove.selectedItem();
  202. $item
  203. .not(selector.unselectable)
  204. .eq(0)
  205. .addClass(className.selected)
  206. ;
  207. },
  208. nextAvailable: function($selected) {
  209. $selected = $selected.eq(0);
  210. var
  211. $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0),
  212. $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0),
  213. hasNext = ($nextAvailable.length > 0)
  214. ;
  215. if(hasNext) {
  216. module.verbose('Moving selection to', $nextAvailable);
  217. $nextAvailable.addClass(className.selected);
  218. }
  219. else {
  220. module.verbose('Moving selection to', $prevAvailable);
  221. $prevAvailable.addClass(className.selected);
  222. }
  223. }
  224. },
  225. setup: {
  226. api: function() {
  227. var
  228. apiSettings = {
  229. debug : settings.debug,
  230. on : false
  231. }
  232. ;
  233. module.verbose('First request, initializing API');
  234. $module
  235. .api(apiSettings)
  236. ;
  237. },
  238. layout: function() {
  239. if( $module.is('select') ) {
  240. module.setup.select();
  241. module.setup.returnedObject();
  242. console.log($module);
  243. }
  244. if( module.is.search() && !module.has.search() ) {
  245. module.verbose('Adding search input');
  246. $search = $('<input />')
  247. .addClass(className.search)
  248. .insertBefore($text)
  249. ;
  250. }
  251. if(settings.allowTab) {
  252. module.set.tabbable();
  253. }
  254. if($menu.length === 0) {
  255. $menu = $('<div />')
  256. .addClass(className.menu)
  257. .appendTo($module)
  258. ;
  259. }
  260. },
  261. select: function() {
  262. var
  263. selectValues = module.get.selectValues()
  264. ;
  265. module.debug('Dropdown initialized on a select', selectValues);
  266. if( $module.is('select') ) {
  267. $input = $module;
  268. }
  269. // see if select is placed correctly already
  270. if($input.parent(selector.dropdown).length > 0) {
  271. module.debug('UI dropdown already exists. Creating dropdown menu only');
  272. $module = $input.closest(selector.dropdown);
  273. $menu = $module.children(selector.menu);
  274. module.setup.menu(selectValues);
  275. }
  276. else {
  277. module.debug('Creating entire dropdown from select');
  278. $module = $('<div />')
  279. .attr('class', $input.attr('class') )
  280. .addClass(className.selection)
  281. .addClass(className.dropdown)
  282. .html( templates.dropdown(selectValues) )
  283. .insertBefore($input)
  284. ;
  285. $input
  286. .removeAttr('class')
  287. .detach()
  288. .prependTo($module)
  289. ;
  290. console.log($module);
  291. }
  292. if($input.is('[multiple]')) {
  293. module.set.multiple();
  294. }
  295. module.refresh();
  296. },
  297. menu: function(values) {
  298. $menu.html( templates.menu( values ));
  299. $item = $menu.find(selector.item);
  300. },
  301. reference: function() {
  302. module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
  303. // replace module reference
  304. $module = $module.parent(selector.dropdown);
  305. module.refresh();
  306. module.setup.returnedObject();
  307. // invoke method in context of current instance
  308. if(methodInvoked) {
  309. instance = module;
  310. module.invoke(query);
  311. }
  312. },
  313. returnedObject: function() {
  314. var
  315. $firstModules = $allModules.slice(0, elementIndex),
  316. $lastModules = $allModules.slice(elementIndex + 1)
  317. ;
  318. // adjust all modules to use correct reference
  319. $allModules = $firstModules.add($module).add($lastModules);
  320. }
  321. },
  322. refresh: function() {
  323. module.refreshSelectors();
  324. module.refreshData();
  325. },
  326. refreshSelectors: function() {
  327. module.verbose('Refreshing selector cache');
  328. $text = $module.find(selector.text);
  329. $search = $module.find(selector.search);
  330. $input = $module.find(selector.input);
  331. $icon = $module.find(selector.icon);
  332. $combo = ($module.prev().find(selector.text).length > 0)
  333. ? $module.prev().find(selector.text)
  334. : $module.prev()
  335. ;
  336. $menu = $module.children(selector.menu);
  337. $item = $menu.find(selector.item);
  338. },
  339. refreshData: function() {
  340. module.verbose('Refreshing cached metadata');
  341. $item
  342. .removeData(metadata.text)
  343. .removeData(metadata.value)
  344. ;
  345. $module
  346. .removeData(metadata.defaultText)
  347. .removeData(metadata.defaultValue)
  348. .removeData(metadata.placeholderText)
  349. ;
  350. },
  351. toggle: function() {
  352. module.verbose('Toggling menu visibility');
  353. if( !module.is.active() ) {
  354. module.show();
  355. }
  356. else {
  357. module.hide();
  358. }
  359. },
  360. show: function(callback) {
  361. callback = $.isFunction(callback)
  362. ? callback
  363. : function(){}
  364. ;
  365. if( module.can.show() && !module.is.active() ) {
  366. module.debug('Showing dropdown');
  367. if(module.is.multiple()) {
  368. if(!module.has.search() && module.is.allFiltered()) {
  369. return true;
  370. }
  371. }
  372. module.animate.show(function() {
  373. if( module.can.click() ) {
  374. module.bind.intent();
  375. }
  376. module.set.visible();
  377. callback.call(element);
  378. });
  379. settings.onShow.call(element);
  380. }
  381. },
  382. hide: function(callback) {
  383. callback = $.isFunction(callback)
  384. ? callback
  385. : function(){}
  386. ;
  387. if( module.is.active() ) {
  388. module.debug('Hiding dropdown');
  389. module.animate.hide(function() {
  390. module.remove.visible();
  391. callback.call(element);
  392. });
  393. settings.onHide.call(element);
  394. }
  395. },
  396. hideOthers: function() {
  397. module.verbose('Finding other dropdowns to hide');
  398. $allModules
  399. .not($module)
  400. .has(selector.menu + '.' + className.visible)
  401. .dropdown('hide')
  402. ;
  403. },
  404. hideMenu: function() {
  405. module.verbose('Hiding menu instantaneously');
  406. module.remove.active();
  407. module.remove.visible();
  408. $menu.transition('hide');
  409. },
  410. hideSubMenus: function() {
  411. var
  412. $subMenus = $menu.children(selector.item).find(selector.menu)
  413. ;
  414. module.verbose('Hiding sub menus', $subMenus);
  415. $subMenus.transition('hide');
  416. },
  417. bind: {
  418. keyboardEvents: function() {
  419. module.debug('Binding keyboard events');
  420. $module
  421. .on('keydown' + eventNamespace, module.event.keydown)
  422. ;
  423. if( module.has.search() ) {
  424. $module
  425. .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input)
  426. ;
  427. }
  428. if( module.is.multiple() ) {
  429. $document
  430. .on('keydown' + elementNamespace, module.event.document.keydown)
  431. ;
  432. }
  433. },
  434. touchEvents: function() {
  435. module.debug('Touch device detected binding additional touch events');
  436. if( module.is.searchSelection() ) {
  437. // do nothing special yet
  438. }
  439. else {
  440. $module
  441. .on('touchstart' + eventNamespace, module.event.test.toggle)
  442. ;
  443. }
  444. $menu
  445. .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
  446. ;
  447. },
  448. mouseEvents: function() {
  449. module.debug('Mouse detected binding mouse events');
  450. if(module.is.multiple()) {
  451. $module
  452. .on('click' + eventNamespace, selector.label, module.event.label.click)
  453. .on('click' + eventNamespace, selector.remove, module.event.remove.click)
  454. ;
  455. }
  456. if( module.is.searchSelection() ) {
  457. $module
  458. .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
  459. .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
  460. .on('click' + eventNamespace, selector.search, module.show)
  461. .on('focus' + eventNamespace, selector.search, module.event.search.focus)
  462. .on('blur' + eventNamespace, selector.search, module.event.search.blur)
  463. .on('click' + eventNamespace, selector.text, module.event.text.focus)
  464. ;
  465. if(module.is.multiple()) {
  466. $module
  467. .on('click' + eventNamespace, module.event.click)
  468. ;
  469. }
  470. }
  471. else {
  472. if(settings.on == 'click') {
  473. $module
  474. .on('click' + eventNamespace, module.event.test.toggle)
  475. ;
  476. }
  477. else if(settings.on == 'hover') {
  478. $module
  479. .on('mouseenter' + eventNamespace, module.delay.show)
  480. .on('mouseleave' + eventNamespace, module.delay.hide)
  481. ;
  482. }
  483. else {
  484. $module
  485. .on(settings.on + eventNamespace, module.toggle)
  486. ;
  487. }
  488. $module
  489. .on('mousedown' + eventNamespace, module.event.mousedown)
  490. .on('mouseup' + eventNamespace, module.event.mouseup)
  491. .on('focus' + eventNamespace, module.event.focus)
  492. .on('blur' + eventNamespace, module.event.blur)
  493. ;
  494. }
  495. $menu
  496. .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
  497. .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
  498. .on('click' + eventNamespace, selector.item, module.event.item.click)
  499. ;
  500. },
  501. intent: function() {
  502. module.verbose('Binding hide intent event to document');
  503. if(hasTouch) {
  504. $document
  505. .on('touchstart' + elementNamespace, module.event.test.touch)
  506. .on('touchmove' + elementNamespace, module.event.test.touch)
  507. ;
  508. }
  509. $document
  510. .on('click' + elementNamespace, module.event.test.hide)
  511. ;
  512. }
  513. },
  514. unbind: {
  515. intent: function() {
  516. module.verbose('Removing hide intent event from document');
  517. if(hasTouch) {
  518. $document
  519. .off('touchstart' + elementNamespace)
  520. .off('touchmove' + elementNamespace)
  521. ;
  522. }
  523. $document
  524. .off('click' + elementNamespace)
  525. ;
  526. }
  527. },
  528. filter: function(query) {
  529. var
  530. searchTerm = (query !== undefined)
  531. ? query
  532. : module.get.query(),
  533. afterFiltered = function() {
  534. if(module.is.multiple()) {
  535. module.filterActive();
  536. }
  537. module.select.firstUnfiltered();
  538. if( module.has.allResultsFiltered() ) {
  539. if( settings.onNoResults.call(element, searchTerm) ) {
  540. if(!settings.allowAdditions) {
  541. module.verbose('All items filtered, showing message', searchTerm);
  542. module.add.message(message.noResults);
  543. }
  544. }
  545. else {
  546. module.verbose('All items filtered, hiding dropdown', searchTerm);
  547. module.hideMenu();
  548. }
  549. }
  550. else {
  551. module.remove.message();
  552. }
  553. if(settings.allowAdditions) {
  554. module.add.userSuggestion(query);
  555. }
  556. if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
  557. module.show();
  558. }
  559. }
  560. ;
  561. if(module.has.maxSelections()) {
  562. return;
  563. }
  564. if(settings.apiSettings) {
  565. if( module.can.useAPI() ) {
  566. module.queryRemote(searchTerm, function() {
  567. afterFiltered();
  568. });
  569. }
  570. else {
  571. module.error(error.noAPI);
  572. }
  573. }
  574. else {
  575. module.filterItems(searchTerm);
  576. afterFiltered();
  577. }
  578. },
  579. queryRemote: function(query, callback) {
  580. var
  581. apiSettings = {
  582. errorDuration : false,
  583. throttle : settings.throttle,
  584. cache : 'local',
  585. urlData : {
  586. query: query
  587. },
  588. onError: function() {
  589. module.add.message(message.serverError);
  590. callback();
  591. },
  592. onFailure: function() {
  593. module.add.message(message.serverError);
  594. callback();
  595. },
  596. onSuccess : function(response) {
  597. module.remove.message();
  598. module.setup.menu({
  599. values: response.results
  600. });
  601. callback();
  602. }
  603. }
  604. ;
  605. if( !$module.api('get request') ) {
  606. module.setup.api();
  607. }
  608. apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings);
  609. $module
  610. .api('setting', apiSettings)
  611. .api('query')
  612. ;
  613. },
  614. filterItems: function(query) {
  615. var
  616. searchTerm = (query !== undefined)
  617. ? query
  618. : module.get.query(),
  619. $results = $(),
  620. escapedTerm = module.escape.regExp(searchTerm),
  621. beginsWithRegExp = new RegExp('^' + escapedTerm, 'igm')
  622. ;
  623. // avoid loop if we're matching nothing
  624. if(searchTerm === '') {
  625. $results = $item;
  626. }
  627. else {
  628. module.verbose('Searching for matching values', searchTerm);
  629. $item
  630. .each(function(){
  631. var
  632. $choice = $(this),
  633. text,
  634. value
  635. ;
  636. if(settings.match == 'both' || settings.match == 'text') {
  637. text = String(module.get.choiceText($choice, false));
  638. if(text.search(beginsWithRegExp) !== -1) {
  639. $results = $results.add($choice);
  640. return true;
  641. }
  642. else if(settings.fullTextSearch && module.fuzzySearch(searchTerm, text)) {
  643. $results = $results.add($choice);
  644. return true;
  645. }
  646. }
  647. if(settings.match == 'both' || settings.match == 'value') {
  648. value = String(module.get.choiceValue($choice, text));
  649. if(value.search(beginsWithRegExp) !== -1) {
  650. $results = $results.add($choice);
  651. return true;
  652. }
  653. else if(settings.fullTextSearch && module.fuzzySearch(searchTerm, value)) {
  654. $results = $results.add($choice);
  655. return true;
  656. }
  657. }
  658. })
  659. ;
  660. }
  661. module.debug('Showing only matched items', searchTerm);
  662. module.remove.filteredItem();
  663. $item
  664. .not($results)
  665. .addClass(className.filtered)
  666. ;
  667. },
  668. fuzzySearch: function(query, term) {
  669. var
  670. termLength = term.length,
  671. queryLength = query.length
  672. ;
  673. query = query.toLowerCase();
  674. term = term.toLowerCase();
  675. if(queryLength > termLength) {
  676. return false;
  677. }
  678. if(queryLength === termLength) {
  679. return (query === term);
  680. }
  681. search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) {
  682. var
  683. queryCharacter = query.charCodeAt(characterIndex)
  684. ;
  685. while(nextCharacterIndex < termLength) {
  686. if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
  687. continue search;
  688. }
  689. }
  690. return false;
  691. }
  692. return true;
  693. },
  694. filterActive: function() {
  695. if(settings.useLabels) {
  696. $item.filter('.' + className.active)
  697. .addClass(className.filtered)
  698. ;
  699. }
  700. },
  701. focusSearch: function() {
  702. if( module.is.search() && !module.is.focusedOnSearch() ) {
  703. $search[0].focus();
  704. }
  705. },
  706. forceSelection: function() {
  707. var
  708. $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0),
  709. $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0),
  710. $selectedItem = ($currentlySelected.length > 0)
  711. ? $currentlySelected
  712. : $activeItem,
  713. hasSelected = ($selectedItem.size() > 0)
  714. ;
  715. if(hasSelected) {
  716. module.debug('Forcing partial selection to selected item', $selectedItem);
  717. module.event.item.click.call($selectedItem);
  718. }
  719. else {
  720. module.hide();
  721. }
  722. },
  723. event: {
  724. focus: function() {
  725. if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) {
  726. module.show();
  727. }
  728. },
  729. click: function(event) {
  730. var
  731. $target = $(event.target)
  732. ;
  733. // focus search
  734. if(($target.is($module) || $target.is($icon)) && !module.is.focusedOnSearch()) {
  735. module.focusSearch();
  736. }
  737. },
  738. blur: function(event) {
  739. pageLostFocus = (document.activeElement === this);
  740. if(!activated && !pageLostFocus) {
  741. module.remove.activeLabel();
  742. module.hide();
  743. }
  744. },
  745. // prevents focus callback from occuring on mousedown
  746. mousedown: function() {
  747. activated = true;
  748. },
  749. mouseup: function() {
  750. activated = false;
  751. },
  752. search: {
  753. focus: function() {
  754. activated = true;
  755. if(module.is.multiple()) {
  756. module.remove.activeLabel();
  757. }
  758. if(settings.showOnFocus) {
  759. module.show();
  760. }
  761. },
  762. blur: function(event) {
  763. pageLostFocus = (document.activeElement === this);
  764. if(!itemActivated && !pageLostFocus) {
  765. if(module.is.multiple()) {
  766. module.remove.activeLabel();
  767. module.hide();
  768. }
  769. else if(settings.forceSelection) {
  770. module.forceSelection();
  771. }
  772. else {
  773. module.hide();
  774. }
  775. }
  776. }
  777. },
  778. text: {
  779. focus: function(event) {
  780. activated = true;
  781. module.focusSearch();
  782. }
  783. },
  784. input: function(event) {
  785. if(module.is.multiple() || module.is.searchSelection()) {
  786. module.set.filtered();
  787. }
  788. clearTimeout(module.timer);
  789. module.timer = setTimeout(module.search, settings.delay.search);
  790. },
  791. label: {
  792. click: function(event) {
  793. var
  794. $label = $(this),
  795. $labels = $module.find(selector.label),
  796. $activeLabels = $labels.filter('.' + className.active),
  797. $nextActive = $label.nextAll('.' + className.active),
  798. $prevActive = $label.prevAll('.' + className.active),
  799. $range = ($nextActive.length > 0)
  800. ? $label.nextUntil($nextActive).add($activeLabels).add($label)
  801. : $label.prevUntil($prevActive).add($activeLabels).add($label)
  802. ;
  803. if(event.shiftKey) {
  804. $activeLabels.removeClass(className.active);
  805. $range.addClass(className.active);
  806. }
  807. else if(event.ctrlKey) {
  808. $label.toggleClass(className.active);
  809. }
  810. else {
  811. $activeLabels.removeClass(className.active);
  812. $label.addClass(className.active);
  813. }
  814. settings.onLabelSelect.apply(this, $labels.filter('.' + className.active));
  815. }
  816. },
  817. remove: {
  818. click: function() {
  819. var
  820. $label = $(this).parent()
  821. ;
  822. if( $label.hasClass(className.active) ) {
  823. // remove all selected labels
  824. module.remove.activeLabels();
  825. }
  826. else {
  827. // remove this label only
  828. module.remove.activeLabels( $label );
  829. }
  830. }
  831. },
  832. test: {
  833. toggle: function(event) {
  834. var
  835. toggleBehavior = (module.is.multiple())
  836. ? module.show
  837. : module.toggle
  838. ;
  839. if( module.determine.eventOnElement(event, toggleBehavior) ) {
  840. event.preventDefault();
  841. }
  842. },
  843. touch: function(event) {
  844. module.determine.eventOnElement(event, function() {
  845. if(event.type == 'touchstart') {
  846. module.timer = setTimeout(module.hide, settings.delay.touch);
  847. }
  848. else if(event.type == 'touchmove') {
  849. clearTimeout(module.timer);
  850. }
  851. });
  852. event.stopPropagation();
  853. },
  854. hide: function(event) {
  855. module.determine.eventInModule(event, module.hide);
  856. }
  857. },
  858. menu: {
  859. mousedown: function() {
  860. itemActivated = true;
  861. },
  862. mouseup: function() {
  863. itemActivated = false;
  864. }
  865. },
  866. item: {
  867. mouseenter: function(event) {
  868. var
  869. $subMenu = $(this).children(selector.menu),
  870. $otherMenus = $(this).siblings(selector.item).children(selector.menu)
  871. ;
  872. if( $subMenu.length > 0 ) {
  873. clearTimeout(module.itemTimer);
  874. module.itemTimer = setTimeout(function() {
  875. module.verbose('Showing sub-menu', $subMenu);
  876. $.each($otherMenus, function() {
  877. module.animate.hide(false, $(this));
  878. });
  879. module.animate.show(false, $subMenu);
  880. }, settings.delay.show);
  881. event.preventDefault();
  882. }
  883. },
  884. mouseleave: function(event) {
  885. var
  886. $subMenu = $(this).children(selector.menu)
  887. ;
  888. if($subMenu.length > 0) {
  889. clearTimeout(module.itemTimer);
  890. module.itemTimer = setTimeout(function() {
  891. module.verbose('Hiding sub-menu', $subMenu);
  892. module.animate.hide(false, $subMenu);
  893. }, settings.delay.hide);
  894. }
  895. },
  896. click: function (event) {
  897. var
  898. $choice = $(this),
  899. $target = (event)
  900. ? $(event.target)
  901. : $(''),
  902. $subMenu = $choice.find(selector.menu),
  903. text = module.get.choiceText($choice),
  904. value = module.get.choiceValue($choice, text),
  905. hasSubMenu = ($subMenu.length > 0),
  906. isBubbledEvent = ($subMenu.find($target).length > 0)
  907. ;
  908. if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
  909. if(!settings.useLabels) {
  910. module.remove.searchTerm();
  911. }
  912. module.determine.selectAction.call(this, text, value);
  913. }
  914. }
  915. },
  916. document: {
  917. // label selection should occur even when element has no focus
  918. keydown: function(event) {
  919. var
  920. pressedKey = event.which,
  921. keys = module.get.shortcutKeys(),
  922. isShortcutKey = module.is.inObject(pressedKey, keys)
  923. ;
  924. if(isShortcutKey) {
  925. var
  926. $label = $module.find(selector.label),
  927. $activeLabel = $label.filter('.' + className.active),
  928. activeValue = $activeLabel.data('value'),
  929. labelIndex = $label.index($activeLabel),
  930. labelCount = $label.length,
  931. hasActiveLabel = ($activeLabel.length > 0),
  932. hasMultipleActive = ($activeLabel.length > 1),
  933. isFirstLabel = (labelIndex === 0),
  934. isLastLabel = (labelIndex + 1 == labelCount),
  935. isSearch = module.is.searchSelection(),
  936. isFocusedOnSearch = module.is.focusedOnSearch(),
  937. isFocused = module.is.focused(),
  938. caretAtStart = (isFocusedOnSearch && module.get.caretPosition() === 0),
  939. $nextLabel
  940. ;
  941. if(isSearch && !hasActiveLabel && !isFocusedOnSearch) {
  942. return;
  943. }
  944. if(pressedKey == keys.leftArrow) {
  945. // activate previous label
  946. if((isFocused || caretAtStart) && !hasActiveLabel) {
  947. module.verbose('Selecting previous label');
  948. $label.last().addClass(className.active);
  949. }
  950. else if(hasActiveLabel) {
  951. if(!event.shiftKey) {
  952. module.verbose('Selecting previous label');
  953. $label.removeClass(className.active);
  954. }
  955. else {
  956. module.verbose('Adding previous label to selection');
  957. }
  958. if(isFirstLabel && !hasMultipleActive) {
  959. $activeLabel.addClass(className.active);
  960. }
  961. else {
  962. $activeLabel.prev(selector.siblingLabel)
  963. .addClass(className.active)
  964. .end()
  965. ;
  966. }
  967. event.preventDefault();
  968. }
  969. }
  970. else if(pressedKey == keys.rightArrow) {
  971. // activate first label
  972. if(isFocused && !hasActiveLabel) {
  973. $label.first().addClass(className.active);
  974. }
  975. // activate next label
  976. if(hasActiveLabel) {
  977. if(!event.shiftKey) {
  978. module.verbose('Selecting next label');
  979. $label.removeClass(className.active);
  980. }
  981. else {
  982. module.verbose('Adding next label to selection');
  983. }
  984. if(isLastLabel) {
  985. if(isSearch) {
  986. if(!isFocusedOnSearch) {
  987. module.focusSearch();
  988. }
  989. else {
  990. $label.removeClass(className.active);
  991. }
  992. }
  993. else if(hasMultipleActive) {
  994. $activeLabel.next(selector.siblingLabel).addClass(className.active);
  995. }
  996. else {
  997. $activeLabel.addClass(className.active);
  998. }
  999. }
  1000. else {
  1001. $activeLabel.next(selector.siblingLabel).addClass(className.active);
  1002. }
  1003. event.preventDefault();
  1004. }
  1005. }
  1006. else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
  1007. if(hasActiveLabel) {
  1008. module.verbose('Removing active labels');
  1009. if(isLastLabel) {
  1010. if(isSearch && !isFocusedOnSearch) {
  1011. module.focusSearch();
  1012. }
  1013. }
  1014. $activeLabel.last().next(selector.siblingLabel).addClass(className.active);
  1015. module.remove.activeLabels($activeLabel);
  1016. event.preventDefault();
  1017. }
  1018. else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) {
  1019. module.verbose('Removing last label on input backspace');
  1020. $activeLabel = $label.last().addClass(className.active);
  1021. module.remove.activeLabels($activeLabel);
  1022. }
  1023. }
  1024. else {
  1025. $activeLabel.removeClass(className.active);
  1026. }
  1027. }
  1028. }
  1029. },
  1030. keydown: function(event) {
  1031. var
  1032. pressedKey = event.which,
  1033. keys = module.get.shortcutKeys(),
  1034. isShortcutKey = module.is.inObject(pressedKey, keys)
  1035. ;
  1036. if(isShortcutKey) {
  1037. var
  1038. $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0),
  1039. $activeItem = $menu.children('.' + className.active).eq(0),
  1040. $selectedItem = ($currentlySelected.length > 0)
  1041. ? $currentlySelected
  1042. : $activeItem,
  1043. $visibleItems = ($selectedItem.length > 0)
  1044. ? $selectedItem.siblings(':not(.' + className.filtered +')').andSelf()
  1045. : $menu.children(':not(.' + className.filtered +')'),
  1046. $subMenu = $selectedItem.children(selector.menu),
  1047. $parentMenu = $selectedItem.closest(selector.menu),
  1048. inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0),
  1049. hasSubMenu = ($subMenu.length> 0),
  1050. hasSelectedItem = ($selectedItem.length > 0),
  1051. selectedIsVisible = ($selectedItem.not(selector.unselectable).length > 0),
  1052. $nextItem,
  1053. isSubMenuItem,
  1054. newIndex
  1055. ;
  1056. // visible menu keyboard shortcuts
  1057. if( module.is.visible() ) {
  1058. // enter (select or open sub-menu)
  1059. if(pressedKey == keys.enter || pressedKey == keys.delimiter) {
  1060. if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) {
  1061. module.verbose('Pressed enter on unselectable category, opening sub menu');
  1062. pressedKey = keys.rightArrow;
  1063. }
  1064. else if(selectedIsVisible) {
  1065. module.verbose('Selecting item from keyboard shortcut', $selectedItem);
  1066. module.event.item.click.call($selectedItem, event);
  1067. if(settings.useLabels && module.is.searchSelection()) {
  1068. module.hideAndClear();
  1069. }
  1070. else {
  1071. module.remove.searchTerm();
  1072. }
  1073. }
  1074. event.preventDefault();
  1075. }
  1076. // left arrow (hide sub-menu)
  1077. if(pressedKey == keys.leftArrow) {
  1078. isSubMenuItem = ($parentMenu[0] !== $menu[0]);
  1079. if(isSubMenuItem) {
  1080. module.verbose('Left key pressed, closing sub-menu');
  1081. module.animate.hide(false, $parentMenu);
  1082. $selectedItem
  1083. .removeClass(className.selected)
  1084. ;
  1085. $parentMenu
  1086. .closest(selector.item)
  1087. .addClass(className.selected)
  1088. ;
  1089. event.preventDefault();
  1090. }
  1091. }
  1092. // right arrow (show sub-menu)
  1093. if(pressedKey == keys.rightArrow) {
  1094. if(hasSubMenu) {
  1095. module.verbose('Right key pressed, opening sub-menu');
  1096. module.animate.show(false, $subMenu);
  1097. $selectedItem
  1098. .removeClass(className.selected)
  1099. ;
  1100. $subMenu
  1101. .find(selector.item).eq(0)
  1102. .addClass(className.selected)
  1103. ;
  1104. event.preventDefault();
  1105. }
  1106. }
  1107. // up arrow (traverse menu up)
  1108. if(pressedKey == keys.upArrow) {
  1109. $nextItem = (hasSelectedItem && inVisibleMenu)
  1110. ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
  1111. : $item.eq(0)
  1112. ;
  1113. if($visibleItems.index( $nextItem ) < 0) {
  1114. module.verbose('Up key pressed but reached top of current menu');
  1115. event.preventDefault();
  1116. return;
  1117. }
  1118. else {
  1119. module.verbose('Up key pressed, changing active item');
  1120. $selectedItem
  1121. .removeClass(className.selected)
  1122. ;
  1123. $nextItem
  1124. .addClass(className.selected)
  1125. ;
  1126. module.set.scrollPosition($nextItem);
  1127. }
  1128. event.preventDefault();
  1129. }
  1130. // down arrow (traverse menu down)
  1131. if(pressedKey == keys.downArrow) {
  1132. $nextItem = (hasSelectedItem && inVisibleMenu)
  1133. ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
  1134. : $item.eq(0)
  1135. ;
  1136. if($nextItem.length === 0) {
  1137. module.verbose('Down key pressed but reached bottom of current menu');
  1138. event.preventDefault();
  1139. return;
  1140. }
  1141. else {
  1142. module.verbose('Down key pressed, changing active item');
  1143. $item
  1144. .removeClass(className.selected)
  1145. ;
  1146. $nextItem
  1147. .addClass(className.selected)
  1148. ;
  1149. module.set.scrollPosition($nextItem);
  1150. }
  1151. event.preventDefault();
  1152. }
  1153. // page down (show next page)
  1154. if(pressedKey == keys.pageUp) {
  1155. module.scrollPage('up');
  1156. event.preventDefault();
  1157. }
  1158. if(pressedKey == keys.pageDown) {
  1159. module.scrollPage('down');
  1160. event.preventDefault();
  1161. }
  1162. // escape (close menu)
  1163. if(pressedKey == keys.escape) {
  1164. module.verbose('Escape key pressed, closing dropdown');
  1165. module.hide();
  1166. }
  1167. }
  1168. else {
  1169. // delimiter key
  1170. if(pressedKey == keys.delimiter) {
  1171. event.preventDefault();
  1172. }
  1173. // down arrow (open menu)
  1174. if(pressedKey == keys.downArrow) {
  1175. module.verbose('Down key pressed, showing dropdown');
  1176. module.show();
  1177. event.preventDefault();
  1178. }
  1179. }
  1180. }
  1181. else {
  1182. if( module.is.selection() && !module.is.search() ) {
  1183. module.set.selectedLetter( String.fromCharCode(pressedKey) );
  1184. }
  1185. }
  1186. }
  1187. },
  1188. determine: {
  1189. selectAction: function(text, value) {
  1190. module.verbose('Determining action', settings.action);
  1191. if( $.isFunction( module.action[settings.action] ) ) {
  1192. module.verbose('Triggering preset action', settings.action, text, value);
  1193. module.action[ settings.action ].call(this, text, value);
  1194. }
  1195. else if( $.isFunction(settings.action) ) {
  1196. module.verbose('Triggering user action', settings.action, text, value);
  1197. settings.action.call(this, text, value);
  1198. }
  1199. else {
  1200. module.error(error.action, settings.action);
  1201. }
  1202. },
  1203. eventInModule: function(event, callback) {
  1204. callback = $.isFunction(callback)
  1205. ? callback
  1206. : function(){}
  1207. ;
  1208. if( $(event.target).closest($module).length === 0 ) {
  1209. module.verbose('Triggering event', callback);
  1210. callback();
  1211. return true;
  1212. }
  1213. else {
  1214. module.verbose('Event occurred in dropdown, canceling callback');
  1215. return false;
  1216. }
  1217. },
  1218. eventOnElement: function(event, callback) {
  1219. var
  1220. $target = $(event.target)
  1221. ;
  1222. callback = $.isFunction(callback)
  1223. ? callback
  1224. : function(){}
  1225. ;
  1226. if($target.closest($menu).length === 0) {
  1227. module.verbose('Triggering event', callback);
  1228. callback();
  1229. return true;
  1230. }
  1231. else {
  1232. module.verbose('Event occurred in dropdown menu, canceling callback');
  1233. return false;
  1234. }
  1235. }
  1236. },
  1237. action: {
  1238. nothing: function() {},
  1239. activate: function(text, value) {
  1240. value = (value !== undefined)
  1241. ? value
  1242. : text
  1243. ;
  1244. module.set.selected(value, $(this));
  1245. if(module.is.multiple() && !module.is.allFiltered()) {
  1246. return;
  1247. }
  1248. else {
  1249. module.hideAndClear();
  1250. }
  1251. },
  1252. select: function(text, value) {
  1253. // mimics action.activate but does not select text
  1254. module.action.activate.call(this);
  1255. },
  1256. combo: function(text, value) {
  1257. value = (value !== undefined)
  1258. ? value
  1259. : text
  1260. ;
  1261. module.set.selected(value, $(this));
  1262. module.hideAndClear();
  1263. },
  1264. hide: function() {
  1265. module.hideAndClear();
  1266. }
  1267. },
  1268. get: {
  1269. id: function() {
  1270. return id;
  1271. },
  1272. text: function() {
  1273. return $text.text();
  1274. },
  1275. query: function() {
  1276. return $.trim($search.val());
  1277. },
  1278. searchWidth: function(characterCount) {
  1279. return (characterCount * settings.glyphWidth) + 'em';
  1280. },
  1281. selectionCount: function() {
  1282. var
  1283. values = module.get.values()
  1284. ;
  1285. return ( module.is.multiple() )
  1286. ? $.isArray(values)
  1287. ? values.length
  1288. : 0
  1289. : (module.get.value() !== '')
  1290. ? 1
  1291. : 0
  1292. ;
  1293. },
  1294. transition: function($subMenu) {
  1295. return (settings.transition == 'auto')
  1296. ? module.is.upward($subMenu)
  1297. ? 'slide up'
  1298. : 'slide down'
  1299. : settings.transition
  1300. ;
  1301. },
  1302. userValues: function() {
  1303. var
  1304. values = module.get.values()
  1305. ;
  1306. if(!values) {
  1307. return false;
  1308. }
  1309. values = $.isArray(values)
  1310. ? values
  1311. : [values]
  1312. ;
  1313. return $.grep(values, function(value) {
  1314. return (module.get.item(value) === false);
  1315. });
  1316. },
  1317. uniqueArray: function(array) {
  1318. return $.grep(array, function (value, index) {
  1319. return $.inArray(value, array) === index;
  1320. });
  1321. },
  1322. caretPosition: function() {
  1323. var
  1324. input = $search.get(0),
  1325. range,
  1326. rangeLength
  1327. ;
  1328. if('selectionStart' in input) {
  1329. return input.selectionStart;
  1330. }
  1331. else if (document.selection) {
  1332. input.focus();
  1333. range = document.selection.createRange();
  1334. rangeLength = range.text.length;
  1335. range.moveStart('character', -input.value.length);
  1336. return range.text.length - rangeLength;
  1337. }
  1338. },
  1339. shortcutKeys: function() {
  1340. return {
  1341. backspace : 8,
  1342. delimiter : 188, // comma
  1343. deleteKey : 46,
  1344. enter : 13,
  1345. escape : 27,
  1346. pageUp : 33,
  1347. pageDown : 34,
  1348. leftArrow : 37,
  1349. upArrow : 38,
  1350. rightArrow : 39,
  1351. downArrow : 40
  1352. };
  1353. },
  1354. value: function() {
  1355. return ($input.length > 0)
  1356. ? $input.val()
  1357. : $module.data(metadata.value)
  1358. ;
  1359. },
  1360. values: function() {
  1361. var
  1362. value = module.get.value()
  1363. ;
  1364. if(value === '') {
  1365. return '';
  1366. }
  1367. return (!$input.is('select') && module.is.multiple())
  1368. ? typeof value == 'string'
  1369. ? value.split(settings.delimiter)
  1370. : ''
  1371. : value
  1372. ;
  1373. },
  1374. remoteValues: function() {
  1375. var
  1376. values = module.get.values(),
  1377. remoteValues = false
  1378. ;
  1379. if(values) {
  1380. if(typeof values == 'string') {
  1381. values = [values];
  1382. }
  1383. remoteValues = {};
  1384. $.each(values, function(index, value) {
  1385. var
  1386. name = module.read.remoteData(value)
  1387. ;
  1388. module.verbose('Restoring value from session data', name, value);
  1389. remoteValues[value] = (name)
  1390. ? name
  1391. : value
  1392. ;
  1393. });
  1394. }
  1395. return remoteValues;
  1396. },
  1397. choiceText: function($choice, preserveHTML) {
  1398. preserveHTML = (preserveHTML !== undefined)
  1399. ? preserveHTML
  1400. : settings.preserveHTML
  1401. ;
  1402. if($choice) {
  1403. if($choice.find(selector.menu).length > 0) {
  1404. module.verbose('Retreiving text of element with sub-menu');
  1405. $choice = $choice.clone();
  1406. $choice.find(selector.menu).remove();
  1407. $choice.find(selector.menuIcon).remove();
  1408. }
  1409. return ($choice.data(metadata.text) !== undefined)
  1410. ? $choice.data(metadata.text)
  1411. : (preserveHTML)
  1412. ? $choice.html().trim()
  1413. : $choice.text().trim()
  1414. ;
  1415. }
  1416. },
  1417. choiceValue: function($choice, choiceText) {
  1418. choiceText = choiceText || module.get.choiceText($choice);
  1419. if(!$choice) {
  1420. return false;
  1421. }
  1422. return ($choice.data(metadata.value) !== undefined)
  1423. ? $choice.data(metadata.value)
  1424. : (typeof choiceText === 'string')
  1425. ? choiceText.toLowerCase().trim()
  1426. : choiceText
  1427. ;
  1428. },
  1429. inputEvent: function() {
  1430. var
  1431. input = $search[0]
  1432. ;
  1433. if(input) {
  1434. return (input.oninput !== undefined)
  1435. ? 'input'
  1436. : (input.onpropertychange !== undefined)
  1437. ? 'propertychange'
  1438. : 'keyup'
  1439. ;
  1440. }
  1441. return false;
  1442. },
  1443. selectValues: function() {
  1444. var
  1445. select = {}
  1446. ;
  1447. select.values = [];
  1448. $module
  1449. .find('option')
  1450. .each(function() {
  1451. var
  1452. $option = $(this),
  1453. name = $option.html(),
  1454. disabled = $option.attr('disabled'),
  1455. value = ( $option.attr('value') !== undefined )
  1456. ? $option.attr('value')
  1457. : name
  1458. ;
  1459. if(settings.placeholder === 'auto' && value === '') {
  1460. select.placeholder = name;
  1461. }
  1462. else {
  1463. select.values.push({
  1464. name : name,
  1465. value : value,
  1466. disabled : disabled
  1467. });
  1468. }
  1469. })
  1470. ;
  1471. if(settings.placeholder && settings.placeholder !== 'auto') {
  1472. module.debug('Setting placeholder value to', settings.placeholder);
  1473. select.placeholder = settings.placeholder;
  1474. }
  1475. if(settings.sortSelect) {
  1476. select.values.sort(function(a, b) {
  1477. return (a.name > b.name)
  1478. ? 1
  1479. : -1
  1480. ;
  1481. });
  1482. module.debug('Retrieved and sorted values from select', select);
  1483. }
  1484. else {
  1485. module.debug('Retreived values from select', select);
  1486. }
  1487. return select;
  1488. },
  1489. activeItem: function() {
  1490. return $item.filter('.' + className.active);
  1491. },
  1492. selectedItem: function() {
  1493. var
  1494. $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected)
  1495. ;
  1496. return ($selectedItem.length > 0)
  1497. ? $selectedItem
  1498. : $item.eq(0)
  1499. ;
  1500. },
  1501. itemWithAdditions: function(value) {
  1502. var
  1503. $items = module.get.item(value),
  1504. $userItems = module.create.userChoice(value),
  1505. hasUserItems = ($userItems && $userItems.length > 0)
  1506. ;
  1507. if(hasUserItems) {
  1508. $items = ($items.length > 0)
  1509. ? $items.add($userItems)
  1510. : $userItems
  1511. ;
  1512. }
  1513. return $items;
  1514. },
  1515. item: function(value, strict) {
  1516. var
  1517. $selectedItem = false,
  1518. shouldSearch,
  1519. isMultiple
  1520. ;
  1521. value = (value !== undefined)
  1522. ? value
  1523. : ( module.get.values() !== undefined)
  1524. ? module.get.values()
  1525. : module.get.text()
  1526. ;
  1527. shouldSearch = (isMultiple)
  1528. ? (value.length > 0)
  1529. : (value !== undefined && value !== '' && value !== null)
  1530. ;
  1531. isMultiple = (module.is.multiple() && $.isArray(value));
  1532. strict = (value === '' || value === 0)
  1533. ? true
  1534. : strict || false
  1535. ;
  1536. if(shouldSearch) {
  1537. $item
  1538. .each(function() {
  1539. var
  1540. $choice = $(this),
  1541. optionText = module.get.choiceText($choice),
  1542. optionValue = module.get.choiceValue($choice, optionText)
  1543. ;
  1544. // safe early exit
  1545. if(optionValue === null || optionValue === undefined) {
  1546. return;
  1547. }
  1548. if(isMultiple) {
  1549. if($.inArray(optionValue.toString(), value) !== -1 || $.inArray(optionText, value) !== -1) {
  1550. $selectedItem = ($selectedItem)
  1551. ? $selectedItem.add($choice)
  1552. : $choice
  1553. ;
  1554. }
  1555. }
  1556. else if(strict) {
  1557. module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
  1558. if( optionValue === value || optionText === value) {
  1559. $selectedItem = $choice;
  1560. return true;
  1561. }
  1562. }
  1563. else {
  1564. if( optionValue.toString() == value.toString() || optionText == value) {
  1565. module.verbose('Found select item by value', optionValue, value);
  1566. $selectedItem = $choice;
  1567. return true;
  1568. }
  1569. }
  1570. })
  1571. ;
  1572. }
  1573. return $selectedItem;
  1574. }
  1575. },
  1576. check: {
  1577. maxSelections: function(selectionCount) {
  1578. if(settings.maxSelections) {
  1579. selectionCount = (selectionCount !== undefined)
  1580. ? selectionCount
  1581. : module.get.selectionCount()
  1582. ;
  1583. if(selectionCount >= settings.maxSelections) {
  1584. module.debug('Maximum selection count reached');
  1585. $item.addClass(className.filtered);
  1586. module.add.message(message.maxSelections);
  1587. return true;
  1588. }
  1589. else {
  1590. module.verbose('No longer at maximum selection count');
  1591. module.remove.message();
  1592. module.remove.filteredItem();
  1593. if(module.is.searchSelection()) {
  1594. module.filterItems();
  1595. }
  1596. return false;
  1597. }
  1598. }
  1599. return true;
  1600. }
  1601. },
  1602. restore: {
  1603. defaults: function() {
  1604. module.restore.defaultText();
  1605. module.restore.defaultValue();
  1606. },
  1607. defaultText: function() {
  1608. var
  1609. defaultText = $module.data(metadata.defaultText)
  1610. ;
  1611. module.debug('Restoring default text', defaultText);
  1612. module.set.text(defaultText);
  1613. $text.addClass(className.placeholder);
  1614. },
  1615. defaultValue: function() {
  1616. var
  1617. defaultValue = $module.data(metadata.defaultValue)
  1618. ;
  1619. if(defaultValue !== undefined) {
  1620. module.debug('Restoring default value', defaultValue);
  1621. if(defaultValue !== '') {
  1622. module.set.value(defaultValue);
  1623. module.set.selected();
  1624. }
  1625. else {
  1626. module.remove.activeItem();
  1627. module.remove.selectedItem();
  1628. }
  1629. }
  1630. },
  1631. labels: function() {
  1632. if(settings.allowAdditions) {
  1633. if(!settings.useLabels) {
  1634. module.error(error.labels);
  1635. settings.useLabels = true;
  1636. }
  1637. module.debug('Restoring selected values');
  1638. module.create.userLabels();
  1639. }
  1640. module.check.maxSelections();
  1641. },
  1642. selected: function() {
  1643. module.restore.values();
  1644. if(module.is.multiple()) {
  1645. module.debug('Restoring previously selected values and labels');
  1646. module.restore.labels();
  1647. }
  1648. else {
  1649. module.debug('Restoring previously selected values');
  1650. }
  1651. },
  1652. values: function() {
  1653. // prevents callbacks from occuring on initial load
  1654. module.set.initialLoad();
  1655. if(settings.apiSettings) {
  1656. if(settings.saveRemoteData) {
  1657. module.restore.remoteValues();
  1658. }
  1659. else {
  1660. module.clearValue();
  1661. }
  1662. }
  1663. else {
  1664. module.set.selected();
  1665. }
  1666. module.remove.initialLoad();
  1667. },
  1668. remoteValues: function() {
  1669. var
  1670. values = module.get.remoteValues()
  1671. ;
  1672. module.debug('Recreating selected from session data', values);
  1673. if(values) {
  1674. if( module.is.single() ) {
  1675. $.each(values, function(value, name) {
  1676. module.set.text(name);
  1677. });
  1678. }
  1679. else {
  1680. $.each(values, function(value, name) {
  1681. module.add.label(value, name);
  1682. });
  1683. }
  1684. }
  1685. }
  1686. },
  1687. read: {
  1688. remoteData: function(value) {
  1689. var
  1690. name
  1691. ;
  1692. if(window.Storage === undefined) {
  1693. module.error(error.noStorage);
  1694. return;
  1695. }
  1696. name = sessionStorage.getItem(value);
  1697. return (name !== undefined)
  1698. ? name
  1699. : false
  1700. ;
  1701. }
  1702. },
  1703. save: {
  1704. defaults: function() {
  1705. module.save.defaultText();
  1706. module.save.placeholderText();
  1707. module.save.defaultValue();
  1708. },
  1709. defaultValue: function() {
  1710. var
  1711. value = module.get.value()
  1712. ;
  1713. module.verbose('Saving default value as', value);
  1714. $module.data(metadata.defaultValue, value);
  1715. },
  1716. defaultText: function() {
  1717. var
  1718. text = module.get.text()
  1719. ;
  1720. module.verbose('Saving default text as', text);
  1721. $module.data(metadata.defaultText, text);
  1722. },
  1723. placeholderText: function() {
  1724. var
  1725. text
  1726. ;
  1727. if($text.hasClass(className.placeholder)) {
  1728. text = module.get.text();
  1729. module.verbose('Saving placeholder text as', text);
  1730. $module.data(metadata.placeholderText, text);
  1731. }
  1732. },
  1733. remoteData: function(name, value) {
  1734. if(window.Storage === undefined) {
  1735. module.error(error.noStorage);
  1736. return;
  1737. }
  1738. module.verbose('Saving remote data to session storage', value, name);
  1739. sessionStorage.setItem(value, name);
  1740. }
  1741. },
  1742. clear: function() {
  1743. if(module.is.multiple()) {
  1744. module.remove.labels();
  1745. }
  1746. else {
  1747. module.remove.activeItem();
  1748. module.remove.selectedItem();
  1749. }
  1750. module.set.placeholderText();
  1751. module.clearValue();
  1752. },
  1753. clearValue: function() {
  1754. module.set.value('');
  1755. },
  1756. scrollPage: function(direction, $selectedItem) {
  1757. var
  1758. $selectedItem = $selectedItem || module.get.selectedItem(),
  1759. $menu = $selectedItem.closest(selector.menu),
  1760. menuHeight = $menu.outerHeight(),
  1761. currentScroll = $menu.scrollTop(),
  1762. itemHeight = $item.eq(0).outerHeight(),
  1763. itemsPerPage = Math.floor(menuHeight / itemHeight),
  1764. maxScroll = $menu.prop('scrollHeight'),
  1765. newScroll = (direction == 'up')
  1766. ? currentScroll - (itemHeight * itemsPerPage)
  1767. : currentScroll + (itemHeight * itemsPerPage),
  1768. $selectableItem = $item.not(selector.unselectable),
  1769. isWithinRange,
  1770. $nextSelectedItem,
  1771. elementIndex
  1772. ;
  1773. elementIndex = (direction == 'up')
  1774. ? $selectableItem.index($selectedItem) - itemsPerPage
  1775. : $selectableItem.index($selectedItem) + itemsPerPage
  1776. ;
  1777. isWithinRange = (direction == 'up')
  1778. ? (elementIndex >= 0)
  1779. : (elementIndex < $selectableItem.length)
  1780. ;
  1781. $nextSelectedItem = (isWithinRange)
  1782. ? $selectableItem.eq(elementIndex)
  1783. : (direction == 'up')
  1784. ? $selectableItem.first()
  1785. : $selectableItem.last()
  1786. ;
  1787. if($nextSelectedItem.length > 0) {
  1788. module.debug('Scrolling page', direction, $nextSelectedItem);
  1789. $selectedItem
  1790. .removeClass(className.selected)
  1791. ;
  1792. $nextSelectedItem
  1793. .addClass(className.selected)
  1794. ;
  1795. $menu
  1796. .scrollTop(newScroll)
  1797. ;
  1798. }
  1799. },
  1800. set: {
  1801. filtered: function() {
  1802. var
  1803. isMultiple = module.is.multiple(),
  1804. isSearch = module.is.searchSelection(),
  1805. isSearchMultiple = (isMultiple && isSearch),
  1806. searchValue = (isSearch)
  1807. ? module.get.query()
  1808. : '',
  1809. hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0),
  1810. searchWidth = module.get.searchWidth(searchValue.length),
  1811. valueIsSet = searchValue !== ''
  1812. ;
  1813. if(isMultiple && hasSearchValue) {
  1814. module.verbose('Adjusting input width', searchWidth, settings.glyphWidth);
  1815. $search.css('width', searchWidth);
  1816. }
  1817. if(hasSearchValue || (isSearchMultiple && valueIsSet)) {
  1818. module.verbose('Hiding placeholder text');
  1819. $text.addClass(className.filtered);
  1820. }
  1821. else if(!isMultiple || (isSearchMultiple && !valueIsSet)) {
  1822. module.verbose('Showing placeholder text');
  1823. $text.removeClass(className.filtered);
  1824. }
  1825. },
  1826. loading: function() {
  1827. $module.addClass(className.loading);
  1828. },
  1829. placeholderText: function(text) {
  1830. text = text || $module.data(metadata.placeholderText);
  1831. if(text) {
  1832. module.debug('Restoring placeholder text');
  1833. module.set.text(text);
  1834. $text.addClass(className.placeholder);
  1835. }
  1836. },
  1837. tabbable: function() {
  1838. if( module.has.search() ) {
  1839. module.debug('Added tabindex to searchable dropdown');
  1840. $search
  1841. .val('')
  1842. .attr('tabindex', 0)
  1843. ;
  1844. $menu
  1845. .attr('tabindex', -1)
  1846. ;
  1847. }
  1848. else {
  1849. module.debug('Added tabindex to dropdown');
  1850. if(!$module.attr('tabindex') ) {
  1851. $module
  1852. .attr('tabindex', 0)
  1853. ;
  1854. $menu
  1855. .attr('tabindex', -1)
  1856. ;
  1857. }
  1858. }
  1859. },
  1860. initialLoad: function() {
  1861. module.verbose('Setting initial load');
  1862. initialLoad = true;
  1863. },
  1864. scrollPosition: function($item, forceScroll) {
  1865. var
  1866. edgeTolerance = 5,
  1867. $menu,
  1868. hasActive,
  1869. offset,
  1870. itemHeight,
  1871. itemOffset,
  1872. menuOffset,
  1873. menuScroll,
  1874. menuHeight,
  1875. abovePage,
  1876. belowPage
  1877. ;
  1878. $item = $item || module.get.selectedItem();
  1879. $menu = $item.closest(selector.menu);
  1880. hasActive = ($item && $item.length > 0);
  1881. forceScroll = (forceScroll !== undefined)
  1882. ? forceScroll
  1883. : false
  1884. ;
  1885. if($item && $menu.length > 0 && hasActive) {
  1886. itemOffset = $item.position().top;
  1887. $menu.addClass(className.loading);
  1888. menuScroll = $menu.scrollTop();
  1889. menuOffset = $menu.offset().top;
  1890. itemOffset = $item.offset().top;
  1891. offset = menuScroll - menuOffset + itemOffset;
  1892. if(!forceScroll) {
  1893. menuHeight = $menu.height();
  1894. belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
  1895. abovePage = ((offset - edgeTolerance) < menuScroll);
  1896. }
  1897. module.debug('Scrolling to active item', offset);
  1898. if(forceScroll || abovePage || belowPage) {
  1899. $menu.scrollTop(offset);
  1900. }
  1901. $menu.removeClass(className.loading);
  1902. }
  1903. },
  1904. text: function(text) {
  1905. if(settings.action !== 'select') {
  1906. if(settings.action == 'combo') {
  1907. module.debug('Changing combo button text', text, $combo);
  1908. if(settings.preserveHTML) {
  1909. $combo.html(text);
  1910. }
  1911. else {
  1912. $combo.text(text);
  1913. }
  1914. }
  1915. else {
  1916. module.debug('Changing text', text, $text);
  1917. $text
  1918. .removeClass(className.filtered)
  1919. .removeClass(className.placeholder)
  1920. ;
  1921. if(settings.preserveHTML) {
  1922. $text.html(text);
  1923. }
  1924. else {
  1925. $text.text(text);
  1926. }
  1927. }
  1928. }
  1929. },
  1930. selectedLetter: function(letter) {
  1931. var
  1932. $selectedItem = $item.filter('.' + className.selected),
  1933. $nextValue = false
  1934. ;
  1935. $item
  1936. .each(function(){
  1937. var
  1938. $choice = $(this),
  1939. text = module.get.choiceText($choice, false),
  1940. firstLetter = String(text).charAt(0).toLowerCase(),
  1941. matchedLetter = letter.toLowerCase()
  1942. ;
  1943. if(firstLetter == matchedLetter) {
  1944. $nextValue = $choice;
  1945. return false;
  1946. }
  1947. })
  1948. ;
  1949. if($nextValue) {
  1950. module.verbose('Scrolling to next value with letter', letter);
  1951. module.set.scrollPosition($nextValue);
  1952. $selectedItem.removeClass(className.selected);
  1953. $nextValue.addClass(className.selected);
  1954. }
  1955. },
  1956. direction: function($menu) {
  1957. if(settings.direction == 'auto') {
  1958. if(module.is.onScreen($menu)) {
  1959. module.remove.upward($menu);
  1960. }
  1961. else {
  1962. module.set.upward($menu);
  1963. }
  1964. }
  1965. else if(settings.direction == 'upward') {
  1966. module.set.upward($menu);
  1967. }
  1968. },
  1969. upward: function($menu) {
  1970. var $element = $menu || $module;
  1971. $element.addClass(className.upward);
  1972. },
  1973. value: function(value, text, $selected) {
  1974. var
  1975. hasInput = ($input.length > 0),
  1976. isAddition = !module.has.value(value),
  1977. currentValue = module.get.values(),
  1978. stringValue = (typeof value == 'number')
  1979. ? value.toString()
  1980. : value,
  1981. newValue
  1982. ;
  1983. if(hasInput) {
  1984. if(stringValue == currentValue) {
  1985. module.verbose('Skipping value update already same value', value, currentValue);
  1986. if(!module.is.initialLoad()) {
  1987. return;
  1988. }
  1989. }
  1990. module.debug('Updating input value', value, currentValue);
  1991. $input
  1992. .val(value)
  1993. .trigger('change')
  1994. ;
  1995. }
  1996. else {
  1997. module.verbose('Storing value in metadata', value, $input);
  1998. if(value !== currentValue) {
  1999. $module.data(metadata.value, value);
  2000. }
  2001. }
  2002. if(settings.fireOnInit === false && module.is.initialLoad()) {
  2003. module.verbose('No callback on initial load', settings.onChange);
  2004. }
  2005. else {
  2006. settings.onChange.call(element, value, text, $selected);
  2007. }
  2008. },
  2009. active: function() {
  2010. $module
  2011. .addClass(className.active)
  2012. ;
  2013. },
  2014. multiple: function() {
  2015. $module.addClass(className.multiple);
  2016. },
  2017. visible: function() {
  2018. $module.addClass(className.visible);
  2019. },
  2020. selected: function(value, $selectedItem) {
  2021. var
  2022. isMultiple = module.is.multiple(),
  2023. $userSelectedItem
  2024. ;
  2025. $selectedItem = (settings.allowAdditions)
  2026. ? $selectedItem || module.get.itemWithAdditions(value)
  2027. : $selectedItem || module.get.item(value)
  2028. ;
  2029. if(!$selectedItem) {
  2030. return;
  2031. }
  2032. module.debug('Setting selected menu item to', $selectedItem);
  2033. if(module.is.single()) {
  2034. module.remove.activeItem();
  2035. module.remove.selectedItem();
  2036. }
  2037. else if(settings.useLabels) {
  2038. module.remove.selectedItem();
  2039. }
  2040. // select each item
  2041. $selectedItem
  2042. .each(function() {
  2043. var
  2044. $selected = $(this),
  2045. selectedText = module.get.choiceText($selected),
  2046. selectedValue = module.get.choiceValue($selected, selectedText),
  2047. isFiltered = $selected.hasClass(className.filtered),
  2048. isActive = $selected.hasClass(className.active),
  2049. isUserValue = $selected.hasClass(className.addition),
  2050. shouldAnimate = (isMultiple && $selectedItem.length == 1)
  2051. ;
  2052. if(isMultiple) {
  2053. if(!isActive || isUserValue) {
  2054. if(settings.apiSettings && settings.saveRemoteData) {
  2055. module.save.remoteData(selectedText, selectedValue);
  2056. }
  2057. if(settings.useLabels) {
  2058. module.add.value(selectedValue, selectedText, $selected);
  2059. module.add.label(selectedValue, selectedText, shouldAnimate);
  2060. $selected.addClass(className.active);
  2061. module.filterActive();
  2062. module.select.nextAvailable($selectedItem);
  2063. }
  2064. else {
  2065. module.add.value(selectedValue, selectedText, $selected);
  2066. module.set.text(module.add.variables(message.count));
  2067. $selected.addClass(className.active);
  2068. }
  2069. }
  2070. else if(!isFiltered) {
  2071. module.debug('Selected active value, removing label');
  2072. module.remove.selected(selectedValue);
  2073. }
  2074. }
  2075. else {
  2076. if(settings.apiSettings && settings.saveRemoteData) {
  2077. module.save.remoteData(selectedText, selectedValue);
  2078. }
  2079. module.set.value(selectedValue, selectedText, $selected);
  2080. module.set.text(selectedText);
  2081. $selected
  2082. .addClass(className.active)
  2083. .addClass(className.selected)
  2084. ;
  2085. }
  2086. })
  2087. ;
  2088. }
  2089. },
  2090. add: {
  2091. label: function(value, text, shouldAnimate) {
  2092. var
  2093. $next = module.is.searchSelection()
  2094. ? $search
  2095. : $text,
  2096. $label
  2097. ;
  2098. $label = $('<a />')
  2099. .addClass(className.label)
  2100. .attr('data-value', value)
  2101. .html(templates.label(value, text))
  2102. ;
  2103. $label = settings.onLabelCreate.call($label, value, text);
  2104. if(module.has.label(value)) {
  2105. module.debug('Label already exists, skipping', value);
  2106. return;
  2107. }
  2108. if(settings.label.variation) {
  2109. $label.addClass(settings.label.variation);
  2110. }
  2111. if(shouldAnimate === true) {
  2112. module.debug('Animating in label', $label);
  2113. $label
  2114. .addClass(className.hidden)
  2115. .insertBefore($next)
  2116. .transition(settings.label.transition, settings.label.duration)
  2117. ;
  2118. }
  2119. else {
  2120. module.debug('Adding selection label', $label);
  2121. $label
  2122. .insertBefore($next)
  2123. ;
  2124. }
  2125. },
  2126. message: function(message) {
  2127. var
  2128. $message = $menu.children(selector.message),
  2129. html = settings.templates.message(module.add.variables(message))
  2130. ;
  2131. if($message.length > 0) {
  2132. $message
  2133. .html(html)
  2134. ;
  2135. }
  2136. else {
  2137. $message = $('<div/>')
  2138. .html(html)
  2139. .addClass(className.message)
  2140. .appendTo($menu)
  2141. ;
  2142. }
  2143. },
  2144. optionValue: function(value) {
  2145. var
  2146. $option = $input.find('option[value="' + value + '"]'),
  2147. hasOption = ($option.length > 0)
  2148. ;
  2149. if(hasOption) {
  2150. return;
  2151. }
  2152. // temporarily disconnect observer
  2153. if(selectObserver) {
  2154. selectObserver.disconnect();
  2155. module.verbose('Temporarily disconnecting mutation observer', value);
  2156. }
  2157. $('<option/>')
  2158. .prop('value', value)
  2159. .html(value)
  2160. .appendTo($input)
  2161. ;
  2162. module.verbose('Adding user addition as an <option>', value);
  2163. if(selectObserver) {
  2164. selectObserver.observe($input[0], {
  2165. childList : true,
  2166. subtree : true
  2167. });
  2168. }
  2169. },
  2170. userSuggestion: function(value) {
  2171. var
  2172. $addition = $menu.children(selector.addition),
  2173. alreadyHasValue = module.get.item(value),
  2174. hasUserSuggestion = $addition.length > 0,
  2175. html
  2176. ;
  2177. if(module.has.maxSelections()) {
  2178. return;
  2179. }
  2180. if(value === '' || alreadyHasValue) {
  2181. $addition.remove();
  2182. return;
  2183. }
  2184. $item
  2185. .removeClass(className.selected)
  2186. ;
  2187. if(hasUserSuggestion) {
  2188. html = settings.templates.addition(value);
  2189. $addition
  2190. .html(html)
  2191. .data(metadata.value, value)
  2192. .removeClass(className.filtered)
  2193. .addClass(className.selected)
  2194. ;
  2195. module.verbose('Replacing user suggestion with new value', $addition);
  2196. }
  2197. else {
  2198. $addition = module.create.userChoice(value);
  2199. $addition
  2200. .prependTo($menu)
  2201. .addClass(className.selected)
  2202. ;
  2203. module.verbose('Adding item choice to menu corresponding with user choice addition', $addition);
  2204. }
  2205. },
  2206. variables: function(message) {
  2207. var
  2208. hasCount = (message.search('{count}') !== -1),
  2209. hasMaxCount = (message.search('{maxCount}') !== -1),
  2210. hasTerm = (message.search('{term}') !== -1),
  2211. values,
  2212. count,
  2213. query
  2214. ;
  2215. module.verbose('Adding templated variables to message', message);
  2216. if(hasCount) {
  2217. count = module.get.selectionCount();
  2218. message = message.replace('{count}', count);
  2219. }
  2220. if(hasMaxCount) {
  2221. count = module.get.selectionCount();
  2222. message = message.replace('{maxCount}', settings.maxSelections);
  2223. }
  2224. if(hasTerm) {
  2225. query = module.get.query();
  2226. message = message.replace('{term}', query);
  2227. }
  2228. return message;
  2229. },
  2230. value: function(addedValue, addedText, $selectedItem) {
  2231. var
  2232. currentValue = module.get.values(),
  2233. newValue
  2234. ;
  2235. if(addedValue === '') {
  2236. module.debug('Cannot select blank values from multiselect');
  2237. return;
  2238. }
  2239. // extend currently array
  2240. if($.isArray(currentValue)) {
  2241. newValue = currentValue.concat([addedValue]);
  2242. newValue = module.get.uniqueArray(newValue);
  2243. }
  2244. else {
  2245. newValue = [addedValue];
  2246. }
  2247. // add values
  2248. if( $input.is('select')) {
  2249. if(settings.allowAdditions) {
  2250. module.add.optionValue(addedValue);
  2251. module.debug('Adding value to select', addedValue, newValue, $input);
  2252. }
  2253. }
  2254. else {
  2255. newValue = newValue.join(settings.delimiter);
  2256. module.debug('Setting hidden input to delimited value', newValue, $input);
  2257. }
  2258. if(settings.fireOnInit === false && module.is.initialLoad()) {
  2259. module.verbose('No callback on initial load', settings.onAdd);
  2260. }
  2261. else {
  2262. settings.onAdd.call(element, addedValue, addedText, $selectedItem);
  2263. }
  2264. module.set.value(newValue, addedValue, addedText, $selectedItem);
  2265. module.check.maxSelections();
  2266. }
  2267. },
  2268. remove: {
  2269. active: function() {
  2270. $module.removeClass(className.active);
  2271. },
  2272. activeLabel: function() {
  2273. $module.find(selector.label).removeClass(className.active);
  2274. },
  2275. loading: function() {
  2276. $module.removeClass(className.loading);
  2277. },
  2278. initialLoad: function() {
  2279. initialLoad = false;
  2280. },
  2281. upward: function($menu) {
  2282. var $element = $menu || $module;
  2283. $element.removeClass(className.upward);
  2284. },
  2285. visible: function() {
  2286. $module.removeClass(className.visible);
  2287. },
  2288. activeItem: function() {
  2289. $item.removeClass(className.active);
  2290. },
  2291. filteredItem: function() {
  2292. if( module.has.maxSelections() ) {
  2293. return;
  2294. }
  2295. if(settings.useLabels) {
  2296. $item.not('.' + className.active).removeClass(className.filtered);
  2297. }
  2298. else {
  2299. $item.removeClass(className.filtered);
  2300. }
  2301. },
  2302. message: function() {
  2303. $menu.children(selector.message).remove();
  2304. },
  2305. searchTerm: function() {
  2306. module.verbose('Cleared search term');
  2307. $search.val('');
  2308. module.set.filtered();
  2309. },
  2310. selected: function(value, $selectedItem) {
  2311. $selectedItem = (settings.allowAdditions)
  2312. ? $selectedItem || module.get.itemWithAdditions(value)
  2313. : $selectedItem || module.get.item(value)
  2314. ;
  2315. if(!$selectedItem) {
  2316. return false;
  2317. }
  2318. $selectedItem
  2319. .each(function() {
  2320. var
  2321. $selected = $(this),
  2322. selectedText = module.get.choiceText($selected),
  2323. selectedValue = module.get.choiceValue($selected, selectedText)
  2324. ;
  2325. if(module.is.multiple()) {
  2326. if(settings.useLabels) {
  2327. module.remove.value(selectedValue, selectedText, $selected);
  2328. module.remove.label(selectedValue);
  2329. }
  2330. else {
  2331. module.remove.value(selectedValue, selectedText, $selected);
  2332. module.set.text(module.add.variables(message.count));
  2333. }
  2334. }
  2335. else {
  2336. module.remove.value(selectedValue, selectedText, $selected);
  2337. }
  2338. $selected
  2339. .removeClass(className.filtered)
  2340. .removeClass(className.active)
  2341. ;
  2342. if(settings.useLabels) {
  2343. $selected.removeClass(className.selected);
  2344. }
  2345. })
  2346. ;
  2347. },
  2348. selectedItem: function() {
  2349. $item.removeClass(className.selected);
  2350. },
  2351. value: function(removedValue, removedText, $removedItem) {
  2352. var
  2353. values = $input.val(),
  2354. newValue
  2355. ;
  2356. if( $input.is('select') ) {
  2357. module.verbose('Input is <select> removing selected option', removedValue);
  2358. newValue = module.remove.arrayValue(removedValue, values);
  2359. }
  2360. else {
  2361. module.verbose('Removing from delimited values', removedValue);
  2362. values = values.split(settings.delimiter);
  2363. newValue = module.remove.arrayValue(removedValue, values);
  2364. newValue = newValue.join(settings.delimiter);
  2365. }
  2366. if(settings.fireOnInit === false && module.is.initialLoad()) {
  2367. module.verbose('No callback on initial load', settings.onRemove);
  2368. }
  2369. else {
  2370. settings.onRemove.call(element, removedValue, removedText, $removedItem);
  2371. }
  2372. module.set.value(newValue, removedText, $removedItem);
  2373. module.check.maxSelections();
  2374. },
  2375. arrayValue: function(removedValue, values) {
  2376. values = $.grep(values, function(value){
  2377. return (removedValue != value);
  2378. });
  2379. module.verbose('Removed value from delimited string', removedValue, values);
  2380. return values;
  2381. },
  2382. label: function(value) {
  2383. var
  2384. $labels = $module.find(selector.label),
  2385. $removedLabel = $labels.filter('[data-value="' + value +'"]'),
  2386. labelCount = $labels.length,
  2387. isLastLabel = ($labels.index($removedLabel) + 1 == labelCount),
  2388. shouldAnimate = ( (!module.is.searchSelection() || !module.is.focusedOnSearch()) && isLastLabel)
  2389. ;
  2390. if(shouldAnimate) {
  2391. module.verbose('Animating and removing label', $removedLabel);
  2392. $removedLabel
  2393. .transition(settings.label.transition, settings.label.duration, function() {
  2394. $removedLabel.remove();
  2395. })
  2396. ;
  2397. }
  2398. else {
  2399. module.verbose('Removing label', $removedLabel);
  2400. $removedLabel.remove();
  2401. }
  2402. },
  2403. activeLabels: function($activeLabels) {
  2404. $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active);
  2405. module.verbose('Removing active label selections', $activeLabels);
  2406. module.remove.labels($activeLabels);
  2407. },
  2408. labels: function($labels) {
  2409. $labels = $labels || $module.find(selector.label);
  2410. module.verbose('Removing labels', $labels);
  2411. $labels
  2412. .each(function(){
  2413. var
  2414. value = $(this).data('value'),
  2415. isUserValue = module.is.userValue(value)
  2416. ;
  2417. if(isUserValue) {
  2418. module.remove.value(value);
  2419. module.remove.label(value);
  2420. }
  2421. else {
  2422. // selected will also remove label
  2423. module.remove.selected(value);
  2424. }
  2425. })
  2426. ;
  2427. },
  2428. tabbable: function() {
  2429. if( module.has.search() ) {
  2430. module.debug('Searchable dropdown initialized');
  2431. $search
  2432. .attr('tabindex', '-1')
  2433. ;
  2434. $menu
  2435. .attr('tabindex', '-1')
  2436. ;
  2437. }
  2438. else {
  2439. module.debug('Simple selection dropdown initialized');
  2440. $module
  2441. .attr('tabindex', '-1')
  2442. ;
  2443. $menu
  2444. .attr('tabindex', '-1')
  2445. ;
  2446. }
  2447. }
  2448. },
  2449. has: {
  2450. search: function() {
  2451. return ($search.length > 0);
  2452. },
  2453. input: function() {
  2454. return ($input.length > 0);
  2455. },
  2456. menu: function() {
  2457. return ($menu.length > 0);
  2458. },
  2459. message: function() {
  2460. return ($menu.children(selector.message).length !== 0);
  2461. },
  2462. label: function(value) {
  2463. var
  2464. $labels = $module.find(selector.label)
  2465. ;
  2466. return ($labels.filter('[data-value="' + value +'"]').length > 0);
  2467. },
  2468. maxSelections: function() {
  2469. return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections);
  2470. },
  2471. allResultsFiltered: function() {
  2472. return ($item.filter(selector.unselectable).length === $item.length);
  2473. },
  2474. value: function(value) {
  2475. var
  2476. values = module.get.values(),
  2477. hasValue = $.isArray(values)
  2478. ? values && ($.inArray(value, values) !== -1)
  2479. : (values == value)
  2480. ;
  2481. return (hasValue)
  2482. ? true
  2483. : false
  2484. ;
  2485. }
  2486. },
  2487. is: {
  2488. active: function() {
  2489. return $module.hasClass(className.active);
  2490. },
  2491. alreadySetup: function() {
  2492. return ($module.is('select') && $module.parent(selector.dropdown).length > 0 && $module.prev().length === 0);
  2493. },
  2494. animating: function($subMenu) {
  2495. return ($subMenu)
  2496. ? $subMenu.transition && $subMenu.transition('is animating')
  2497. : $menu.transition && $menu.transition('is animating')
  2498. ;
  2499. },
  2500. focused: function() {
  2501. return (document.activeElement === $module[0]);
  2502. },
  2503. focusedOnSearch: function() {
  2504. return (document.activeElement === $search[0]);
  2505. },
  2506. allFiltered: function() {
  2507. return( (module.is.multiple() || module.has.search()) && !module.has.message() && module.has.allResultsFiltered() );
  2508. },
  2509. hidden: function($subMenu) {
  2510. return !module.is.visible($subMenu);
  2511. },
  2512. initialLoad: function() {
  2513. return initialLoad;
  2514. },
  2515. onScreen: function($subMenu) {
  2516. var
  2517. $currentMenu = $subMenu || $menu,
  2518. canOpenDownward = true,
  2519. onScreen = {},
  2520. calculations
  2521. ;
  2522. $currentMenu.addClass(className.loading);
  2523. calculations = {
  2524. context: {
  2525. scrollTop : $context.scrollTop(),
  2526. height : $context.outerHeight()
  2527. },
  2528. menu : {
  2529. offset: $currentMenu.offset(),
  2530. height: $currentMenu.outerHeight()
  2531. }
  2532. };
  2533. onScreen = {
  2534. above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.menu.height,
  2535. below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top + calculations.menu.height
  2536. };
  2537. if(onScreen.below) {
  2538. module.verbose('Dropdown can fit in context downward', onScreen);
  2539. canOpenDownward = true;
  2540. }
  2541. else if(!onScreen.below && !onScreen.above) {
  2542. module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen);
  2543. canOpenDownward = true;
  2544. }
  2545. else {
  2546. module.verbose('Dropdown cannot fit below, opening upward', onScreen);
  2547. canOpenDownward = false;
  2548. }
  2549. $currentMenu.removeClass(className.loading);
  2550. return canOpenDownward;
  2551. },
  2552. inObject: function(needle, object) {
  2553. var
  2554. found = false
  2555. ;
  2556. $.each(object, function(index, property) {
  2557. if(property == needle) {
  2558. found = true;
  2559. return true;
  2560. }
  2561. });
  2562. return found;
  2563. },
  2564. multiple: function() {
  2565. return $module.hasClass(className.multiple);
  2566. },
  2567. single: function() {
  2568. return !module.is.multiple();
  2569. },
  2570. selectMutation: function(mutations) {
  2571. var
  2572. selectChanged = false
  2573. ;
  2574. $.each(mutations, function(index, mutation) {
  2575. if(mutation.target && $(mutation.target).is('select')) {
  2576. selectChanged = true;
  2577. return true;
  2578. }
  2579. });
  2580. return selectChanged;
  2581. },
  2582. search: function() {
  2583. return $module.hasClass(className.search);
  2584. },
  2585. searchSelection: function() {
  2586. return ( module.has.search() && $search.closest(selector.menu).length === 0 );
  2587. },
  2588. selection: function() {
  2589. return $module.hasClass(className.selection);
  2590. },
  2591. userValue: function(value) {
  2592. return ($.inArray(value, module.get.userValues()) !== -1);
  2593. },
  2594. upward: function($menu) {
  2595. var $element = $menu || $module;
  2596. return $element.hasClass(className.upward);
  2597. },
  2598. visible: function($subMenu) {
  2599. return ($subMenu)
  2600. ? $subMenu.hasClass(className.visible)
  2601. : $menu.hasClass(className.visible)
  2602. ;
  2603. }
  2604. },
  2605. can: {
  2606. click: function() {
  2607. return (hasTouch || settings.on == 'click');
  2608. },
  2609. show: function() {
  2610. return !$module.hasClass(className.disabled) && $item.length > 0;
  2611. },
  2612. useAPI: function() {
  2613. return $.fn.api !== undefined;
  2614. }
  2615. },
  2616. animate: {
  2617. show: function(callback, $subMenu) {
  2618. var
  2619. $currentMenu = $subMenu || $menu,
  2620. start = ($subMenu)
  2621. ? function() {}
  2622. : function() {
  2623. module.hideSubMenus();
  2624. module.hideOthers();
  2625. module.set.active();
  2626. },
  2627. transition
  2628. ;
  2629. callback = $.isFunction(callback)
  2630. ? callback
  2631. : function(){}
  2632. ;
  2633. module.verbose('Doing menu show animation', $currentMenu);
  2634. module.set.direction($subMenu);
  2635. transition = module.get.transition($subMenu);
  2636. if( module.is.selection() ) {
  2637. module.set.scrollPosition(module.get.selectedItem(), true);
  2638. }
  2639. if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) {
  2640. if(transition == 'none') {
  2641. start();
  2642. $currentMenu.transition('show');
  2643. callback.call(element);
  2644. }
  2645. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  2646. $currentMenu
  2647. .transition({
  2648. animation : transition + ' in',
  2649. debug : settings.debug,
  2650. verbose : settings.verbose,
  2651. duration : settings.duration,
  2652. queue : true,
  2653. onStart : start,
  2654. onComplete : function() {
  2655. callback.call(element);
  2656. }
  2657. })
  2658. ;
  2659. }
  2660. else {
  2661. module.error(error.noTransition, transition);
  2662. }
  2663. }
  2664. },
  2665. hide: function(callback, $subMenu) {
  2666. var
  2667. $currentMenu = $subMenu || $menu,
  2668. duration = ($subMenu)
  2669. ? (settings.duration * 0.9)
  2670. : settings.duration,
  2671. start = ($subMenu)
  2672. ? function() {}
  2673. : function() {
  2674. if( module.can.click() ) {
  2675. module.unbind.intent();
  2676. }
  2677. module.remove.active();
  2678. },
  2679. transition = module.get.transition($subMenu)
  2680. ;
  2681. callback = $.isFunction(callback)
  2682. ? callback
  2683. : function(){}
  2684. ;
  2685. if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) {
  2686. module.verbose('Doing menu hide animation', $currentMenu);
  2687. if(transition == 'none') {
  2688. start();
  2689. $currentMenu.transition('hide');
  2690. callback.call(element);
  2691. }
  2692. else if($.fn.transition !== undefined && $module.transition('is supported')) {
  2693. $currentMenu
  2694. .transition({
  2695. animation : transition + ' out',
  2696. duration : settings.duration,
  2697. debug : settings.debug,
  2698. verbose : settings.verbose,
  2699. queue : true,
  2700. onStart : start,
  2701. onComplete : function() {
  2702. if(settings.direction == 'auto') {
  2703. module.remove.upward($subMenu);
  2704. }
  2705. callback.call(element);
  2706. }
  2707. })
  2708. ;
  2709. }
  2710. else {
  2711. module.error(error.transition);
  2712. }
  2713. }
  2714. }
  2715. },
  2716. hideAndClear: function() {
  2717. if(module.has.search()) {
  2718. module.remove.searchTerm();
  2719. module.hide(function() {
  2720. module.remove.filteredItem();
  2721. });
  2722. }
  2723. else {
  2724. module.hide();
  2725. }
  2726. },
  2727. delay: {
  2728. show: function() {
  2729. module.verbose('Delaying show event to ensure user intent');
  2730. clearTimeout(module.timer);
  2731. module.timer = setTimeout(module.show, settings.delay.show);
  2732. },
  2733. hide: function() {
  2734. module.verbose('Delaying hide event to ensure user intent');
  2735. clearTimeout(module.timer);
  2736. module.timer = setTimeout(module.hide, settings.delay.hide);
  2737. }
  2738. },
  2739. escape: {
  2740. regExp: function(text) {
  2741. text = String(text);
  2742. return text.replace(regExp.escape, '\\$&');
  2743. }
  2744. },
  2745. setting: function(name, value) {
  2746. module.debug('Changing setting', name, value);
  2747. if( $.isPlainObject(name) ) {
  2748. $.extend(true, settings, name);
  2749. }
  2750. else if(value !== undefined) {
  2751. settings[name] = value;
  2752. }
  2753. else {
  2754. return settings[name];
  2755. }
  2756. },
  2757. internal: function(name, value) {
  2758. if( $.isPlainObject(name) ) {
  2759. $.extend(true, module, name);
  2760. }
  2761. else if(value !== undefined) {
  2762. module[name] = value;
  2763. }
  2764. else {
  2765. return module[name];
  2766. }
  2767. },
  2768. debug: function() {
  2769. if(settings.debug) {
  2770. if(settings.performance) {
  2771. module.performance.log(arguments);
  2772. }
  2773. else {
  2774. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  2775. module.debug.apply(console, arguments);
  2776. }
  2777. }
  2778. },
  2779. verbose: function() {
  2780. if(settings.verbose && settings.debug) {
  2781. if(settings.performance) {
  2782. module.performance.log(arguments);
  2783. }
  2784. else {
  2785. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  2786. module.verbose.apply(console, arguments);
  2787. }
  2788. }
  2789. },
  2790. error: function() {
  2791. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  2792. module.error.apply(console, arguments);
  2793. },
  2794. performance: {
  2795. log: function(message) {
  2796. var
  2797. currentTime,
  2798. executionTime,
  2799. previousTime
  2800. ;
  2801. if(settings.performance) {
  2802. currentTime = new Date().getTime();
  2803. previousTime = time || currentTime;
  2804. executionTime = currentTime - previousTime;
  2805. time = currentTime;
  2806. performance.push({
  2807. 'Name' : message[0],
  2808. 'Arguments' : [].slice.call(message, 1) || '',
  2809. 'Element' : element,
  2810. 'Execution Time' : executionTime
  2811. });
  2812. }
  2813. clearTimeout(module.performance.timer);
  2814. module.performance.timer = setTimeout(module.performance.display, 500);
  2815. },
  2816. display: function() {
  2817. var
  2818. title = settings.name + ':',
  2819. totalTime = 0
  2820. ;
  2821. time = false;
  2822. clearTimeout(module.performance.timer);
  2823. $.each(performance, function(index, data) {
  2824. totalTime += data['Execution Time'];
  2825. });
  2826. title += ' ' + totalTime + 'ms';
  2827. if(moduleSelector) {
  2828. title += ' \'' + moduleSelector + '\'';
  2829. }
  2830. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  2831. console.groupCollapsed(title);
  2832. if(console.table) {
  2833. console.table(performance);
  2834. }
  2835. else {
  2836. $.each(performance, function(index, data) {
  2837. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  2838. });
  2839. }
  2840. console.groupEnd();
  2841. }
  2842. performance = [];
  2843. }
  2844. },
  2845. invoke: function(query, passedArguments, context) {
  2846. var
  2847. object = instance,
  2848. maxDepth,
  2849. found,
  2850. response
  2851. ;
  2852. passedArguments = passedArguments || queryArguments;
  2853. context = element || context;
  2854. if(typeof query == 'string' && object !== undefined) {
  2855. query = query.split(/[\. ]/);
  2856. maxDepth = query.length - 1;
  2857. $.each(query, function(depth, value) {
  2858. var camelCaseValue = (depth != maxDepth)
  2859. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  2860. : query
  2861. ;
  2862. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  2863. object = object[camelCaseValue];
  2864. }
  2865. else if( object[camelCaseValue] !== undefined ) {
  2866. found = object[camelCaseValue];
  2867. return false;
  2868. }
  2869. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  2870. object = object[value];
  2871. }
  2872. else if( object[value] !== undefined ) {
  2873. found = object[value];
  2874. return false;
  2875. }
  2876. else {
  2877. module.error(error.method, query);
  2878. return false;
  2879. }
  2880. });
  2881. }
  2882. if ( $.isFunction( found ) ) {
  2883. response = found.apply(context, passedArguments);
  2884. }
  2885. else if(found !== undefined) {
  2886. response = found;
  2887. }
  2888. if($.isArray(returnedValue)) {
  2889. returnedValue.push(response);
  2890. }
  2891. else if(returnedValue !== undefined) {
  2892. returnedValue = [returnedValue, response];
  2893. }
  2894. else if(response !== undefined) {
  2895. returnedValue = response;
  2896. }
  2897. return found;
  2898. }
  2899. };
  2900. if(methodInvoked) {
  2901. if(instance === undefined) {
  2902. module.initialize();
  2903. }
  2904. module.invoke(query);
  2905. }
  2906. else {
  2907. if(instance !== undefined) {
  2908. instance.invoke('destroy');
  2909. }
  2910. module.initialize();
  2911. }
  2912. })
  2913. ;
  2914. return (returnedValue !== undefined)
  2915. ? returnedValue
  2916. : $allModules
  2917. ;
  2918. };
  2919. $.fn.dropdown.settings = {
  2920. debug : false,
  2921. verbose : false,
  2922. performance : true,
  2923. on : 'click', // what event should show menu action on item selection
  2924. action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){})
  2925. apiSettings : false,
  2926. saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh
  2927. throttle : 200, // How long to wait after last user input to search remotely
  2928. context : window, // Context to use when determining if on screen
  2929. direction : 'auto', // Whether dropdown should always open in one direction
  2930. keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing
  2931. match : 'both', // what to match against with search selection (both, text, or label)
  2932. fullTextSearch : false, // search anywhere in value
  2933. placeholder : 'auto', // whether to convert blank <select> values to placeholder text
  2934. preserveHTML : true, // preserve html when selecting value
  2935. sortSelect : false, // sort selection on init
  2936. forceSelection : true, // force a choice on blur with search selection
  2937. allowAdditions : false, // whether multiple select should allow user added values
  2938. maxSelections : false, // When set to a number limits the number of selections to this count
  2939. useLabels : true, // whether multiple select should filter currently active selections from choices
  2940. delimiter : ',', // when multiselect uses normal <input> the values will be delmited with this character
  2941. showOnFocus : true, // show menu on focus
  2942. allowTab : true, // add tabindex to element
  2943. allowCategorySelection : false, // allow elements with sub-menus to be selected
  2944. fireOnInit : false, // Whether callbacks should fire when initializing dropdown values
  2945. transition : 'auto', // auto transition will slide down or up based on direction
  2946. duration : 200, // duration of transition
  2947. glyphWidth : 1.0714, // widest glyph width in em (W is 1.0714 em) used to calculate multiselect input width
  2948. // label settings on multi-select
  2949. label: {
  2950. transition : 'scale',
  2951. duration : 200,
  2952. variation : false
  2953. },
  2954. // delay before event
  2955. delay : {
  2956. hide : 300,
  2957. show : 200,
  2958. search : 20,
  2959. touch : 50
  2960. },
  2961. /* Callbacks */
  2962. onChange : function(value, text, $selected){},
  2963. onAdd : function(value, text, $selected){},
  2964. onRemove : function(value, text, $selected){},
  2965. onLabelSelect : function($selectedLabels){},
  2966. onLabelCreate : function(value, text) { return $(this); },
  2967. onNoResults : function(searchTerm) { return true; },
  2968. onShow : function(){},
  2969. onHide : function(){},
  2970. /* Component */
  2971. name : 'Dropdown',
  2972. namespace : 'dropdown',
  2973. message: {
  2974. addResult : 'Add <b>{term}</b>',
  2975. count : '{count} selected',
  2976. maxSelections : 'Max {maxCount} selections',
  2977. noResults : 'No results found.',
  2978. serverError : 'There was an error contacting the server'
  2979. },
  2980. error : {
  2981. action : 'You called a dropdown action that was not defined',
  2982. alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown',
  2983. labels : 'Allowing user additions currently requires the use of labels.',
  2984. method : 'The method you called is not defined.',
  2985. noAPI : 'The API module is required to load resources remotely',
  2986. noStorage : 'Saving remote data requires session storage',
  2987. noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
  2988. },
  2989. regExp : {
  2990. escape : /[-[\]{}()*+?.,\\^$|#\s]/g,
  2991. },
  2992. metadata : {
  2993. defaultText : 'defaultText',
  2994. defaultValue : 'defaultValue',
  2995. placeholderText : 'placeholder',
  2996. text : 'text',
  2997. value : 'value'
  2998. },
  2999. selector : {
  3000. addition : '.addition',
  3001. dropdown : '.ui.dropdown',
  3002. icon : '> .dropdown.icon',
  3003. input : '> input[type="hidden"], > select',
  3004. item : '.item',
  3005. label : '> .label',
  3006. remove : '> .label > .delete.icon',
  3007. siblingLabel : '.label',
  3008. menu : '.menu',
  3009. message : '.message',
  3010. menuIcon : '.dropdown.icon',
  3011. search : 'input.search, .menu > .search > input',
  3012. text : '> .text:not(.icon)',
  3013. unselectable : '.disabled, .filtered'
  3014. },
  3015. className : {
  3016. active : 'active',
  3017. addition : 'addition',
  3018. animating : 'animating',
  3019. disabled : 'disabled',
  3020. dropdown : 'ui dropdown',
  3021. filtered : 'filtered',
  3022. hidden : 'hidden transition',
  3023. item : 'item',
  3024. label : 'ui label',
  3025. loading : 'loading',
  3026. menu : 'menu',
  3027. message : 'message',
  3028. multiple : 'multiple',
  3029. placeholder : 'default',
  3030. search : 'search',
  3031. selected : 'selected',
  3032. selection : 'selection',
  3033. upward : 'upward',
  3034. visible : 'visible'
  3035. }
  3036. };
  3037. /* Templates */
  3038. $.fn.dropdown.settings.templates = {
  3039. // generates dropdown from select values
  3040. dropdown: function(select) {
  3041. var
  3042. placeholder = select.placeholder || false,
  3043. values = select.values || {},
  3044. html = ''
  3045. ;
  3046. html += '<i class="dropdown icon"></i>';
  3047. if(select.placeholder) {
  3048. html += '<div class="default text">' + placeholder + '</div>';
  3049. }
  3050. else {
  3051. html += '<div class="text"></div>';
  3052. }
  3053. html += '<div class="menu">';
  3054. $.each(select.values, function(index, option) {
  3055. html += (option.disabled)
  3056. ? '<div class="disabled item" data-value="' + option.value + '">' + option.name + '</div>'
  3057. : '<div class="item" data-value="' + option.value + '">' + option.name + '</div>'
  3058. ;
  3059. });
  3060. html += '</div>';
  3061. return html;
  3062. },
  3063. // generates just menu from select
  3064. menu: function(response) {
  3065. var
  3066. values = response.values || {},
  3067. html = ''
  3068. ;
  3069. $.each(response.values, function(index, option) {
  3070. html += '<div class="item" data-value="' + option.value + '">' + option.name + '</div>';
  3071. });
  3072. return html;
  3073. },
  3074. // generates label for multiselect
  3075. label: function(value, text) {
  3076. return text + '<i class="delete icon"></i>';
  3077. },
  3078. // generates messages like "No results"
  3079. message: function(message) {
  3080. return message;
  3081. },
  3082. // generates user addition to selection menu
  3083. addition: function(choice) {
  3084. return choice;
  3085. }
  3086. };
  3087. })( jQuery, window , document );