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

3223 lines
110 KiB

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