User:Galil/Sandbox/ajaxifier.js
From Guild Wars Wiki
< User:Galil | Sandbox
Jump to navigationJump to search
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/**** * ajaxifier.js, as used in [[User:Galil/GWW Ajaxifier]]. * * by [[User:Galil]] * * Use however you wish, but be sure to put credit where credit is due. * Uses following libraries, see their pages for the related code: * [http://orangoo.com/labs/AJS/ AJS], [http://orangoo.com/labs/AJS/ AJS.fx], [http://orangoo.com/labs/GreyBox/ GreyBox] <nowiki> */ /******************************************************************************* ** Stylesheet ** *******************************************************************************/ var ssheet = // autocomplete ' ul#autoCompleteList li { overflow: hidden; padding-left: 4px; padding-right: 4px; ' + ' white-space: nowrap; cursor: default; } ' + ' ul#autoCompleteList li:hover { background-color: #f9f9f9; } ' + ' ul#autoCompleteList li span { font-weight: 900; } ' + ' ul#autoCompleteList { margin: 0px; padding: 0px; } ' + ' div#autoComplete { background-color: #ffffff; border: 1px solid #aaaaaa; ' + ' padding: 1px; position: absolute; z-index: 5; } ' // ajax watchpage adding + ' li#ca-watch a img { margin-right: 4px; } ' + ' li#ca-unwatch a img { margin-right: 4px; } ' // ajax patrol marking + ' td.diff-ntitle a img { margin-right: 4px; } ' // gallery + ' #GB_overlay { background-color: #000000; position: absolute; margin: auto; top: 0; left: 0; ' + ' z-index: 100; } ' + ' #GB_window { left: 0; top: 0; font-size: 1px; position: absolute; overflow: visible; ' + ' z-index: 150; } ' + ' #GB_window .content { width: auto; margin: 0; padding: 0; text-align: center; } ' + ' #GB_frame #GB_loading { margin-top: 50px; margin-left: auto; margin-right: auto; } ' + ' #GB_frame { border: 0; margin: 0; padding: 0; overflow: auto; white-space: nowrap; } ' + ' .GB_Gallery { margin: 0 22px 0 22px; } ' + ' .GB_Gallery .content { background-color: #fff; border: 3px solid #ddd; } ' + ' .GB_header { top: 10px; left: 0; margin: 0; z-index: 500; position: absolute; ' + ' border-bottom: 2px solid #555555; border-top: 2px solid #555555; } ' + ' .GB_header .inner { background-color: #333333; font-family: Arial, Verdana, sans-serif; ' + ' padding: 2px 20px 2px 20px; } ' + ' .GB_header table { background-color: inherit; margin: 0; width: 100%; ' + ' border-collapse: collapse; } ' + ' .GB_header .caption { text-align: left; color: #eeeeee; white-space: nowrap; font-size: 20px; } ' + ' .GB_header .close { text-align: right; } ' + ' .GB_header .close img { z-index: 500; cursor: pointer; } ' + ' .GB_header .middle { white-space: nowrap; text-align: center; } ' + ' #GB_middle { color: #eeeeee; } ' + ' #GB_middle img { cursor: pointer; vertical-align: middle; } ' + ' #GB_middle .disabled { cursor: default; } ' + ' #GB_middle .left { padding-right: 10px; } ' + ' #GB_middle .right { padding-left: 10px; } ' + ' .GB_Window .content { background-color: #ffffff; border: 3px solid #cccccc; border-top: none; } ' + ' .GB_Window .header { border-bottom: 1px solid #aaaaaa; border-top: 1px solid #999999; ' + ' border-left: 3px solid #cccccc; border-right: 3px solid #cccccc; margin: 0; ' + ' height: 22px; font-size: 12px; padding: 3px 0; color: #333333; } ' + ' .GB_Window .caption { font-size: 12px; text-align: left; font-weight: bold; ' + ' white-space: nowrap; padding-right: 20px; } ' + ' .GB_Window .close { text-align: right; } ' + ' .GB_Window .close span { font-size: 12px; cursor: pointer; } ' + ' .GB_Window .close img { cursor: pointer; padding: 0 3px 0 0; } ' + ' .GB_Window .on { border-bottom: 1px solid #333333; } ' + ' .GB_Window .click { border-bottom: 1px solid red; } '; var sstyle = AJS.createDOM('style', { type: 'text/css' }); AJS.setHTML(sstyle, ssheet); AJS.$bytc('head', null)[0].appendChild(sstyle); /******************************************************************************* ** Resources ** *******************************************************************************/ var Resources = { HeaderLoadingImage: "data:image/gif;base64,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", TangoCheckIcon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9gBDAMXErFz6W0AAAFJSURBVCjPY2QgAih4tfMzMDDwMDAwPGckpNg+dVaosrzkNGbG/xxHz942Y8Gn2Dh6soWGqtyc/wwMbNduPIz99uPXDZwatEP6xIx0lJawsrHxXrt+L+PIvPQ1DAwMDEy4NBhoyvVz8fIov3vzbsW9x69nw8SxarCIm+olLikW+efXz2eXbjwuerCt8j+KBgWvdmat4F4bKJvL1EClh4GJieH23afVV9YUvkA2jEXFt5Pb3FClT1ZKJIGHa6qruAifHjc/v8bn9+9P37r/Yhm67SyMjIzsosJ8Nt8ZOdic7Q22MTAwMjEzMzGevniv8cG2yl/oGpjf3dz9/YeAyUFtFYmID7+Z+X/9Z2L9/eXj5XVdkUXY/MfEwMDAcGFF/tWTZ29kywlxMCiK8zA8eoIIFQwnwRh3H75a+WvXSSY2VhaeZ68+bMKlAQBOGm9OVewMhgAAAABJRU5ErkJggg==", TangoCrossIcon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAALEgAACxIB0t1+/AAAAAd0SU1FB9gBDAMXMoodyaUAAAERSURBVCjPlZG9SgNREIW/mXvd5YY10c0uKCnEwlZRFCwU7H0HsfUNbBXxJUxnKwhBSWOwEyvBRhtNoWAhAdFC0SBjEyHkp8ipZuCcmTNzYES4rnoJmAc+gE8gABtACrz0CgvrIbSOs8wW4vgOKFfiuFYtlWxT5AvI+1aNiRych2C3SWKp948neW7X3luiegOUB9nz06qXzRDsPsvs2XubU30HtoFo2E2LO4VC+9t723POgCowMYwczUTRxUOxaA3nrCliK849AZMD2bHq/lmSWF3ViiJvR87Zlaqlqqc93wRgfFn19RBsVrUFbOWq9V0RWxVpA5V/onSJpoA14BdoAD+dHDKg1smnD9IzhAH9aPgDrrlBOx9lMpwAAAAASUVORK5CYII=" }; /******************************************************************************* ** Helper functions ** *******************************************************************************/ function findPos(obj) { var curleft = curtop = 0; if (obj.offsetParent) { curleft = obj.offsetLeft curtop = obj.offsetTop while (obj = obj.offsetParent) { curleft += obj.offsetLeft curtop += obj.offsetTop } } return [curleft,curtop]; } function getElement(path) { return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; } function extend(obj, methods) { for (var i in methods) obj[i] = methods[i]; } /******************************************************************************* ** Search box auto completion ** *******************************************************************************/ var SearchBox = { initialize: function(elem) { if (elem) { AJS.bindMethods(SearchBox); // turns off IE's and firefox's autocomplete "feature" elem.setAttribute('autocomplete', 'off'); this.element = elem; // initialize sub-objects this.autoComplete.initialize(); this.cache.initialize(); // set up events AJS.AEV(elem, 'blur', this.onElementBlur); AJS.AEV(elem, 'focus', this.onElementFocus); AJS.AEV(elem, 'keyup', this.onElementKeyUp); } }, onElementBlur: function(event) { if (this.timeout) { window.clearTimeout(this.timeout); this.timeout = null; } // we have to wait a bit with hiding, so click events go through this.timeout = window.setTimeout('SearchBox.autoComplete.hide()', 100); }, onElementFocus: function(event) { if (this.element.value.length > 0) this.autoComplete.display(); }, onElementKeyUp: function(event) { if (this.timeout) { window.clearTimeout(this.timeout); this.timeout = null; } if (event.keyCode == 40) { // down arrow this.autoComplete.next(); this.autoComplete.display(); this.element.value = this.autoComplete.selected(); } else if (event.keyCode == 38) { // up arrow this.autoComplete.previous(); this.autoComplete.display(); this.element.value = this.autoComplete.selected(); } else if (event.keyCode == 13) { // enter key this.autoComplete.hide(); } else if (event.keyCode == 27) { // escape key this.autoComplete.hide(); } else if (this.element.value) { if (this.cache.contains(this.element.value)){ this.autoComplete.populate(this.element.value, this.cache.fetch(this.element.value)); this.autoComplete.display(); } else { this.timeout = window.setTimeout('SearchBox.fetch()', 1000); } } }, fetch: function() { var input = this.element.value; if (this.timeout) this.timeout = null; if (input.length > 0) { var ajax = AJS.getRequest('http://wiki.guildwars.com/api.php?action=opensearch&search='+input, null, 'GET'); ajax.addCallback(function(res_txt, req) { var json = AJS.evalTxt(res_txt); if (json.length == 2) { SearchBox.cache.add(json[0], json[1]); SearchBox.autoComplete.populate(json[0], json[1]); if (SearchBox.autoComplete.count() > 0) SearchBox.autoComplete.display(); else SearchBox.autoComplete.hide(); } else { SearchBox.cache.add(input, []); SearchBox.autoComplete.populate(input, []); SearchBox.autoComplete.hide(); } }); ajax.sendReq(); } }, autoComplete: { initialize: function() { var fn = AJS.bind(function() { AJS.bindMethods(SearchBox.autoComplete); this.current = -1; this.key = ''; this.arr = new Array(); this.list = AJS.UL({ id: 'autoCompleteList' }); this.container = AJS.DIV({ id: 'autoComplete' }, this.list); AJS.hideElement(this.container); AJS.getBody().appendChild(this.container); }, SearchBox.autoComplete, []); fn(); }, clear: function() { while (this.list.hasChildNodes()) { this.list.removeChild(this.list.firstChild); } this.arr = new Array(); }, count: function() { return this.arr.length; }, display: function() { if (this.count() > 0 && !this.visible()) { var elem = SearchBox.element; var div = this.container; elem_pos = findPos(elem); AJS.setLeft(div, elem_pos[0] - 1); AJS.setTop(div, elem_pos[1] + elem.offsetHeight); AJS.setWidth(div, elem.offsetWidth - 4); AJS.showElement(div); } }, hide: function() { AJS.hideElement(this.container); }, visible: function() { return !(AJS.isElementHidden(this.container)); }, next: function() { if (this.current == this.count() - 1) this.setCurrent(-1); else this.setCurrent(this.current + 1); }, previous: function() { if (this.current == -1) this.setCurrent(this.count() - 1); else this.setCurrent(this.current - 1); }, reset: function() { if (this.list.childNodes.length > this.current && this.current != -1) { this.list.childNodes[this.current].style.backgroundColor = ''; this.list.childNodes[this.current].style.color = 'highlighttext'; } this.current = -1; }, populate: function(key, arr) { if (key && arr) { if (key.length > 0) { this.clear(); this.key = key; this.arr = arr; this.reset(); for (var i in arr) { var li = AJS.LI({ id: 'autoCompleteLi' + i , childid: i, title: arr[i]}, AJS.SPAN({}, key), arr[i].substr(key.length)); this.list.appendChild(li); var self = this; AJS.AEV(li, 'click', function(event){ var elem = event.target; var i = elem.getAttribute('childid'); self.setCurrent(i); SearchBox.element.value = self.selected(); self.hide(); }); } if (this.count() <= 0 && this.visible()) this.hide(); } } }, selected: function() { if (this.current != -1) { return this.arr[this.current]; } else { return this.key; } }, setCurrent: function(id) { if (this.current != -1) { this.list.childNodes[this.current].style.backgroundColor = ''; this.list.childNodes[this.current].style.color = ''; } if (id != -1) { this.list.childNodes[id].style.backgroundColor = 'highlight'; this.list.childNodes[id].style.color = 'highlighttext'; } this.current = id; } }, cache: { initialize: function() { AJS.bindMethods(SearchBox.cache); SearchBox.cache.cache = new Object(); }, add: function(key, arr) { this.cache[key] = arr; }, contains: function(key) { if (this.cache[key]) return true; else return false; }, count: function(key) { if (this.cache[key]) return this.cache[key].length; else return -1; }, fetch: function(key) { return this.cache[key]; }, remove: function(key) { if (this.cache[key]) this.cache[key] = null; } } }; /******************************************************************************* ** AJAX link class ** *******************************************************************************/ function AjaxLink(elem) { this.initialize(elem); } extend(AjaxLink.prototype, { initialize: function(elem) { if (!elem) return; // init variables this.link = elem; this.url = elem.href; this.working = false; // "nullify" the url this.link.href = 'javascript:void(0);'; // insert the loading image first in the link this.image = AJS.IMG({ src: Resources.HeaderLoadingImage, alt: 'Working' }); AJS.hideElement(this.image); if (this.link.hasChildNodes()) this.link.insertBefore(this.image, this.link.firstChild); else this.link.appendChild(this.image); // events and binds var self = this; this.onLinkClick = AJS.bind(this.onLinkClick, this); this.disable = AJS.bind(this.disable, this); AJS.AEV(this.link, 'click', this.onLinkClick); }, onLinkClick: function(event) { this.working = true; var self = this; var ajax = AJS.getRequest(this.url, null, 'GET'); ajax.addCallback(function(res_txt, req) { self.working = false; AJS.hideElement(self.image); var fn = AJS.bind(self.onSuccess, self, [ res_txt ]); fn(); }); ajax.addErrback(function(res_txt, req) { self.working = false; AJS.hideElement(self.image); var fn = AJS.bind(self.onError, self, [ res_txt ]); fn(); }); this.image.src = Resources.HeaderLoadingImage; AJS.showElement(this.image); ajax.sendReq(); }, disable: function() { AJS.REV(this.link, 'click', this.onLinkClick); }, onError: function(response) { // empty since it's to be overridden anyway }, onSuccess: function(response) { // empty since it's to be overridden anyway } }); /******************************************************************************* ** Various AJAX links ** *******************************************************************************/ var WatchPage = { initialize: function() { AJS.bindMethods(WatchPage); // init variables if (AJS.$('ca-watch')) this.link = new AjaxLink(AJS.$('ca-watch').firstChild); else if (AJS.$('ca-unwatch')) this.link = new AjaxLink(AJS.$('ca-unwatch').firstChild); else return; // is this page watched? if (this.link.url.indexOf('action=watch') != -1) this.watched = false; else this.watched = true; // set up events this.link.onSuccess = this.toggle; }, toggle: function(response) { var startIndex = 0; var stopIndex = 0; this.watched = !(this.watched); if (this.watched) { this.link.url = this.link.url.replace(/action=watch/i, 'action=unwatch'); startIndex = response.indexOf('<li id="ca-unwatch" class="selected">'); this.link.link.parentNode.id = 'ca-unwatch'; var i = this.link.link.title.indexOf(' ['); if (ta) { if (i != -1) this.link.link.title = ta['ca-unwatch'][1] + this.link.link.title.substr(i); else this.link.link.title = ta['ca-unwatch'][1]; } } else { this.link.url = this.link.url.replace(/action=unwatch/i, 'action=watch'); startIndex = response.indexOf('<li id="ca-watch" class="selected">'); this.link.link.parentNode.id = 'ca-watch'; var i = this.link.link.title.indexOf(' ['); if (ta) { if (i != -1) this.link.link.title = ta['ca-watch'][1] + this.link.link.title.substr(i); else this.link.link.title = ta['ca-watch'][1]; } } startIndex = response.indexOf('>', startIndex + 40) + 1; stopIndex = response.indexOf('<', startIndex); this.link.link.lastChild.nodeValue = response.substr(startIndex, stopIndex - startIndex); } }; var PatrolPage = { initialize: function() { AJS.bindMethods(PatrolPage); // init variables var elem = getElement("//td[@class='diff-ntitle']/a[contains(@href, 'action=markpatrolled')]"); if (!elem) return; this.link = new AjaxLink(elem); // set up events this.link.onError = this.failed; this.link.onSuccess = this.marked; }, marked: function(response) { var startIndex = 0; var stopIndex = 0; startIndex = response.indexOf('<h1 class="firstHeading">') + 25; stopIndex = response.indexOf('</h1>', startIndex); this.link.link.lastChild.nodeValue = response.substr(startIndex, stopIndex - startIndex); this.link.image.src = Resources.TangoCheckIcon; this.link.image.alt = 'Success'; AJS.showElement(this.link.image); this.link.disable(); }, failed: function(response) { var startIndex = 0; var stopIndex = 0; startIndex = response.indexOf('<h1 class="firstHeading">') + 25; stopIndex = response.indexOf('</h1>', startIndex); this.link.link.lastChild.nodeValue = response.substr(startIndex, stopIndex - startIndex); this.link.image.src = Resources.TangoCrossIcon; this.link.image.alt = 'Failed'; AJS.showElement(this.link.image); } }; /******************************************************************************* ** Gallery ** *******************************************************************************/ var Gallery = { initialize: function() { if (wgPageName.indexOf('Image:') == 0) return; var a = AJS.A({ href: 'javascript:void(0);', title: 'View this page\'s gallery' }, 'Gallery'); this.link = AJS.LI({ id: 'ca-gallery' }, a); Gallery.imageList.initialize(); Gallery.imageList.populate(AJS.$('bodyContent')); if (Gallery.imageList.count > 0) { var actionList = getElement("//div[@id='p-cactions']/div[@class='pBody']/ul"); if (actionList) actionList.appendChild(this.link); } AJS.AEV(a, 'click', function(event){ var fun = AJS.bind(Gallery.activate, Gallery); fun(); }); }, activate: function() { if (Gallery.imageList.count == 1) GB_showImage(Gallery.imageList.images[0].caption, Gallery.imageList.images[0].url); else if (Gallery.imageList.count > 1) GB_showImageSet(Gallery.imageList.images, 1); }, imageList: { initialize: function() { this.count = 0; this.images = new Array(); this.minSize = 4097; // this is in number of pixels, since we DO want to display // armors that are 64x100 (6400) pixels and such, but not // skills icons that are 64x64 pixels (4096), so add 1px ;) }, populate: function(fromElement) { if (!fromElement) return; var imgs = AJS.$bytc('img', null, fromElement); if (imgs) { for (var i = 0; i < imgs.length; i++) { if (imgs[i].width * imgs[i].height >= this.minSize) { var img = imgs[i]; this.count++; this.images.push({ 'caption': unescape(img.parentNode.href.substr(img.parentNode.href.indexOf('Image:')).replace(/_/g, ' ')), 'url': img.src.replace(/\/thumb(\/.*?)\/[^\/]*?$/i, '$1') }); } } } } } }; AJS.AEV(window, 'load', function() { SearchBox.initialize(AJS.$('searchInput')); WatchPage.initialize(); PatrolPage.initialize(); Gallery.initialize(); }); /*** * </nowiki> */