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.

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