drupal/core/misc/collapse.js

191 lines
5.0 KiB
JavaScript

(function ($, Drupal) {
"use strict";
/**
* Details feature detection.
*
* @todo This is a stop-gap fix only. collapse.js needs to be replaced with a
* proper HTML5 details polyfill.
*
* @author Mathias Bynens
* @see http://mathiasbynens.be/notes/html5-details-jquery
*/
var isDetailsSupported = (function (doc) {
var el = doc.createElement('details'),
fake,
root,
diff;
if (!('open' in el)) {
return false;
}
root = doc.body || (function () {
var de = doc.documentElement;
fake = true;
return de.insertBefore(doc.createElement('body'), de.firstElementChild || de.firstChild);
}());
el.innerHTML = '<summary>a</summary>b';
el.style.display = 'block';
root.appendChild(el);
diff = el.offsetHeight;
el.open = true;
diff = diff !== el.offsetHeight;
root.removeChild(el);
if (fake) {
root.parentNode.removeChild(root);
}
return diff;
}(document));
/**
* The collapsible details object represents a single collapsible details element.
*/
function CollapsibleDetails(node, settings) {
this.$node = $(node);
this.$node.data('details', this);
this.settings = $.extend({
duration:'fast',
easing:'linear'
},
settings
);
// Expand details if there are errors inside, or if it contains an
// element that is targeted by the URI fragment identifier.
var anchor = location.hash && location.hash !== '#' ? ', ' + location.hash : '';
if (this.$node.find('.error' + anchor).length) {
this.$node.prop('open', true);
}
// Initialize and setup the summary,
this.setupSummary();
// Initialize and setup the legend.
this.setupLegend();
}
/**
* Extend CollapsibleDetails function.
*/
$.extend(CollapsibleDetails, {
/**
* Holds references to instantiated CollapsibleDetails objects.
*/
instances: []
});
/**
* Extend CollapsibleDetails prototype.
*/
$.extend(CollapsibleDetails.prototype, {
/**
* Flag preventing multiple simultaneous animations.
*/
animating: false,
/**
* Initialize and setup summary events and markup.
*/
setupSummary: function () {
this.$summary = $('<span class="summary"></span>');
this.$node
.bind('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
.trigger('summaryUpdated');
},
/**
* Initialize and setup legend markup.
*/
setupLegend: function () {
// Turn the summary into a clickable link.
var $legend = this.$node.find('> summary');
$('<span class="details-summary-prefix element-invisible"></span>')
.append(this.$node.prop('open') ? Drupal.t('Hide') : Drupal.t('Show'))
.prependTo($legend)
.after(' ');
// .wrapInner() does not retain bound events.
var $link = $('<a class="details-title"></a>')
.attr('href', '#' + this.$node.attr('id'))
.prepend($legend.contents())
.appendTo($legend)
.click($.proxy(this.onLegendClick, this));
$legend.append(this.$summary);
},
/**
* Handle legend clicks
*/
onLegendClick: function (e) {
e.preventDefault();
this.toggle();
},
/**
* Update summary
*/
onSummaryUpdated: function () {
var text = $.trim(this.$node.drupalGetSummary());
this.$summary.html(text ? ' (' + text + ')' : '');
},
/**
* Toggle the visibility of a details element using smooth animations.
*/
toggle: function () {
// Don't animate multiple times.
if (this.animating) {
return;
}
if (!this.$node.prop('open')) {
var $content = this.$node.find('> .details-wrapper').hide();
this.$node
.trigger({ type:'collapsed', value:false })
.find('> summary span.details-summary-prefix').html(Drupal.t('Hide'));
$content.slideDown(
$.extend(this.settings, {
complete:$.proxy(this.onCompleteSlideDown, this)
})
);
}
else {
this.$node.trigger({ type:'collapsed', value:true });
this.$node.find('> .details-wrapper').slideUp(
$.extend(this.settings, {
complete:$.proxy(this.onCompleteSlideUp, this)
})
);
}
},
/**
* Completed opening details element.
*/
onCompleteSlideDown: function () {
this.$node.prop('open', true);
this.$node.trigger('completeSlideDown');
this.animating = false;
},
/**
* Completed closing details element.
*/
onCompleteSlideUp: function () {
this.$node.prop('open', false);
this.$node
.find('> summary span.details-summary-prefix').html(Drupal.t('Show'));
this.$node.trigger('completeSlideUp');
this.animating = false;
}
});
Drupal.behaviors.collapse = {
attach: function (context, settings) {
if (isDetailsSupported) {
return;
}
var $collapsibleDetails = $(context).find('details').once('collapse');
if ($collapsibleDetails.length) {
for (var i = 0; i < $collapsibleDetails.length; i++) {
CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i], settings.collapsibleDetails));
}
}
}
};
// Expose constructor in the public space.
Drupal.CollapsibleDetails = CollapsibleDetails;
})(jQuery, Drupal);