jquery.easydropdown.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. /*
  2. * EASYDROPDOWN - A Drop-down Builder for Styleable Inputs and Menus
  3. * Version: 2.1.4
  4. * License: Creative Commons Attribution 3.0 Unported - CC BY 3.0
  5. * http://creativecommons.org/licenses/by/3.0/
  6. * This software may be used freely on commercial and non-commercial projects with attribution to the author/copyright holder.
  7. * Author: Patrick Kunka
  8. * Copyright 2013 Patrick Kunka, All Rights Reserved
  9. */
  10. (function($){
  11. function EasyDropDown(){
  12. this.isField = true,
  13. this.down = false,
  14. this.inFocus = false,
  15. this.disabled = false,
  16. this.cutOff = false,
  17. this.hasLabel = false,
  18. this.keyboardMode = false,
  19. this.nativeTouch = true,
  20. this.wrapperClass = 'dropdown',
  21. this.onChange = null;
  22. };
  23. EasyDropDown.prototype = {
  24. constructor: EasyDropDown,
  25. instances: {},
  26. init: function(domNode, settings){
  27. var self = this;
  28. $.extend(self, settings);
  29. self.$select = $(domNode);
  30. self.id = domNode.id;
  31. self.options = [];
  32. self.$options = self.$select.find('option');
  33. self.isTouch = 'ontouchend' in document;
  34. self.$select.removeClass(self.wrapperClass+' dropdown');
  35. if(self.$select.is(':disabled')){
  36. self.disabled = true;
  37. };
  38. if(self.$options.length){
  39. self.$options.each(function(i){
  40. var $option = $(this);
  41. if($option.is(':selected')){
  42. self.selected = {
  43. index: i,
  44. title: $option.text()
  45. }
  46. self.focusIndex = i;
  47. };
  48. if($option.hasClass('label') && i == 0){
  49. self.hasLabel = true;
  50. self.label = $option.text();
  51. $option.attr('value','');
  52. } else {
  53. self.options.push({
  54. domNode: $option[0],
  55. title: $option.text(),
  56. value: $option.val(),
  57. selected: $option.is(':selected')
  58. });
  59. };
  60. });
  61. if(!self.selected){
  62. self.selected = {
  63. index: 0,
  64. title: self.$options.eq(0).text()
  65. }
  66. self.focusIndex = 0;
  67. };
  68. self.render();
  69. };
  70. },
  71. render: function(){
  72. var self = this,
  73. touchClass = self.isTouch && self.nativeTouch ? ' touch' : '',
  74. disabledClass = self.disabled ? ' disabled' : '';
  75. self.$container = self.$select.wrap('<div class="'+self.wrapperClass+touchClass+disabledClass+'"><span class="old"/></div>').parent().parent();
  76. self.$active = $('<span class="selected">'+self.selected.title+'</span>').appendTo(self.$container);
  77. self.$carat = $('<span class="carat"/>').appendTo(self.$container);
  78. self.$scrollWrapper = $('<div><ul/></div>').appendTo(self.$container);
  79. self.$dropDown = self.$scrollWrapper.find('ul');
  80. self.$form = self.$container.closest('form');
  81. $.each(self.options, function(){
  82. var option = this,
  83. active = option.selected ? ' class="active"':'';
  84. self.$dropDown.append('<li'+active+'>'+option.title+'</li>');
  85. });
  86. self.$items = self.$dropDown.find('li');
  87. if(self.cutOff && self.$items.length > self.cutOff)self.$container.addClass('scrollable');
  88. self.getMaxHeight();
  89. if(self.isTouch && self.nativeTouch){
  90. self.bindTouchHandlers();
  91. } else {
  92. self.bindHandlers();
  93. };
  94. },
  95. getMaxHeight: function(){
  96. var self = this;
  97. self.maxHeight = 0;
  98. for(i = 0; i < self.$items.length; i++){
  99. var $item = self.$items.eq(i);
  100. self.maxHeight += $item.outerHeight();
  101. if(self.cutOff == i+1){
  102. break;
  103. };
  104. };
  105. },
  106. bindTouchHandlers: function(){
  107. var self = this;
  108. self.$container.on('click.easyDropDown',function(){
  109. self.$select.focus();
  110. });
  111. self.$select.on({
  112. change: function(){
  113. var $selected = $(this).find('option:selected'),
  114. title = $selected.text(),
  115. value = $selected.val();
  116. self.$active.text(title);
  117. if(typeof self.onChange === 'function'){
  118. self.onChange.call(self.$select[0],{
  119. title: title,
  120. value: value
  121. });
  122. };
  123. },
  124. focus: function(){
  125. self.$container.addClass('focus');
  126. },
  127. blur: function(){
  128. self.$container.removeClass('focus');
  129. }
  130. });
  131. },
  132. bindHandlers: function(){
  133. var self = this;
  134. self.query = '';
  135. self.$container.on({
  136. 'click.easyDropDown': function(){
  137. if(!self.down && !self.disabled){
  138. self.open();
  139. } else {
  140. self.close();
  141. };
  142. },
  143. 'mousemove.easyDropDown': function(){
  144. if(self.keyboardMode){
  145. self.keyboardMode = false;
  146. };
  147. }
  148. });
  149. $('body').on('click.easyDropDown.'+self.id,function(e){
  150. var $target = $(e.target),
  151. classNames = self.wrapperClass.split(' ').join('.');
  152. if(!$target.closest('.'+classNames).length && self.down){
  153. self.close();
  154. };
  155. });
  156. self.$items.on({
  157. 'click.easyDropDown': function(){
  158. var index = $(this).index();
  159. self.select(index);
  160. self.$select.focus();
  161. },
  162. 'mouseover.easyDropDown': function(){
  163. if(!self.keyboardMode){
  164. var $t = $(this);
  165. $t.addClass('focus').siblings().removeClass('focus');
  166. self.focusIndex = $t.index();
  167. };
  168. },
  169. 'mouseout.easyDropDown': function(){
  170. if(!self.keyboardMode){
  171. $(this).removeClass('focus');
  172. };
  173. }
  174. });
  175. self.$select.on({
  176. 'focus.easyDropDown': function(){
  177. self.$container.addClass('focus');
  178. self.inFocus = true;
  179. },
  180. 'blur.easyDropDown': function(){
  181. self.$container.removeClass('focus');
  182. self.inFocus = false;
  183. },
  184. 'keydown.easyDropDown': function(e){
  185. if(self.inFocus){
  186. self.keyboardMode = true;
  187. var key = e.keyCode;
  188. if(key == 38 || key == 40 || key == 32){
  189. e.preventDefault();
  190. if(key == 38){
  191. self.focusIndex--
  192. self.focusIndex = self.focusIndex < 0 ? self.$items.length - 1 : self.focusIndex;
  193. } else if(key == 40){
  194. self.focusIndex++
  195. self.focusIndex = self.focusIndex > self.$items.length - 1 ? 0 : self.focusIndex;
  196. };
  197. if(!self.down){
  198. self.open();
  199. };
  200. self.$items.removeClass('focus').eq(self.focusIndex).addClass('focus');
  201. if(self.cutOff){
  202. self.scrollToView();
  203. };
  204. self.query = '';
  205. };
  206. if(self.down){
  207. if(key == 9 || key == 27){
  208. self.close();
  209. } else if(key == 13){
  210. e.preventDefault();
  211. self.select(self.focusIndex);
  212. self.close();
  213. return false;
  214. } else if(key == 8){
  215. e.preventDefault();
  216. self.query = self.query.slice(0,-1);
  217. self.search();
  218. clearTimeout(self.resetQuery);
  219. return false;
  220. } else if(key != 38 && key != 40){
  221. var letter = String.fromCharCode(key);
  222. self.query += letter;
  223. self.search();
  224. clearTimeout(self.resetQuery);
  225. };
  226. };
  227. };
  228. },
  229. 'keyup.easyDropDown': function(){
  230. self.resetQuery = setTimeout(function(){
  231. self.query = '';
  232. },1200);
  233. }
  234. });
  235. self.$dropDown.on('scroll.easyDropDown',function(e){
  236. if(self.$dropDown[0].scrollTop >= self.$dropDown[0].scrollHeight - self.maxHeight){
  237. self.$container.addClass('bottom');
  238. } else {
  239. self.$container.removeClass('bottom');
  240. };
  241. });
  242. if(self.$form.length){
  243. self.$form.on('reset.easyDropDown', function(){
  244. var active = self.hasLabel ? self.label : self.options[0].title;
  245. self.$active.text(active);
  246. });
  247. };
  248. },
  249. unbindHandlers: function(){
  250. var self = this;
  251. self.$container
  252. .add(self.$select)
  253. .add(self.$items)
  254. .add(self.$form)
  255. .add(self.$dropDown)
  256. .off('.easyDropDown');
  257. $('body').off('.'+self.id);
  258. },
  259. open: function(){
  260. var self = this,
  261. scrollTop = window.scrollY || document.documentElement.scrollTop,
  262. scrollLeft = window.scrollX || document.documentElement.scrollLeft,
  263. scrollOffset = self.notInViewport(scrollTop);
  264. self.closeAll();
  265. self.getMaxHeight();
  266. self.$select.focus();
  267. window.scrollTo(scrollLeft, scrollTop+scrollOffset);
  268. self.$container.addClass('open');
  269. self.$scrollWrapper.css('height',self.maxHeight+'px');
  270. self.down = true;
  271. },
  272. close: function(){
  273. var self = this;
  274. self.$container.removeClass('open');
  275. self.$scrollWrapper.css('height','0px');
  276. self.focusIndex = self.selected.index;
  277. self.query = '';
  278. self.down = false;
  279. },
  280. closeAll: function(){
  281. var self = this,
  282. instances = Object.getPrototypeOf(self).instances;
  283. for(var key in instances){
  284. var instance = instances[key];
  285. instance.close();
  286. };
  287. },
  288. select: function(index){
  289. var self = this;
  290. if(typeof index === 'string'){
  291. index = self.$select.find('option[value='+index+']').index() - 1;
  292. };
  293. var option = self.options[index],
  294. selectIndex = self.hasLabel ? index + 1 : index;
  295. self.$items.removeClass('active').eq(index).addClass('active');
  296. self.$active.text(option.title);
  297. self.$select
  298. .find('option')
  299. .removeAttr('selected')
  300. .eq(selectIndex)
  301. .prop('selected',true)
  302. .parent()
  303. .trigger('change');
  304. self.selected = {
  305. index: index,
  306. title: option.title
  307. };
  308. self.focusIndex = i;
  309. if(typeof self.onChange === 'function'){
  310. self.onChange.call(self.$select[0],{
  311. title: option.title,
  312. value: option.value
  313. });
  314. };
  315. },
  316. search: function(){
  317. var self = this,
  318. lock = function(i){
  319. self.focusIndex = i;
  320. self.$items.removeClass('focus').eq(self.focusIndex).addClass('focus');
  321. self.scrollToView();
  322. },
  323. getTitle = function(i){
  324. return self.options[i].title.toUpperCase();
  325. };
  326. for(i = 0; i < self.options.length; i++){
  327. var title = getTitle(i);
  328. if(title.indexOf(self.query) == 0){
  329. lock(i);
  330. return;
  331. };
  332. };
  333. for(i = 0; i < self.options.length; i++){
  334. var title = getTitle(i);
  335. if(title.indexOf(self.query) > -1){
  336. lock(i);
  337. break;
  338. };
  339. };
  340. },
  341. scrollToView: function(){
  342. var self = this;
  343. if(self.focusIndex >= self.cutOff){
  344. var $focusItem = self.$items.eq(self.focusIndex),
  345. scroll = ($focusItem.outerHeight() * (self.focusIndex + 1)) - self.maxHeight;
  346. self.$dropDown.scrollTop(scroll);
  347. };
  348. },
  349. notInViewport: function(scrollTop){
  350. var self = this,
  351. range = {
  352. min: scrollTop,
  353. max: scrollTop + (window.innerHeight || document.documentElement.clientHeight)
  354. },
  355. menuBottom = self.$dropDown.offset().top + self.maxHeight;
  356. if(menuBottom >= range.min && menuBottom <= range.max){
  357. return 0;
  358. } else {
  359. return (menuBottom - range.max) + 5;
  360. };
  361. },
  362. destroy: function(){
  363. var self = this;
  364. self.unbindHandlers();
  365. self.$select.unwrap().siblings().remove();
  366. self.$select.unwrap();
  367. delete Object.getPrototypeOf(self).instances[self.$select[0].id];
  368. },
  369. disable: function(){
  370. var self = this;
  371. self.disabled = true;
  372. self.$container.addClass('disabled');
  373. self.$select.attr('disabled',true);
  374. if(!self.down)self.close();
  375. },
  376. enable: function(){
  377. var self = this;
  378. self.disabled = false;
  379. self.$container.removeClass('disabled');
  380. self.$select.attr('disabled',false);
  381. }
  382. };
  383. var instantiate = function(domNode, settings){
  384. domNode.id = !domNode.id ? 'EasyDropDown'+rand() : domNode.id;
  385. var instance = new EasyDropDown();
  386. if(!instance.instances[domNode.id]){
  387. instance.instances[domNode.id] = instance;
  388. instance.init(domNode, settings);
  389. };
  390. },
  391. rand = function(){
  392. return ('00000'+(Math.random()*16777216<<0).toString(16)).substr(-6).toUpperCase();
  393. };
  394. $.fn.easyDropDown = function(){
  395. var args = arguments,
  396. dataReturn = [],
  397. eachReturn;
  398. eachReturn = this.each(function(){
  399. if(args && typeof args[0] === 'string'){
  400. var data = EasyDropDown.prototype.instances[this.id][args[0]](args[1], args[2]);
  401. if(data)dataReturn.push(data);
  402. } else {
  403. instantiate(this, args[0]);
  404. };
  405. });
  406. if(dataReturn.length){
  407. return dataReturn.length > 1 ? dataReturn : dataReturn[0];
  408. } else {
  409. return eachReturn;
  410. };
  411. };
  412. $(function(){
  413. if(typeof Object.getPrototypeOf !== 'function'){
  414. if(typeof 'test'.__proto__ === 'object'){
  415. Object.getPrototypeOf = function(object){
  416. return object.__proto__;
  417. };
  418. } else {
  419. Object.getPrototypeOf = function(object){
  420. return object.constructor.prototype;
  421. };
  422. };
  423. };
  424. $('select.dropdown').each(function(){
  425. var json = $(this).attr('data-settings');
  426. settings = json ? $.parseJSON(json) : {};
  427. instantiate(this, settings);
  428. });
  429. });
  430. })(jQuery);