summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
authorSteven Wittens <steven@10.no-reply.drupal.org>2006-08-31 23:31:25 +0000
committerSteven Wittens <steven@10.no-reply.drupal.org>2006-08-31 23:31:25 +0000
commit7fd9aa5c8af5c2f20921c7096ad07f72da7bf7f8 (patch)
treefe0b9b598b2df1ce6735668a8d41623eaaaff0e2 /misc
parent291c7b8bb1a0ad6166f88d79c183973683adf183 (diff)
downloadbrdo-7fd9aa5c8af5c2f20921c7096ad07f72da7bf7f8.tar.gz
brdo-7fd9aa5c8af5c2f20921c7096ad07f72da7bf7f8.tar.bz2
#69786: jQuery JavaScript Library in Drupal core
Diffstat (limited to 'misc')
-rw-r--r--misc/autocomplete.js231
-rw-r--r--misc/collapse.js136
-rw-r--r--misc/drupal.js351
-rw-r--r--misc/progress.js87
-rw-r--r--misc/textarea.js140
-rw-r--r--misc/update.js23
-rw-r--r--misc/upload.js128
7 files changed, 445 insertions, 651 deletions
diff --git a/misc/autocomplete.js b/misc/autocomplete.js
index 49e7e0210..e71f4afea 100644
--- a/misc/autocomplete.js
+++ b/misc/autocomplete.js
@@ -1,76 +1,51 @@
// $Id$
-// Global Killswitch
-if (isJsEnabled()) {
- addLoadEvent(autocompleteAutoAttach);
-}
-
/**
* Attaches the autocomplete behaviour to all required fields
*/
-function autocompleteAutoAttach() {
+Drupal.autocompleteAutoAttach = function () {
var acdb = [];
- var inputs = document.getElementsByTagName('input');
- for (i = 0; input = inputs[i]; i++) {
- if (input && hasClass(input, 'autocomplete')) {
- uri = input.value;
- if (!acdb[uri]) {
- acdb[uri] = new ACDB(uri);
- }
- input = $(input.id.substr(0, input.id.length - 13));
- input.setAttribute('autocomplete', 'OFF');
- addSubmitEvent(input.form, autocompleteSubmit);
- new jsAC(input, acdb[uri]);
+ $('input.autocomplete').each(function () {
+ var uri = this.value;
+ if (!acdb[uri]) {
+ acdb[uri] = new Drupal.ACDB(uri);
}
- }
+ var input = $('#' + this.id.substr(0, this.id.length - 13))
+ .attr('autocomplete', 'OFF')[0];
+ $(input.form).submit(Drupal.autocompleteSubmit);
+ new Drupal.jsAC(input, acdb[uri]);
+ });
}
/**
* Prevents the form from submitting if the suggestions popup is open
+ * and closes the suggestions popup when doing so.
*/
-function autocompleteSubmit() {
- var popup = document.getElementById('autocomplete');
- if (popup) {
- popup.owner.hidePopup();
- return false;
- }
- return true;
+Drupal.autocompleteSubmit = function () {
+ return $('#autocomplete').each(function () {
+ this.owner.hidePopup();
+ }).size() == 0;
}
-
/**
* An AutoComplete object
*/
-function jsAC(input, db) {
+Drupal.jsAC = function (input, db) {
var ac = this;
this.input = input;
this.db = db;
- this.input.onkeydown = function (event) { return ac.onkeydown(this, event); };
- this.input.onkeyup = function (event) { ac.onkeyup(this, event) };
- this.input.onblur = function () { ac.hidePopup(); ac.db.cancel(); };
- this.popup = document.createElement('div');
- this.popup.id = 'autocomplete';
- this.popup.owner = this;
-};
-/**
- * Hides the autocomplete suggestions
- */
-jsAC.prototype.hidePopup = function (keycode) {
- if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
- this.input.value = this.selected.autocompleteValue;
- }
- if (this.popup.parentNode && this.popup.parentNode.tagName) {
- removeNode(this.popup);
- }
- this.selected = false;
-}
+ $(this.input)
+ .keydown(function (event) { return ac.onkeydown(this, event); })
+ .keyup(function (event) { ac.onkeyup(this, event) })
+ .blur(function () { ac.hidePopup(); ac.db.cancel(); });
+};
/**
* Handler for the "keydown" event
*/
-jsAC.prototype.onkeydown = function (input, e) {
+Drupal.jsAC.prototype.onkeydown = function (input, e) {
if (!e) {
e = window.event;
}
@@ -89,7 +64,7 @@ jsAC.prototype.onkeydown = function (input, e) {
/**
* Handler for the "keyup" event
*/
-jsAC.prototype.onkeyup = function (input, e) {
+Drupal.jsAC.prototype.onkeyup = function (input, e) {
if (!e) {
e = window.event;
}
@@ -126,21 +101,21 @@ jsAC.prototype.onkeyup = function (input, e) {
/**
* Puts the currently highlighted suggestion into the autocomplete field
*/
-jsAC.prototype.select = function (node) {
+Drupal.jsAC.prototype.select = function (node) {
this.input.value = node.autocompleteValue;
}
/**
* Highlights the next suggestion
*/
-jsAC.prototype.selectDown = function () {
+Drupal.jsAC.prototype.selectDown = function () {
if (this.selected && this.selected.nextSibling) {
this.highlight(this.selected.nextSibling);
}
else {
- var lis = this.popup.getElementsByTagName('li');
- if (lis.length > 0) {
- this.highlight(lis[0]);
+ var lis = $('li', this.popup);
+ if (lis.size() > 0) {
+ this.highlight(lis.get(0));
}
}
}
@@ -148,7 +123,7 @@ jsAC.prototype.selectDown = function () {
/**
* Highlights the previous suggestion
*/
-jsAC.prototype.selectUp = function () {
+Drupal.jsAC.prototype.selectUp = function () {
if (this.selected && this.selected.previousSibling) {
this.highlight(this.selected.previousSibling);
}
@@ -157,30 +132,61 @@ jsAC.prototype.selectUp = function () {
/**
* Highlights a suggestion
*/
-jsAC.prototype.highlight = function (node) {
- removeClass(this.selected, 'selected');
- addClass(node, 'selected');
+Drupal.jsAC.prototype.highlight = function (node) {
+ if (this.selected) {
+ $(this.selected).removeClass('selected');
+ }
+ $(node).addClass('selected');
this.selected = node;
}
/**
* Unhighlights a suggestion
*/
-jsAC.prototype.unhighlight = function (node) {
- removeClass(node, 'selected');
+Drupal.jsAC.prototype.unhighlight = function (node) {
+ $(node).removeClass('selected');
+ this.selected = false;
+}
+
+/**
+ * Hides the autocomplete suggestions
+ */
+Drupal.jsAC.prototype.hidePopup = function (keycode) {
+ // Select item if the right key or mousebutton was pressed
+ if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
+ this.input.value = this.selected.autocompleteValue;
+ }
+ // Hide popup
+ var popup = this.popup;
+ if (popup) {
+ this.popup = null;
+ $(popup).fadeOut('fast', function() { $(popup).remove(); });
+ }
this.selected = false;
}
/**
* Positions the suggestions popup and starts a search
*/
-jsAC.prototype.populatePopup = function () {
- var ac = this;
- var pos = absolutePosition(this.input);
+Drupal.jsAC.prototype.populatePopup = function () {
+ // Show popup
+ if (this.popup) {
+ $(this.popup).remove();
+ }
+ var pos = Drupal.absolutePosition(this.input);
this.selected = false;
- this.popup.style.top = (pos.y + this.input.offsetHeight) +'px';
- this.popup.style.left = pos.x +'px';
- this.popup.style.width = (this.input.offsetWidth - 4) +'px';
+ this.popup = document.createElement('div');
+ this.popup.id = 'autocomplete';
+ this.popup.owner = this;
+ $(this.popup).css({
+ top: (pos.y + this.input.offsetHeight) +'px',
+ left: pos.x +'px',
+ width: (this.input.offsetWidth - 4) +'px',
+ display: 'none'
+ });
+ $('body').append(this.popup);
+
+ // Do search
this.db.owner = this;
this.db.search(this.input.value);
}
@@ -188,45 +194,40 @@ jsAC.prototype.populatePopup = function () {
/**
* Fills the suggestion popup with any matches received
*/
-jsAC.prototype.found = function (matches) {
- while (this.popup.hasChildNodes()) {
- this.popup.removeChild(this.popup.childNodes[0]);
- }
- if (!this.popup.parentNode || !this.popup.parentNode.tagName) {
- document.getElementsByTagName('body')[0].appendChild(this.popup);
- }
+Drupal.jsAC.prototype.found = function (matches) {
+ // Prepare matches
var ul = document.createElement('ul');
var ac = this;
-
for (key in matches) {
var li = document.createElement('li');
- var div = document.createElement('div');
- div.innerHTML = matches[key];
- li.appendChild(div);
+ $(li)
+ .html('<div>'+ matches[key] +'</div>')
+ .mousedown(function () { ac.select(this); })
+ .mouseover(function () { ac.highlight(this); })
+ .mouseout(function () { ac.unhighlight(this); });
li.autocompleteValue = key;
- li.onmousedown = function() { ac.select(this); };
- li.onmouseover = function() { ac.highlight(this); };
- li.onmouseout = function() { ac.unhighlight(this); };
- ul.appendChild(li);
+ $(ul).append(li);
}
+ // Show popup with matches, if any
if (ul.childNodes.length > 0) {
- this.popup.appendChild(ul);
+ $(this.popup).empty().append(ul).show();
}
else {
+ $(this.popup).css({visibility: 'hidden'});
this.hidePopup();
}
}
-jsAC.prototype.setStatus = function (status) {
+Drupal.jsAC.prototype.setStatus = function (status) {
switch (status) {
case 'begin':
- addClass(this.input, 'throbbing');
+ $(this.input).addClass('throbbing');
break;
case 'cancel':
case 'error':
case 'found':
- removeClass(this.input, 'throbbing');
+ $(this.input).removeClass('throbbing');
break;
}
}
@@ -234,7 +235,7 @@ jsAC.prototype.setStatus = function (status) {
/**
* An AutoComplete DataBase object
*/
-function ACDB(uri) {
+Drupal.ACDB = function (uri) {
this.uri = uri;
this.delay = 300;
this.cache = {};
@@ -243,47 +244,55 @@ function ACDB(uri) {
/**
* Performs a cached and delayed search
*/
-ACDB.prototype.search = function(searchString) {
+Drupal.ACDB.prototype.search = function (searchString) {
+ var db = this;
this.searchString = searchString;
+
+ // See if this key has been searched for before
if (this.cache[searchString]) {
return this.owner.found(this.cache[searchString]);
}
+
+ // Initiate delayed search
if (this.timer) {
clearTimeout(this.timer);
}
- var db = this;
this.timer = setTimeout(function() {
db.owner.setStatus('begin');
- db.transport = HTTPGet(db.uri +'/'+ encodeURIComponent(searchString), db.receive, db);
- }, this.delay);
-}
-/**
- * HTTP callback function. Passes suggestions to the autocomplete object
- */
-ACDB.prototype.receive = function(string, xmlhttp, acdb) {
- // Note: Safari returns 'undefined' status if the request returns no data.
- if (xmlhttp.status != 200 && typeof xmlhttp.status != 'undefined') {
- acdb.owner.setStatus('error');
- return alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ acdb.uri);
- }
- // Parse back result
- var matches = parseJson(string);
- if (typeof matches['status'] == 'undefined' || matches['status'] != 0) {
- acdb.cache[acdb.searchString] = matches;
- acdb.owner.found(matches);
- acdb.owner.setStatus('found');
- }
+ // Ajax GET request for autocompletion
+ $.ajax({
+ type: "GET",
+ url: db.uri +'/'+ Drupal.encodeURIComponent(searchString),
+ success: function (xmlhttp) {
+ // Parse back result
+ var matches = Drupal.parseJson(xmlhttp.responseText);
+ if (typeof matches['status'] == 'undefined' || matches['status'] != 0) {
+ db.cache[searchString] = matches;
+ // Verify if these are still the matches the user wants to see
+ if (db.searchString == searchString) {
+ db.owner.found(matches);
+ }
+ db.owner.setStatus('found');
+ }
+ },
+ error: function (xmlhttp) {
+ alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ db.uri);
+ }
+ });
+ }, this.delay);
}
/**
* Cancels the current autocomplete request
*/
-ACDB.prototype.cancel = function() {
+Drupal.ACDB.prototype.cancel = function() {
if (this.owner) this.owner.setStatus('cancel');
if (this.timer) clearTimeout(this.timer);
- if (this.transport) {
- this.transport.onreadystatechange = function() {};
- this.transport.abort();
- }
+ this.searchString = '';
+}
+
+// Global Killswitch
+if (Drupal.jsEnabled) {
+ $(document).ready(Drupal.autocompleteAutoAttach);
}
diff --git a/misc/collapse.js b/misc/collapse.js
index b3695b51e..548c2b24c 100644
--- a/misc/collapse.js
+++ b/misc/collapse.js
@@ -1,70 +1,100 @@
// $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);
+}
diff --git a/misc/drupal.js b/misc/drupal.js
index 927403ab1..fb0cf0ddf 100644
--- a/misc/drupal.js
+++ b/misc/drupal.js
@@ -1,39 +1,14 @@
// $Id$
-/**
- * Only enable Javascript functionality if all required features are supported.
- */
-function isJsEnabled() {
- if (typeof document.jsEnabled == 'undefined') {
- // Note: ! casts to boolean implicitly.
- document.jsEnabled = !(
- !document.getElementsByTagName ||
- !document.createElement ||
- !document.createTextNode ||
- !document.documentElement ||
- !document.getElementById);
- }
- return document.jsEnabled;
-}
-
-// Global Killswitch on the <html> element
-if (isJsEnabled()) {
- document.documentElement.className = 'js';
-}
+var Drupal = Drupal || {};
/**
- * The global Drupal variable.
+ * Set the variable that indicates if JavaScript behaviors should be applied
*/
-Drupal = { };
+Drupal.jsEnabled = document.getElementsByTagName && document.createElement && document.createTextNode && document.documentElement && document.getElementById;
/**
- * Merge an object into the Drupal namespace.
- *
- * @param obj
- * The object that should be merged into the Drupal namespace. Arbitrary objects
- * containing functions, variables or other objects can be used. An example object
- * would be { settings: { tree: { '/js/menu/tree': { mid: 206 } } } }. This item
- * can now be accessed at Drupal.settings.tree['/js/menu/tree'].mid.
+ * Extends the current object with the parameter. Works recursively.
*/
Drupal.extend = function(obj) {
for (var i in obj) {
@@ -44,109 +19,26 @@ Drupal.extend = function(obj) {
this[i] = obj[i];
}
}
-};
-
-/**
- * Make IE's XMLHTTP object accessible through XMLHttpRequest()
- */
-if (typeof XMLHttpRequest == 'undefined') {
- XMLHttpRequest = function () {
- var msxmls = ['MSXML3', 'MSXML2', 'Microsoft']
- for (var i=0; i < msxmls.length; i++) {
- try {
- return new ActiveXObject(msxmls[i]+'.XMLHTTP')
- }
- catch (e) { }
- }
- throw new Error("No XML component installed!");
- }
-}
-
-/**
- * Creates an HTTP GET request and sends the response to the callback function.
- *
- * Note that dynamic arguments in the URI should be escaped with encodeURIComponent().
- */
-function HTTPGet(uri, callbackFunction, callbackParameter) {
- var xmlHttp = new XMLHttpRequest();
- var bAsync = true;
- if (!callbackFunction) {
- bAsync = false;
- }
-
- xmlHttp.open('GET', uri, bAsync);
- xmlHttp.send(null);
-
- if (bAsync) {
- xmlHttp.onreadystatechange = function() {
- if (xmlHttp.readyState == 4) {
- callbackFunction(xmlHttp.responseText, xmlHttp, callbackParameter);
- }
- }
- return xmlHttp;
- }
- else {
- return xmlHttp.responseText;
- }
-}
-
-/**
- * Creates an HTTP POST request and sends the response to the callback function
- *
- * Note: passing null or undefined for 'object' makes the request fail in Opera 8.
- * Pass an empty string instead.
- */
-function HTTPPost(uri, callbackFunction, callbackParameter, object) {
- var xmlHttp = new XMLHttpRequest();
- var bAsync = true;
- if (!callbackFunction) {
- bAsync = false;
- }
- xmlHttp.open('POST', uri, bAsync);
-
- var toSend = '';
- if (typeof object == 'object') {
- xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
- for (var i in object) {
- toSend += (toSend ? '&' : '') + i + '=' + encodeURIComponent(object[i]);
- }
- }
- else {
- toSend = object;
- }
- xmlHttp.send(toSend);
-
- if (bAsync) {
- xmlHttp.onreadystatechange = function() {
- if (xmlHttp.readyState == 4) {
- callbackFunction(xmlHttp.responseText, xmlHttp, callbackParameter);
- }
- }
- return xmlHttp;
- }
- else {
- return xmlHttp.responseText;
- }
-}
+},
/**
* Redirects a button's form submission to a hidden iframe and displays the result
* in a given wrapper. The iframe should contain a call to
* window.parent.iframeHandler() after submission.
*/
-function redirectFormButton(uri, button, handler) {
- // (Re)create an iframe to target.
- createIframe();
-
+Drupal.redirectFormButton = function (uri, button, handler) {
// Trap the button
button.onmouseover = button.onfocus = function() {
button.onclick = function() {
+ // Create target iframe
+ Drupal.createIframe();
+
// Prepare variables for use in anonymous function.
var button = this;
var action = button.form.action;
var target = button.form.target;
- // Redirect form submission
+ // Redirect form submission to iframe
this.form.action = uri;
this.form.target = 'redirect-target';
@@ -154,7 +46,7 @@ function redirectFormButton(uri, button, handler) {
// Set iframe handler for later
window.iframeHandler = function () {
- var iframe = $('redirect-target');
+ var iframe = $('#redirect-target').get(0);
// Restore form submission
button.form.action = action;
button.form.target = target;
@@ -173,16 +65,15 @@ function redirectFormButton(uri, button, handler) {
response = null;
}
- $('redirect-target').onload = null;
- $('redirect-target').src = 'about:blank';
-
- response = parseJson(response);
+ response = Drupal.parseJson(response);
// Check response code
if (response.status == 0) {
handler.onerror(response.data);
return;
}
handler.oncomplete(response.data);
+
+ return true;
}
return true;
@@ -191,43 +82,12 @@ function redirectFormButton(uri, button, handler) {
button.onmouseout = button.onblur = function() {
button.onclick = null;
}
-}
-
-/**
- * Adds a function to the window onload event
- */
-function addLoadEvent(func) {
- var oldOnload = window.onload;
- if (typeof window.onload != 'function') {
- window.onload = func;
- }
- else {
- window.onload = function() {
- oldOnload();
- func();
- }
- }
-}
-
-/**
- * Adds a function to a given form's submit event
- */
-function addSubmitEvent(form, func) {
- var oldSubmit = form.onsubmit;
- if (typeof oldSubmit != 'function') {
- form.onsubmit = func;
- }
- else {
- form.onsubmit = function() {
- return oldSubmit() && func();
- }
- }
-}
+},
/**
* Retrieves the absolute position of an element on the screen
*/
-function absolutePosition(el) {
+Drupal.absolutePosition = function (el) {
var sLeft = 0, sTop = 0;
var isDiv = /^div$/i.test(el.tagName);
if (isDiv && el.scrollLeft) {
@@ -238,152 +98,109 @@ function absolutePosition(el) {
}
var r = { x: el.offsetLeft - sLeft, y: el.offsetTop - sTop };
if (el.offsetParent) {
- var tmp = absolutePosition(el.offsetParent);
+ var tmp = Drupal.absolutePosition(el.offsetParent);
r.x += tmp.x;
r.y += tmp.y;
}
return r;
-};
-
-function dimensions(el) {
- return { width: el.offsetWidth, height: el.offsetHeight };
-}
+},
/**
- * Returns true if an element has a specified class name
+ * Return the dimensions of an element on the screen
*/
-function hasClass(node, className) {
- if (node.className == className) {
- return true;
- }
- var reg = new RegExp('(^| )'+ className +'($| )')
- if (reg.test(node.className)) {
- return true;
- }
- return false;
-}
-
-/**
- * Adds a class name to an element
- */
-function addClass(node, className) {
- if (hasClass(node, className)) {
- return false;
- }
- node.className += ' '+ className;
- return true;
-}
-
-/**
- * Removes a class name from an element
- */
-function removeClass(node, className) {
- if (!hasClass(node, className)) {
- return false;
- }
- // Replaces words surrounded with whitespace or at a string border with a space. Prevents multiple class names from being glued together.
- node.className = eregReplace('(^|\\s+)'+ className +'($|\\s+)', ' ', node.className);
- return true;
-}
-
-/**
- * Toggles a class name on or off for an element
- */
-function toggleClass(node, className) {
- if (!removeClass(node, className) && !addClass(node, className)) {
- return false;
- }
- return true;
-}
-
-/**
- * Emulate PHP's ereg_replace function in javascript
- */
-function eregReplace(search, replace, subject) {
- return subject.replace(new RegExp(search,'g'), replace);
-}
-
-/**
- * Removes an element from the page
- */
-function removeNode(node) {
- if (typeof node == 'string') {
- node = $(node);
- }
- if (node && node.parentNode) {
- return node.parentNode.removeChild(node);
- }
- else {
- return false;
- }
-}
+Drupal.dimensions = function (el) {
+ return { width: el.offsetWidth, height: el.offsetHeight };
+},
/**
- * Prevents an event from propagating.
+ * Returns the position of the mouse cursor based on the event object passed
*/
-function stopEvent(event) {
- if (event.preventDefault) {
- event.preventDefault();
- event.stopPropagation();
- }
- else {
- event.returnValue = false;
- event.cancelBubble = true;
- }
-}
+Drupal.mousePosition = function(e) {
+ return { x: e.clientX + document.documentElement.scrollLeft, y: e.clientY + document.documentElement.scrollTop };
+},
/**
* Parse a JSON response.
*
* The result is either the JSON object, or an object with 'status' 0 and 'data' an error message.
*/
-function parseJson(data) {
+Drupal.parseJson = function (data) {
if ((data.substring(0, 1) != '{') && (data.substring(0, 1) != '[')) {
return { status: 0, data: data.length ? data : 'Unspecified error' };
}
return eval('(' + data + ');');
-}
+},
/**
* Create an invisible iframe for form submissions.
*/
-function createIframe() {
- // Delete any previous iframe
- deleteIframe();
+Drupal.createIframe = function () {
+ if ($('#redirect-holder').size()) {
+ return;
+ }
// Note: some browsers require the literal name/id attributes on the tag,
// some want them set through JS. We do both.
window.iframeHandler = function () {};
var div = document.createElement('div');
div.id = 'redirect-holder';
- div.innerHTML = '<iframe name="redirect-target" id="redirect-target" class="redirect" onload="window.iframeHandler();"></iframe>';
+ $(div).html('<iframe name="redirect-target" id="redirect-target" class="redirect" onload="window.iframeHandler();"></iframe>');
var iframe = div.firstChild;
- with (iframe) {
- name = 'redirect-target';
- setAttribute('name', 'redirect-target');
- id = 'redirect-target';
- }
- with (iframe.style) {
- position = 'absolute';
- height = '1px';
- width = '1px';
- visibility = 'hidden';
- }
- document.body.appendChild(div);
-}
+ $(iframe)
+ .attr({
+ name: 'redirect-target',
+ id: 'redirect-target'
+ })
+ .css({
+ position: 'absolute',
+ height: '1px',
+ width: '1px',
+ visibility: 'hidden'
+ });
+ $('body').append(div);
+},
/**
- * Delete the invisible iframe for form submissions.
+ * Delete the invisible iframe
*/
-function deleteIframe() {
- var holder = $('redirect-holder');
- if (holder != null) {
- removeNode(holder);
- }
+Drupal.deleteIframe = function () {
+ $('#redirect-holder').remove();
+},
+
+/**
+ * Freeze the current body height (as minimum height). Used to prevent
+ * unnecessary upwards scrolling when doing DOM manipulations.
+ */
+Drupal.freezeHeight = function () {
+ Drupal.unfreezeHeight();
+ var div = document.createElement('div');
+ $(div).css({
+ position: 'absolute',
+ top: '0px',
+ left: '0px',
+ width: '1px',
+ height: $('body').css('height')
+ }).attr('id', 'freeze-height');
+ $('body').append(div);
+},
+
+/**
+ * Unfreeze the body height
+ */
+Drupal.unfreezeHeight = function () {
+ $('#freeze-height').remove();
}
/**
- * Wrapper around document.getElementById().
+ * Wrapper to address the mod_rewrite url encoding bug
+ * (equivalent of drupal_urlencode() in PHP).
*/
-function $(id) {
- return document.getElementById(id);
+Drupal.encodeURIComponent = function (item, uri) {
+ uri = uri || location.href;
+ item = encodeURIComponent(item).replace('%2F', '/');
+ return uri.indexOf('?q=') ? item : item.replace('%26', '%2526').replace('%23', '%2523');
+}
+
+// Global Killswitch on the <html> element
+if (Drupal.jsEnabled) {
+ document.documentElement.className = 'js';
}
diff --git a/misc/progress.js b/misc/progress.js
index b519f066b..1c2e93f05 100644
--- a/misc/progress.js
+++ b/misc/progress.js
@@ -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);
diff --git a/misc/textarea.js b/misc/textarea.js
index 3e5f4789c..bf8932937 100644
--- a/misc/textarea.js
+++ b/misc/textarea.js
@@ -1,122 +1,34 @@
// $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);
}
-
diff --git a/misc/update.js b/misc/update.js
index 9177296a1..46272f6ef 100644
--- a/misc/update.js
+++ b/misc/update.js
@@ -1,12 +1,11 @@
// $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);
- }
+ });
});
}
diff --git a/misc/upload.js b/misc/upload.js
index 4365749cf..5dd32cf81 100644
--- a/misc/upload.js
+++ b/misc/upload.js
@@ -1,76 +1,116 @@
// $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
+ $('table tr:last-of-type td', div).hide();
+
+ // Note: workaround because jQuery's #id selector does not work outside of 'document'
+ // Should be: $(this.hide, div).hide();
+ var hide = this.hide;
+ $('div', div).each(function() {
+ if (('#'+ this.id) == hide) {
+ this.style.display = 'none';
+ }
+ });
+
+ // Replace form, fade in items and re-attach behaviour
+ $(this.wrapper).append(div);
+ $('table tr:last-of-type td', div).fadeIn('slow');
+ $(this.hide, div).fadeIn('slow');
+ Drupal.uploadAutoAttach();
+ }
+ Drupal.unfreezeHeight();
}
/**
* Handler for the form redirection error.
*/
-jsUpload.prototype.onerror = function (error) {
+Drupal.jsUpload.prototype.onerror = function (error) {
alert('An error occurred:\n\n'+ error);
// Remove progressbar
- removeNode(this.progress.element);
+ $(this.progress.element).remove();
this.progress = null;
// Undo hide
- $(this.hide).style.position = 'static';
- $(this.hide).style.left = '0px';
+ $(this.hide).css({
+ position: 'static',
+ left: '0px'
+ });
+}
+
+
+// Global killswitch
+if (Drupal.jsEnabled) {
+ $(document).ready(Drupal.uploadAutoAttach);
}