blob: c6e5314b6a4612f52eff940cd132a36b93fe61ba [file] [log] [blame]
/*
Copyright (c) 2004-2005, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
*/
dojo.provide("dojo.lang");
dojo.provide("dojo.AdapterRegistry");
dojo.provide("dojo.lang.Lang");
dojo.lang.mixin = function(obj, props){
var tobj = {};
for(var x in props){
if(typeof tobj[x] == "undefined" || tobj[x] != props[x]) {
obj[x] = props[x];
}
}
// IE doesn't recognize custom toStrings in for..in
if(dojo.render.html.ie && dojo.lang.isFunction(props["toString"]) && props["toString"] != obj["toString"]) {
obj.toString = props.toString;
}
return obj;
}
dojo.lang.extend = function(ctor, props){
this.mixin(ctor.prototype, props);
}
dojo.lang.extendPrototype = function(obj, props){
this.extend(obj.constructor, props);
}
dojo.lang.anonCtr = 0;
dojo.lang.anon = {};
dojo.lang.nameAnonFunc = function(anonFuncPtr, namespaceObj){
var nso = (namespaceObj || dojo.lang.anon);
if((dj_global["djConfig"])&&(djConfig["slowAnonFuncLookups"] == true)){
for(var x in nso){
if(nso[x] === anonFuncPtr){
return x;
}
}
}
var ret = "__"+dojo.lang.anonCtr++;
while(typeof nso[ret] != "undefined"){
ret = "__"+dojo.lang.anonCtr++;
}
nso[ret] = anonFuncPtr;
return ret;
}
/**
* Runs a function in a given scope (thisObject), can
* also be used to preserve scope.
*
* hitch(foo, "bar"); // runs foo.bar() in the scope of foo
* hitch(foo, myFunction); // runs myFunction in the scope of foo
*/
dojo.lang.hitch = function(thisObject, method) {
if(dojo.lang.isString(method)) {
var fcn = thisObject[method];
} else {
var fcn = method;
}
return function() {
return fcn.apply(thisObject, arguments);
}
}
dojo.lang.forward = function(funcName){
// Returns a function that forwards a method call to this.func(...)
return function(){
return this[funcName].apply(this, arguments);
};
}
dojo.lang.curry = function(ns, func /* args ... */){
var outerArgs = [];
ns = ns||dj_global;
if(dojo.lang.isString(func)){
func = ns[func];
}
for(var x=2; x<arguments.length; x++){
outerArgs.push(arguments[x]);
}
var ecount = func.length - outerArgs.length;
// borrowed from svend tofte
function gather(nextArgs, innerArgs, expected){
var texpected = expected;
var totalArgs = innerArgs.slice(0); // copy
for(var x=0; x<nextArgs.length; x++){
totalArgs.push(nextArgs[x]);
}
// check the list of provided nextArgs to see if it, plus the
// number of innerArgs already supplied, meets the total
// expected.
expected = expected-nextArgs.length;
if(expected<=0){
var res = func.apply(ns, totalArgs);
expected = texpected;
return res;
}else{
return function(){
return gather(arguments,// check to see if we've been run
// with enough args
totalArgs, // a copy
expected); // how many more do we need to run?;
}
}
}
return gather([], outerArgs, ecount);
}
dojo.lang.curryArguments = function(ns, func, args, offset){
var targs = [];
var x = offset||0;
for(x=offset; x<args.length; x++){
targs.push(args[x]); // ensure that it's an arr
}
return dojo.lang.curry.apply(dojo.lang, [ns, func].concat(targs));
}
/**
* Sets a timeout in milliseconds to execute a function in a given context
* with optional arguments.
*
* setTimeout (Object context, function func, number delay[, arg1[, ...]]);
* setTimeout (function func, number delay[, arg1[, ...]]);
*/
dojo.lang.setTimeout = function(func, delay){
var context = window, argsStart = 2;
if(!dojo.lang.isFunction(func)){
context = func;
func = delay;
delay = arguments[2];
argsStart++;
}
if(dojo.lang.isString(func)){
func = context[func];
}
var args = [];
for (var i = argsStart; i < arguments.length; i++) {
args.push(arguments[i]);
}
return setTimeout(function () { func.apply(context, args); }, delay);
}
/**
* Partial implmentation of is* functions from
* http://www.crockford.com/javascript/recommend.html
* NOTE: some of these may not be the best thing to use in all situations
* as they aren't part of core JS and therefore can't work in every case.
* See WARNING messages inline for tips.
*
* The following is* functions are fairly "safe"
*/
dojo.lang.isObject = function(wh) {
return typeof wh == "object" || dojo.lang.isArray(wh) || dojo.lang.isFunction(wh);
}
dojo.lang.isArray = function(wh) {
return (wh instanceof Array || typeof wh == "array");
}
dojo.lang.isArrayLike = function(wh) {
if(dojo.lang.isString(wh)){ return false; }
if(dojo.lang.isArray(wh)){ return true; }
if(typeof wh != "undefined" && wh
&& dojo.lang.isNumber(wh.length) && isFinite(wh.length)){ return true; }
return false;
}
dojo.lang.isFunction = function(wh) {
return (wh instanceof Function || typeof wh == "function");
}
dojo.lang.isString = function(wh) {
return (wh instanceof String || typeof wh == "string");
}
dojo.lang.isAlien = function(wh) {
return !dojo.lang.isFunction() && /\{\s*\[native code\]\s*\}/.test(String(wh));
}
dojo.lang.isBoolean = function(wh) {
return (wh instanceof Boolean || typeof wh == "boolean");
}
/**
* The following is***() functions are somewhat "unsafe". Fortunately,
* there are workarounds the the language provides and are mentioned
* in the WARNING messages.
*
* WARNING: In most cases, isNaN(wh) is sufficient to determine whether or not
* something is a number or can be used as such. For example, a number or string
* can be used interchangably when accessing array items (arr["1"] is the same as
* arr[1]) and isNaN will return false for both values ("1" and 1). Should you
* use isNumber("1"), that will return false, which is generally not too useful.
* Also, isNumber(NaN) returns true, again, this isn't generally useful, but there
* are corner cases (like when you want to make sure that two things are really
* the same type of thing). That is really where isNumber "shines".
*
* RECOMMENDATION: Use isNaN(wh) when possible
*/
dojo.lang.isNumber = function(wh) {
return (wh instanceof Number || typeof wh == "number");
}
/**
* WARNING: In some cases, isUndefined will not behave as you
* might expect. If you do isUndefined(foo) and there is no earlier
* reference to foo, an error will be thrown before isUndefined is
* called. It behaves correctly if you scope yor object first, i.e.
* isUndefined(foo.bar) where foo is an object and bar isn't a
* property of the object.
*
* RECOMMENDATION: Use `typeof foo == "undefined"` when possible
*
* FIXME: Should isUndefined go away since it is error prone?
*/
dojo.lang.isUndefined = function(wh) {
return ((wh == undefined)&&(typeof wh == "undefined"));
}
// end Crockford functions
dojo.lang.whatAmI = function(wh) {
try {
if(dojo.lang.isArray(wh)) { return "array"; }
if(dojo.lang.isFunction(wh)) { return "function"; }
if(dojo.lang.isString(wh)) { return "string"; }
if(dojo.lang.isNumber(wh)) { return "number"; }
if(dojo.lang.isBoolean(wh)) { return "boolean"; }
if(dojo.lang.isAlien(wh)) { return "alien"; }
if(dojo.lang.isUndefined(wh)) { return "undefined"; }
// FIXME: should this go first?
for(var name in dojo.lang.whatAmI.custom) {
if(dojo.lang.whatAmI.custom[name](wh)) {
return name;
}
}
if(dojo.lang.isObject(wh)) { return "object"; }
} catch(E) {}
return "unknown";
}
/*
* dojo.lang.whatAmI.custom[typeName] = someFunction
* will return typeName is someFunction(wh) returns true
*/
dojo.lang.whatAmI.custom = {};
/**
* See if val is in arr. Call signatures:
* find(array, value, identity)
* find(value, array, identity)
**/
dojo.lang.find = function(arr, val, identity){
// support both (arr, val) and (val, arr)
if(!dojo.lang.isArrayLike(arr) && dojo.lang.isArrayLike(val)) {
var a = arr;
arr = val;
val = a;
}
var isString = dojo.lang.isString(arr);
if(isString) { arr = arr.split(""); }
if(identity){
for(var i=0;i<arr.length;++i){
if(arr[i] === val){ return i; }
}
}else{
for(var i=0;i<arr.length;++i){
if(arr[i] == val){ return i; }
}
}
return -1;
}
dojo.lang.indexOf = dojo.lang.find;
dojo.lang.findLast = function(arr, val, identity) {
// support both (arr, val) and (val, arr)
if(!dojo.lang.isArrayLike(arr) && dojo.lang.isArrayLike(val)) {
var a = arr;
arr = val;
val = a;
}
var isString = dojo.lang.isString(arr);
if(isString) { arr = arr.split(""); }
if(identity){
for(var i = arr.length-1; i >= 0; i--) {
if(arr[i] === val){ return i; }
}
}else{
for(var i = arr.length-1; i >= 0; i--) {
if(arr[i] == val){ return i; }
}
}
return -1;
}
dojo.lang.lastIndexOf = dojo.lang.findLast;
dojo.lang.inArray = function(arr, val){
return dojo.lang.find(arr, val) > -1;
}
dojo.lang.getNameInObj = function(ns, item){
if(!ns){ ns = dj_global; }
for(var x in ns){
if(ns[x] === item){
return new String(x);
}
}
return null;
}
// FIXME: Is this worthless since you can do: if(name in obj)
// is this the right place for this?
dojo.lang.has = function(obj, name){
return (typeof obj[name] !== 'undefined');
}
dojo.lang.isEmpty = function(obj) {
if(dojo.lang.isObject(obj)) {
var tmp = {};
var count = 0;
for(var x in obj){
if(obj[x] && (!tmp[x])){
count++;
break;
}
}
return (count == 0);
} else if(dojo.lang.isArrayLike(obj) || dojo.lang.isString(obj)) {
return obj.length == 0;
}
}
dojo.lang.forEach = function(arr, unary_func, fix_length){
var isString = dojo.lang.isString(arr);
if(isString) { arr = arr.split(""); }
var il = arr.length;
for(var i=0; i< ((fix_length) ? il : arr.length); i++){
if(unary_func(arr[i], i, arr) == "break"){
break;
}
}
}
dojo.lang.map = function(arr, obj, unary_func){
var isString = dojo.lang.isString(arr);
if(isString){
arr = arr.split("");
}
if(dojo.lang.isFunction(obj)&&(!unary_func)){
unary_func = obj;
obj = dj_global;
}else if(dojo.lang.isFunction(obj) && unary_func){
// ff 1.5 compat
var tmpObj = obj;
obj = unary_func;
unary_func = tmpObj;
}
if(Array.map){
var outArr = Array.map(arr, unary_func, obj);
}else{
var outArr = [];
for(var i=0;i<arr.length;++i){
outArr.push(unary_func.call(obj, arr[i]));
}
}
if(isString) {
return outArr.join("");
} else {
return outArr;
}
}
dojo.lang.tryThese = function(){
for(var x=0; x<arguments.length; x++){
try{
if(typeof arguments[x] == "function"){
var ret = (arguments[x]());
if(ret){
return ret;
}
}
}catch(e){
dojo.debug(e);
}
}
}
dojo.lang.delayThese = function(farr, cb, delay, onend){
/**
* alternate: (array funcArray, function callback, function onend)
* alternate: (array funcArray, function callback)
* alternate: (array funcArray)
*/
if(!farr.length){
if(typeof onend == "function"){
onend();
}
return;
}
if((typeof delay == "undefined")&&(typeof cb == "number")){
delay = cb;
cb = function(){};
}else if(!cb){
cb = function(){};
if(!delay){ delay = 0; }
}
setTimeout(function(){
(farr.shift())();
cb();
dojo.lang.delayThese(farr, cb, delay, onend);
}, delay);
}
dojo.lang.shallowCopy = function(obj) {
var ret = {}, key;
for(key in obj) {
if(dojo.lang.isUndefined(ret[key])) {
ret[key] = obj[key];
}
}
return ret;
}
dojo.lang.every = function(arr, callback, thisObject) {
var isString = dojo.lang.isString(arr);
if(isString) { arr = arr.split(""); }
if(Array.every) {
return Array.every(arr, callback, thisObject);
} else {
if(!thisObject) {
if(arguments.length >= 3) { dojo.raise("thisObject doesn't exist!"); }
thisObject = dj_global;
}
for(var i = 0; i < arr.length; i++) {
if(!callback.call(thisObject, arr[i], i, arr)) {
return false;
}
}
return true;
}
}
dojo.lang.some = function(arr, callback, thisObject) {
var isString = dojo.lang.isString(arr);
if(isString) { arr = arr.split(""); }
if(Array.some) {
return Array.some(arr, callback, thisObject);
} else {
if(!thisObject) {
if(arguments.length >= 3) { dojo.raise("thisObject doesn't exist!"); }
thisObject = dj_global;
}
for(var i = 0; i < arr.length; i++) {
if(callback.call(thisObject, arr[i], i, arr)) {
return true;
}
}
return false;
}
}
dojo.lang.filter = function(arr, callback, thisObject) {
var isString = dojo.lang.isString(arr);
if(isString) { arr = arr.split(""); }
if(Array.filter) {
var outArr = Array.filter(arr, callback, thisObject);
} else {
if(!thisObject) {
if(arguments.length >= 3) { dojo.raise("thisObject doesn't exist!"); }
thisObject = dj_global;
}
var outArr = [];
for(var i = 0; i < arr.length; i++) {
if(callback.call(thisObject, arr[i], i, arr)) {
outArr.push(arr[i]);
}
}
}
if(isString) {
return outArr.join("");
} else {
return outArr;
}
}
dojo.AdapterRegistry = function(){
/***
A registry to facilitate adaptation.
Pairs is an array of [name, check, wrap] triples
All check/wrap functions in this registry should be of the same arity.
***/
this.pairs = [];
}
dojo.lang.extend(dojo.AdapterRegistry, {
register: function (name, check, wrap, /* optional */ override){
/***
The check function should return true if the given arguments are
appropriate for the wrap function.
If override is given and true, the check function will be given
highest priority. Otherwise, it will be the lowest priority
adapter.
***/
if (override) {
this.pairs.unshift([name, check, wrap]);
} else {
this.pairs.push([name, check, wrap]);
}
},
match: function (/* ... */) {
/***
Find an adapter for the given arguments.
If no suitable adapter is found, throws NotFound.
***/
for(var i = 0; i < this.pairs.length; i++){
var pair = this.pairs[i];
if(pair[1].apply(this, arguments)){
return pair[2].apply(this, arguments);
}
}
throw new Error("No match found");
// dojo.raise("No match found");
},
unregister: function (name) {
/***
Remove a named adapter from the registry
***/
for(var i = 0; i < this.pairs.length; i++){
var pair = this.pairs[i];
if(pair[0] == name){
this.pairs.splice(i, 1);
return true;
}
}
return false;
}
});
dojo.lang.reprRegistry = new dojo.AdapterRegistry();
dojo.lang.registerRepr = function(name, check, wrap, /*optional*/ override){
/***
Register a repr function. repr functions should take
one argument and return a string representation of it
suitable for developers, primarily used when debugging.
If override is given, it is used as the highest priority
repr, otherwise it will be used as the lowest.
***/
dojo.lang.reprRegistry.register(name, check, wrap, override);
};
dojo.lang.repr = function(obj){
/***
Return a "programmer representation" for an object
***/
if(typeof(obj) == "undefined"){
return "undefined";
}else if(obj === null){
return "null";
}
try{
if(typeof(obj["__repr__"]) == 'function'){
return obj["__repr__"]();
}else if((typeof(obj["repr"]) == 'function')&&(obj.repr != arguments.callee)){
return obj["repr"]();
}
return dojo.lang.reprRegistry.match(obj);
}catch(e){
if(typeof(obj.NAME) == 'string' && (
obj.toString == Function.prototype.toString ||
obj.toString == Object.prototype.toString
)){
return o.NAME;
}
}
if(typeof(obj) == "function"){
obj = (obj + "").replace(/^\s+/, "");
var idx = obj.indexOf("{");
if(idx != -1){
obj = obj.substr(0, idx) + "{...}";
}
}
return obj + "";
}
dojo.lang.reprArrayLike = function(arr){
try{
var na = dojo.lang.map(arr, dojo.lang.repr);
return "[" + na.join(", ") + "]";
}catch(e){ }
};
dojo.lang.reprString = function(str){
return ('"' + str.replace(/(["\\])/g, '\\$1') + '"'
).replace(/[\f]/g, "\\f"
).replace(/[\b]/g, "\\b"
).replace(/[\n]/g, "\\n"
).replace(/[\t]/g, "\\t"
).replace(/[\r]/g, "\\r");
};
dojo.lang.reprNumber = function(num){
return num + "";
};
(function(){
var m = dojo.lang;
m.registerRepr("arrayLike", m.isArrayLike, m.reprArrayLike);
m.registerRepr("string", m.isString, m.reprString);
m.registerRepr("numbers", m.isNumber, m.reprNumber);
m.registerRepr("boolean", m.isBoolean, m.reprNumber);
// m.registerRepr("numbers", m.typeMatcher("number", "boolean"), m.reprNumber);
})();
/**
* Creates a 1-D array out of all the arguments passed,
* unravelling any array-like objects in the process
*
* Ex:
* unnest(1, 2, 3) ==> [1, 2, 3]
* unnest(1, [2, [3], [[[4]]]]) ==> [1, 2, 3, 4]
*/
dojo.lang.unnest = function(/* ... */) {
var out = [];
for(var i = 0; i < arguments.length; i++) {
if(dojo.lang.isArrayLike(arguments[i])) {
var add = dojo.lang.unnest.apply(this, arguments[i]);
out = out.concat(add);
} else {
out.push(arguments[i]);
}
}
return out;
}
/**
* Return the first argument that isn't undefined
*/
dojo.lang.firstValued = function(/* ... */) {
for(var i = 0; i < arguments.length; i++) {
if(typeof arguments[i] != "undefined") {
return arguments[i];
}
}
return undefined;
}
/**
* Converts an array-like object (i.e. arguments, DOMCollection)
* to an array
**/
dojo.lang.toArray = function(arrayLike, startOffset) {
var array = [];
for(var i = startOffset||0; i < arrayLike.length; i++) {
array.push(arrayLike[i]);
}
return array;
}