2
0

foundation.forms.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. (function ($, window, document, undefined) {
  2. 'use strict';
  3. Foundation.libs.forms = {
  4. name: 'forms',
  5. version: '4.2.1',
  6. cache: {},
  7. settings: {
  8. disable_class: 'no-custom',
  9. last_combo : null
  10. },
  11. init: function (scope, method, options) {
  12. if (typeof method === 'object') {
  13. $.extend(true, this.settings, method);
  14. }
  15. if (typeof method != 'string') {
  16. if (!this.settings.init) {
  17. this.events();
  18. }
  19. this.assemble();
  20. return this.settings.init;
  21. } else {
  22. return this[method].call(this, options);
  23. }
  24. },
  25. assemble: function () {
  26. $('form.custom input[type="radio"]', $(this.scope)).not('[data-customforms="disabled"]')
  27. .each(this.append_custom_markup);
  28. $('form.custom input[type="checkbox"]', $(this.scope)).not('[data-customforms="disabled"]')
  29. .each(this.append_custom_markup);
  30. $('form.custom select', $(this.scope))
  31. .not('[data-customforms="disabled"]')
  32. .not('[multiple=multiple]')
  33. .each(this.append_custom_select);
  34. },
  35. events: function () {
  36. var self = this;
  37. $(this.scope)
  38. .on('click.fndtn.forms', 'form.custom span.custom.checkbox', function (e) {
  39. e.preventDefault();
  40. e.stopPropagation();
  41. self.toggle_checkbox($(this));
  42. })
  43. .on('click.fndtn.forms', 'form.custom span.custom.radio', function (e) {
  44. e.preventDefault();
  45. e.stopPropagation();
  46. self.toggle_radio($(this));
  47. })
  48. .on('change.fndtn.forms', 'form.custom select:not([data-customforms="disabled"])', function (e, force_refresh) {
  49. self.refresh_custom_select($(this), force_refresh);
  50. })
  51. .on('click.fndtn.forms', 'form.custom label', function (e) {
  52. if ($(e.target).is('label')) {
  53. var $associatedElement = $('#' + self.escape($(this).attr('for')) + ':not([data-customforms="disabled"])'),
  54. $customCheckbox,
  55. $customRadio;
  56. if ($associatedElement.length !== 0) {
  57. if ($associatedElement.attr('type') === 'checkbox') {
  58. e.preventDefault();
  59. $customCheckbox = $(this).find('span.custom.checkbox');
  60. //the checkbox might be outside after the label or inside of another element
  61. if ($customCheckbox.length == 0) {
  62. $customCheckbox = $associatedElement.add(this).siblings('span.custom.checkbox').first();
  63. }
  64. self.toggle_checkbox($customCheckbox);
  65. } else if ($associatedElement.attr('type') === 'radio') {
  66. e.preventDefault();
  67. $customRadio = $(this).find('span.custom.radio');
  68. //the radio might be outside after the label or inside of another element
  69. if ($customRadio.length == 0) {
  70. $customRadio = $associatedElement.add(this).siblings('span.custom.radio').first();
  71. }
  72. self.toggle_radio($customRadio);
  73. }
  74. }
  75. }
  76. })
  77. .on('mousedown.fndtn.forms', 'form.custom div.custom.dropdown', function () {
  78. return false;
  79. })
  80. .on('click.fndtn.forms', 'form.custom div.custom.dropdown a.current, form.custom div.custom.dropdown a.selector', function (e) {
  81. var $this = $(this),
  82. $dropdown = $this.closest('div.custom.dropdown'),
  83. $select = getFirstPrevSibling($dropdown, 'select');
  84. // make sure other dropdowns close
  85. if (!$dropdown.hasClass('open')) $(self.scope).trigger('click');
  86. e.preventDefault();
  87. if (false === $select.is(':disabled')) {
  88. $dropdown.toggleClass('open');
  89. if ($dropdown.hasClass('open')) {
  90. $(self.scope).on('click.fndtn.forms.customdropdown', function () {
  91. $dropdown.removeClass('open');
  92. $(self.scope).off('.fndtn.forms.customdropdown');
  93. });
  94. } else {
  95. $(self.scope).on('.fndtn.forms.customdropdown');
  96. }
  97. return false;
  98. }
  99. })
  100. .on('click.fndtn.forms touchend.fndtn.forms', 'form.custom div.custom.dropdown li', function (e) {
  101. var $this = $(this),
  102. $customDropdown = $this.closest('div.custom.dropdown'),
  103. $select = getFirstPrevSibling($customDropdown, 'select'),
  104. selectedIndex = 0;
  105. e.preventDefault();
  106. e.stopPropagation();
  107. if (!$(this).hasClass('disabled')) {
  108. $('div.dropdown').not($customDropdown).removeClass('open');
  109. var $oldThis = $this.closest('ul')
  110. .find('li.selected');
  111. $oldThis.removeClass('selected');
  112. $this.addClass('selected');
  113. $customDropdown.removeClass('open')
  114. .find('a.current')
  115. .text($this.text());
  116. $this.closest('ul').find('li').each(function (index) {
  117. if ($this[0] == this) {
  118. selectedIndex = index;
  119. }
  120. });
  121. $select[0].selectedIndex = selectedIndex;
  122. //store the old value in data
  123. $select.data('prevalue', $oldThis.html());
  124. $select.trigger('change');
  125. }
  126. });
  127. $(window).on('keydown', function (e) {
  128. var focus = document.activeElement,
  129. self = Foundation.libs.forms,
  130. dropdown = $('.custom.dropdown.open');
  131. if (dropdown.length > 0) {
  132. e.preventDefault();
  133. if (e.which === 13) {
  134. dropdown.find('li.selected').trigger('click');
  135. }
  136. if (e.which === 27) {
  137. dropdown.removeClass('open');
  138. }
  139. if (e.which >= 65 && e.which <= 90) {
  140. var next = self.go_to(dropdown, e.which),
  141. current = dropdown.find('li.selected');
  142. if (next) {
  143. current.removeClass('selected');
  144. self.scrollTo(next.addClass('selected'), 300);
  145. }
  146. }
  147. if (e.which === 38) {
  148. var current = dropdown.find('li.selected'),
  149. prev = current.prev(':not(.disabled)');
  150. if (prev.length > 0) {
  151. prev.parent()[0].scrollTop = prev.parent().scrollTop() - self.outerHeight(prev);
  152. current.removeClass('selected');
  153. prev.addClass('selected');
  154. }
  155. } else if (e.which === 40) {
  156. var current = dropdown.find('li.selected'),
  157. next = current.next(':not(.disabled)');
  158. if (next.length > 0) {
  159. next.parent()[0].scrollTop = next.parent().scrollTop() + self.outerHeight(next);
  160. current.removeClass('selected');
  161. next.addClass('selected');
  162. }
  163. }
  164. }
  165. });
  166. this.settings.init = true;
  167. },
  168. go_to: function (dropdown, character) {
  169. var lis = dropdown.find('li'),
  170. count = lis.length;
  171. if (count > 0) {
  172. for (var i = 0; i < count; i++) {
  173. var first_letter = lis.eq(i).text().charAt(0).toLowerCase();
  174. if (first_letter === String.fromCharCode(character).toLowerCase()) return lis.eq(i);
  175. }
  176. }
  177. },
  178. scrollTo: function (el, duration) {
  179. if (duration < 0) return;
  180. var parent = el.parent();
  181. var li_height = this.outerHeight(el);
  182. var difference = (li_height * (el.index())) - parent.scrollTop();
  183. var perTick = difference / duration * 10;
  184. this.scrollToTimerCache = setTimeout(function () {
  185. if (!isNaN(parseInt(perTick, 10))) {
  186. parent[0].scrollTop = parent.scrollTop() + perTick;
  187. this.scrollTo(el, duration - 10);
  188. }
  189. }.bind(this), 10);
  190. },
  191. append_custom_markup: function (idx, sel) {
  192. var $this = $(sel),
  193. type = $this.attr('type'),
  194. $span = $this.next('span.custom.' + type);
  195. if ($span.length === 0) {
  196. $span = $('<span class="custom ' + type + '"></span>').insertAfter($this);
  197. }
  198. $span.toggleClass('checked', $this.is(':checked'));
  199. $span.toggleClass('disabled', $this.is(':disabled'));
  200. },
  201. append_custom_select: function (idx, sel) {
  202. var self = Foundation.libs.forms,
  203. $this = $(sel),
  204. $customSelect = $this.next('div.custom.dropdown'),
  205. $customList = $customSelect.find('ul'),
  206. $selectCurrent = $customSelect.find(".current"),
  207. $selector = $customSelect.find(".selector"),
  208. $options = $this.find('option'),
  209. $selectedOption = $options.filter(':selected'),
  210. copyClasses = $this.attr('class') ? $this.attr('class').split(' ') : [],
  211. maxWidth = 0,
  212. liHtml = '',
  213. $listItems,
  214. $currentSelect = false;
  215. if ($this.hasClass(self.settings.disable_class)) return;
  216. if ($customSelect.length === 0) {
  217. var customSelectSize = $this.hasClass('small') ? 'small' : $this.hasClass('medium') ? 'medium' : $this.hasClass('large') ? 'large' : $this.hasClass('expand') ? 'expand' : '';
  218. $customSelect = $('<div class="' + ['custom', 'dropdown', customSelectSize].concat(copyClasses).filter(function (item, idx, arr) {
  219. if (item == '') return false;
  220. return arr.indexOf(item) == idx;
  221. }).join(' ') + '"><a href="#" class="selector"></a><ul /></div>');
  222. $selector = $customSelect.find(".selector");
  223. $customList = $customSelect.find("ul");
  224. liHtml = $options.map(function () {
  225. var copyClasses = $(this).attr('class') ? $(this).attr('class') : '';
  226. return "<li class='" + copyClasses + "'>" + $(this).html() + "</li>";
  227. }).get().join('');
  228. $customList.append(liHtml);
  229. $currentSelect = $customSelect
  230. .prepend('<a href="#" class="current">' + $selectedOption.html() + '</a>')
  231. .find(".current");
  232. $this.after($customSelect)
  233. .addClass('hidden-field');
  234. } else {
  235. liHtml = $options.map(function () {
  236. return "<li>" + $(this).html() + "</li>";
  237. })
  238. .get().join('');
  239. $customList.html('')
  240. .append(liHtml);
  241. } // endif $customSelect.length === 0
  242. self.assign_id($this, $customSelect);
  243. $customSelect.toggleClass('disabled', $this.is(':disabled'));
  244. $listItems = $customList.find('li');
  245. // cache list length
  246. self.cache[$customSelect.data('id')] = $listItems.length;
  247. $options.each(function (index) {
  248. if (this.selected) {
  249. $listItems.eq(index).addClass('selected');
  250. if ($currentSelect) {
  251. $currentSelect.html($(this).html());
  252. }
  253. }
  254. if ($(this).is(':disabled')) {
  255. $listItems.eq(index).addClass('disabled');
  256. }
  257. });
  258. //
  259. // If we're not specifying a predetermined form size.
  260. //
  261. if (!$customSelect.is('.small, .medium, .large, .expand')) {
  262. // ------------------------------------------------------------------------------------
  263. // This is a work-around for when elements are contained within hidden parents.
  264. // For example, when custom-form elements are inside of a hidden reveal modal.
  265. //
  266. // We need to display the current custom list element as well as hidden parent elements
  267. // in order to properly calculate the list item element's width property.
  268. // -------------------------------------------------------------------------------------
  269. $customSelect.addClass('open');
  270. //
  271. // Quickly, display all parent elements.
  272. // This should help us calcualate the width of the list item's within the drop down.
  273. //
  274. var self = Foundation.libs.forms;
  275. self.hidden_fix.adjust($customList);
  276. maxWidth = (self.outerWidth($listItems) > maxWidth) ? self.outerWidth($listItems) : maxWidth;
  277. Foundation.libs.forms.hidden_fix.reset();
  278. $customSelect.removeClass('open');
  279. } // endif
  280. },
  281. assign_id: function ($select, $customSelect) {
  282. var id = [+new Date(), Foundation.random_str(5)].join('-');
  283. $select.attr('data-id', id);
  284. $customSelect.attr('data-id', id);
  285. },
  286. refresh_custom_select: function ($select, force_refresh) {
  287. var self = this;
  288. var maxWidth = 0,
  289. $customSelect = $select.next(),
  290. $options = $select.find('option'),
  291. $listItems = $customSelect.find('li');
  292. if ($listItems.length != this.cache[$customSelect.data('id')] || force_refresh) {
  293. $customSelect.find('ul').html('');
  294. $options.each(function () {
  295. var $li = $('<li>' + $(this).html() + '</li>');
  296. $customSelect.find('ul').append($li);
  297. });
  298. // re-populate
  299. $options.each(function (index) {
  300. if (this.selected) {
  301. $customSelect.find('li').eq(index).addClass('selected');
  302. $customSelect.find('.current').html($(this).html());
  303. }
  304. if ($(this).is(':disabled')) {
  305. $customSelect.find('li').eq(index).addClass('disabled');
  306. }
  307. });
  308. // fix width
  309. $customSelect.removeAttr('style')
  310. .find('ul').removeAttr('style');
  311. $customSelect.find('li').each(function () {
  312. $customSelect.addClass('open');
  313. if (self.outerWidth($(this)) > maxWidth) {
  314. maxWidth = self.outerWidth($(this));
  315. }
  316. $customSelect.removeClass('open');
  317. });
  318. $listItems = $customSelect.find('li');
  319. // cache list length
  320. this.cache[$customSelect.data('id')] = $listItems.length;
  321. }
  322. },
  323. toggle_checkbox: function ($element) {
  324. var $input = $element.prev(),
  325. input = $input[0];
  326. if (false === $input.is(':disabled')) {
  327. input.checked = ((input.checked) ? false : true);
  328. $element.toggleClass('checked');
  329. $input.trigger('change');
  330. }
  331. },
  332. toggle_radio: function ($element) {
  333. var $input = $element.prev(),
  334. $form = $input.closest('form.custom'),
  335. input = $input[0];
  336. if (false === $input.is(':disabled')) {
  337. $form.find('input[type="radio"][name="' + this.escape($input.attr('name')) + '"]')
  338. .next().not($element).removeClass('checked');
  339. if (!$element.hasClass('checked')) {
  340. $element.toggleClass('checked');
  341. }
  342. input.checked = $element.hasClass('checked');
  343. $input.trigger('change');
  344. }
  345. },
  346. escape: function (text) {
  347. return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
  348. },
  349. hidden_fix: {
  350. /**
  351. * Sets all hidden parent elements and self to visibile.
  352. *
  353. * @method adjust
  354. * @param {jQuery Object} $child
  355. */
  356. // We'll use this to temporarily store style properties.
  357. tmp: [],
  358. // We'll use this to set hidden parent elements.
  359. hidden: null,
  360. adjust: function ($child) {
  361. // Internal reference.
  362. var _self = this;
  363. // Set all hidden parent elements, including this element.
  364. _self.hidden = $child.parents();
  365. _self.hidden = _self.hidden.add($child).filter(":hidden");
  366. // Loop through all hidden elements.
  367. _self.hidden.each(function () {
  368. // Cache the element.
  369. var $elem = $(this);
  370. // Store the style attribute.
  371. // Undefined if element doesn't have a style attribute.
  372. _self.tmp.push($elem.attr('style'));
  373. // Set the element's display property to block,
  374. // but ensure it's visibility is hidden.
  375. $elem.css({
  376. 'visibility': 'hidden',
  377. 'display': 'block'
  378. });
  379. });
  380. }, // end adjust
  381. /**
  382. * Resets the elements previous state.
  383. *
  384. * @method reset
  385. */
  386. reset: function () {
  387. // Internal reference.
  388. var _self = this;
  389. // Loop through our hidden element collection.
  390. _self.hidden.each(function (i) {
  391. // Cache this element.
  392. var $elem = $(this),
  393. _tmp = _self.tmp[i]; // Get the stored 'style' value for this element.
  394. // If the stored value is undefined.
  395. if (_tmp === undefined)
  396. // Remove the style attribute.
  397. $elem.removeAttr('style');
  398. else
  399. // Otherwise, reset the element style attribute.
  400. $elem.attr('style', _tmp);
  401. });
  402. // Reset the tmp array.
  403. _self.tmp = [];
  404. // Reset the hidden elements variable.
  405. _self.hidden = null;
  406. } // end reset
  407. },
  408. off: function () {
  409. $(this.scope).off('.fndtn.forms');
  410. },
  411. reflow : function () {}
  412. };
  413. var getFirstPrevSibling = function($el, selector) {
  414. var $el = $el.prev();
  415. while ($el.length) {
  416. if ($el.is(selector)) return $el;
  417. $el = $el.prev();
  418. }
  419. return $();
  420. };
  421. }(Foundation.zj, this, this.document));