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.

3137 lines
108 KiB

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