foundation.util.triggers.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. 'use strict';
  2. import $ from 'jquery';
  3. import { onLoad } from './foundation.core.utils';
  4. import { Motion } from './foundation.util.motion';
  5. const MutationObserver = (function () {
  6. var prefixes = ['WebKit', 'Moz', 'O', 'Ms', ''];
  7. for (var i=0; i < prefixes.length; i++) {
  8. if (`${prefixes[i]}MutationObserver` in window) {
  9. return window[`${prefixes[i]}MutationObserver`];
  10. }
  11. }
  12. return false;
  13. }());
  14. const triggers = (el, type) => {
  15. el.data(type).split(' ').forEach(id => {
  16. $(`#${id}`)[ type === 'close' ? 'trigger' : 'triggerHandler'](`${type}.zf.trigger`, [el]);
  17. });
  18. };
  19. var Triggers = {
  20. Listeners: {
  21. Basic: {},
  22. Global: {}
  23. },
  24. Initializers: {}
  25. }
  26. Triggers.Listeners.Basic = {
  27. openListener: function() {
  28. triggers($(this), 'open');
  29. },
  30. closeListener: function() {
  31. let id = $(this).data('close');
  32. if (id) {
  33. triggers($(this), 'close');
  34. }
  35. else {
  36. $(this).trigger('close.zf.trigger');
  37. }
  38. },
  39. toggleListener: function() {
  40. let id = $(this).data('toggle');
  41. if (id) {
  42. triggers($(this), 'toggle');
  43. } else {
  44. $(this).trigger('toggle.zf.trigger');
  45. }
  46. },
  47. closeableListener: function(e) {
  48. e.stopPropagation();
  49. let animation = $(this).data('closable');
  50. if(animation !== ''){
  51. Motion.animateOut($(this), animation, function() {
  52. $(this).trigger('closed.zf');
  53. });
  54. }else{
  55. $(this).fadeOut().trigger('closed.zf');
  56. }
  57. },
  58. toggleFocusListener: function() {
  59. let id = $(this).data('toggle-focus');
  60. $(`#${id}`).triggerHandler('toggle.zf.trigger', [$(this)]);
  61. }
  62. };
  63. // Elements with [data-open] will reveal a plugin that supports it when clicked.
  64. Triggers.Initializers.addOpenListener = ($elem) => {
  65. $elem.off('click.zf.trigger', Triggers.Listeners.Basic.openListener);
  66. $elem.on('click.zf.trigger', '[data-open]', Triggers.Listeners.Basic.openListener);
  67. }
  68. // Elements with [data-close] will close a plugin that supports it when clicked.
  69. // If used without a value on [data-close], the event will bubble, allowing it to close a parent component.
  70. Triggers.Initializers.addCloseListener = ($elem) => {
  71. $elem.off('click.zf.trigger', Triggers.Listeners.Basic.closeListener);
  72. $elem.on('click.zf.trigger', '[data-close]', Triggers.Listeners.Basic.closeListener);
  73. }
  74. // Elements with [data-toggle] will toggle a plugin that supports it when clicked.
  75. Triggers.Initializers.addToggleListener = ($elem) => {
  76. $elem.off('click.zf.trigger', Triggers.Listeners.Basic.toggleListener);
  77. $elem.on('click.zf.trigger', '[data-toggle]', Triggers.Listeners.Basic.toggleListener);
  78. }
  79. // Elements with [data-closable] will respond to close.zf.trigger events.
  80. Triggers.Initializers.addCloseableListener = ($elem) => {
  81. $elem.off('close.zf.trigger', Triggers.Listeners.Basic.closeableListener);
  82. $elem.on('close.zf.trigger', '[data-closeable], [data-closable]', Triggers.Listeners.Basic.closeableListener);
  83. }
  84. // Elements with [data-toggle-focus] will respond to coming in and out of focus
  85. Triggers.Initializers.addToggleFocusListener = ($elem) => {
  86. $elem.off('focus.zf.trigger blur.zf.trigger', Triggers.Listeners.Basic.toggleFocusListener);
  87. $elem.on('focus.zf.trigger blur.zf.trigger', '[data-toggle-focus]', Triggers.Listeners.Basic.toggleFocusListener);
  88. }
  89. // More Global/complex listeners and triggers
  90. Triggers.Listeners.Global = {
  91. resizeListener: function($nodes) {
  92. if(!MutationObserver){//fallback for IE 9
  93. $nodes.each(function(){
  94. $(this).triggerHandler('resizeme.zf.trigger');
  95. });
  96. }
  97. //trigger all listening elements and signal a resize event
  98. $nodes.attr('data-events', "resize");
  99. },
  100. scrollListener: function($nodes) {
  101. if(!MutationObserver){//fallback for IE 9
  102. $nodes.each(function(){
  103. $(this).triggerHandler('scrollme.zf.trigger');
  104. });
  105. }
  106. //trigger all listening elements and signal a scroll event
  107. $nodes.attr('data-events', "scroll");
  108. },
  109. closeMeListener: function(e, pluginId){
  110. let plugin = e.namespace.split('.')[0];
  111. let plugins = $(`[data-${plugin}]`).not(`[data-yeti-box="${pluginId}"]`);
  112. plugins.each(function(){
  113. let _this = $(this);
  114. _this.triggerHandler('close.zf.trigger', [_this]);
  115. });
  116. }
  117. }
  118. // Global, parses whole document.
  119. Triggers.Initializers.addClosemeListener = function(pluginName) {
  120. var yetiBoxes = $('[data-yeti-box]'),
  121. plugNames = ['dropdown', 'tooltip', 'reveal'];
  122. if(pluginName){
  123. if(typeof pluginName === 'string'){
  124. plugNames.push(pluginName);
  125. }else if(typeof pluginName === 'object' && typeof pluginName[0] === 'string'){
  126. plugNames = plugNames.concat(pluginName);
  127. }else{
  128. console.error('Plugin names must be strings');
  129. }
  130. }
  131. if(yetiBoxes.length){
  132. let listeners = plugNames.map((name) => {
  133. return `closeme.zf.${name}`;
  134. }).join(' ');
  135. $(window).off(listeners).on(listeners, Triggers.Listeners.Global.closeMeListener);
  136. }
  137. }
  138. function debounceGlobalListener(debounce, trigger, listener) {
  139. let timer, args = Array.prototype.slice.call(arguments, 3);
  140. $(window).off(trigger).on(trigger, function(e) {
  141. if (timer) { clearTimeout(timer); }
  142. timer = setTimeout(function(){
  143. listener.apply(null, args);
  144. }, debounce || 10);//default time to emit scroll event
  145. });
  146. }
  147. Triggers.Initializers.addResizeListener = function(debounce){
  148. let $nodes = $('[data-resize]');
  149. if($nodes.length){
  150. debounceGlobalListener(debounce, 'resize.zf.trigger', Triggers.Listeners.Global.resizeListener, $nodes);
  151. }
  152. }
  153. Triggers.Initializers.addScrollListener = function(debounce){
  154. let $nodes = $('[data-scroll]');
  155. if($nodes.length){
  156. debounceGlobalListener(debounce, 'scroll.zf.trigger', Triggers.Listeners.Global.scrollListener, $nodes);
  157. }
  158. }
  159. Triggers.Initializers.addMutationEventsListener = function($elem) {
  160. if(!MutationObserver){ return false; }
  161. let $nodes = $elem.find('[data-resize], [data-scroll], [data-mutate]');
  162. //element callback
  163. var listeningElementsMutation = function (mutationRecordsList) {
  164. var $target = $(mutationRecordsList[0].target);
  165. //trigger the event handler for the element depending on type
  166. switch (mutationRecordsList[0].type) {
  167. case "attributes":
  168. if ($target.attr("data-events") === "scroll" && mutationRecordsList[0].attributeName === "data-events") {
  169. $target.triggerHandler('scrollme.zf.trigger', [$target, window.pageYOffset]);
  170. }
  171. if ($target.attr("data-events") === "resize" && mutationRecordsList[0].attributeName === "data-events") {
  172. $target.triggerHandler('resizeme.zf.trigger', [$target]);
  173. }
  174. if (mutationRecordsList[0].attributeName === "style") {
  175. $target.closest("[data-mutate]").attr("data-events","mutate");
  176. $target.closest("[data-mutate]").triggerHandler('mutateme.zf.trigger', [$target.closest("[data-mutate]")]);
  177. }
  178. break;
  179. case "childList":
  180. $target.closest("[data-mutate]").attr("data-events","mutate");
  181. $target.closest("[data-mutate]").triggerHandler('mutateme.zf.trigger', [$target.closest("[data-mutate]")]);
  182. break;
  183. default:
  184. return false;
  185. //nothing
  186. }
  187. };
  188. if ($nodes.length) {
  189. //for each element that needs to listen for resizing, scrolling, or mutation add a single observer
  190. for (var i = 0; i <= $nodes.length - 1; i++) {
  191. var elementObserver = new MutationObserver(listeningElementsMutation);
  192. elementObserver.observe($nodes[i], { attributes: true, childList: true, characterData: false, subtree: true, attributeFilter: ["data-events", "style"] });
  193. }
  194. }
  195. }
  196. Triggers.Initializers.addSimpleListeners = function() {
  197. let $document = $(document);
  198. Triggers.Initializers.addOpenListener($document);
  199. Triggers.Initializers.addCloseListener($document);
  200. Triggers.Initializers.addToggleListener($document);
  201. Triggers.Initializers.addCloseableListener($document);
  202. Triggers.Initializers.addToggleFocusListener($document);
  203. }
  204. Triggers.Initializers.addGlobalListeners = function() {
  205. let $document = $(document);
  206. Triggers.Initializers.addMutationEventsListener($document);
  207. Triggers.Initializers.addResizeListener();
  208. Triggers.Initializers.addScrollListener();
  209. Triggers.Initializers.addClosemeListener();
  210. }
  211. Triggers.init = function ($, Foundation) {
  212. onLoad($(window), function () {
  213. if ($.triggersInitialized !== true) {
  214. Triggers.Initializers.addSimpleListeners();
  215. Triggers.Initializers.addGlobalListeners();
  216. $.triggersInitialized = true;
  217. }
  218. });
  219. if(Foundation) {
  220. Foundation.Triggers = Triggers;
  221. // Legacy included to be backwards compatible for now.
  222. Foundation.IHearYou = Triggers.Initializers.addGlobalListeners
  223. }
  224. }
  225. export {Triggers};