Commit 5371104a authored by Dries's avatar Dries

- Patch #316225 by sun et al: allow behaviors to detach from AHAH/AJAX.

parent 068febde
...@@ -15,17 +15,19 @@ ...@@ -15,17 +15,19 @@
/** /**
* Attaches the ahah behavior to each ahah form element. * Attaches the ahah behavior to each ahah form element.
*/ */
Drupal.behaviors.ahah = function(context) { Drupal.behaviors.ahah = {
for (var base in Drupal.settings.ahah) { attach: function(context) {
if (!$('#'+ base + '.ahah-processed').size()) { for (var base in Drupal.settings.ahah) {
var element_settings = Drupal.settings.ahah[base]; if (!$('#'+ base + '.ahah-processed').size()) {
var element_settings = Drupal.settings.ahah[base];
$(element_settings.selector).each(function() {
element_settings.element = this; $(element_settings.selector).each(function() {
var ahah = new Drupal.ahah(base, element_settings); element_settings.element = this;
}); var ahah = new Drupal.ahah(base, element_settings);
});
$('#'+ base).addClass('ahah-processed');
$('#'+ base).addClass('ahah-processed');
}
} }
} }
}; };
......
...@@ -3,19 +3,21 @@ ...@@ -3,19 +3,21 @@
/** /**
* Attaches the autocomplete behavior to all required fields. * Attaches the autocomplete behavior to all required fields.
*/ */
Drupal.behaviors.autocomplete = function (context) { Drupal.behaviors.autocomplete = {
var acdb = []; attach: function(context) {
$('input.autocomplete:not(.autocomplete-processed)', context).each(function () { var acdb = [];
var uri = this.value; $('input.autocomplete:not(.autocomplete-processed)', context).each(function () {
if (!acdb[uri]) { var uri = this.value;
acdb[uri] = new Drupal.ACDB(uri); if (!acdb[uri]) {
} acdb[uri] = new Drupal.ACDB(uri);
var input = $('#' + this.id.substr(0, this.id.length - 13)) }
.attr('autocomplete', 'OFF')[0]; var input = $('#' + this.id.substr(0, this.id.length - 13))
$(input.form).submit(Drupal.autocompleteSubmit); .attr('autocomplete', 'OFF')[0];
new Drupal.jsAC(input, acdb[uri]); $(input.form).submit(Drupal.autocompleteSubmit);
$(this).addClass('autocomplete-processed'); new Drupal.jsAC(input, acdb[uri]);
}); $(this).addClass('autocomplete-processed');
});
}
}; };
/** /**
......
...@@ -3,36 +3,38 @@ ...@@ -3,36 +3,38 @@
/** /**
* Attaches the batch behavior to progress bars. * Attaches the batch behavior to progress bars.
*/ */
Drupal.behaviors.batch = function (context) { Drupal.behaviors.batch = {
// This behavior attaches by ID, so is only valid once on a page. attach: function(context) {
if ($('#progress.batch-processed').size()) { // This behavior attaches by ID, so is only valid once on a page.
return; if ($('#progress.batch-processed').size()) {
} return;
$('#progress', context).addClass('batch-processed').each(function () { }
var holder = this; $('#progress', context).addClass('batch-processed').each(function () {
var uri = Drupal.settings.batch.uri; var holder = this;
var initMessage = Drupal.settings.batch.initMessage; var uri = Drupal.settings.batch.uri;
var errorMessage = Drupal.settings.batch.errorMessage; var initMessage = Drupal.settings.batch.initMessage;
var errorMessage = Drupal.settings.batch.errorMessage;
// Success: redirect to the summary. // Success: redirect to the summary.
var updateCallback = function (progress, status, pb) { var updateCallback = function (progress, status, pb) {
if (progress == 100) { if (progress == 100) {
pb.stopMonitoring(); pb.stopMonitoring();
window.location = uri+'&op=finished'; window.location = uri+'&op=finished';
} }
}; };
var errorCallback = function (pb) { var errorCallback = function (pb) {
var div = document.createElement('p'); var div = document.createElement('p');
div.className = 'error'; div.className = 'error';
$(div).html(errorMessage); $(div).html(errorMessage);
$(holder).prepend(div); $(holder).prepend(div);
$('#wait').hide(); $('#wait').hide();
}; };
var progress = new Drupal.progressBar('updateprogress', updateCallback, "POST", errorCallback); var progress = new Drupal.progressBar('updateprogress', updateCallback, "POST", errorCallback);
progress.setProgress(-1, initMessage); progress.setProgress(-1, initMessage);
$(holder).append(progress.element); $(holder).append(progress.element);
progress.startMonitoring(uri+'&op=do', 10); progress.startMonitoring(uri+'&op=do', 10);
}); });
}
}; };
...@@ -50,28 +50,30 @@ Drupal.collapseScrollIntoView = function (node) { ...@@ -50,28 +50,30 @@ Drupal.collapseScrollIntoView = function (node) {
} }
}; };
Drupal.behaviors.collapse = function (context) { Drupal.behaviors.collapse = {
$('fieldset.collapsible > legend:not(.collapse-processed)', context).each(function() { attach: function(context) {
var fieldset = $(this.parentNode); $('fieldset.collapsible > legend:not(.collapse-processed)', context).each(function() {
// Expand if there are errors inside var fieldset = $(this.parentNode);
if ($('input.error, textarea.error, select.error', fieldset).size() > 0) { // Expand if there are errors inside
fieldset.removeClass('collapsed'); if ($('input.error, textarea.error, select.error', fieldset).size() > 0) {
} fieldset.removeClass('collapsed');
}
// Turn the legend into a clickable link and wrap the contents of the fieldset // Turn the legend into a clickable link and wrap the contents of the fieldset
// in a div for easier animation // in a div for easier animation
var text = this.innerHTML; var text = this.innerHTML;
$(this).empty().append($('<a href="#">'+ text +'</a>').click(function() { $(this).empty().append($('<a href="#">'+ text +'</a>').click(function() {
var fieldset = $(this).parents('fieldset:first')[0]; var fieldset = $(this).parents('fieldset:first')[0];
// Don't animate multiple times // Don't animate multiple times
if (!fieldset.animating) { if (!fieldset.animating) {
fieldset.animating = true; fieldset.animating = true;
Drupal.toggleFieldset(fieldset); Drupal.toggleFieldset(fieldset);
} }
return false; return false;
})) }))
.after($('<div class="fieldset-wrapper"></div>') .after($('<div class="fieldset-wrapper"></div>')
.append(fieldset.children(':not(legend):not(.action)'))) .append(fieldset.children(':not(legend):not(.action)')))
.addClass('collapse-processed'); .addClass('collapse-processed');
}); });
}
}; };
...@@ -12,10 +12,15 @@ Drupal.jsEnabled = document.getElementsByTagName && document.createElement && do ...@@ -12,10 +12,15 @@ Drupal.jsEnabled = document.getElementsByTagName && document.createElement && do
* *
* Behaviors are event-triggered actions that attach to page elements, enhancing * Behaviors are event-triggered actions that attach to page elements, enhancing
* default non-Javascript UIs. Behaviors are registered in the Drupal.behaviors * default non-Javascript UIs. Behaviors are registered in the Drupal.behaviors
* object as follows: * object using the method 'attach' and optionally also 'detach' as follows:
* @code * @code
* Drupal.behaviors.behaviorName = function () { * Drupal.behaviors.behaviorName = {
* ... * attach: function(context) {
* ...
* },
* detach: function(context) {
* ...
* }
* }; * };
* @endcode * @endcode
* *
...@@ -38,7 +43,38 @@ Drupal.attachBehaviors = function(context) { ...@@ -38,7 +43,38 @@ Drupal.attachBehaviors = function(context) {
context = context || document; context = context || document;
// Execute all of them. // Execute all of them.
jQuery.each(Drupal.behaviors, function() { jQuery.each(Drupal.behaviors, function() {
this(context); if (jQuery.isFunction(this.attach)) {
this.attach(context);
}
});
};
/**
* Detach registered behaviors from a page element.
*
* Developers implementing AHAH/AJAX in their solutions should call this
* function before page content is about to be removed, feeding in an element
* to be processed, in order to allow special behaviors to detach from the
* content.
*
* Such implementations should look for the class name that was added in their
* corresponding Drupal.behaviors.behaviorName.attach implementation, i.e.
* behaviorName-processed, to ensure the behavior is detached only from
* previously processed elements.
*
* @param context
* An element to detach behaviors from. If none is given, the document element
* is used.
*
* @see Drupal.attachBehaviors
*/
Drupal.detachBehaviors = function(context) {
context = context || document;
// Execute all of them.
jQuery.each(Drupal.behaviors, function() {
if (jQuery.isFunction(this.detach)) {
this.detach(context);
}
}); });
}; };
......
// $Id$ // $Id$
Drupal.behaviors.multiselectSelector = function() { Drupal.behaviors.multiselectSelector = {
// Automatically selects the right radio button in a multiselect control. attach: function(context) {
$('.multiselect select:not(.multiselectSelector-processed)') // Automatically selects the right radio button in a multiselect control.
.addClass('multiselectSelector-processed').change(function() { $('.multiselect select:not(.multiselectSelector-processed)', context)
$('.multiselect input:radio[value="'+ this.id.substr(5) +'"]') .addClass('multiselectSelector-processed').change(function() {
.attr('checked', true); $('.multiselect input:radio[value="'+ this.id.substr(5) +'"]')
}); .attr('checked', true);
});
}
}; };
...@@ -11,18 +11,20 @@ ...@@ -11,18 +11,20 @@
* overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods. * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods.
* See blocks.js for an example of adding additional functionality to tableDrag. * See blocks.js for an example of adding additional functionality to tableDrag.
*/ */
Drupal.behaviors.tableDrag = function(context) { Drupal.behaviors.tableDrag = {
for (var base in Drupal.settings.tableDrag) { attach: function(context) {
if (!$('#' + base + '.tabledrag-processed', context).size()) { for (var base in Drupal.settings.tableDrag) {
var tableSettings = Drupal.settings.tableDrag[base]; if (!$('#' + base + '.tabledrag-processed', context).size()) {
var tableSettings = Drupal.settings.tableDrag[base];
$('#' + base).filter(':not(.tabledrag-processed)').each(function() {
// Create the new tableDrag instance. Save in the Drupal variable $('#' + base).filter(':not(.tabledrag-processed)').each(function() {
// to allow other scripts access to the object. // Create the new tableDrag instance. Save in the Drupal variable
Drupal.tableDrag[base] = new Drupal.tableDrag(this, tableSettings); // to allow other scripts access to the object.
}); Drupal.tableDrag[base] = new Drupal.tableDrag(this, tableSettings);
});
$('#' + base).addClass('tabledrag-processed'); $('#' + base).addClass('tabledrag-processed');
}
} }
} }
}; };
......
...@@ -6,108 +6,110 @@ Drupal.tableHeaderDoScroll = function() { ...@@ -6,108 +6,110 @@ Drupal.tableHeaderDoScroll = function() {
} }
}; };
Drupal.behaviors.tableHeader = function (context) { Drupal.behaviors.tableHeader = {
// This breaks in anything less than IE 7. Prevent it from running. attach: function(context) {
if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 7) { // This breaks in anything less than IE 7. Prevent it from running.
return; if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 7) {
} return;
}
// Keep track of all cloned table headers. // Keep track of all cloned table headers.
var headers = []; var headers = [];
$('table.sticky-enabled thead:not(.tableHeader-processed)', context).each(function () { $('table.sticky-enabled thead:not(.tableHeader-processed)', context).each(function () {
// Clone thead so it inherits original jQuery properties. // Clone thead so it inherits original jQuery properties.
var headerClone = $(this).clone(true).insertBefore(this.parentNode).wrap('<table class="sticky-header"></table>').parent().css({ var headerClone = $(this).clone(true).insertBefore(this.parentNode).wrap('<table class="sticky-header"></table>').parent().css({
position: 'fixed', position: 'fixed',
top: '0px' top: '0px'
}); });
headerClone = $(headerClone)[0]; headerClone = $(headerClone)[0];
headers.push(headerClone); headers.push(headerClone);
// Store parent table. // Store parent table.
var table = $(this).parent('table')[0]; var table = $(this).parent('table')[0];
headerClone.table = table; headerClone.table = table;
// Finish initialzing header positioning. // Finish initialzing header positioning.
tracker(headerClone); tracker(headerClone);
$(table).addClass('sticky-table'); $(table).addClass('sticky-table');
$(this).addClass('tableHeader-processed'); $(this).addClass('tableHeader-processed');
}); });
// Define the anchor holding var. // Define the anchor holding var.
var prevAnchor = ''; var prevAnchor = '';
// Track positioning and visibility. // Track positioning and visibility.
function tracker(e) { function tracker(e) {
// Save positioning data. // Save positioning data.
var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight; var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
if (e.viewHeight != viewHeight) { if (e.viewHeight != viewHeight) {
e.viewHeight = viewHeight; e.viewHeight = viewHeight;
e.vPosition = $(e.table).offset().top - 4; e.vPosition = $(e.table).offset().top - 4;
e.hPosition = $(e.table).offset().left; e.hPosition = $(e.table).offset().left;
e.vLength = e.table.clientHeight - 100; e.vLength = e.table.clientHeight - 100;
// Resize header and its cell widths. // Resize header and its cell widths.
var parentCell = $('th', e.table); var parentCell = $('th', e.table);
$('th', e).each(function(index) { $('th', e).each(function(index) {
var cellWidth = parentCell.eq(index).css('width'); var cellWidth = parentCell.eq(index).css('width');
// Exception for IE7. // Exception for IE7.
if (cellWidth == 'auto') { if (cellWidth == 'auto') {
cellWidth = parentCell.get(index).clientWidth +'px'; cellWidth = parentCell.get(index).clientWidth +'px';
} }
$(this).css('width', cellWidth); $(this).css('width', cellWidth);
}); });
$(e).css('width', $(e.table).css('width')); $(e).css('width', $(e.table).css('width'));
} }
// Track horizontal positioning relative to the viewport and set visibility. // Track horizontal positioning relative to the viewport and set visibility.
var hScroll = document.documentElement.scrollLeft || document.body.scrollLeft; var hScroll = document.documentElement.scrollLeft || document.body.scrollLeft;
var vOffset = (document.documentElement.scrollTop || document.body.scrollTop) - e.vPosition; var vOffset = (document.documentElement.scrollTop || document.body.scrollTop) - e.vPosition;
var visState = (vOffset > 0 && vOffset < e.vLength) ? 'visible' : 'hidden'; var visState = (vOffset > 0 && vOffset < e.vLength) ? 'visible' : 'hidden';
$(e).css({left: -hScroll + e.hPosition +'px', visibility: visState}); $(e).css({left: -hScroll + e.hPosition +'px', visibility: visState});
// Check the previous anchor to see if we need to scroll to make room for the header. // Check the previous anchor to see if we need to scroll to make room for the header.
// Get the height of the header table and scroll up that amount. // Get the height of the header table and scroll up that amount.
if (prevAnchor != location.hash) { if (prevAnchor != location.hash) {
if (location.hash != '') { if (location.hash != '') {
var scrollLocation = $('td' + location.hash).offset().top - $(e).height(); var scrollLocation = $('td' + location.hash).offset().top - $(e).height();
$('body, html').scrollTop(scrollLocation); $('body, html').scrollTop(scrollLocation);
}
prevAnchor = location.hash;
} }
prevAnchor = location.hash;
} }
}
// Only attach to scrollbars once, even if Drupal.attachBehaviors is called
// multiple times.
if (!$('body').hasClass('tableHeader-processed')) {
$('body').addClass('tableHeader-processed');
$(window).scroll(Drupal.tableHeaderDoScroll);
$(document.documentElement).scroll(Drupal.tableHeaderDoScroll);
}
// Track scrolling.
Drupal.tableHeaderOnScroll = function() {
$(headers).each(function () {
tracker(this);
});
};
// Track resizing. // Only attach to scrollbars once, even if Drupal.attachBehaviors is called
var time = null; // multiple times.
var resize = function () { if (!$('body').hasClass('tableHeader-processed')) {
// Ensure minimum time between adjustments. $('body').addClass('tableHeader-processed');
if (time) { $(window).scroll(Drupal.tableHeaderDoScroll);
return; $(document.documentElement).scroll(Drupal.tableHeaderDoScroll);
} }
time = setTimeout(function () {
$('table.sticky-header').each(function () { // Track scrolling.
// Force cell width calculation. Drupal.tableHeaderOnScroll = function() {
this.viewHeight = 0; $(headers).each(function () {
tracker(this); tracker(this);
}); });
// Reset timer. };
time = null;
}, 250); // Track resizing.
}; var time = null;
$(window).resize(resize); var resize = function () {
// Ensure minimum time between adjustments.
if (time) {
return;
}
time = setTimeout(function () {
$('table.sticky-header').each(function () {
// Force cell width calculation.
this.viewHeight = 0;
tracker(this);
});
// Reset timer.
time = null;
}, 250);
};
$(window).resize(resize);
}
}; };
// $Id$ // $Id$
Drupal.behaviors.tableSelect = function (context) { Drupal.behaviors.tableSelect = {
$('form table:has(th.select-all):not(.tableSelect-processed)', context).each(Drupal.tableSelect); attach: function(context) {
$('form table:has(th.select-all):not(.tableSelect-processed)', context).each(Drupal.tableSelect);
}
}; };
Drupal.tableSelect = function() { Drupal.tableSelect = function() {
......
...@@ -5,92 +5,94 @@ ...@@ -5,92 +5,94 @@
* *
* Note: depends on resizable textareas. * Note: depends on resizable textareas.
*/ */
Drupal.behaviors.teaser = function(context) { Drupal.behaviors.teaser = {
// This breaks in Konqueror. Prevent it from running. attach: function(context) {
if (/KDE/.test(navigator.vendor)) { // This breaks in Konqueror. Prevent it from running.
return; if (/KDE/.test(navigator.vendor)) {
} return;
}
$('textarea.teaser:not(.teaser-processed)', context).each(function() { $('textarea.teaser:not(.teaser-processed)', context).each(function() {
var teaser = $(this).addClass('teaser-processed'); var teaser = $(this).addClass('teaser-processed');
// Move teaser textarea before body, and remove its form-item wrapper. // Move teaser textarea before body, and remove its form-item wrapper.
var body = $('#'+ Drupal.settings.teaser[this.id]); var body = $('#'+ Drupal.settings.teaser[this.id]);
var checkbox = $('#'+ Drupal.settings.teaserCheckbox[this.id]).parent(); var checkbox = $('#'+ Drupal.settings.teaserCheckbox[this.id]).parent();
var checked = $(checkbox).children('input').attr('checked') ? true : false; var checked = $(checkbox).children('input').attr('checked') ? true : false;
var parent = teaser[0].parentNode; var parent = teaser[0].parentNode;
$(body).before(teaser); $(body).before(teaser);
$(parent).remove(); $(parent).remove();
function trim(text) { function trim(text) {
return text.replace(/^\s+/g, '').replace(/\s+$/g, ''); return text.replace(/^\s+/g, '').replace(/\s+$/g, '');
} }
// Join the teaser back to the body. // Join the teaser back to the body.
function join_teaser() { function join_teaser() {
if (teaser.val()) { if (teaser.val()) {
body.val(trim(teaser.val()) +'\r\n\r\n'+ trim(body.val())); body.val(trim(teaser.val()) +'\r\n\r\n'+ trim(body.val()));
}
// Empty, hide and disable teaser.
teaser[0].value = '';
$(teaser).attr('disabled', 'disabled');
$(teaser).parent().slideUp('fast');
// Change label.
$(this).val(Drupal.t('Split summary at cursor'));
// Hide separate teaser checkbox.
$(checkbox).hide();
// Force a hidden checkbox to be checked (to ensure that the body is
// correctly processed on form submit when teaser/body are in joined
// state), and remember the current checked status.
checked = $(checkbox).children('input').attr('checked') ? true : false;
$(checkbox).children('input').attr('checked', true);
} }
// Empty, hide and disable teaser.
teaser[0].value = '';
$(teaser).attr('disabled', 'disabled');
$(teaser).parent().slideUp('fast');
// Change label.
$(this).val(Drupal.t('Split summary at cursor'));
// Hide separate teaser checkbox.
$(checkbox).hide();
// Force a hidden checkbox to be checked (to ensure that the body is
// correctly processed on form submit when teaser/body are in joined
// state), and remember the current checked status.
checked = $(checkbox).children('input').attr('checked') ? true : false;
$(checkbox).children('input').attr('checked', true);
}