Commit e6e29ac1 authored by Dries's avatar Dries

- Patch #444344 by kkaefer, sun, Rob Loach: this change introduces a jQuery...

- 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.
parent 41dca3c4
......@@ -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']) {
......
......@@ -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');
});
}
};
......
......@@ -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.
......
......@@ -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)'))
);
});
}
};
......
......@@ -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
......
......@@ -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 () {
......
......@@ -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]);
});
}
}
};
......
......@@ -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 () {
......
......@@ -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) {
......
......@@ -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;
......
......@@ -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,
......
......@@ -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');
});
}
};
......
......@@ -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) {
......
......@@ -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) {
......
......@@ -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);
}
});
}
......
......@@ -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) {
......
......@@ -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',
......
......@@ -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;
});
}
};
......
......@@ -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();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment