- Patch #444344 by kkaefer, sun, Rob Loach: this change introduces a jQuery .once() method which streamlines the way behavior functions work. Previously, we had to manually ensure that an element is only initialized once. Usually, this happens by adding classes and selecting only those elements which do not have that class. However, this process can be separated out into a jQuery ‘filtering’ function which does all the grunt work.

merge-requests/26/head
Dries Buytaert 2009-08-31 05:51:08 +00:00
parent 41dca3c4e0
commit e6e29ac1b0
19 changed files with 73 additions and 94 deletions

View File

@ -2960,8 +2960,9 @@ function drupal_add_js($data = NULL, $options = NULL) {
'preprocess' => TRUE,
),
);
// jQuery itself is registered as a library.
// Register all required libraries.
drupal_add_library('system', 'jquery');
drupal_add_library('system', 'once');
}
switch ($options['type']) {

View File

@ -7,7 +7,7 @@
Drupal.behaviors.autocomplete = {
attach: function (context, settings) {
var acdb = [];
$('input.autocomplete:not(.autocomplete-processed)', context).each(function () {
$('input.autocomplete', context).once('autocomplete', function () {
var uri = this.value;
if (!acdb[uri]) {
acdb[uri] = new Drupal.ACDB(uri);
@ -16,7 +16,6 @@ Drupal.behaviors.autocomplete = {
.attr('autocomplete', 'OFF')[0];
$(input.form).submit(Drupal.autocompleteSubmit);
new Drupal.jsAC(input, acdb[uri]);
$(this).addClass('autocomplete-processed');
});
}
};

View File

@ -6,11 +6,7 @@
*/
Drupal.behaviors.batch = {
attach: function (context, settings) {
// This behavior attaches by ID, so is only valid once on a page.
if ($('#progress.batch-processed').size()) {
return;
}
$('#progress', context).addClass('batch-processed').each(function () {
$('#progress', context).once('batch', function () {
var holder = $(this);
// Success: redirect to the summary.

View File

@ -53,7 +53,7 @@ Drupal.collapseScrollIntoView = function (node) {
Drupal.behaviors.collapse = {
attach: function (context, settings) {
$('fieldset.collapsible > legend:not(.collapse-processed)', context).each(function () {
$('fieldset.collapsible > legend', context).once('collapse', function () {
var fieldset = $(this.parentNode);
// Expand if there are errors inside
if ($('input.error, textarea.error, select.error', fieldset).size() > 0) {
@ -81,9 +81,10 @@ Drupal.behaviors.collapse = {
return false;
}))
.append(summary)
.after($('<div class="fieldset-wrapper"></div>')
.append(fieldset.children(':not(legend):not(.action)'))
).addClass('collapse-processed');
.after(
$('<div class="fieldset-wrapper"></div>')
.append(fieldset.children(':not(legend):not(.action)'))
);
});
}
};

View File

@ -12,7 +12,6 @@ if ($ === undefined) {
};
}
(function ($) {
/**
@ -38,10 +37,15 @@ if ($ === undefined) {
* loaded, feeding in an element to be processed, in order to attach all
* behaviors to the new content.
*
* Behaviors should use a class in the form behaviorName-processed to ensure
* the behavior is attached only once to a given element. (Doing so enables
* the reprocessing of given elements, which may be needed on occasion despite
* the ability to limit behavior attachment to a particular element.)
* Behaviors should use
* @code
* $(selector).once('behavior-name', function () {
* ...
* });
* @endcode
* to ensure the behavior is attached only once to a given element. (Doing so
* enables the reprocessing of given elements, which may be needed on occasion
* despite the ability to limit behavior attachment to a particular element.)
*
* @param context
* An element to attach behaviors to. If none is given, the document element

View File

@ -61,8 +61,7 @@ Drupal.behaviors.formUpdated = {
Drupal.behaviors.multiselectSelector = {
attach: function (context, settings) {
// Automatically selects the right radio button in a multiselect control.
$('.multiselect select:not(.multiselectSelector-processed)', context)
.addClass('multiselectSelector-processed').change(function () {
$('.multiselect select', context).once('multiselect').change(function () {
$('.multiselect input:radio[value="' + this.id.substr(5) + '"]')
.attr('checked', true);
});
@ -75,8 +74,7 @@ Drupal.behaviors.multiselectSelector = {
*/
Drupal.behaviors.filterGuidelines = {
attach: function (context) {
$('.filter-guidelines:not(.filter-guidelines-processed)', context)
.addClass('filter-guidelines-processed')
$('.filter-guidelines', context).once('filter-guidelines')
.find('label').hide()
.parents('.filter-wrapper').find('select.filter-list')
.bind('change', function () {

View File

@ -15,17 +15,11 @@
Drupal.behaviors.tableDrag = {
attach: function (context, settings) {
for (var base in settings.tableDrag) {
if (!$('#' + base + '.tabledrag-processed', context).size()) {
var tableSettings = settings.tableDrag[base];
$('#' + base).filter(':not(.tabledrag-processed)').each(function () {
// Create the new tableDrag instance. Save in the Drupal variable
// to allow other scripts access to the object.
Drupal.tableDrag[base] = new Drupal.tableDrag(this, tableSettings);
});
$('#' + base).addClass('tabledrag-processed');
}
$('#' + base, context).once('tabledrag', function () {
// Create the new tableDrag instance. Save in the Drupal variable
// to allow other scripts access to the object.
Drupal.tableDrag[base] = new Drupal.tableDrag(this, settings.tableDrag[base]);
});
}
}
};

View File

@ -17,7 +17,7 @@ Drupal.behaviors.tableHeader = {
// Keep track of all cloned table headers.
var headers = [];
$('table.sticky-enabled thead:not(.tableHeader-processed)', context).each(function () {
$('table.sticky-enabled thead', context).once('tableheader', function () {
// Clone thead so it inherits original jQuery properties.
var headerClone = $(this).clone(true).insertBefore(this.parentNode).wrap('<table class="sticky-header"></table>').parent().css({
position: 'fixed',
@ -34,7 +34,6 @@ Drupal.behaviors.tableHeader = {
tracker(headerClone);
$(table).addClass('sticky-table');
$(this).addClass('tableHeader-processed');
});
// Define the anchor holding var.
@ -81,11 +80,10 @@ Drupal.behaviors.tableHeader = {
// Only attach to scrollbars once, even if Drupal.attachBehaviors is called
// multiple times.
if (!$('body').hasClass('tableHeader-processed')) {
$('body').addClass('tableHeader-processed');
$('body').once(function () {
$(window).scroll(Drupal.tableHeaderDoScroll);
$(document.documentElement).scroll(Drupal.tableHeaderDoScroll);
}
});
// Track scrolling.
Drupal.tableHeaderOnScroll = function () {

View File

@ -3,7 +3,7 @@
Drupal.behaviors.tableSelect = {
attach: function (context, settings) {
$('form table:has(th.select-all):not(.tableSelect-processed)', context).each(Drupal.tableSelect);
$('form table:has(th.select-all)', context).once('table-select', Drupal.tableSelect);
}
};
@ -56,7 +56,6 @@ Drupal.tableSelect = function () {
// Keep track of the last checked checkbox.
lastChecked = e.target;
});
$(this).addClass('tableSelect-processed');
};
Drupal.tableSelectRange = function (from, to, state) {

View File

@ -3,20 +3,16 @@
Drupal.behaviors.textarea = {
attach: function (context, settings) {
$('textarea.resizable:not(.textarea-processed)', context).each(function () {
// Avoid non-processed teasers.
if ($(this).is(('textarea.teaser:not(.teaser-processed)'))) {
return false;
}
var textarea = $(this).addClass('textarea-processed'), staticOffset = null;
$('textarea.resizable', context).once('textarea', function () {
// When wrapping the text area, work around an IE margin bug. See:
// http://jaspan.com/ie-inherited-margin-bug-form-elements-and-haslayout
$(this).wrap('<div class="resizable-textarea"><span></span></div>')
.parent().append($('<div class="grippie"></div>').mousedown(startDrag));
var staticOffset = null;
var textarea = $(this).wrap('<div class="resizable-textarea"><span></span></div>');
var grippie = $('<div class="grippie"></div>').mousedown(startDrag);
var grippie = $('div.grippie', $(this).parent())[0];
grippie.style.marginRight = (grippie.offsetWidth - $(this)[0].offsetWidth) + 'px';
grippie
.insertAfter(textarea)
.css('margin-right', grippie.width() - textarea.width());
function startDrag(e) {
staticOffset = textarea.height() - e.pageY;

View File

@ -6,7 +6,7 @@
*/
Drupal.behaviors.setTimezone = {
attach: function (context, settings) {
$('select.timezone-detect:not(.timezone-processed)', context).addClass('timezone-processed').each(function () {
$('select.timezone-detect', context).once('timezone', function () {
var dateString = Date();
// In some client environments, date strings include a time zone
// abbreviation, between 3 and 5 letters enclosed in parentheses,

View File

@ -15,7 +15,7 @@
*/
Drupal.behaviors.verticalTabs = {
attach: function (context) {
$('.vertical-tabs-panes:not(.vertical-tabs-processed)', context).each(function () {
$('.vertical-tabs-panes', context).once('vertical-tabs', function () {
var focusID = $(':hidden.vertical-tabs-active-tab', this).val();
var focus;
// Create the tab column.
@ -42,7 +42,7 @@ Drupal.behaviors.verticalTabs = {
focus = $('> .vertical-tabs-pane:first', this);
}
focus.data('verticalTab').focus();
}).addClass('vertical-tabs-processed');
});
}
};

View File

@ -51,7 +51,7 @@ Drupal.behaviors.blockDrag = {
};
// Add the behavior to each region select list.
$('select.block-region-select:not(.blockregionselect-processed)', context).each(function () {
$('select.block-region-select', context).once('block-region-select', function () {
$(this).change(function (event) {
// Make our new row and select field.
var row = $(this).parents('tr:first');
@ -82,7 +82,6 @@ Drupal.behaviors.blockDrag = {
// Remove focus from selectbox.
select.get(0).blur();
});
$(this).addClass('blockregionselect-processed');
});
var checkEmptyRegions = function (table, rowObject) {

View File

@ -4,10 +4,10 @@
Drupal.behaviors.color = {
attach: function (context, settings) {
// This behavior attaches by ID, so is only valid once on a page.
if ($('#color_scheme_form .color-form.color-processed').size()) {
var form = $('#system-theme-settings .color-form', context).once('color');
if (form.length == 0) {
return;
}
var form = $('#system-theme-settings .color-form', context);
var inputs = [];
var hooks = [];
var locks = [];
@ -24,9 +24,7 @@ Drupal.behaviors.color = {
}
// Build a preview.
$('#preview:not(.color-processed)')
.append('<div id="gradient"></div>')
.addClass('color-processed');
$('#preview').once('color').append('<div id="gradient"></div>');
var gradient = $('#preview #gradient');
var h = parseInt(gradient.css('height')) / 10;
for (i = 0; i < h; ++i) {

View File

@ -6,9 +6,7 @@ Drupal.behaviors.comment = {
$.each(['name', 'homepage', 'mail'], function () {
var cookie = Drupal.comment.getCookie('comment_info_' + this);
if (cookie) {
$('#comment-form input[name=' + this + ']:not(.comment-processed)', context)
.val(cookie)
.addClass('comment-processed');
$('#comment-form input[name=' + this + ']', context).once('comment').val(cookie);
}
});
}

View File

@ -32,7 +32,7 @@ Drupal.behaviors.cleanURLsSettingsCheck = {
// This behavior attaches by ID, so is only valid once on a page.
// Also skip if we are on an install page, as Drupal.cleanURLsInstallCheck will handle
// the processing.
if (!($('#edit-clean-url').size()) || $('.clean-url-processed, #edit-clean-url.install').size()) {
if (!($('#edit-clean-url').length) || $('#edit-clean-url.install').once('clean-url').length) {
return;
}
var url = settings.basePath + 'admin/config/search/clean-urls/check';
@ -44,7 +44,6 @@ Drupal.behaviors.cleanURLsSettingsCheck = {
location = settings.basePath +"admin/config/search/clean-urls";
}
});
$('#clean-url').addClass('clean-url-processed');
}
};
@ -68,7 +67,6 @@ Drupal.cleanURLsInstallCheck = function () {
$('#edit-clean-url').attr('value', 1);
}
});
$('#edit-clean-url').addClass('clean-url-processed');
};
/**
@ -79,21 +77,17 @@ Drupal.cleanURLsInstallCheck = function () {
Drupal.behaviors.copyFieldValue = {
attach: function (context, settings) {
for (var sourceId in settings.copyFieldValue) {
// Get the list of target fields.
targetIds = settings.copyFieldValue[sourceId];
if (!$('#'+ sourceId + '.copy-field-values-processed', context).size()) {
$('#' + sourceId, context).once('copy-field-values').bind('blur', function () {
// Get the list of target fields.
var targetIds = settings.copyFieldValue[sourceId];
// Add the behavior to update target fields on blur of the primary field.
sourceField = $('#' + sourceId);
sourceField.bind('blur', function () {
for (var delta in targetIds) {
var targetField = $('#'+ targetIds[delta]);
if (targetField.val() == '') {
targetField.val(this.value);
}
for (var delta in targetIds) {
var targetField = $('#' + targetIds[delta]);
if (targetField.val() == '') {
targetField.val(this.value);
}
});
sourceField.addClass('copy-field-values-processed');
}
}
});
}
}
};
@ -104,12 +98,12 @@ Drupal.behaviors.copyFieldValue = {
Drupal.behaviors.dateTime = {
attach: function (context, settings) {
// Show/hide custom format depending on the select's value.
$('select.date-format:not(.date-time-processed)', context).change(function () {
$(this).addClass('date-time-processed').parents('div.date-container').children('div.custom-container')[$(this).val() == 'custom' ? 'show' : 'hide']();
$('select.date-format', context).once('date-time').change(function () {
$(this).parents('div.date-container').children('div.custom-container')[$(this).val() == 'custom' ? 'show' : 'hide']();
});
// Attach keyup handler to custom format inputs.
$('input.custom-format:not(.date-time-processed)', context).addClass('date-time-processed').keyup(function () {
$('input.custom-format', context).once('date-time').keyup(function () {
var input = $(this);
var url = settings.dateTime.lookup + (settings.dateTime.lookup.match(/\?q=/) ? '&format=' : '?format=') + encodeURIComponent(input.val());
$.getJSON(url, function (data) {

View File

@ -933,6 +933,16 @@ function system_library() {
),
);
// jQuery Once.
$libraries['once'] = array(
'title' => 'jQuery Once',
'website' => 'http://plugins.jquery.com/project/once',
'version' => '1.2',
'js' => array(
'misc/jquery.once.js' => array('weight' => JS_LIBRARY - 19),
),
);
// jQuery Form Plugin.
$libraries['form'] = array(
'title' => 'jQuery Form Plugin',

View File

@ -8,18 +8,12 @@ Drupal.behaviors.admin = {
attach: function() {
// Set the intial state of the toolbar.
$('#toolbar:not(.processed)').each(function() {
Drupal.admin.toolbar.init();
$(this).addClass('processed');
});
$('#toolbar', context).once('toolbar', Drupal.admin.toolbar.init);
// Toggling of admin shortcuts visibility.
$('#toolbar span.toggle:not(.processed)').each(function() {
$(this).click(function() {
Drupal.admin.toolbar.toggle();
return false;
});
$(this).addClass('processed');
$('#toolbar span.toggle', context).once('toolbar-toggle').click(function() {
Drupal.admin.toolbar.toggle();
return false;
});
}
};

View File

@ -8,8 +8,8 @@
Drupal.behaviors.password = {
attach: function (context, settings) {
var translate = settings.password;
$('input.password-field:not(.password-processed)', context).each(function () {
var passwordInput = $(this).addClass('password-processed');
$('input.password-field', context).once('password', function () {
var passwordInput = $(this);
var innerWrapper = $(this).parent();
var outerWrapper = $(this).parent().parent();