/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Daniel Dyer, Yahoo! 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 Hudson
// 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) {
this.fieldName = crumbField;
this.value = crumbValue;
},
/**
* Adds the crumb value into the given hash and returns the hash.
*/
wrap: function(hash) {
if(this.fieldName!=null)
hash[this.fieldName]=this.value;
return hash;
},
/**
* 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) {
next.target.innerHTML = x.responseText;
FormChecker.inProgress--;
FormChecker.schedule();
}
});
this.inProgress++;
}
}
function toValue(e) {
// 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 encodeURIComponent(e.value);
}
// 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 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.tagName != "TR" || tr.className != className);
return tr;
}
/**
* 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) {
function prev(e) {
var p = e.previousSibling;
if(p==null) return e.parentNode;
while(p.lastChild!=null)
p = p.lastChild;
return p;
}
while(src!=null) {
src = prev(src);
if(src==null) break;
if(filter(src))
return src;
}
return null;
}
/**
* Traverses a form in the reverse document order and finds an INPUT element that matches the given name.
*/
function findPreviousFormItem(src,name) {
var name2 = "_."+name; // handles notation silently
return findPrevious(src,function(e){ return (e.tagName=="INPUT" || e.tagName=="TEXTAREA") && (e.name==name || e.name==name2); });
}
// 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";
FormChecker.delayedCheck(e.targetUrl(), method, e.targetElement);
var checker = function() {
var target = this.targetElement;
FormChecker.sendRequest(this.targetUrl(), {
method : method,
onComplete : function(x) {
target.innerHTML = x.responseText;
}
});
}
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 into YUI button.
*
* @param e
* button element
* @param onclick
* onclick handler
*/
function makeButton(e,onclick) {
var h = e.onclick;
var clsName = e.className;
var n = e.name;
var btn = new YAHOO.widget.Button(e,{});
if(onclick!=null)
btn.addListener("click",onclick);
if(h!=null)
btn.addListener("click",h);
var be = btn.get("element");
Element.addClassName(be,clsName);
if(n!=null) // copy the name
be.setAttribute("name",n);
return btn;
}
/*
If we are inside 'to-be-removed' class, some HTML altering behaviors interact badly, because
the behavior re-executes when the removed master copy gets reinserted later.
*/
function isInsideRemovable(e) {
return Element.ancestors(e).find(function(f){return f.hasClassName("to-be-removed");});
}
var hudsonRules = {
"BODY" : function() {
tooltip = new YAHOO.widget.Tooltip("tt", {context:[], zindex:999});
},
// do the ones that extract innerHTML so that they can get their original HTML before
// other behavior rules change them (like YUI buttons.)
"DIV.hetero-list-container" : function(e) {
if(isInsideRemovable(e)) return;
// components for the add button
var menu = document.createElement("SELECT");
var btn = findElementsBySelector(e,"INPUT.hetero-list-add")[0];
YAHOO.util.Dom.insertAfter(menu,btn);
var prototypes = e.lastChild;
while(!Element.hasClassName(prototypes,"prototypes"))
prototypes = prototypes.previousSibling;
var insertionPoint = prototypes.previousSibling; // this is where the new item is inserted.
// extract templates
var templates = []; var i=0;
for(var n=prototypes.firstChild;n!=null;n=n.nextSibling,i++) {
var name = n.getAttribute("name");
var tooltip = n.getAttribute("tooltip");
menu.options[i] = new Option(n.getAttribute("title"),""+i);
templates.push({html:n.innerHTML, name:name, tooltip:tooltip});
}
Element.remove(prototypes);
// D&D support
function prepareDD(e) {
var dd = new DragDrop(e);
var h = e;
// locate a handle
while(!Element.hasClassName(h,"dd-handle"))
h = h.firstChild;
dd.setHandleElId(h);
}
var withDragDrop = Element.hasClassName(e,"with-drag-drop");
if(withDragDrop) {
for(e=e.firstChild; e!=null; e=e.nextSibling) {
if(Element.hasClassName(e,"repeated-chunk"))
prepareDD(e);
}
}
var menuButton = new YAHOO.widget.Button(btn, { type: "menu", menu: menu });
menuButton.getMenu().clickEvent.subscribe(function(type,args,value) {
var t = templates[parseInt(args[1].value)]; // where this args[1] comes is a real mystery
var nc = document.createElement("div");
nc.className = "repeated-chunk";
nc.setAttribute("name",t.name);
nc.innerHTML = t.html;
insertionPoint.parentNode.insertBefore(nc, insertionPoint);
if(withDragDrop) prepareDD(nc);
Behaviour.applySubtree(nc);
});
menuButton.getMenu().renderEvent.subscribe(function(type,args,value) {
// hook up tooltip for menu items
var items = menuButton.getMenu().getItems();
for(i=0; i TBODY
var tr = link;
while (tr.tagName != "TR")
tr = tr.parentNode;
// move the contents of the advanced portion into the main table
var nameRef = tr.getAttribute("nameref");
while (container.lastChild != null) {
var row = container.lastChild;
if(nameRef!=null)
row.setAttribute("nameref",nameRef);
tr.parentNode.insertBefore(row, tr.nextSibling);
}
});
e = null; // avoid memory leak
},
"INPUT.expandButton" : function(e) {
makeButton(e,function(e) {
var link = e.target;
while(!Element.hasClassName(link,"advancedLink"))
link = link.parentNode;
link.style.display = "none";
link.nextSibling.style.display="block";
});
e = null; // avoid memory leak
},
// scripting for having default value in the input field
"INPUT.has-default-text" : function(e) {
var defaultValue = e.value;
Element.addClassName(e, "defaulted");
e.onfocus = function() {
if (this.value == defaultValue) {
this.value = "";
Element.removeClassName(this, "defaulted");
}
}
e.onblur = function() {
if (this.value == "") {
this.value = defaultValue;
Element.addClassName(this, "defaulted");
}
}
e = null; // avoid memory leak
},
// that doesn't use ID, so that it can be copied in
"LABEL.attach-previous" : function(e) {
e.onclick = function() {
var e = this;
while(e.tagName!="INPUT")
e=e.previousSibling;
e.click();
}
e = null;
},
// form fields that are validated via AJAX call to the server
// elements with this class should have two attributes 'checkUrl' that evaluates to the server URL.
"INPUT.validated" : registerValidator,
"SELECT.validated" : registerValidator,
"TEXTAREA.validated" : registerValidator,
// validate required form values
"INPUT.required" : function(e) { registerRegexpValidator(e,/./,"Field is required"); },
// validate form values to be a number
"INPUT.number" : function(e) { registerRegexpValidator(e,/^(\d+|)$/,"Not a number"); },
"INPUT.positive-number" : function(e) {
registerRegexpValidator(e,/^(\d*[1-9]\d*|)$/,"Not a positive number");
},
"A.help-button" : function(e) {
e.onclick = function() {
var tr = findFollowingTR(this, "help-area");
var div = tr.firstChild.nextSibling.firstChild;
if (div.style.display != "block") {
div.style.display = "block";
// make it visible
new Ajax.Request(this.getAttribute("helpURL"), {
method : 'get',
onComplete : function(x) {
div.innerHTML = x.responseText;
}
});
} else {
div.style.display = "none";
}
return false;
}
e = null; // avoid memory leak
},
// deferred client-side clickable map.
// this is useful where the generation of element is time consuming
"IMG[lazymap]" : function(e) {
new Ajax.Request(
e.getAttribute("lazymap"),
{
method : 'get',
onSuccess : function(x) {
var div = document.createElement("div");
document.body.appendChild(div);
div.innerHTML = x.responseText;
var id = "map" + (iota++);
div.firstChild.setAttribute("name", id);
e.setAttribute("usemap", "#" + id);
}
});
},
// button to add a new repeatable block
"INPUT.repeatable-add" : function(e) {
makeButton(e,function(e) {
repeatableSupport.onAdd(e.target);
});
e = null; // avoid memory leak
},
"INPUT.repeatable-delete" : function(e) {
makeButton(e,function(e) {
repeatableSupport.onDelete(e.target);
});
e = null; // avoid memory leak
},
// resizable text area
"TEXTAREA" : function(textarea) {
if(Element.hasClassName(textarea,"rich-editor")) {
// rich HTML editor
try {
var editor = new YAHOO.widget.Editor(textarea, {
dompath: true,
animate: true,
handleSubmit: true
});
// probably due to the timing issue, we need to let the editor know
// that DOM is ready
editor.DOMReady=true;
editor.fireQueue();
editor.render();
} catch(e) {
alert(e);
}
return;
}
var handle = textarea.nextSibling;
if(handle==null || handle.className!="textarea-handle") return;
var Event = YAHOO.util.Event;
handle.onmousedown = function(ev) {
ev = Event.getEvent(ev);
var offset = textarea.offsetHeight-Event.getPageY(ev);
textarea.style.opacity = 0.5;
document.onmousemove = function(ev) {
ev = Event.getEvent(ev);
function max(a,b) { if(a correctly, don't overwrite the existing value
if(x.getAttribute("nameRef")==null)
x.setAttribute("nameRef",id);
}
}
// used by optionalBlock.jelly to update the form status
// @param c checkbox element
function updateOptionalBlock(c,scroll) {
// find the start TR
var s = c;
while(!Element.hasClassName(s, "optional-block-start"))
s = s.parentNode;
var tbl = s.parentNode;
var i = false;
var o = false;
var checked = xor(c.checked,Element.hasClassName(c,"negative"));
var lastRow = null;
for (var j = 0; tbl.rows[j]; j++) {
var n = tbl.rows[j];
if (i && Element.hasClassName(n, "optional-block-end"))
o = true;
if (i && !o) {
if (checked) {
n.style.display = "";
lastRow = n;
} else
n.style.display = "none";
}
if (n==s) {
if (n.getAttribute('hasHelp') == 'true')
j++;
i = true;
}
}
if(checked && scroll) {
var D = YAHOO.util.Dom;
var r = D.getRegion(s);
if(lastRow!=null) r = r.union(D.getRegion(lastRow));
scrollIntoView(r);
}
if (c.name == 'hudson-tools-InstallSourceProperty') {
// Hack to hide tool home when "Install automatically" is checked.
var homeField = findPreviousFormItem(c, 'home');
if (homeField != null && homeField.value == '') {
var tr = findAncestor(homeField, 'TR');
if (tr != null) {
tr.style.display = c.checked ? 'none' : '';
}
}
}
}
//
// Auto-scroll support for progressive log output.
// See http://radio.javaranch.com/pascarello/2006/08/17/1155837038219.html
//
function AutoScroller(scrollContainer) {
// get the height of the viewport.
// See http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
function getViewportHeight() {
if (typeof( window.innerWidth ) == 'number') {
//Non-IE
return window.innerHeight;
} else if (document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight )) {
//IE 6+ in 'standards compliant mode'
return document.documentElement.clientHeight;
} else if (document.body && ( document.body.clientWidth || document.body.clientHeight )) {
//IE 4 compatible
return document.body.clientHeight;
}
return null;
}
return {
bottomThreshold : 25,
scrollContainer: scrollContainer,
getCurrentHeight : function() {
var scrollDiv = $(this.scrollContainer);
if (scrollDiv.scrollHeight > 0)
return scrollDiv.scrollHeight;
else
if (objDiv.offsetHeight > 0)
return scrollDiv.offsetHeight;
return null; // huh?
},
// return true if we are in the "stick to bottom" mode
isSticking : function() {
var scrollDiv = $(this.scrollContainer);
var currentHeight = this.getCurrentHeight();
// when used with the BODY tag, the height needs to be the viewport height, instead of
// the element height.
//var height = ((scrollDiv.style.pixelHeight) ? scrollDiv.style.pixelHeight : scrollDiv.offsetHeight);
var height = getViewportHeight();
var diff = currentHeight - scrollDiv.scrollTop - height;
// window.alert("currentHeight=" + currentHeight + ",scrollTop=" + scrollDiv.scrollTop + ",height=" + height);
return diff < this.bottomThreshold;
},
scrollToBottom : function() {
var scrollDiv = $(this.scrollContainer);
scrollDiv.scrollTop = this.getCurrentHeight();
}
};
}
// scroll the current window to display the given element or the region.
function scrollIntoView(e) {
function calcDelta(ex1,ex2,vx1,vw) {
var vx2=vx1+vw;
var a;
a = Math.min(vx1-ex1,vx2-ex2);
if(a>0) return -a;
a = Math.min(ex1-vx1,ex2-vx2);
if(a>0) return a;
return 0;
}
var D = YAHOO.util.Dom;
var r;
if(e.tagName!=null) r = D.getRegion(e);
else r = e;
var dx = calcDelta(r.left,r.right, document.body.scrollLeft, D.getViewportWidth());
var dy = calcDelta(r.top, r.bottom,document.body.scrollTop, D.getViewportHeight());
window.scrollBy(dx,dy);
}
// used in expandableTextbox.jelly to change a input field into a text area
function expandTextArea(button,id) {
button.style.display="none";
var field = document.getElementById(id);
var value = field.value.replace(/ +/g,'\n');
var n = field;
while(n.tagName!="TABLE")
n = n.parentNode;
n.parentNode.innerHTML =
"";
}
// refresh a part of the HTML specified by the given ID,
// by using the contents fetched from the given URL.
function refreshPart(id,url) {
var f = function() {
new Ajax.Request(url, {
onSuccess: function(rsp) {
var hist = $(id);
var p = hist.parentNode;
var next = hist.nextSibling;
p.removeChild(hist);
var div = document.createElement('div');
div.innerHTML = rsp.responseText;
var node = div.firstChild;
p.insertBefore(node, next);
Behaviour.applySubtree(node);
if(isRunAsTest) return;
refreshPart(id,url);
}
});
};
// if run as test, just do it once and do it now to make sure it's working,
// but don't repeat.
if(isRunAsTest) f();
else window.setTimeout(f, 5000);
}
/*
Perform URL encode.
Taken from http://www.cresc.co.jp/tech/java/URLencoding/JavaScript_URLEncoding.htm
@deprecated Use standard javascript method "encodeURIComponent" instead
*/
function encode(str){
var s, u;
var s0 = ""; // encoded str
for (var i = 0; i < str.length; i++){ // scan the source
s = str.charAt(i);
u = str.charCodeAt(i); // get unicode of the char
if (s == " "){s0 += "+";} // SP should be converted to "+"
else {
if ( u == 0x2a || u == 0x2d || u == 0x2e || u == 0x5f || ((u >= 0x30) && (u <= 0x39)) || ((u >= 0x41) && (u <= 0x5a)) || ((u >= 0x61) && (u <= 0x7a))){ // check for escape
s0 = s0 + s; // don't escape
} else { // escape
if ((u >= 0x0) && (u <= 0x7f)){ // single byte format
s = "0"+u.toString(16);
s0 += "%"+ s.substr(s.length-2);
} else
if (u > 0x1fffff){ // quaternary byte format (extended)
s0 += "%" + (0xF0 + ((u & 0x1c0000) >> 18)).toString(16);
s0 += "%" + (0x80 + ((u & 0x3f000) >> 12)).toString(16);
s0 += "%" + (0x80 + ((u & 0xfc0) >> 6)).toString(16);
s0 += "%" + (0x80 + (u & 0x3f)).toString(16);
} else
if (u > 0x7ff){ // triple byte format
s0 += "%" + (0xe0 + ((u & 0xf000) >> 12)).toString(16);
s0 += "%" + (0x80 + ((u & 0xfc0) >> 6)).toString(16);
s0 += "%" + (0x80 + (u & 0x3f)).toString(16);
} else { // double byte format
s0 += "%" + (0xc0 + ((u & 0x7c0) >> 6)).toString(16);
s0 += "%" + (0x80 + (u & 0x3f)).toString(16);
}
}
}
}
return s0;
}
// when there are multiple form elements of the same name,
// this method returns the input field of the given name that pairs up
// with the specified 'base' input element.
Form.findMatchingInput = function(base, name) {
// find the FORM element that owns us
var f = base;
while (f.tagName != "FORM")
f = f.parentNode;
var bases = Form.getInputs(f, null, base.name);
var targets = Form.getInputs(f, null, name);
for (var i=0; i and to control visibility
function updateDropDownList(sel) {
for (var i = 0; i < sel.subForms.length; i++) {
var show = sel.selectedIndex == i;
var f = sel.subForms[i];
var td = f.start;
while (true) {
td.style.display = (show ? "" : "none");
if(show)
td.removeAttribute("field-disabled");
else // buildFormData uses this attribute and ignores the contents
td.setAttribute("field-disabled","true");
if (td == f.end) break;
td = td.nextSibling;
}
}
}
// code for supporting repeatable.jelly
var repeatableSupport = {
// set by the inherited instance to the insertion point DIV
insertionPoint: null,
// HTML text of the repeated chunk
blockHTML: null,
// containing .
container: null,
// block name for structured HTML
name : null,
// do the initialization
init : function(container,master,insertionPoint) {
this.container = $(container);
this.container.tag = this;
master = $(master);
this.blockHTML = master.innerHTML;
master.parentNode.removeChild(master);
this.insertionPoint = $(insertionPoint);
this.name = master.getAttribute("name");
this.update();
},
// insert one more block at the insertion position
expand : function() {
// importNode isn't supported in IE.
// nc = document.importNode(node,true);
var nc = document.createElement("div");
nc.className = "repeated-chunk";
nc.setAttribute("name",this.name);
nc.innerHTML = this.blockHTML;
this.insertionPoint.parentNode.insertBefore(nc, this.insertionPoint);
Behaviour.applySubtree(nc);
this.update();
},
// update CSS classes associated with repeated items.
update : function() {
var children = [];
for( var n=this.container.firstChild; n!=null; n=n.nextSibling )
if(Element.hasClassName(n,"repeated-chunk"))
children.push(n);
if(children.length==0) {
// noop
} else
if(children.length==1) {
children[0].className = "repeated-chunk first last only";
} else {
children[0].className = "repeated-chunk first";
for(var i=1; i 2 && Element.hasClassName(rows[1], "transitive"))
Element.remove(rows[1]);
// insert new rows
var div = document.createElement('div');
div.innerHTML = rsp.responseText;
Behaviour.applySubtree(div);
var pivot = rows[0];
var newRows = div.firstChild.rows;
for (var i = newRows.length - 1; i >= 0; i--) {
pivot.parentNode.insertBefore(newRows[i], pivot.nextSibling);
}
// next update
bh.headers = ["n",rsp.getResponseHeader("n")];
window.setTimeout(updateBuilds, 5000);
}
});
}
window.setTimeout(updateBuilds, 5000);
}
// send async request to the given URL (which will send back serialized ListBoxModel object),
// then use the result to fill the list box.
function updateListBox(listBox,url) {
new Ajax.Request(url, {
onSuccess: function(rsp) {
var l = $(listBox);
while(l.length>0) l.options[0] = null;
var opts = eval('('+rsp.responseText+')').values;
for( var i=0; ib) return a; else return b; }
sizer.innerHTML = box.value;
var w = max(sizer.offsetWidth,minW.offsetWidth);
box.style.width =
comp.style.width =
comp.firstChild.style.width = (w+60)+"px";
var pos = YAHOO.util.Dom.getXY(box);
pos[1] += YAHOO.util.Dom.get(box).offsetHeight + 2;
YAHOO.util.Dom.setXY(comp, pos);
}
updatePos();
box.onkeyup = updatePos;
}
//
// structured form submission handling
// see http://hudson.gotdns.com/wiki/display/HUDSON/Structured+Form+Submission
function buildFormTree(form) {
try {
// I initially tried to use an associative array with DOM elemnets as keys
// but that doesn't seem to work neither on IE nor Firefox.
// so I switch back to adding a dynamic property on DOM.
form.formDom = {}; // root object
var doms = []; // DOMs that we added 'formDom' for.
doms.push(form);
function shortenName(name) {
// [abc.def.ghi] -> abc.def.ghi
if(name.startsWith('['))
return name.substring(1,name.length-1);
// abc.def.ghi -> ghi
var idx = name.lastIndexOf('.');
if(idx>=0) name = name.substring(idx+1);
return name;
}
function addProperty(parent,name,value) {
name = shortenName(name);
if(parent[name]!=null) {
if(parent[name].push==null) // is this array?
parent[name] = [ parent[name] ];
parent[name].push(value);
} else {
parent[name] = value;
}
}
// find the grouping parent node, which will have @name.
// then return the corresponding object in the map
function findParent(e) {
while(e!=form) {
e = e.parentNode;
// this is used to create a group where no single containing parent node exists,
// like
var nameRef = e.getAttribute("nameRef");
if(nameRef!=null)
e = $(nameRef);
if(e.getAttribute("field-disabled")!=null)
return {}; // this field shouldn't contribute to the final result
var name = e.getAttribute("name");
if(name!=null) {
if(e.tagName=="INPUT" && !xor(e.checked,Element.hasClassName(e,"negative")))
return {}; // field is not active
var m = e.formDom;
if(m==null) {
// this is a new grouping node
doms.push(e);
e.formDom = m = {};
addProperty(findParent(e), name, m);
}
return m;
}
}
return form.formDom; // guaranteed non-null
}
var jsonElement = null;
for( var i=0; i this.lastY) {
this.goingUp = false;
}
this.lastY = y;
},
onDragOver: function(e, id) {
var srcEl = this.getEl();
var destEl = Dom.get(id);
// We are only concerned with list items, we ignore the dragover
// notifications for the list.
if (destEl.nodeName == "DIV" && Dom.hasClass(destEl,"repeated-chunk")) {
var p = destEl.parentNode;
// if going up, insert above the target element
p.insertBefore(srcEl, this.goingUp?destEl:destEl.nextSibling);
DDM.refreshCache();
}
}
});
})();
function loadScript(href) {
var s = document.createElement("script");
s.setAttribute("src",href);
document.getElementsByTagName("HEAD")[0].appendChild(s);
}
var downloadService = {
continuations: {},
download : function(id,url,info, postBack,completionHandler) {
this.continuations[id] = {postBack:postBack,completionHandler:completionHandler};
loadScript(url+"?"+Hash.toQueryString(info));
},
post : function(id,data) {
var o = this.continuations[id];
new Ajax.Request(o.postBack, {
parameters:{json:Object.toJSON(data)},
onSuccess: function() {
if(o.completionHandler!=null)
o.completionHandler();
}
});
}
};
// update center service. for historical reasons,
// this is separate from downloadSerivce
var updateCenter = {
postBackURL : null,
info: {},
completionHandler: null,
url: "https://hudson.dev.java.net/",
checkUpdates : function() {
loadScript(updateCenter.url+"update-center.json?"+Hash.toQueryString(updateCenter.info));
},
post : function(data) {
new Ajax.Request(updateCenter.postBackURL, {
parameters:{json:Object.toJSON(data)},
onSuccess: function() {
if(updateCenter.completionHandler!=null)
updateCenter.completionHandler();
}
});
}
};
/*
redirects to a page once the page is ready.
@param url
Specifies the URL to redirect the user.
*/
function applySafeRedirector(url) {
var i=0;
new PeriodicalExecuter(function() {
i = (i+1)%4;
var s = "";
for( var j=0; j
function validateButton(checkUrl,paramList,button) {
button = button._button;
var parameters = {};
paramList.split(',').each(function(name) {
var p = findPreviousFormItem(button,name);
if(p!=null)
parameters[name] = p.value;
});
var spinner = Element.up(button,"DIV").nextSibling;
var target = spinner.nextSibling;
spinner.style.display="block";
new Ajax.Request(checkUrl, {
parameters: parameters,
onComplete: function(rsp) {
spinner.style.display="none";
target.innerHTML = rsp.responseText;
var s = rsp.getResponseHeader("script");
if(s!=null)
try {
eval(s);
} catch(e) {
window.alert("failed to evaluate "+s+"\n"+e.message);
}
}
});
}
// create a combobox.
// @param id
// ID of the element that becomes a combobox.
// @param valueFunction
// Function that returns all the candidates as an array
function createComboBox(id,valueFunction) {
var candidates = valueFunction();
Behaviour.addLoadEvent(function() {
var callback = function(value /*, comboBox*/) {
var items = new Array();
if (value.length > 0) { // if no value, we'll not provide anything
value = value.toLowerCase();
for (var i = 0; i= 0) {
items.push(candidates[i]);
if(items.length>20)
break; // 20 items in the list should be enough
}
}
}
return items; // equiv to: comboBox.setItems(items);
};
if (document.getElementById(id) != null) {
new ComboBox(id,callback);
}
});
}