railsFlashNotificationsHelper.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /*
  2. * Copyright (C) 2016 - present Instructure, Inc.
  3. *
  4. * This file is part of Canvas.
  5. *
  6. * Canvas is free software: you can redistribute it and/or modify it under
  7. * the terms of the GNU Affero General Public License as published by the Free
  8. * Software Foundation, version 3 of the License.
  9. *
  10. * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
  11. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. * details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License along
  16. * with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. import I18n from 'i18n!shared.flash_notices'
  19. import $ from 'jquery'
  20. import _ from 'underscore'
  21. import htmlEscape from 'str/htmlEscape'
  22. import 'jqueryui/effects/drop'
  23. import 'jquery.cookie'
  24. function updateAriaLive ({ polite } = { polite: false }) {
  25. if (this.screenreaderHolderReady()) {
  26. const value = polite ? 'polite' : 'assertive';
  27. $(this.screenreader_holder).attr('aria-live', value);
  28. }
  29. }
  30. class RailsFlashNotificationsHelper {
  31. constructor() {
  32. this.holder = null;
  33. this.screenreader_holder = null;
  34. }
  35. initHolder() {
  36. const $current_holders = $('#flash_message_holder');
  37. if ($current_holders.length === 0) {
  38. this.holder = null;
  39. } else {
  40. this.holder = $current_holders[0];
  41. $(this.holder).on('click', '.close_link', (event) => {
  42. event.preventDefault();
  43. });
  44. $(this.holder).on('click', 'li', (event) => {
  45. if ($(event.currentTarget).hasClass('no_close')) {
  46. return;
  47. }
  48. if ($(event.currentTarget).hasClass('unsupported_browser')) {
  49. $.cookie('unsupported_browser_dismissed');
  50. }
  51. $(event.currentTarget).stop(true, true).remove();
  52. });
  53. }
  54. }
  55. holderReady() {
  56. return this.holder != null;
  57. }
  58. createNode(type, content, timeout, cssOptions = {}) {
  59. if(this.holderReady()) {
  60. const node = this.generateNodeHTML(type, content);
  61. $(node).appendTo($(this.holder)).
  62. css(_.extend({zIndex: 2}, cssOptions)).
  63. show('fast').
  64. delay(timeout || 7000).
  65. fadeOut('slow', function() { $(this).remove(); });
  66. }
  67. }
  68. generateNodeHTML(type, content) {
  69. const icon = this.getIconType(type);
  70. // See generateScreenreaderNodeHtml for SR features
  71. return `
  72. <li class="ic-flash-${htmlEscape(type)}" aria-hidden="true">
  73. <div class="ic-flash__icon">
  74. <i class="icon-${htmlEscape(icon)}"></i>
  75. </div>
  76. ${this.escapeContent(content)}
  77. <button type="button" class="Button Button--icon-action close_link">
  78. <i class="icon-x"></i>
  79. </button>
  80. </li>
  81. `;
  82. }
  83. getIconType(type) {
  84. if (type === 'success') {
  85. return 'check';
  86. } else if (type === 'warning' || type === 'error') {
  87. return 'warning';
  88. } else {
  89. return 'info';
  90. }
  91. }
  92. initScreenreaderHolder() {
  93. const $current_screenreader_holders = $('#flash_screenreader_holder');
  94. if ($current_screenreader_holders.length === 0) {
  95. this.screenreader_holder = null;
  96. } else {
  97. this.screenreader_holder = $current_screenreader_holders[0];
  98. this.setScreenreaderAttributes();
  99. }
  100. }
  101. screenreaderHolderReady() {
  102. return this.screenreader_holder != null;
  103. }
  104. createScreenreaderNode (content, closable = true) {
  105. if (this.screenreaderHolderReady()) {
  106. updateAriaLive.call(this, { polite: false });
  107. const node = $(this.generateScreenreaderNodeHTML(content, closable));
  108. node.appendTo($(this.screenreader_holder));
  109. window.setTimeout( () => {
  110. // Accessibility attributes must be removed for the deletion of the node
  111. // and then reapplied because JAWS/IE will not respect the
  112. // "aria-relevant" attribute and read when the node is deleted if
  113. // the attributes are in place
  114. this.resetScreenreaderAttributes();
  115. node.remove();
  116. this.setScreenreaderAttributes();
  117. }, 10000);
  118. }
  119. }
  120. setScreenreaderAttributes() {
  121. if(this.screenreaderHolderReady()) {
  122. // These attributes are added for accessibility. However, adding them
  123. // to the DOM at load causes some screenreaders to read "alert" when
  124. // the page is loaded. That is why these attributes are added here.
  125. $(this.screenreader_holder).attr('role', 'alert');
  126. $(this.screenreader_holder).attr('aria-live', 'assertive');
  127. $(this.screenreader_holder).attr('aria-relevant', 'additions');
  128. $(this.screenreader_holder).attr('class', 'screenreader-only');
  129. $(this.screenreader_holder).attr('aria-atomic', 'false');
  130. }
  131. }
  132. resetScreenreaderAttributes() {
  133. if(this.screenreaderHolderReady()) {
  134. $(this.screenreader_holder).removeAttr('role');
  135. $(this.screenreader_holder).removeAttr('aria-live');
  136. $(this.screenreader_holder).removeAttr('aria-relevant');
  137. $(this.screenreader_holder).removeAttr('class');
  138. $(this.screenreader_holder).removeAttr('aria-atomic');
  139. }
  140. }
  141. createScreenreaderNodeExclusive (content, polite = false) {
  142. if (this.screenreaderHolderReady()) {
  143. updateAriaLive.call(this, { polite });
  144. this.screenreader_holder.innerHTML = '';
  145. const node = $(this.generateScreenreaderNodeHTML(content, false));
  146. node.appendTo($(this.screenreader_holder));
  147. }
  148. }
  149. generateScreenreaderNodeHTML(content, closable) {
  150. let closeContent;
  151. if(closable) {
  152. closeContent = I18n.t('Close');
  153. } else {
  154. closeContent = '';
  155. }
  156. return `
  157. <span>
  158. ${this.escapeContent(content)}
  159. ${htmlEscape(closeContent)}
  160. </span>
  161. `;
  162. }
  163. /*
  164. xsslint safeString.method escapeContent
  165. */
  166. escapeContent(content) {
  167. if(content.hasOwnProperty('html')) {
  168. return content.html;
  169. } else {
  170. return htmlEscape(content);
  171. }
  172. }
  173. }
  174. export default RailsFlashNotificationsHelper