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.

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