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.

3416 lines
118 KiB

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