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.

3713 lines
130 KiB

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