summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
authorSteven Wittens <steven@10.no-reply.drupal.org>2005-05-24 06:00:22 +0000
committerSteven Wittens <steven@10.no-reply.drupal.org>2005-05-24 06:00:22 +0000
commit58bddf8abc2cb4c51b49a1ad1dc6c7eca411eb22 (patch)
tree21911a0ec62eca709567daf3ddaf9f467409ddf7 /misc
parent0de88f50baa5eed0eb90448d7964aa04c4fbc1de (diff)
downloadbrdo-58bddf8abc2cb4c51b49a1ad1dc6c7eca411eb22.tar.gz
brdo-58bddf8abc2cb4c51b49a1ad1dc6c7eca411eb22.tar.bz2
* cue Star Wars theme tune *
Return of the JavaScript! - #22519: form_autocomplete(): Ajax based autocompletion. Currently used for user names and folksonomy tags.
Diffstat (limited to 'misc')
-rw-r--r--misc/autocomplete.js263
-rw-r--r--misc/drupal.css25
-rw-r--r--misc/drupal.js165
3 files changed, 453 insertions, 0 deletions
diff --git a/misc/autocomplete.js b/misc/autocomplete.js
new file mode 100644
index 000000000..534b2fe4f
--- /dev/null
+++ b/misc/autocomplete.js
@@ -0,0 +1,263 @@
+// Global Killswitch
+if (isJsEnabled()) {
+ addLoadEvent(autocompleteAutoAttach);
+}
+
+/**
+ * Attaches the autocomplete behaviour to all required fields
+ */
+function autocompleteAutoAttach() {
+ 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);
+ }
+ id = input.id.substr(0, input.id.length - 13);
+ input = document.getElementById(id);
+ input.setAttribute('autocomplete', 'OFF');
+ input.form.onsubmit = autocompleteSubmit;
+ new jsAC(input, acdb[uri]);
+ }
+ }
+}
+
+/**
+ * Prevents the form from submitting if the suggestions popup is open
+ */
+function autocompleteSubmit() {
+ var popup = document.getElementById('autocomplete');
+ if (popup) {
+ popup.owner.hidePopup();
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * An AutoComplete object
+ */
+function jsAC(input, db) {
+ var ac = this;
+ this.input = input;
+ this.db = db;
+ this.db.owner = this;
+ 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() };
+ 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;
+}
+
+
+/**
+ * Handler for the "keydown" event
+ */
+jsAC.prototype.onkeydown = function (input, e) {
+ if (!e) {
+ e = window.event;
+ }
+ switch (e.keyCode) {
+ case 40: // down arrow
+ this.selectDown();
+ return false;
+ case 38: // up arrow
+ this.selectUp();
+ return false;
+ default: // all other keys
+ return true;
+ }
+}
+
+/**
+ * Handler for the "keyup" event
+ */
+jsAC.prototype.onkeyup = function (input, e) {
+ if (!e) {
+ e = window.event;
+ }
+ switch (e.keyCode) {
+ case 16: // shift
+ case 17: // ctrl
+ case 18: // alt
+ case 20: // caps lock
+ case 33: // page up
+ case 34: // page down
+ case 35: // end
+ case 36: // home
+ case 37: // left arrow
+ case 38: // up arrow
+ case 39: // right arrow
+ case 40: // down arrow
+ return true;
+
+ case 9: // tab
+ case 13: // enter
+ case 27: // esc
+ this.hidePopup(e.keyCode);
+ return true;
+
+ default: // all other keys
+ if (input.value.length > 0)
+ this.populatePopup();
+ else
+ this.hidePopup(e.keyCode);
+ return true;
+ }
+}
+
+/**
+ * Puts the currently highlighted suggestion into the autocomplete field
+ */
+jsAC.prototype.select = function (node) {
+ this.input.value = node.autocompleteValue;
+}
+
+/**
+ * Highlights the next suggestion
+ */
+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]);
+ }
+ }
+}
+
+/**
+ * Highlights the previous suggestion
+ */
+jsAC.prototype.selectUp = function () {
+ if (this.selected && this.selected.previousSibling) {
+ this.highlight(this.selected.previousSibling);
+ }
+}
+
+/**
+ * Highlights a suggestion
+ */
+jsAC.prototype.highlight = function (node) {
+ removeClass(this.selected, 'selected');
+ addClass(node, 'selected');
+ this.selected = node;
+}
+
+/**
+ * Unhighlights a suggestion
+ */
+jsAC.prototype.unhighlight = function (node) {
+ removeClass(node, 'selected');
+ this.selected = false;
+}
+
+/**
+ * Positions the suggestions popup and starts a search
+ */
+jsAC.prototype.populatePopup = function () {
+ var ac = this;
+ var pos = 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';
+ addClass(this.input, 'throbbing');
+ this.db.search(this.input.value);
+}
+
+/**
+ * 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);
+ }
+ var ul = document.createElement('ul');
+ var ac = this;
+ if (matches.length > 0) {
+ for (i in matches) {
+ li = document.createElement('li');
+ div = document.createElement('div');
+ div.innerHTML = matches[i][1];
+ li.appendChild(div);
+ li.autocompleteValue = matches[i][0];
+ li.onmousedown = function() { ac.select(this); };
+ li.onmouseover = function() { ac.highlight(this); };
+ li.onmouseout = function() { ac.unhighlight(this); };
+ ul.appendChild(li);
+ }
+ this.popup.appendChild(ul);
+ }
+ else {
+ this.hidePopup();
+ }
+ removeClass(this.input, 'throbbing');
+}
+
+/**
+ * An AutoComplete DataBase object
+ */
+function ACDB(uri) {
+ this.uri = uri;
+ this.max = 15;
+ this.delay = 300;
+ this.cache = {};
+}
+
+/**
+ * Performs a cached and delayed search
+ */
+ACDB.prototype.search = function(searchString) {
+ if (this.docache) {
+ this.searchString = searchString;
+ if (this.cache[searchString]) {
+ return this.match(this.cache[searchString]);
+ }
+ }
+ if (this.timer) {
+ clearTimeout(this.timer);
+ }
+ var db = this;
+ this.timer = setTimeout(function() { HTTPGet(db.uri +'/'+ searchString +'/'+ db.max, db.receive, db); }, this.delay);
+}
+
+/**
+ * HTTP callback function. Passes suggestions to the autocomplete object
+ */
+ACDB.prototype.receive = function(string, xmlhttp, acdb) {
+ if (xmlhttp.status != 200) {
+ return alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ acdb.uri);
+ }
+ // Split into array of key->value pairs
+ var matches = string.length > 0 ? string.split('||') : [];
+ for (i in matches) {
+ matches[i] = matches[i].length > 0 ? matches[i].split('|') : [];
+ // Decode textfield pipes back to plain-text
+ matches[i][0] = eregReplace('&#124;', '|', matches[i][0]);
+ }
+ acdb.cache[acdb.searchString] = matches;
+ acdb.owner.found(matches);
+}
diff --git a/misc/drupal.css b/misc/drupal.css
index 1cedbad9d..618b8ffd7 100644
--- a/misc/drupal.css
+++ b/misc/drupal.css
@@ -551,3 +551,28 @@ ul.secondary a {
ul.secondary a.active {
border-bottom: 4px solid #999;
}
+
+/*
+** Autocomplete styles
+*/
+#autocomplete {
+ position: absolute;
+ border: 1px solid;
+ overflow: hidden;
+}
+#autocomplete ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+#autocomplete li {
+ background: #fff;
+ color: #000;
+ white-space: pre;
+ cursor: default;
+}
+#autocomplete li.selected {
+ background: #0072b9;
+ color: #fff;
+}
+
diff --git a/misc/drupal.js b/misc/drupal.js
new file mode 100644
index 000000000..02ea60493
--- /dev/null
+++ b/misc/drupal.js
@@ -0,0 +1,165 @@
+/**
+ * Only enable Javascript functionality if all required features are supported.
+ */
+function isJsEnabled() {
+ if (document.jsEnabled == undefined) {
+ // Note: ! casts to boolean implicitly.
+ document.jsEnabled = !(
+ !document.getElementsByTagName ||
+ !document.createElement ||
+ !document.createTextNode ||
+ !document.getElementById);
+ }
+ return document.jsEnabled;
+}
+
+// Global Killswitch
+if (isJsEnabled()) {
+
+}
+
+/**
+ * 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 request and sends the response to the callback function
+ */
+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) {
+ if (callbackFunction) {
+ xmlHttp.onreadystatechange = function() {
+ if (xmlHttp.readyState == 4)
+ callbackFunction(xmlHttp.responseText, xmlHttp, callbackParameter)
+ }
+ }
+ return true;
+ }
+ else {
+ return xmlHttp.responseText;
+ }
+}
+
+/**
+ * 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();
+ }
+ }
+}
+
+/**
+ * Retrieves the absolute position of an element on the screen
+ */
+function absolutePosition(el) {
+ var sLeft = 0, sTop = 0;
+ var isDiv = /^div$/i.test(el.tagName);
+ if (isDiv && el.scrollLeft) {
+ sLeft = el.scrollLeft;
+ }
+ if (isDiv && el.scrollTop) {
+ sTop = el.scrollTop;
+ }
+ var r = { x: el.offsetLeft - sLeft, y: el.offsetTop - sTop };
+ if (el.offsetParent) {
+ var tmp = absolutePosition(el.offsetParent);
+ r.x += tmp.x;
+ r.y += tmp.y;
+ }
+ return r;
+};
+
+/**
+ * Returns true if an element has a specified class name
+ */
+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;
+ }
+ node.className = eregReplace('(^| )'+ className +'($| )', '', 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 = document.getElementById(node);
+ }
+ if (node && node.parentNode) {
+ return node.parentNode.removeChild(node);
+ }
+ else {
+ return false;
+ }
+}