Commit 7fd9aa5c authored by Steven Wittens's avatar Steven Wittens

#69786: jQuery JavaScript Library in Drupal core

parent 291c7b8b
......@@ -8,6 +8,7 @@ Drupal x.x.x, xxxx-xx-xx (development version)
* automatically generate the database configuration file
* install pre-made 'install profiles' or distributions
* import the database structure with automatic table prefixing
- included the jQuery JavaScript library and converted all core JavaScript to use it
- introduced the ability to alter mail sent from system
- moved core modules to their own directories to allow additional flexibility
- added support for different cache backends
......
......@@ -1369,7 +1369,8 @@ function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer
$javascript[$scope] = array('core' => array(), 'module' => array(), 'theme' => array(), 'setting' => array(), 'inline' => array());
if (empty($javascript['header']['core']['misc/drupal.js'])) {
drupal_add_js('misc/drupal.js', 'core');
drupal_add_js('misc/jquery.js', 'core', 'header', FALSE, $cache);
drupal_add_js('misc/drupal.js', 'core', 'header', FALSE, $cache);
}
}
......
This diff is collapsed.
// $Id$
if (isJsEnabled()) {
addLoadEvent(collapseAutoAttach);
}
function collapseAutoAttach() {
var fieldsets = document.getElementsByTagName('fieldset');
var legend, fieldset;
for (var i = 0; fieldset = fieldsets[i]; i++) {
if (!hasClass(fieldset, 'collapsible')) {
continue;
}
legend = fieldset.getElementsByTagName('legend');
if (legend.length == 0) {
continue;
}
legend = legend[0];
Drupal.collapseAutoAttach = function () {
$('fieldset.collapsible legend').each(function () {
// Turn the legend into clickable link
var a = document.createElement('a');
a.href = '#';
a.onclick = function() {
toggleClass(this.parentNode.parentNode, 'collapsed');
if (!hasClass(this.parentNode.parentNode, 'collapsed')) {
collapseScrollIntoView(this.parentNode.parentNode);
if (typeof textAreaAutoAttach != 'undefined') {
// Add the grippie to a textarea in a collapsed fieldset.
textAreaAutoAttach(null, this.parentNode.parentNode);
$(a)
.click(function() {
var fieldset = this.parentNode.parentNode;
// Prevent double animations
if (fieldset.animating) {
return false;
}
fieldset.animating = true;
if ($(fieldset).is('.collapsed')) {
// Open fieldset with animation
$(fieldset.contentWrapper).hide();
$(fieldset).removeClass('collapsed');
$(fieldset.contentWrapper).slideDown(300,
{
// Make sure we open to height auto
complete: function() {
$(fieldset.contentWrapper).css('height', 'auto');
Drupal.collapseScrollIntoView(fieldset);
fieldset.animating = false;
},
// Scroll the fieldset into view
step: function() {
Drupal.collapseScrollIntoView(fieldset);
}
}
);
if (typeof Drupal.textareaAttach != 'undefined') {
// Initialize resizable textareas that are now revealed
Drupal.textareaAttach(null, fieldset);
}
}
else {
// Collapse fieldset with animation (reverse of opening)
$(fieldset.contentWrapper)
.slideUp('medium', function () { $(fieldset).addClass('collapsed'); fieldset.animating = false; } )
.show();
}
}
this.blur();
return false;
};
a.innerHTML = legend.innerHTML;
while (legend.hasChildNodes()) {
removeNode(legend.childNodes[0]);
this.blur();
return false;
})
.html(this.innerHTML);
$(this)
.empty()
.append(a);
// Wrap fieldsets contents (except for the legend) into wrapper divs for animating.
// div1 is used to avoid margin problems inside fieldsets,
// div2 is the one that is actually animated.
var div1 = document.createElement('div');
var div2 = document.createElement('div');
this.parentNode.contentWrapper = div2;
$(this).after(div1);
$(div1).append(div2);
var el = div1.nextSibling;
while (el != null) {
var next = el.nextSibling;
$(el).remove();
$(div2).append(el);
el = next;
}
legend.appendChild(a);
collapseEnsureErrorsVisible(fieldset);
}
}
// Avoid jumping around due to margins collapsing into fieldset border
$(div1).css('overflow', 'hidden');
function collapseEnsureErrorsVisible(fieldset) {
if (!hasClass(fieldset, 'collapsed')) {
return;
}
var inputs = [];
inputs = inputs.concat(fieldset.getElementsByTagName('input'));
inputs = inputs.concat(fieldset.getElementsByTagName('textarea'));
inputs = inputs.concat(fieldset.getElementsByTagName('select'));
for (var j = 0; j<3; j++) {
for (var i = 0; i < inputs[j].length; i++) {
if (hasClass(inputs[j][i], 'error')) {
return removeClass(fieldset, 'collapsed');
}
// Expand if there are errors inside
if ($('input.error, textarea.error, select.error', this.parentNode).size() > 0) {
$(this.parentNode).removeClass('collapsed');
}
}
});
}
function collapseScrollIntoView(node) {
/**
* Scroll a given fieldset into view as much as possible.
*/
Drupal.collapseScrollIntoView = function (node) {
var h = self.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0;
var offset = self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
var pos = absolutePosition(node);
if (pos.y + node.scrollHeight > h + offset) {
if (node.scrollHeight > h) {
var pos = Drupal.absolutePosition(node);
var fudge = 55;
if (pos.y + node.offsetHeight + fudge > h + offset) {
if (node.offsetHeight > h) {
window.scrollTo(0, pos.y);
} else {
window.scrollTo(0, pos.y + node.scrollHeight - h);
window.scrollTo(0, pos.y + node.offsetHeight - h + fudge);
}
}
}
// Global Killswitch
if (Drupal.jsEnabled) {
$(document).ready(Drupal.collapseAutoAttach);
}
This diff is collapsed.
......@@ -5,45 +5,35 @@
* the DOM afterwards through progressBar.element.
*
* method is the function which will perform the HTTP request to get the
* progress bar state. Either HTTPGet or HTTPPost.
* progress bar state. Either "GET" or "POST".
*
* e.g. pb = new progressBar('myProgressBar');
* some_element.appendChild(pb.element);
*/
function progressBar(id, updateCallback, method, errorCallback) {
Drupal.progressBar = function (id, updateCallback, method, errorCallback) {
var pb = this;
this.id = id;
this.method = method ? method : HTTPGet;
this.method = method || "GET";
this.updateCallback = updateCallback;
this.errorCallback = errorCallback;
this.element = document.createElement('div');
this.element.id = id;
this.element.className = 'progress';
this.element.innerHTML = '<div class="percentage"></div>'+
'<div class="message">&nbsp;</div>'+
'<div class="bar"><div class="filled"></div></div>';
$(this.element).html('<div class="percentage"></div>'+
'<div class="message">&nbsp;</div>'+
'<div class="bar"><div class="filled"></div></div>');
}
/**
* Set the percentage and status message for the progressbar.
*/
progressBar.prototype.setProgress = function (percentage, message) {
var divs = this.element.getElementsByTagName('div');
var div;
for (var i = 0; div = divs[i]; ++i) {
if (percentage >= 0) {
if (hasClass(divs[i], 'filled')) {
divs[i].style.width = percentage + '%';
}
if (hasClass(divs[i], 'percentage')) {
divs[i].innerHTML = percentage + '%';
}
}
if (hasClass(divs[i], 'message')) {
divs[i].innerHTML = message;
}
Drupal.progressBar.prototype.setProgress = function (percentage, message) {
if (percentage >= 0 && percentage <= 100) {
$('div.filled', this.element).css('width', percentage +'%');
$('div.percentage', this.element).html(percentage +'%');
}
$('div.message', this.element).html(message);
if (this.updateCallback) {
this.updateCallback(percentage, message, this);
}
......@@ -52,7 +42,7 @@ progressBar.prototype.setProgress = function (percentage, message) {
/**
* Start monitoring progress via Ajax.
*/
progressBar.prototype.startMonitoring = function (uri, delay) {
Drupal.progressBar.prototype.startMonitoring = function (uri, delay) {
this.delay = delay;
this.uri = uri;
this.sendPing();
......@@ -61,7 +51,7 @@ progressBar.prototype.startMonitoring = function (uri, delay) {
/**
* Stop monitoring progress via Ajax.
*/
progressBar.prototype.stopMonitoring = function () {
Drupal.progressBar.prototype.stopMonitoring = function () {
clearTimeout(this.timer);
// This allows monitoring to be stopped from within the callback
this.uri = null;
......@@ -70,47 +60,44 @@ progressBar.prototype.stopMonitoring = function () {
/**
* Request progress data from server.
*/
progressBar.prototype.sendPing = function () {
Drupal.progressBar.prototype.sendPing = function () {
if (this.timer) {
clearTimeout(this.timer);
}
if (this.uri) {
this.method(this.uri, this.receivePing, this, '');
}
}
/**
* HTTP callback function. Passes data back to the progressbar and sets a new
* timer for the next ping.
*/
progressBar.prototype.receivePing = function (string, xmlhttp, pb) {
if (xmlhttp.status != 200) {
return pb.displayError('An HTTP error '+ xmlhttp.status +' occured.\n'+ pb.uri);
}
// Parse response
var progress = parseJson(string);
// Display errors
if (progress.status == 0) {
pb.displayError(progress.data);
return;
var pb = this;
$.ajax({
type: this.method,
url: this.uri,
success: function (xmlhttp) {
// Parse response
var progress = Drupal.parseJson(xmlhttp.responseText);
// Display errors
if (progress.status == 0) {
pb.displayError(progress.data);
return;
}
// Update display
pb.setProgress(progress.percentage, progress.message);
// Schedule next timer
pb.timer = setTimeout(function() { pb.sendPing(); }, pb.delay);
},
error: function (xmlhttp) {
pb.displayError('An HTTP error '+ xmlhttp.status +' occured.\n'+ pb.uri);
}
});
}
// Update display
pb.setProgress(progress.percentage, progress.message);
// Schedule next timer
pb.timer = setTimeout(function() { pb.sendPing(); }, pb.delay);
}
/**
* Display errors on the page.
*/
progressBar.prototype.displayError = function (string) {
Drupal.progressBar.prototype.displayError = function (string) {
var error = document.createElement('div');
error.className = 'error';
error.innerHTML = string;
this.element.style.display = 'none';
this.element.parentNode.insertBefore(error, this.element);
$(this.element).before(error).hide();
if (this.errorCallback) {
this.errorCallback(this);
......
// $Id$
if (isJsEnabled()) {
addLoadEvent(textAreaAutoAttach);
}
function textAreaAutoAttach(event, parent) {
if (typeof parent == 'undefined') {
// Attach to all visible textareas.
textareas = document.getElementsByTagName('textarea');
}
else {
// Attach to all visible textareas inside parent.
textareas = parent.getElementsByTagName('textarea');
}
var textarea;
for (var i = 0; textarea = textareas[i]; ++i) {
if (hasClass(textarea, 'resizable') && !hasClass(textarea.nextSibling, 'grippie')) {
if (typeof dimensions(textarea).width != 'undefined' && dimensions(textarea).width != 0) {
new textArea(textarea);
}
Drupal.textareaAttach = function() {
$('textarea.resizable:not(.processed)').each(function() {
var textarea = $(this).addClass('processed'), staticOffset = null;
$(this).wrap('<div class="resizable-textarea"></div>')
.parent().append($('<div class="grippie"></div>').mousedown(startDrag));
var grippie = $('div.grippie', $(this).parent())[0];
grippie.style.marginRight = (grippie.offsetWidth - $(this)[0].offsetWidth) +'px';
function startDrag(e) {
staticOffset = textarea.height() - Drupal.mousePosition(e).y;
textarea.css('opacity', 0.25);
$(document).mousemove(performDrag).mouseup(endDrag);
return false;
}
}
}
function textArea(element) {
var ta = this;
this.element = element;
this.parent = this.element.parentNode;
this.dimensions = dimensions(element);
// Prepare wrapper
this.wrapper = document.createElement('div');
this.wrapper.className = 'resizable-textarea';
this.parent.insertBefore(this.wrapper, this.element);
// Add grippie and measure it
this.grippie = document.createElement('div');
this.grippie.className = 'grippie';
this.wrapper.appendChild(this.grippie);
this.grippie.dimensions = dimensions(this.grippie);
this.grippie.onmousedown = function (e) { ta.beginDrag(e); };
// Set wrapper and textarea dimensions
this.wrapper.style.height = this.dimensions.height + this.grippie.dimensions.height + 1 +'px';
this.element.style.marginBottom = '0px';
this.element.style.width = '100%';
this.element.style.height = this.dimensions.height +'px';
// Wrap textarea
removeNode(this.element);
this.wrapper.insertBefore(this.element, this.grippie);
// Measure difference between desired and actual textarea dimensions to account for padding/borders
this.widthOffset = dimensions(this.wrapper).width - this.dimensions.width;
// Make the grippie line up in various browsers
if (window.opera) {
// Opera
this.grippie.style.marginRight = '4px';
}
if (document.all && !window.opera) {
// IE
this.grippie.style.width = '100%';
this.grippie.style.paddingLeft = '2px';
}
// Mozilla
this.element.style.MozBoxSizing = 'border-box';
this.heightOffset = absolutePosition(this.grippie).y - absolutePosition(this.element).y - this.dimensions.height;
}
textArea.prototype.beginDrag = function (event) {
if (document.isDragging) {
return;
}
document.isDragging = true;
event = event || window.event;
// Capture mouse
var cp = this;
this.oldMoveHandler = document.onmousemove;
document.onmousemove = function(e) { cp.handleDrag(e); };
this.oldUpHandler = document.onmouseup;
document.onmouseup = function(e) { cp.endDrag(e); };
// Store drag offset from grippie top
var pos = absolutePosition(this.grippie);
this.dragOffset = event.clientY - pos.y;
// Make transparent
this.element.style.opacity = 0.5;
// Process
this.handleDrag(event);
}
textArea.prototype.handleDrag = function (event) {
event = event || window.event;
// Get coordinates relative to text area
var pos = absolutePosition(this.element);
var y = event.clientY - pos.y;
// Set new height
var height = Math.max(32, y - this.dragOffset - this.heightOffset);
this.wrapper.style.height = height + this.grippie.dimensions.height + 1 + 'px';
this.element.style.height = height + 'px';
function performDrag(e) {
textarea.height(Math.max(32, staticOffset + Drupal.mousePosition(e).y) + 'px');
return false;
}
// Avoid text selection
stopEvent(event);
function endDrag(e) {
$(document).unmousemove(performDrag).unmouseup(endDrag);
textarea.css('opacity', 1);
}
});
}
textArea.prototype.endDrag = function (event) {
// Uncapture mouse
document.onmousemove = this.oldMoveHandler;
document.onmouseup = this.oldUpHandler;
// Restore opacity
this.element.style.opacity = 1.0;
document.isDragging = false;
if (Drupal.jsEnabled) {
$(document).ready(Drupal.textareaAttach);
}
// $Id$
if (isJsEnabled()) {
addLoadEvent(function() {
if ($('edit-has_js')) {
$('edit-has_js').value = 1;
}
if (Drupal.jsEnabled) {
$(document).ready(function() {
$('#edit-has_js').each(function() { this.value = 1; });
$('#progress').each(function () {
var holder = this;
if ($('progress')) {
// Success: redirect to the summary.
var updateCallback = function (progress, status, pb) {
if (progress == 100) {
......@@ -19,15 +18,15 @@ if (isJsEnabled()) {
var errorCallback = function (pb) {
var div = document.createElement('p');
div.className = 'error';
div.innerHTML = 'An unrecoverable error has occured. You can find the error message below. It is advised to copy it to the clipboard for reference. Please continue to the <a href="update.php?op=error">update summary</a>';
$('progress').insertBefore(div, $('progress').firstChild);
$('wait').style.display = 'none';
$(div).html('An unrecoverable error has occured. You can find the error message below. It is advised to copy it to the clipboard for reference. Please continue to the <a href="update.php?op=error">update summary</a>');
$(holder).prepend(div);
$('#wait').hide();
}
var progress = new progressBar('updateprogress', updateCallback, HTTPPost, errorCallback);
var progress = new Drupal.progressBar('updateprogress', updateCallback, "POST", errorCallback);
progress.setProgress(-1, 'Starting updates');
$('progress').appendChild(progress.element);
$(holder).append(progress.element);
progress.startMonitoring('update.php?op=do_update', 0);
}
});
});
}
// $Id$
// Global killswitch
if (isJsEnabled()) {
addLoadEvent(uploadAutoAttach);
}
/**
* Attaches the upload behaviour to the upload form.
*/
function uploadAutoAttach() {
var inputs = document.getElementsByTagName('input');
for (i = 0; input = inputs[i]; i++) {
if (input && hasClass(input, 'upload')) {
var uri = input.value;
// Extract the base name from the id (edit-attach-url -> attach).
var base = input.id.substring(5, input.id.length - 4);
var button = base + '-button';
var wrapper = base + '-wrapper';
var hide = base + '-hide';
var upload = new jsUpload(uri, button, wrapper, hide);
}
}
Drupal.uploadAutoAttach = function() {
$('input.upload').each(function () {
var uri = this.value;
// Extract the base name from the id (edit-attach-url -> attach).
var base = this.id.substring(5, this.id.length - 4);
var button = base + '-button';
var wrapper = base + '-wrapper';
var hide = base + '-hide';
var upload = new Drupal.jsUpload(uri, button, wrapper, hide);
});
}
/**
* JS upload object.
*/
function jsUpload(uri, button, wrapper, hide) {
this.button = button;
this.wrapper = wrapper;
this.hide = hide;
redirectFormButton(uri, $(button), this);
Drupal.jsUpload = function(uri, button, wrapper, hide) {
// Note: these elements are replaced after an upload, so we re-select them
// everytime they are needed.
this.button = '#'+ button;
this.wrapper = '#'+ wrapper;
this.hide = '#'+ hide;
Drupal.redirectFormButton(uri, $(this.button).get(0), this);
}
/**
* Handler for the form redirection submission.
*/
jsUpload.prototype.onsubmit = function () {
var hide = $(this.hide);
Drupal.jsUpload.prototype.onsubmit = function () {
// Insert progressbar and stretch to take the same space.
this.progress = new progressBar('uploadprogress');
this.progress = new Drupal.progressBar('uploadprogress');
this.progress.setProgress(-1, 'Uploading file');
this.progress.element.style.width = '28em';
this.progress.element.style.height = hide.offsetHeight +'px';
hide.parentNode.insertBefore(this.progress.element, hide);
// Hide file form (cannot use display: none, this mysteriously aborts form
// submission in Konqueror)
hide.style.position = 'absolute';
hide.style.left = '-2000px';
var hide = this.hide;
var el = this.progress.element;
var offset = $(hide).get(0).offsetHeight;
$(el).css({
width: '28em',
height: offset +'px',
paddingTop: '10px',
display: 'none'
});
$(hide).css('position', 'absolute');
$(hide).after(el);
$(el).fadeIn('slow');
$(hide).fadeOut('slow');
}
/**
* Handler for the form redirection completion.
*/
jsUpload.prototype.oncomplete = function (data) {
// Remove progressbar
removeNode(this.progress.element);
this.progress = null;
// Replace form and re-attach behaviour
$(this.wrapper).innerHTML = data;
uploadAutoAttach();
Drupal.jsUpload.prototype.oncomplete = function (data) {
// Remove old form
Drupal.freezeHeight(); // Avoid unnecessary scrolling
$(this.wrapper).html('');
// Place HTML into temporary div
var div = document.createElement('div');
$(div).html(data);
// If uploading the first attachment fade in everything
if ($('tr', div).size() == 2) {
// Replace form and re-attach behaviour
$(div).hide();
$(this.wrapper).append(div);
$(div).fadeIn('slow');
Drupal.uploadAutoAttach();
}
// Else fade in only the last table row
else {
// Hide form and last table row