/* * The MIT License * * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, * Daniel Dyer, Yahoo! Inc., Alan Harder, InfraDNA, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ // // // JavaScript for Jenkins // See http://www.ibm.com/developerworks/web/library/wa-memleak/?ca=dgr-lnxw97JavascriptLeaks // for memory leak patterns and how to prevent them. // // create a new object whose prototype is the given object function object(o) { function F() {} F.prototype = o; return new F(); } // id generator var iota = 0; // crumb information var crumb = { fieldName: null, value: null, init: function(crumbField, crumbValue) { if (crumbField=="") return; // layout.jelly passes in "" whereas it means null. this.fieldName = crumbField; this.value = crumbValue; }, /** * Adds the crumb value into the given hash or array and returns it. */ wrap: function(headers) { if (this.fieldName!=null) { if (headers instanceof Array) headers.push(this.fieldName, this.value); else headers[this.fieldName]=this.value; } return headers; }, /** * Puts a hidden input field to the form so that the form submission will have the crumb value */ appendToForm : function(form) { if(this.fieldName==null) return; // noop var div = document.createElement("div"); div.innerHTML = ""; form.appendChild(div); } } // Form check code //======================================================== var FormChecker = { // pending requests queue : [], // conceptually boolean, but doing so create concurrency problem. // that is, during unit tests, the AJAX.send works synchronously, so // the onComplete happens before the send method returns. On a real environment, // more likely it's the other way around. So setting a boolean flag to true or false // won't work. inProgress : 0, /** * Schedules a form field check. Executions are serialized to reduce the bandwidth impact. * * @param url * Remote doXYZ URL that performs the check. Query string should include the field value. * @param method * HTTP method. GET or POST. I haven't confirmed specifics, but some browsers seem to cache GET requests. * @param target * HTML element whose innerHTML will be overwritten when the check is completed. */ delayedCheck : function(url, method, target) { if(url==null || method==null || target==null) return; // don't know whether we should throw an exception or ignore this. some broken plugins have illegal parameters this.queue.push({url:url, method:method, target:target}); this.schedule(); }, sendRequest : function(url, params) { if (params.method == "post") { var idx = url.indexOf('?'); params.parameters = url.substring(idx + 1); url = url.substring(0, idx); } new Ajax.Request(url, params); }, schedule : function() { if (this.inProgress>0) return; if (this.queue.length == 0) return; var next = this.queue.shift(); this.sendRequest(next.url, { method : next.method, onComplete : function(x) { var i; next.target.innerHTML = x.status==200 ? x.responseText : 'ERROR'; Behaviour.applySubtree(next.target); FormChecker.inProgress--; FormChecker.schedule(); layoutUpdateCallback.call(); } }); this.inProgress++; } } /** * Find the sibling (in the sense of the structured form submission) form item of the given name, * and returns that DOM node. * * @param {HTMLElement} e * @param {string} name * Name of the control to find. Can include "../../" etc in the prefix. * See @RelativePath. */ function findNearBy(e,name) { while (name.startsWith("../")) { name = name.substring(3); e = findFormParent(e,null,true); } // does 'e' itself match the criteria? // as some plugins use the field name as a parameter value, instead of 'value' var p = findFormItem(e,name,function(e,filter) { if (filter(e)) return e; return null; }); if (p!=null) return p; var owner = findFormParent(e,null,true); p = findPreviousFormItem(e,name); if (p!=null && findFormParent(p,null,true)==owner) return p; var n = findNextFormItem(e,name); if (n!=null && findFormParent(n,null,true)==owner) return n; return null; // not found } function controlValue(e) { if (e==null) return null; // compute the form validation value to be sent to the server var type = e.getAttribute("type"); if(type!=null && type.toLowerCase()=="checkbox") return e.checked; return e.value; } function toValue(e) { return encodeURIComponent(controlValue(e)); } /** * Builds a query string in a fluent API pattern. * @param {HTMLElement} owner * The 'this' control. */ function qs(owner) { return { params : "", append : function(s) { if (this.params.length==0) this.params+='?'; else this.params+='&'; this.params += s; return this; }, nearBy : function(name) { var e = findNearBy(owner,name); if (e==null) return this; // skip return this.append(Path.tail(name)+'='+toValue(e)); }, addThis : function() { return this.append("value="+toValue(owner)); }, toString : function() { return this.params; } }; } // find the nearest ancestor node that has the given tag name function findAncestor(e, tagName) { do { e = e.parentNode; } while (e != null && e.tagName != tagName); return e; } function findAncestorClass(e, cssClass) { do { e = e.parentNode; } while (e != null && !Element.hasClassName(e,cssClass)); return e; } function findFollowingTR(input, className) { // identify the parent TR var tr = input; while (tr.tagName != "TR") tr = tr.parentNode; // then next TR that matches the CSS do { tr = tr.nextSibling; } while (tr != null && (tr.tagName != "TR" || !Element.hasClassName(tr,className))); return tr; } function find(src,filter,traversalF) { while(src!=null) { src = traversalF(src); if(src!=null && filter(src)) return src; } return null; } /** * Traverses a form in the reverse document order starting from the given element (but excluding it), * until the given filter matches, or run out of an element. */ function findPrevious(src,filter) { return find(src,filter,function (e) { var p = e.previousSibling; if(p==null) return e.parentNode; while(p.lastChild!=null) p = p.lastChild; return p; }); } function findNext(src,filter) { return find(src,filter,function (e) { var n = e.nextSibling; if(n==null) return e.parentNode; while(n.firstChild!=null) n = n.firstChild; return n; }); } function findFormItem(src,name,directionF) { var name2 = "_."+name; // handles notation silently return directionF(src,function(e){ return (e.tagName=="INPUT" || e.tagName=="TEXTAREA" || e.tagName=="SELECT") && (e.name==name || e.name==name2); }); } /** * Traverses a form in the reverse document order and finds an INPUT element that matches the given name. */ function findPreviousFormItem(src,name) { return findFormItem(src,name,findPrevious); } function findNextFormItem(src,name) { return findFormItem(src,name,findNext); } /** * Parse HTML into DOM. */ function parseHtml(html) { var c = document.createElement("div"); c.innerHTML = html; return c.firstChild; } /** * Emulate the firing of an event. * * @param {HTMLElement} element * The element that will fire the event * @param {String} event * like 'change', 'blur', etc. */ function fireEvent(element,event){ if (document.createEvent) { // dispatch for firefox + others var evt = document.createEvent("HTMLEvents"); evt.initEvent(event, true, true ); // event type,bubbling,cancelable return !element.dispatchEvent(evt); } else { // dispatch for IE var evt = document.createEventObject(); return element.fireEvent('on'+event,evt) } } // shared tooltip object var tooltip; // Behavior rules //======================================================== // using tag names in CSS selector makes the processing faster function registerValidator(e) { e.targetElement = findFollowingTR(e, "validation-error-area").firstChild.nextSibling; e.targetUrl = function() { return eval(this.getAttribute("checkUrl")); }; var method = e.getAttribute("checkMethod"); if (!method) method = "get"; var url = e.targetUrl(); try { FormChecker.delayedCheck(url, method, e.targetElement); } catch (x) { // this happens if the checkUrl refers to a non-existing element. // don't let this kill off the entire JavaScript YAHOO.log("Failed to register validation method: "+e.getAttribute("checkUrl")+" : "+e); return; } var checker = function() { var target = this.targetElement; FormChecker.sendRequest(this.targetUrl(), { method : method, onComplete : function(x) { target.innerHTML = x.responseText; Behaviour.applySubtree(target); } }); } var oldOnchange = e.onchange; if(typeof oldOnchange=="function") { e.onchange = function() { checker.call(this); oldOnchange.call(this); } } else e.onchange = checker; e.onblur = checker; e = null; // avoid memory leak } function registerRegexpValidator(e,regexp,message) { e.targetElement = findFollowingTR(e, "validation-error-area").firstChild.nextSibling; var checkMessage = e.getAttribute('checkMessage'); if (checkMessage) message = checkMessage; var oldOnchange = e.onchange; e.onchange = function() { var set = oldOnchange != null ? oldOnchange.call(this) : false; if (this.value.match(regexp)) { if (!set) this.targetElement.innerHTML = ""; } else { this.targetElement.innerHTML = "
" + message + "
"; set = true; } return set; } e.onchange.call(e); e = null; // avoid memory leak } /** * Wraps a