collapse.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /**
  2. * @file
  3. * Polyfill for HTML5 details elements.
  4. */
  5. (function ($, Modernizr, Drupal) {
  6. 'use strict';
  7. /**
  8. * The collapsible details object represents a single details element.
  9. *
  10. * @constructor Drupal.CollapsibleDetails
  11. *
  12. * @param {HTMLElement} node
  13. * The details element.
  14. */
  15. function CollapsibleDetails(node) {
  16. this.$node = $(node);
  17. this.$node.data('details', this);
  18. // Expand details if there are errors inside, or if it contains an
  19. // element that is targeted by the URI fragment identifier.
  20. var anchor = location.hash && location.hash !== '#' ? ', ' + location.hash : '';
  21. if (this.$node.find('.error' + anchor).length) {
  22. this.$node.attr('open', true);
  23. }
  24. // Initialize and setup the summary,
  25. this.setupSummary();
  26. // Initialize and setup the legend.
  27. this.setupLegend();
  28. }
  29. $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{
  30. /**
  31. * Holds references to instantiated CollapsibleDetails objects.
  32. *
  33. * @type {Array.<Drupal.CollapsibleDetails>}
  34. */
  35. instances: []
  36. });
  37. $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{
  38. /**
  39. * Initialize and setup summary events and markup.
  40. *
  41. * @fires event:summaryUpdated
  42. *
  43. * @listens event:summaryUpdated
  44. */
  45. setupSummary: function () {
  46. this.$summary = $('<span class="summary"></span>');
  47. this.$node
  48. .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
  49. .trigger('summaryUpdated');
  50. },
  51. /**
  52. * Initialize and setup legend markup.
  53. */
  54. setupLegend: function () {
  55. // Turn the summary into a clickable link.
  56. var $legend = this.$node.find('> summary');
  57. $('<span class="details-summary-prefix visually-hidden"></span>')
  58. .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
  59. .prependTo($legend)
  60. .after(document.createTextNode(' '));
  61. // .wrapInner() does not retain bound events.
  62. $('<a class="details-title"></a>')
  63. .attr('href', '#' + this.$node.attr('id'))
  64. .prepend($legend.contents())
  65. .appendTo($legend);
  66. $legend
  67. .append(this.$summary)
  68. .on('click', $.proxy(this.onLegendClick, this));
  69. },
  70. /**
  71. * Handle legend clicks.
  72. *
  73. * @param {jQuery.Event} e
  74. * The event triggered.
  75. */
  76. onLegendClick: function (e) {
  77. this.toggle();
  78. e.preventDefault();
  79. },
  80. /**
  81. * Update summary.
  82. */
  83. onSummaryUpdated: function () {
  84. var text = $.trim(this.$node.drupalGetSummary());
  85. this.$summary.html(text ? ' (' + text + ')' : '');
  86. },
  87. /**
  88. * Toggle the visibility of a details element using smooth animations.
  89. */
  90. toggle: function () {
  91. var isOpen = !!this.$node.attr('open');
  92. var $summaryPrefix = this.$node.find('> summary span.details-summary-prefix');
  93. if (isOpen) {
  94. $summaryPrefix.html(Drupal.t('Show'));
  95. }
  96. else {
  97. $summaryPrefix.html(Drupal.t('Hide'));
  98. }
  99. // Delay setting the attribute to emulate chrome behavior and make
  100. // details-aria.js work as expected with this polyfill.
  101. setTimeout(function () {
  102. this.$node.attr('open', !isOpen);
  103. }.bind(this), 0);
  104. }
  105. });
  106. /**
  107. * Polyfill HTML5 details element.
  108. *
  109. * @type {Drupal~behavior}
  110. *
  111. * @prop {Drupal~behaviorAttach} attach
  112. * Attaches behavior for the details element.
  113. */
  114. Drupal.behaviors.collapse = {
  115. attach: function (context) {
  116. if (Modernizr.details) {
  117. return;
  118. }
  119. var $collapsibleDetails = $(context).find('details').once('collapse').addClass('collapse-processed');
  120. if ($collapsibleDetails.length) {
  121. for (var i = 0; i < $collapsibleDetails.length; i++) {
  122. CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i]));
  123. }
  124. }
  125. }
  126. };
  127. // Expose constructor in the public space.
  128. Drupal.CollapsibleDetails = CollapsibleDetails;
  129. })(jQuery, Modernizr, Drupal);