566 lines
15 KiB
JavaScript
566 lines
15 KiB
JavaScript
/* Utility functions for Bugzilla scripts
|
|
* Rewritten without YAHOO UI
|
|
* License: Dual-license GPL 3.0+ or MPL 1.1+
|
|
* Contributor(s): Vitaliy Filippov <vitalif@mail.ru>
|
|
*/
|
|
|
|
// Get the position of 'obj' from the page top
|
|
// Returns [x, y], in pixels
|
|
function findPos(obj)
|
|
{
|
|
if (obj.offsetParent)
|
|
{
|
|
var r = [ 0, 0 ];
|
|
while (obj)
|
|
{
|
|
r[0] += obj.offsetLeft;
|
|
r[1] += obj.offsetTop;
|
|
obj = obj.offsetParent;
|
|
}
|
|
return r;
|
|
}
|
|
else if (obj.y)
|
|
return [ obj.x, obj.y ];
|
|
}
|
|
|
|
// Escape special HTML/XML characters
|
|
function htmlspecialchars(s)
|
|
{
|
|
s = s.replace(/&/g, '&'); //&
|
|
s = s.replace(/</g, '<'); //<
|
|
s = s.replace(/>/g, '>'); //>
|
|
s = s.replace(/"/g, '"'); //"
|
|
return s;
|
|
}
|
|
|
|
window.http_build_query = function(data)
|
|
{
|
|
var encoded = '';
|
|
for (var i in data)
|
|
{
|
|
encoded = encoded+'&'+encodeURIComponent(i)+'='+(data[i] === false || data[i] === null ? '' : encodeURIComponent(data[i]));
|
|
}
|
|
return encoded.substr(1);
|
|
};
|
|
|
|
// Checks if a specified value 'val' is in the specified array 'arr'
|
|
function bz_isValueInArray(arr, val)
|
|
{
|
|
for (var i = arr.length-1; i >= 0; i--)
|
|
if (arr[i] == val)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Create wanted options in a select form control.
|
|
*
|
|
* @param aSelect Select form control to manipulate.
|
|
* @param aValue Value attribute of the new option element.
|
|
* @param aTextValue Value of a text node appended to the new option
|
|
* element.
|
|
* @return Created option element.
|
|
*/
|
|
function bz_createOptionInSelect(aSelect, aTextValue, aValue)
|
|
{
|
|
var myOption = new Option(aTextValue, aValue);
|
|
aSelect.options[aSelect.length] = myOption;
|
|
return myOption;
|
|
}
|
|
|
|
/**
|
|
* Clears all options from a select form control.
|
|
*
|
|
* @param aSelect Select form control of which options to clear.
|
|
*/
|
|
function bz_clearOptions(aSelect)
|
|
{
|
|
var length = aSelect.options.length;
|
|
for (var i = 0; i < length; i++)
|
|
aSelect.removeChild(aSelect.options[0]);
|
|
}
|
|
|
|
/**
|
|
* Takes an array and moves all the values to an select.
|
|
*
|
|
* @param aSelect Select form control to populate. Will be cleared
|
|
* before array values are created in it.
|
|
* @param aArray Array with values to populate select with.
|
|
*/
|
|
function bz_populateSelectFromArray(aSelect, aArray)
|
|
{
|
|
// Clear the field
|
|
bz_clearOptions(aSelect);
|
|
for (var i = 0; i < aArray.length; i++)
|
|
{
|
|
var item = aArray[i];
|
|
bz_createOptionInSelect(aSelect, item[1], item[0]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tells you whether or not a particular value is selected in a select,
|
|
* whether it's a multi-select or a single-select. The check is
|
|
* case-sensitive.
|
|
*
|
|
* @param aSelect The select you're checking.
|
|
* @param aValue The value that you want to know about.
|
|
*/
|
|
function bz_valueSelected(aSelect, aValue)
|
|
{
|
|
var options = aSelect.options;
|
|
for (var i = 0; i < options.length; i++)
|
|
if (options[i].selected && options[i].value == aValue)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Tells you where (what index) in a <select> a particular option is.
|
|
* Returns -1 if the value is not in the <select>
|
|
*
|
|
* @param aSelect The select you're checking.
|
|
* @param aValue The value you want to know the index of.
|
|
*/
|
|
function bz_optionIndex(aSelect, aValue)
|
|
{
|
|
for (var i = 0; i < aSelect.options.length; i++)
|
|
if (aSelect.options[i].value == aValue)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Used to fire an event programmatically.
|
|
*
|
|
* @param anElement The element you want to fire the event of.
|
|
* @param anEvent The name of the event you want to fire,
|
|
* without the word "on" in front of it.
|
|
*/
|
|
function bz_fireEvent(anElement, anEvent)
|
|
{
|
|
// IE
|
|
if (document.createEventObject)
|
|
{
|
|
var evt = document.createEventObject();
|
|
return anElement.fireEvent('on' + anEvent, evt);
|
|
}
|
|
// Firefox, etc.
|
|
var evt = document.createEvent("HTMLEvents");
|
|
evt.initEvent(anEvent, true, true); // event type, bubbling, cancelable
|
|
return !anElement.dispatchEvent(evt);
|
|
}
|
|
|
|
/* map { $_ => 1 } %h */
|
|
function array_hash(ar)
|
|
{
|
|
var h = {};
|
|
if (ar.length == 1 && ar[0].length == 0)
|
|
return h;
|
|
for (i in ar)
|
|
h[ar[i]] = 1;
|
|
return h;
|
|
}
|
|
|
|
/* Calculates the difference between two arrays.
|
|
from, to ---> added, removed
|
|
[a,b,c], [d,b] ---> {d:1},{a:1,c:1} */
|
|
function diff_arrays(a1, a2)
|
|
{
|
|
var h1 = array_hash(a1);
|
|
var h2 = array_hash(a2);
|
|
var add = {}, rem = {};
|
|
for (i in a1)
|
|
if (!h2[a1[i]])
|
|
rem[a1[i]] = 1;
|
|
for (i in a2)
|
|
if (!h1[a2[i]])
|
|
add[a2[i]] = 1;
|
|
return [ add, rem ];
|
|
}
|
|
|
|
/* join ",", grep { $h{$_} } keys %h */
|
|
function hash_join(h)
|
|
{
|
|
var a = [];
|
|
for (i in h)
|
|
if (h[i])
|
|
a.push(i);
|
|
return a.join(", ");
|
|
}
|
|
|
|
/* CustIS Bug 64559 - Submit form on Ctrl-Enter */
|
|
function ctrlEnter(event, formElem)
|
|
{
|
|
if (event.ctrlKey && (event.keyCode == 0xA || event.keyCode == 0xD))
|
|
{
|
|
formElem.commit.click();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (typeof Node == 'undefined')
|
|
{
|
|
/* MSIE doesn't define Node, so provide a compatibility object */
|
|
window.Node = {
|
|
TEXT_NODE: 3,
|
|
ENTITY_REFERENCE_NODE: 5
|
|
};
|
|
}
|
|
|
|
/* Functions for comment preview */
|
|
|
|
// pseudo-ajax through form submit to an invisible iframe
|
|
// probably this could be replaced with conventional AJAX (jQuery...)
|
|
// but this also works good
|
|
window.iframeajax_call = 0;
|
|
window.iframeajax = function(url, data)
|
|
{
|
|
var f = document.createElement('form');
|
|
var i = document.createElement('iframe');
|
|
f.target = i.name = i.id = 'iframeajax_'+(window.iframeajax_call++);
|
|
f.method = 'POST';
|
|
f.action = url;
|
|
var d = document.createElement('div');
|
|
d.id = 'div_'+i.id;
|
|
data['iframeajaxid'] = i.id.substr(11);
|
|
for (var k in data)
|
|
{
|
|
var n = document.createElement('input');
|
|
n.type = 'hidden';
|
|
n.name = k;
|
|
n.value = data[k];
|
|
f.appendChild(n);
|
|
}
|
|
d.style.display = 'none';
|
|
d.appendChild(f);
|
|
d.appendChild(i);
|
|
document.body.appendChild(d);
|
|
window.frames[i.id].name = i.id;
|
|
addListener(i, 'load', function() {
|
|
i.contentWindow.loaded();
|
|
i.parentNode.removeChild(i);
|
|
});
|
|
f.submit();
|
|
};
|
|
|
|
window.addListener = function(obj, event, handler)
|
|
{
|
|
if (typeof(obj) == 'string')
|
|
obj = document.getElementById(obj);
|
|
if (!obj)
|
|
return;
|
|
if (obj.addEventListener)
|
|
obj.addEventListener(event, handler, false);
|
|
else if (obj.attachEvent)
|
|
{
|
|
obj._attached = obj._attached || {};
|
|
obj._attached[handler] = function() { handler.call(obj, window.event) };
|
|
obj.attachEvent('on'+event, obj._attached[handler]);
|
|
}
|
|
};
|
|
window.removeListener = function(obj, event, handler)
|
|
{
|
|
if (typeof(obj) == 'string')
|
|
obj = document.getElementById(obj);
|
|
if (!obj)
|
|
return;
|
|
if (obj.addEventListener)
|
|
obj.removeEventListener(event, handler, false);
|
|
else if (obj.attachEvent && obj._attached && obj._attached[handler])
|
|
{
|
|
obj.detachEvent('on'+event, obj._attached[handler]);
|
|
obj._attached[handler] = null;
|
|
}
|
|
};
|
|
window.eventTarget = function(ev)
|
|
{
|
|
if (!ev) var ev = window.event;
|
|
var t = ev.target;
|
|
if (!t) t = ev.srcElement;
|
|
if (t && t.nodeType == 3) t = t.parentNode;
|
|
return t;
|
|
};
|
|
|
|
/**
|
|
* addClass : add CSS class to an element
|
|
* returns (void)
|
|
* removeClass : remove CSS class from element
|
|
* returns (modified => true)
|
|
* hasClass : check if element has the specified CSS class
|
|
* returns (has class => true)
|
|
* toggleClass : add / remove CSS class if object doesn't have it / has it.
|
|
* returns (has class after toggle => true)
|
|
* FIXME all have jQuery alternatives
|
|
*
|
|
* @param anElement/obj The element to toggle the class on
|
|
* @param aClass/c The name of the CSS class to toggle.
|
|
*/
|
|
window.addClass = function(obj, c)
|
|
{
|
|
if (obj !== null && (obj instanceof Array)) // ie8 needs explicit check for null here O_o
|
|
{
|
|
for (var i = 0; i < obj.length; i++)
|
|
addClass(obj[i], c);
|
|
return;
|
|
}
|
|
if (typeof(obj) == 'string')
|
|
obj = document.getElementById(obj);
|
|
if (obj)
|
|
obj.className = obj.className+' '+c;
|
|
};
|
|
window.removeClass = function(obj, c)
|
|
{
|
|
if (obj !== null && (obj instanceof Array))
|
|
{
|
|
for (var i = 0; i < obj.length; i++)
|
|
removeClass(obj[i], c);
|
|
return;
|
|
}
|
|
if (typeof(obj) == 'string')
|
|
obj = document.getElementById(obj);
|
|
if (!obj)
|
|
return false;
|
|
var l = obj.className.split(/\s+/);
|
|
var l1 = [];
|
|
for (var i = l.length-1; i >= 0; i--)
|
|
if (l[i] != c)
|
|
l1.push(l[i]);
|
|
obj.className = l1.length ? l1.join(' ') : '';
|
|
return l1.length != l.length;
|
|
};
|
|
window.hasClass = function(obj, c)
|
|
{
|
|
if (typeof(obj) == 'string')
|
|
obj = document.getElementById(obj);
|
|
if (obj.className === undefined)
|
|
return false;
|
|
var l = obj.className.split(/\s+/);
|
|
for (var i = l.length-1; i >= 0; i--)
|
|
if (l[i] == c)
|
|
return true;
|
|
return false;
|
|
};
|
|
window.toggleClass = function(anElement, aClass)
|
|
{
|
|
if (typeof(anElement) == 'string')
|
|
anElement = document.getElementById(anElement);
|
|
if (hasClass(anElement, aClass))
|
|
{
|
|
removeClass(anElement, aClass);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
addClass(anElement, aClass);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
window.scrollDocTo = function(obj) { window.scroll(0, findPos(obj)[1]); }
|
|
window.scrTo = function(id) { scrollDocTo(document.getElementById(id)); }
|
|
window.hidepreview = function()
|
|
{
|
|
document.getElementById('wrapcommentpreview').style.display = 'none';
|
|
};
|
|
window.showcommentpreview = function(textarea_id)
|
|
{
|
|
document.getElementById('wrapcommentpreview').style.display = '';
|
|
iframeajax('page.cgi?id=previewcomment.html', { 'comment': document.getElementById(textarea_id || 'comment_textarea').value });
|
|
scrTo('wrapcommentpreview');
|
|
};
|
|
|
|
RegExp.escape = function(text) {
|
|
if (!arguments.callee.sRE) {
|
|
var specials = [
|
|
'/', '.', '*', '+', '?', '|',
|
|
'(', ')', '[', ']', '{', '}', '\\'
|
|
];
|
|
arguments.callee.sRE = new RegExp(
|
|
'(\\' + specials.join('|\\') + ')', 'g'
|
|
);
|
|
}
|
|
return text.replace(arguments.callee.sRE, '\\$1');
|
|
};
|
|
|
|
/* Parse time in hours:
|
|
"1,5" or "1:30" (HH:MM) = 1.5,
|
|
"1.5d" (days) = 12 */
|
|
function bzParseTime(time)
|
|
{
|
|
time = time+"";
|
|
time = time.replace(',','.');
|
|
if (m = time.match(/^\s*(-?)(\d+):(\d+)\s*$/))
|
|
{
|
|
for (var i = 2; i < 5; i++)
|
|
{
|
|
if (!m[i]) m[i] = 0;
|
|
else m[i] = parseInt(m[i]);
|
|
}
|
|
if (!m[1]) m[1] = '';
|
|
time = Math.floor(parseFloat(m[1] + (m[2] + m[3]/60))*100+0.5)/100;
|
|
}
|
|
else if (m = time.match(/^\s*(-?\d+(?:\.\d+)?)d\s*$/))
|
|
time = parseFloat(m[1])*8;
|
|
else
|
|
time = parseFloat(time);
|
|
return time;
|
|
};
|
|
|
|
/* Gets named cookie */
|
|
window.getCookie = function(name)
|
|
{
|
|
var matches = document.cookie.match(new RegExp(
|
|
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
|
|
));
|
|
return matches ? decodeURIComponent(matches[1]) : undefined;
|
|
};
|
|
|
|
/* Sets a new cookie name=value
|
|
props: {expires:, path:, domain:, secure:, httponly:} */
|
|
window.setCookie = function(name, value, props)
|
|
{
|
|
props = props || {};
|
|
var exp = props.expires;
|
|
if (typeof exp == "number" && exp)
|
|
{
|
|
var d = new Date();
|
|
d.setTime(d.getTime() + exp*1000);
|
|
exp = props.expires = d;
|
|
}
|
|
if (exp && exp.toUTCString)
|
|
props.expires = exp.toUTCString();
|
|
|
|
value = encodeURIComponent(value);
|
|
var updatedCookie = name + "=" + value;
|
|
for (var propName in props)
|
|
{
|
|
updatedCookie += "; " + propName;
|
|
var propValue = props[propName];
|
|
if (propValue !== true)
|
|
updatedCookie += "=" + propValue;
|
|
}
|
|
document.cookie = updatedCookie;
|
|
};
|
|
|
|
/* Removes named cookie */
|
|
window.deleteCookie = function(name)
|
|
{
|
|
setCookie(name, null, { expires: -1 });
|
|
};
|
|
|
|
/* Removes leading and trailing whitespace */
|
|
if (!String.prototype.trim)
|
|
{
|
|
String.prototype.trim = function()
|
|
{
|
|
return this.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
|
|
};
|
|
}
|
|
|
|
function AjaxLoader(url, callback)
|
|
{
|
|
var x;
|
|
if (window.XMLHttpRequest)
|
|
x = new XMLHttpRequest();
|
|
else
|
|
{
|
|
try { x = new ActiveXObject("Msxml2.XMLHTTP"); }
|
|
catch (e) { x = new ActiveXObject("Microsoft.XMLHTTP"); }
|
|
}
|
|
x.onreadystatechange = function()
|
|
{
|
|
if (x.readyState == 4)
|
|
{
|
|
callback(x);
|
|
}
|
|
};
|
|
x.open('GET', url, true);
|
|
x.send(null);
|
|
}
|
|
|
|
function existElement(el_id)
|
|
{
|
|
var el = document.getElementById(el_id);
|
|
return (typeof (el) != undefined && typeof (el) != null && typeof (el) != 'undefined' && el !== null);
|
|
}
|
|
|
|
window.onDomReady = (function()
|
|
{
|
|
var readyBound = false;
|
|
var bindReady = function()
|
|
{
|
|
if (readyBound)
|
|
return;
|
|
readyBound = true;
|
|
if (document.addEventListener)
|
|
{
|
|
document.addEventListener("DOMContentLoaded", function()
|
|
{
|
|
document.removeEventListener("DOMContentLoaded", arguments.callee, false);
|
|
ready();
|
|
}, false);
|
|
}
|
|
else if (document.attachEvent)
|
|
{
|
|
document.attachEvent("onreadystatechange", function()
|
|
{
|
|
if (document.readyState === "complete")
|
|
{
|
|
document.detachEvent( "onreadystatechange", arguments.callee );
|
|
ready();
|
|
}
|
|
});
|
|
if (document.documentElement.doScroll && window == window.top)
|
|
{
|
|
(function()
|
|
{
|
|
if (isReady)
|
|
return;
|
|
try
|
|
{
|
|
document.documentElement.doScroll("left");
|
|
}
|
|
catch(error)
|
|
{
|
|
setTimeout(arguments.callee, 0);
|
|
return;
|
|
}
|
|
ready();
|
|
})();
|
|
}
|
|
}
|
|
if (window.addEventListener)
|
|
window.addEventListener('load', ready, false);
|
|
else if (window.attachEvent)
|
|
window.attachEvent('onload', ready);
|
|
else
|
|
window.onload = ready;
|
|
};
|
|
var isReady = false;
|
|
var readyList = [];
|
|
var ready = function()
|
|
{
|
|
if (!isReady)
|
|
{
|
|
isReady = true;
|
|
if (readyList)
|
|
{
|
|
var fn_temp = null;
|
|
while (fn_temp = readyList.shift())
|
|
fn_temp.call(document);
|
|
readyList = null;
|
|
}
|
|
}
|
|
};
|
|
return function(fn) {
|
|
bindReady();
|
|
if (isReady)
|
|
fn.call(document);
|
|
else
|
|
readyList.push(fn);
|
|
return this;
|
|
};
|
|
})();
|