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.

3147 lines
108 KiB

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