bugzilla-4intranet/js/util.js

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, '&amp;'); //&
s = s.replace(/</g, '&lt;'); //<
s = s.replace(/>/g, '&gt;'); //>
s = s.replace(/"/g, '&quot;'); //"
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;
};
})();