theme/boost/amd/src/drawer.js

  1. // This file is part of Moodle - http://moodle.org/
  2. //
  3. // Moodle is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // Moodle is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * Contain the logic for a drawer.
  17. *
  18. * @module theme_boost/drawer
  19. * @copyright 2016 Damyon Wiese
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. define(['jquery', 'core/custom_interaction_events', 'core/log', 'core/pubsub', 'core/aria', 'core_user/repository'],
  23. function($, CustomEvents, Log, PubSub, Aria, UserRepository) {
  24. var SELECTORS = {
  25. TOGGLE_REGION: '[data-region="drawer-toggle"]',
  26. TOGGLE_ACTION: '[data-action="toggle-drawer"]',
  27. TOGGLE_TARGET: 'aria-controls',
  28. TOGGLE_SIDE: 'left',
  29. BODY: 'body',
  30. SECTION: '.list-group-item[href*="#section-"]',
  31. DRAWER: '#nav-drawer'
  32. };
  33. var small = $(document).width() < 768;
  34. /**
  35. * Constructor for the Drawer.
  36. */
  37. var Drawer = function() {
  38. if (!$(SELECTORS.TOGGLE_REGION).length) {
  39. Log.debug('Page is missing a drawer region');
  40. }
  41. if (!$(SELECTORS.TOGGLE_ACTION).length) {
  42. Log.debug('Page is missing a drawer toggle link');
  43. }
  44. $(SELECTORS.TOGGLE_REGION).each(function(index, ele) {
  45. var trigger = $(ele).find(SELECTORS.TOGGLE_ACTION);
  46. var drawerid = trigger.attr('aria-controls');
  47. var drawer = $(document.getElementById(drawerid));
  48. var hidden = trigger.attr('aria-expanded') == 'false';
  49. var side = trigger.attr('data-side');
  50. var body = $(SELECTORS.BODY);
  51. var preference = trigger.attr('data-preference');
  52. if (small) {
  53. UserRepository.setUserPreference(preference, false);
  54. }
  55. drawer.on('mousewheel DOMMouseScroll', this.preventPageScroll);
  56. if (!hidden) {
  57. body.addClass('drawer-open-' + side);
  58. trigger.attr('aria-expanded', 'true');
  59. } else {
  60. trigger.attr('aria-expanded', 'false');
  61. }
  62. }.bind(this));
  63. this.registerEventListeners();
  64. if (small) {
  65. this.closeAll();
  66. }
  67. };
  68. Drawer.prototype.closeAll = function() {
  69. $(SELECTORS.TOGGLE_REGION).each(function(index, ele) {
  70. var trigger = $(ele).find(SELECTORS.TOGGLE_ACTION);
  71. var side = trigger.attr('data-side');
  72. var body = $(SELECTORS.BODY);
  73. var drawerid = trigger.attr('aria-controls');
  74. var drawer = $(document.getElementById(drawerid));
  75. var preference = trigger.attr('data-preference');
  76. trigger.attr('aria-expanded', 'false');
  77. body.removeClass('drawer-open-' + side);
  78. Aria.hide(drawer.get());
  79. drawer.addClass('closed');
  80. if (!small) {
  81. UserRepository.setUserPreference(preference, false);
  82. }
  83. });
  84. };
  85. /**
  86. * Open / close the blocks drawer.
  87. *
  88. * @method toggleDrawer
  89. * @param {Event} e
  90. */
  91. Drawer.prototype.toggleDrawer = function(e) {
  92. var trigger = $(e.target).closest('[data-action=toggle-drawer]');
  93. var drawerid = trigger.attr('aria-controls');
  94. var drawer = $(document.getElementById(drawerid));
  95. var body = $(SELECTORS.BODY);
  96. var side = trigger.attr('data-side');
  97. var preference = trigger.attr('data-preference');
  98. if (small) {
  99. UserRepository.setUserPreference(preference, false);
  100. }
  101. body.addClass('drawer-ease');
  102. var open = trigger.attr('aria-expanded') == 'true';
  103. if (!open) {
  104. // Open.
  105. trigger.attr('aria-expanded', 'true');
  106. Aria.unhide(drawer.get());
  107. drawer.focus();
  108. body.addClass('drawer-open-' + side);
  109. drawer.removeClass('closed');
  110. if (!small) {
  111. UserRepository.setUserPreference(preference, true);
  112. }
  113. } else {
  114. // Close.
  115. body.removeClass('drawer-open-' + side);
  116. trigger.attr('aria-expanded', 'false');
  117. drawer.addClass('closed').delay(500).queue(function() {
  118. // Ensure that during the delay, the drawer wasn't re-opened.
  119. if ($(this).hasClass('closed')) {
  120. Aria.hide(this);
  121. }
  122. $(this).dequeue();
  123. });
  124. if (!small) {
  125. UserRepository.setUserPreference(preference, false);
  126. }
  127. }
  128. // Publish an event to tell everything that the drawer has been toggled.
  129. // The drawer transitions closed so another event will fire once teh transition
  130. // has completed.
  131. PubSub.publish('nav-drawer-toggle-start', open);
  132. };
  133. /**
  134. * Prevent the page from scrolling when the drawer is at max scroll.
  135. *
  136. * @method preventPageScroll
  137. * @param {Event} e
  138. */
  139. Drawer.prototype.preventPageScroll = function(e) {
  140. var delta = e.wheelDelta || (e.originalEvent && e.originalEvent.wheelDelta) || -e.originalEvent.detail,
  141. bottomOverflow = (this.scrollTop + $(this).outerHeight() - this.scrollHeight) >= 0,
  142. topOverflow = this.scrollTop <= 0;
  143. if ((delta < 0 && bottomOverflow) || (delta > 0 && topOverflow)) {
  144. e.preventDefault();
  145. }
  146. };
  147. /**
  148. * Set up all of the event handling for the modal.
  149. *
  150. * @method registerEventListeners
  151. */
  152. Drawer.prototype.registerEventListeners = function() {
  153. $(SELECTORS.TOGGLE_ACTION).each(function(index, element) {
  154. CustomEvents.define($(element), [CustomEvents.events.activate]);
  155. $(element).on(CustomEvents.events.activate, function(e, data) {
  156. this.toggleDrawer(data.originalEvent);
  157. data.originalEvent.preventDefault();
  158. }.bind(this));
  159. }.bind(this));
  160. $(SELECTORS.SECTION).click(function() {
  161. if (small) {
  162. this.closeAll();
  163. }
  164. }.bind(this));
  165. // Publish an event to tell everything that the drawer completed the transition
  166. // to either an open or closed state.
  167. $(SELECTORS.DRAWER).on('webkitTransitionEnd msTransitionEnd transitionend', function(e) {
  168. var drawer = $(e.target).closest(SELECTORS.DRAWER);
  169. // Note: aria-hidden is either present, or absent. It should not be set to false.
  170. var open = !!drawer.attr('aria-hidden');
  171. PubSub.publish('nav-drawer-toggle-end', open);
  172. });
  173. };
  174. return {
  175. 'init': function() {
  176. return new Drawer();
  177. }
  178. };
  179. });