foundation.toggler.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. 'use strict';
  2. import $ from 'jquery';
  3. import { Motion } from './foundation.util.motion';
  4. import { Plugin } from './foundation.core.plugin';
  5. import { RegExpEscape } from './foundation.core.utils';
  6. import { Triggers } from './foundation.util.triggers';
  7. /**
  8. * Toggler module.
  9. * @module foundation.toggler
  10. * @requires foundation.util.motion
  11. * @requires foundation.util.triggers
  12. */
  13. class Toggler extends Plugin {
  14. /**
  15. * Creates a new instance of Toggler.
  16. * @class
  17. * @name Toggler
  18. * @fires Toggler#init
  19. * @param {Object} element - jQuery object to add the trigger to.
  20. * @param {Object} options - Overrides to the default plugin settings.
  21. */
  22. _setup(element, options) {
  23. this.$element = element;
  24. this.options = $.extend({}, Toggler.defaults, element.data(), options);
  25. this.className = '';
  26. this.className = 'Toggler'; // ie9 back compat
  27. // Triggers init is idempotent, just need to make sure it is initialized
  28. Triggers.init($);
  29. this._init();
  30. this._events();
  31. }
  32. /**
  33. * Initializes the Toggler plugin by parsing the toggle class from data-toggler, or animation classes from data-animate.
  34. * @function
  35. * @private
  36. */
  37. _init() {
  38. var input;
  39. // Parse animation classes if they were set
  40. if (this.options.animate) {
  41. input = this.options.animate.split(' ');
  42. this.animationIn = input[0];
  43. this.animationOut = input[1] || null;
  44. }
  45. // Otherwise, parse toggle class
  46. else {
  47. input = this.$element.data('toggler');
  48. // Allow for a . at the beginning of the string
  49. this.className = input[0] === '.' ? input.slice(1) : input;
  50. }
  51. // Add ARIA attributes to triggers:
  52. var id = this.$element[0].id,
  53. $triggers = $(`[data-open~="${id}"], [data-close~="${id}"], [data-toggle~="${id}"]`);
  54. // - aria-expanded: according to the element visibility.
  55. $triggers.attr('aria-expanded', !this.$element.is(':hidden'));
  56. // - aria-controls: adding the element id to it if not already in it.
  57. $triggers.each((index, trigger) => {
  58. const $trigger = $(trigger);
  59. const controls = $trigger.attr('aria-controls') || '';
  60. const containsId = new RegExp(`\\b${RegExpEscape(id)}\\b`).test(controls);
  61. if (!containsId) $trigger.attr('aria-controls', controls ? `${controls} ${id}` : id);
  62. });
  63. }
  64. /**
  65. * Initializes events for the toggle trigger.
  66. * @function
  67. * @private
  68. */
  69. _events() {
  70. this.$element.off('toggle.zf.trigger').on('toggle.zf.trigger', this.toggle.bind(this));
  71. }
  72. /**
  73. * Toggles the target class on the target element. An event is fired from the original trigger depending on if the resultant state was "on" or "off".
  74. * @function
  75. * @fires Toggler#on
  76. * @fires Toggler#off
  77. */
  78. toggle() {
  79. this[ this.options.animate ? '_toggleAnimate' : '_toggleClass']();
  80. }
  81. _toggleClass() {
  82. this.$element.toggleClass(this.className);
  83. var isOn = this.$element.hasClass(this.className);
  84. if (isOn) {
  85. /**
  86. * Fires if the target element has the class after a toggle.
  87. * @event Toggler#on
  88. */
  89. this.$element.trigger('on.zf.toggler');
  90. }
  91. else {
  92. /**
  93. * Fires if the target element does not have the class after a toggle.
  94. * @event Toggler#off
  95. */
  96. this.$element.trigger('off.zf.toggler');
  97. }
  98. this._updateARIA(isOn);
  99. this.$element.find('[data-mutate]').trigger('mutateme.zf.trigger');
  100. }
  101. _toggleAnimate() {
  102. var _this = this;
  103. if (this.$element.is(':hidden')) {
  104. Motion.animateIn(this.$element, this.animationIn, function() {
  105. _this._updateARIA(true);
  106. this.trigger('on.zf.toggler');
  107. this.find('[data-mutate]').trigger('mutateme.zf.trigger');
  108. });
  109. }
  110. else {
  111. Motion.animateOut(this.$element, this.animationOut, function() {
  112. _this._updateARIA(false);
  113. this.trigger('off.zf.toggler');
  114. this.find('[data-mutate]').trigger('mutateme.zf.trigger');
  115. });
  116. }
  117. }
  118. _updateARIA(isOn) {
  119. var id = this.$element[0].id;
  120. $(`[data-open="${id}"], [data-close="${id}"], [data-toggle="${id}"]`)
  121. .attr({
  122. 'aria-expanded': isOn ? true : false
  123. });
  124. }
  125. /**
  126. * Destroys the instance of Toggler on the element.
  127. * @function
  128. */
  129. _destroy() {
  130. this.$element.off('.zf.toggler');
  131. }
  132. }
  133. Toggler.defaults = {
  134. /**
  135. * Tells the plugin if the element should animated when toggled.
  136. * @option
  137. * @type {boolean}
  138. * @default false
  139. */
  140. animate: false
  141. };
  142. export {Toggler};