/** @function autoExpand
@desc Used as a callback on keydown/press in textareas,
resizes them to accommodate full height of text while typing.
@arg {Element|jQuery} $element - Textarea to resize.
@arg {Function} callback - Function to call with new height after resizing.
*/
function autoExpand($element, callback)
{
element = jUnwrap($element);
callback = callback || function () {};
//element.style.height = (element.scrollHeight > element.clientHeight) ? (element.scrollHeight) + 'px' : '1.5rem';
element.style.height = '0px';
setTimeout(function ()
{
var height = element.style.height = (element.scrollHeight) + 'px';
callback.apply(element, [height]);
},
1);
}
/** @function convertToLocalTime
@desc Adds local timezone offset to a numeric UTC timestamp.
@arg {Number} timeUTC - UTC timestamp.
@returns {Object} New Date instance.
*/
function convertToLocalTime(timeUTC)
{
var offset = parseInt(new Date().getTimezoneOffset()),
timeLocal = new Date(timeUTC.getTime() - (offset * T_MINUTE));
return timeLocal;
}
/** @function dataArrayToObject
@desc Transforms a list of data objects into an object of data objects. Each key in the containing object
corresponds to a specific property value of each data object. Each data object must contain the property
used for indexing. Useful for fast lookups without looping.
@arg {Array} data - List of data to transform.
@returns {String} indexed - Property within each data object whose value to use as key in indexed object.
@returns {Object} Indexed data object.
@see {@link dataObjectToArray}
*/
function dataArrayToObject(data, indexed)
{
var funcName = 'window.dataArrayToObject',
type = dltypeof(data),
indexed = dltypeof(indexed) === 'string' ? indexed : false,
dataNew = {};
// validate
if (!indexed) { log(L_ERROR, funcName, 'invalid indexing key'); return false; }
if (type !== 'array') { log(L_ERROR, funcName, 'data is not an array'); return false; }
if (!data.length) { log(L_INFO , funcName, 'data is empty'); return dataNew; }
// convert
for (var i = 0; i < data.length; i++)
{
var item = data[i],
key = item[indexed];
if (typeof key !== 'undefined') { dataNew[key] = item; } else
{
log(L_ERROR, funcName, 'data index {0} lacks key "{1}"'.format(i, indexed));
return false;
}
}
return dataNew;
}
/** @function dataObjectToArray
@desc Transform indexed data object into plain list of data objects, optionally populating each with value
formerly used as keys.
@arg {Object} data - Indexed data object.
@arg {String} [indexed] - Property within each data object to restore using containing object's keys as values.
@returns {Array} List of data objects.
@see {@link dataArrayToObject}
@see {@link Object#toArray}
*/
function dataObjectToArray(data, indexed)
{
var funcName = 'window.dataObjectToArray',
type = dltypeof(data),
indexed = dltypeof(indexed) === 'string' ? indexed : false,
dataNew = [];
// validate
if (!~$.inArray(type, ['object', 'jsobject'])) { log(L_ERROR, funcName, 'data is not an object'); return false; }
// convert
for (var key in data) { if (data.hasOwnProperty(key))
{
var item = data[key];
if (!~$.inArray(dltypeof(item), ['object', 'jsobject']))
{
log(L_ERROR, funcName, 'data key "{0}" is not an object'.format(key));
return false;
}
// add index value to item if asked
indexed
&& typeof item[indexed] === 'undefined'
&& (item[indexed] = key);
dataNew.push(item);
}}
if (!dataNew.length) { log(L_INFO , funcName, 'data is empty'); }
return dataNew;
}
/** @function decodeURIComponentSafe
@desc A version of decodeURIComponent that catches exceptions, allowing code to continue execution.
@arg {String} encoded - String to decode safely.
@returns {String} Decoded string.
*/
function decodeURIComponentSafe(encoded)
{
var decoded;
try { decoded = decodeURIComponent(encoded); } catch (e)
{
var caller = arguments.callee.caller.toString() // the responsible function body
.replace(/[\r\n]/i, '') // strip line breaks
.substr(0, 100); // truncate
decoded = encoded;
log
(
L_ERROR,
'window.decodeURIComponentSafe',
'found an unsafe string: "{0}"'.format(encoded),
'called by: "{0}"'.format(caller)
);
}
return decoded;
}
/** @function detectStyles
@desc Searches DOM for unprocessed less styleheets, and processes them.
@returns {Number} Count of stylesheets detected and processed.
*/
function detectStyles()
{
var timer = new TimerClass(),
found = 0,
$lessStyles = $('link[type="text/less"]');
if ($lessStyles.length)
{
$lessStyles.each(function ()
{
var $link = $(this);
if (!$link.hasClass('processed'))
{
$link.addClass('processed');
less.sheets.push(this);
found++;
}
$link.hasClass('processed') && $link.remove();
});
less.refresh();
}
log(L_INFO, 'window.detectStyles', '{0} in {1}'.format(found, timer.elapsed(1)));
return found;
}
/** @function detectTemplates
@desc Searches document for unprocessed templates, compiles them, and saves generator functions as both
properties in the globally accessible {@link templates} object,
and partials usable by id within other templates.
@arg {String} prefix - Used to filter what template ids are to be processed.
@arg {Boolean} [force=false] - Whether to replace saved generators if a duplicate is found.
@returns {Number} Count of templates detected and processed.
*/
function detectTemplates(prefix, force)
{
var timer = new TimerClass(),
prefix = prefix || '',
force = force || false,
pattern = new RegExp('^' + prefix + '(.+)$', 'i'),
matches ,
id ,
source ,
generator ,
found = 0;
$('script[type*="handlebars"]').each(function ()
{
if ((matches = this.id.match(pattern)) !== null)
{
id = matches[1];
if (!force && (id in window.templates)) { return true; } // continue
var $element = $(this);
log(L_INFO, 'window.detectTemplates', '#' + $element.attr('id'));
source = $.trim($element.html());
generator = Handlebars.compile(source);
window.templates[id] = generator; // template
Handlebars.registerPartial(id, source); // partial
$element.remove();
found++;
}
});
log(L_INFO, 'window.detectTemplates', found + ' in ' + timer.elapsed(1));
return found;
};
/** @function deval
@desc As close as possible to the opposite of eval. Like JSON.stringify without type restrictions.
@arg {Mixed} thing - Any value.
@returns {String} Representation of value suitable for direct eval.
*/
function deval(thing)
{
var self = arguments.callee;
// static
typeof self.whitespace === 'undefined' && (self.whitespace = true);
typeof self.comments === 'undefined' && (self.comments = true);
// private
var s = self.whitespace ? ' ' : '', // space?
separator = ',' + s,
type = dltypeof(thing) || 'jsobject'; // if dltypeof says undefined, it's probably an object
switch (type) // what is this thing?
{
case 'array' :
var values = [],
thingLength = thing.length;
for (var i = 0; i < thingLength; i++) { values.push(self(thing[i])); }
return '[' + values.join(self.separator) + ']';
case 'object' :
case 'jsobject' :
case 'arguments' :
var values = [];
for (key in thing)
{
thing.hasOwnProperty(key)
&& values.push('"' + key + '":' + s + self(thing[key]));
}
return '{' + values.join(self.separator) + '}';
case 'boolean' :
case 'number' :
case 'funciton' : return thing.toString();
case 'string' : return '"' + thing.replace(/\"/g, '\\"') + '"';
case 'domelement' : return (s && '/* element */ ') + '$("' + html(thing).replace(/\"/g, '\\"') + '")[0]';
case 'date' : return 'new Date(' + thing.getTimestamp() + ')';
case 'math' : return 'Math';
case 'window' : return 'window';
case 'undefined' : return 'undefined';
case 'null' : return 'null';
case 'error' : return '"[Error]"';
case 'domcollection' : return '"[DOM Collection]"';
case 'mimetypecollection': return '"[Mime Type Collection]"';
case 'plugincollection' : return '"[Plugin Collection]"';
case 'textnode' : return '"[Text Node]"';
case 'textrange' : return '"[Text Range]"';
case 'regexp' : return (self.comments ? '/*'+s+'regexp'+s+'*/'+s : '')
+ '(function'+s+'(i)'+s+'{var x'+s+'='+s+'/'
+ thing.source + '/'
+ (thing.global ? 'g' : '')
+ (thing.ignoreCase ? 'i' : '')
+ (thing.multiline ? 'm' : '')
+ (thing.sticky ? 'y' : '')
+ ';'+s+'x.lastIndex'+s+'='+s+'i;'+s+'return x;'+s+'})('
+ thing.lastIndex + ')';
default : return (/^(html|element)/i.test(type)) // html-ifiable?
? (self.comments ? '/*'+s+'element'+s+'*/'+s : '') + '$("'
+ html(thing).replace(/\"/g, '\\"') + '")[0]'
: (self.comments ? '/*'+s+ type +s+'*/' : '') + 'null';
}
}
// in development
function diff(before, after)
{
var beforeType = dltypeof(before),
afterType = dltypeof(after);
if (beforeType !== afterType)
{
log(L_ERROR, 'window.diff', 'types do not match');
return false;
}
switch (beforeType)
{
case 'object' :
case 'jsobject':
var output =
{
'added' : {},
'modified': {},
'removed' : {}
};
for (var prop in before) { if (before.hasOwnProperty(prop))
{
}}
return;
case 'array':
var output =
{
'added' : after .filter(function (item, index) { return before.indexOf(item) < 0; }),
'removed': before.filter(function (item, index) { return after .indexOf(item) < 0; })
};
return output;
default:
log(L_ERROR, 'window.diff', 'unsupported type "{0}"'.format(typeA));
return false;
}
}
// in development
function diffDataArray(a, b)
{
var aType = dltypeof(a),
bType = dltypeof(b),
output = {'added': [], 'modified': [], 'deleted': []};
if (aType !== bType)
{
log(L_ERROR, 'window.diff', 'types do not match');
return output;
}
if (aType !== 'array')
{
log(L_ERROR, 'window.diff', 'types are not arrays');
return output;
}
var idsGet = function (item, index) { return item.id; },
ids = [],
idsA = a.map(idsGet),
idsB = a.map(idsGet);
$.extend(ids, idsA, idsB);
a = dataArrayToObject(a, 'id');
b = dataArrayToObject(b, 'id');
for (var id in ids)
{
if (typeof a[id] === 'undefined') { output.added.push(b[id]); }
else if (typeof b[id] === 'undefined') { output.deleted.push(a[id]); }
else if (diffDataObject(a[id], b[id])) { output.modified.push(b[id]); }
}
return output;
}
// in development
function diffDataObject(a, b)
{
var keys = [],
keysA = [],
keysB = [],
modified = false;
for (var key in a) { a.hasOwnProperty(key) && keysA.push(key); }
for (var key in b) { b.hasOwnProperty(key) && keysB.push(key); }
$.extend(keys, keysA, keysB);
$.each(keys, function (index, value)
{
if (a[key] !== b[key]) { modified = true; return false; }
});
return modified;
}
/** @function exists
@desc Checks if a value isn't undefined. Detects false, null, and NaN.
@arg {Mixed} thing - Any value.
@returns {Boolean} Whether thing exists.
*/
function exists(thing) { return typeof thing !== 'undefined'; }
/** @function fork
@desc Faux-forks a function call via setTimeout.
This allows procedural execution to continue while the function executes in semi-parallel (round-robin).
Useful for allowing layout/reflow/repaint so up to date values can be queried from the DOM.
@arg {Function} func - Function to call, less-than-procedurally.
@arg {Number} [delay=0] -
@returns {Number} Positive if f was actually a function and was forked.
*/
function fork(func, delay) { return typeof func === 'function' ? setTimeout(func, Number(delay) || 0) : 0; }
/** @function format_mysql_date
@desc Transforms a timestamp into a mysql-style formatted date string.
@deprecated since version 1.9
@arg {Number} date - Integer UTC timestamp.
@arg {String} [day_offset='0'] - Number of days to offset. Actually only responds to '0' vs any other value.
@returns {String} Formatted date.
*/
function format_mysql_date(date, day_offset)
{
if (date === '0') { return date; }
var offset = parseInt(new Date().getTimezoneOffset())
formatted_date = new Date(date)
local_time = new Date
(
formatted_date.getTime()
+ offset * T_MINUTE
+ (day_offset === '0' ? 0 : T_DAY)
),
month = parseInt(local_time.getMonth()) + 1,
mysql_date = local_time.getFullYear()
+ '-' + month
+ '-' + local_time.getDate()
+ ' ' + local_time.getHours()
+ ':' + local_time.getMinutes()
+' :' + local_time.getSeconds();
return mysql_date;
}
/** @function getAttributes
@desc Reads element attributes into an object.
@arg {jQuery|Element} $element - Source of attributes.
@returns {Object} Populated object.
*/
function getAttributes($element)
{
var keys = jWrap($element)[0].attributes,
keysLength = keys.length;
attributes = {};
for (var i = 0; i < keysLength; i++)
{
var key = keys[i];
attributes[key.name] = key.value;
}
return attributes;
}
/** @function html
@desc Transforms element into representative markup.
@arg {jQuery|Element} element - Element to transform.
@returns {String} Representative HTML.
*/
function html(element) { return $('<div></div>').append(jWrap(element).clone()).html().replace(/\s{2,}/g, ' '); }
/** @function htmlDecode
@desc Unescapes HTML entities within a string.
@arg {String} html - String to decode.
@returns {String} Decoded string.
*/
function htmlDecode(html) { return $('<div></div>').html(html).text(); }
/** @function htmlEncode
@desc Escapes HTML-unsafe characters within a string.
@arg {String} text - String to encode.
@returns {String} Encoded string.
*/
function htmlEncode(text) { return $('<div></div>').text(text).html(); }
/** @function isExternal
@desc Tries to determine if a given URL points within the scope of the current page.
@arg {String} url - Location to analyze.
@returns {Boolean} Whether url is external.
*/
function isExternal(url)
{
var match = url.match(/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/);
if
(
typeof match[1] === "string"
&& match[1].length > 0
&& match[1].toLowerCase() !== location.protocol
)
{ return true; }
if
(
typeof match[2] === "string"
&& match[2].length > 0
&& match[2].replace(new RegExp(":("+{"http:":80,"https:":443}[location.protocol]+")?$"), "") !== location.host
)
{ return true; }
return false;
}
/** @function jUnwrap
@desc Removes jQuery wrapper from element, if any.
@arg {jQuery|Element} $element - Reference to strip of jQuery wrapper.
@returns {Element} Guaranteed bare element.
*/
function jUnwrap($element) { return $element instanceof $ ? $element[0] : $element; }
/** @function jWrap
@desc Adds jQuery wrapper to element if not already wrapped.
@arg {jQuery|Element} $element - Reference to wrap with jQuery.
@returns {Element} Guaranteed wrapped element.
*/
function jWrap(element) { return element instanceof $ ? element : $(element); }
/** @function killEvent
@desc Shorthand way of stopping propagation and preventing default action for a given event.
@arg {Object} event - Event to neutralize.
*/
function killEvent(event) { event.preventDefault(); event.stopPropagation(); }
/** @function linkCallback
@desc Wraps a function reference in a global uniquely named function, and returns that name.
Useful for passing arbitrary or anonymous functions into Framework methods, which only accept strings.
@arg {Function} callback - Function to represent via string.
@arg {Object} [context=window] - <this> value when calling wrapped function.
@arg {Boolean} [persist=false] - Whether the wrapper should persist after one call, or destruct itself.
@arg {Object} [deferred] - Deferred object to resolve in addition to calling wrapped function.
@returns {String} Global callback wrapper function name.
@property {Number} counter - How many wrapper functions have been generated.
@property {Number} expire - How long before non-persisting functions automatically destruct themselves.
@see {@link linkCallback(2)}
@see {@link unlinkCallback}
*/
/** @function linkCallback(2)
@desc Logs current settings (static properties).
@returns {Boolean} false
@property {Number} counter - How many wrapper functions have been generated.
@property {Number} expire - How long before non-persisting functions automatically destruct themselves.
@see {@link linkCallback}
@see {@link unlinkCallback}
*/
function linkCallback(callback, context, persist, deferred)
{
var self = arguments.callee,
argumentsLenth = arguments.length;
// static
typeof self.counter == 'undefined' && (self.counter = 0);
typeof self.expire == 'undefined' && (self.expire = T_MINUTE * 15);
var context = context || window,
persist = persist || false,
uniqueName = 'callback_' + ++self.counter;
if (!argumentsLenth) // display settings?
{
log(L_ALWAYS, 'window.linkCallback', 'settings',
{
'counter': self.counter,
'expire' : self.expire
});
return false;
}
// wrapper
log(L_DEBUG, 'window.linkCallback', 'linking ' + uniqueName + ' (persist: ' + persist + ')');
window[uniqueName] = function ()
{
//log(L_DEBUG, 'window.' + uniqueName, persist ? 'persisting' : 'unlinking', arguments);
var self = arguments.callee;
callback && callback.apply(context, arguments);
deferred && deferred.resolve.apply(deferred, arguments);
!self.persist && unlinkCallback(uniqueName);
};
// wrapper static
window[uniqueName].callback = callback;
window[uniqueName].persist = persist;
window[uniqueName].timeout = persist ? null : setTimeout(function ()
{
log(L_DEBUG, 'window.' + uniqueName, 'auto expire') && unlinkCallback(uniqueName);
},
self.expire);
return uniqueName;
}
/** @function log
@desc Send stringified values to console or file for debugging or analytics.
Will always output regardless of verbosity and squelch settings.
@arg {...Mixed} value - Any number of any value.
@returns {Boolean} true
@property {Object} verbosity - Range of message levels to output.
@property {Number} verbosity.min - Minimum depth of message levels to log, set with logging constants.
@property {Number} verbosity.max - Maximum depth of message levels to log, set with logging constants.
@property {Object} squelch - Range of message levels to {@link deval} arguments/html/xml.
@property {Number} squelch.min - Minimum depth of message levels to pass squelch,
set with logging constants.
@property {Number} squelch.max - Maximum depth of message levels to pass squelch,
set with logging constants.
@property {Object} truncate - Max length of stringified arguments or output, 0 = unlimited.
@property {Number} truncate.argument - Max length of each argument logged.
@property {Number} truncate.output - Max length of total output.
@property {Boolean} compress - Whether to LZ compress data portion of log line, requires LZString.
@property {Array.<String>} names - Custom identifiers for message levels,
pad to same length to align log lines.
@property {String} separator - Characters to delineate values within a line.
@property {Boolean} console - Whether to redirect messages to the console instead of file.
@property {Boolean} ms - Whether to display millisecond timestamp.
@property {Number} msPad - How many places to pad timestamps.
@property {Object} timer - Timer instance for generating timestamps.
@property {String} identifier - String used to indicate which WebView emitted a message, defaults to
{@link Starlet.identifier} or 'connection'.
@property {Number} leaderLength - Used to adaptively pad the leader of each line to align data into
columns for readability.
@property {Array.<RegExp>} exclude - List of patterns to test on final output, logs nothing if any match.
@see {@link log(2)}
@see {@link deval}
*/
/** @function log(2)
@desc Send stringified values to console or file for debugging or analytics.
@arg {Number} level - Message level of line, use logging constants.
@arg {...Mixed} value - Any number of any value.
@returns {Boolean} true
@property {Object} verbosity - Range of message levels to output.
@property {Number} verbosity.min - Minimum depth of message levels to log, set with logging constants.
@property {Number} verbosity.max - Maximum depth of message levels to log, set with logging constants.
@property {Object} squelch - Range of message levels to {@link deval} arguments/html/xml.
@property {Number} squelch.min - Minimum depth of message levels to pass squelch,
set with logging constants.
@property {Number} squelch.max - Maximum depth of message levels to pass squelch,
set with logging constants.
@property {Object} truncate - Max length of stringified arguments or output, 0 = unlimited.
@property {Number} truncate.argument - Max length of each argument logged.
@property {Number} truncate.output - Max length of total output.
@property {Boolean} compress - Whether to LZ compress data portion of log line, requires LZString.
@property {Array.<String>} names - Custom identifiers for message levels,
pad to same length to align log lines.
@property {String} separator - Characters to delineate values within a line.
@property {Boolean} console - Whether to redirect messages to the console instead of file.
@property {Boolean} ms - Whether to display millisecond timestamp.
@property {Number} msPad - How many places to pad timestamps.
@property {Object} timer - Timer instance for generating timestamps.
@property {String} identifier - String used to indicate which WebView emitted a message, defaults to
{@link Starlet.identifier} or 'connection'.
@property {Number} leaderLength - Used to adaptively pad the leader of each line to align data into
columns for readability.
@property {Array.<RegExp>} exclude - List of patterns to test on final output, logs nothing if any match.
@see {@link log}
@see {@link deval}
*/
function log()
{
var self = arguments.callee,
argumentsLength = arguments.length,
level = 0,
type = '',
message = [],
fwLevelsText = ['DEBUG', 'INFO', 'WARN', 'ERROR'],
fwLevels =
/* framework
d i w e
e n a r
b f r r
u o n o
g r */
[
1, // always s
3, // error c
2, // warn r
1, // main i
1, // func p
1, // info t
0 // debug
];
// static
!exists(self.verbosity ) && (self.verbosity = {'min' : L_ALWAYS, 'max' : L_MAIN});
!exists(self.squelch ) && (self.squelch = {'min' : L_ALWAYS, 'max' : L_MAIN});
!exists(self.truncate ) && (self.truncate = {'argument': 0 , 'output': 300});
!exists(self.compress ) && (self.compress = false);
!exists(self.names ) && (self.names = ['always ', 'error ', 'warn ', 'main ', 'function', 'info ', 'debug ']);
!exists(self.separator ) && (self.separator = ', ');
!exists(self.console ) && (self.console = false);
!exists(self.ms ) && (self.ms = false);
!exists(self.msPad ) && (self.msPad = (T_MONTH * 12).toString().length);
!exists(self.timer ) && (self.timer = new TimerClass());
!exists(self.identifier ) && (self.identifier = pad(typeof Starlet !== 'undefined' ? Starlet.identifier : 'connection', 20));
!exists(self.leaderLength) && (self.leaderLength = 'yyyymmdd hh:mm:ss.mmmuuu (DEBUG) : [{0}] : '.format
(
typeof Starlet !== 'undefined'
? Starlet.name
: 'connection_starlet'
)
.length
);
!exists(self.exclude ) && (self.exclude = []);
// each arg
for (var i = 0; i < argumentsLength; i++)
{
var arg = arguments[i];
// first arg is message level?
if (!i && typeof arg === 'number')
{
level = arg;
if (level >= self.verbosity.min && level <= self.verbosity.max) // in range
{
var text = [];
//text.push(self.identifier); // source webview
self.ms && text.push(pad(self.timer.elapsed(), self.msPad, '0')); // time since first call
text.push(self.names[level]); // message level
message.push('[' + text.join(':') + ']'); // final info string
continue;
}
else { return true; /* courtesy */ } // not in range
}
// squelch appropriately
switch (type = dltypeof(arg))
{
case 'array' : case 'boolean' : case 'date' : case 'domcollection' :
case 'error' : case 'funciton' : case 'jsobject' : case 'math' :
case 'mimetypecollection': case 'null' : case 'number' : case 'object' :
case 'plugincollection' : case 'regexp' : case 'string' : case 'textnode' :
case 'textrange' : case 'undefined' : case 'window' : break;
case 'arguments' :
case 'domelement' : if (level < self.squelch.min || level > self.squelch.max) { message.push('/* ' + type + ' */'); continue; } break;
default : if (/^(html|element)/i.test(type)) // html-like?
{
if (level < self.squelch.min || level > self.squelch.max) { message.push('/* ' + type + ' */'); continue; } break;
}
else { break; }
}
// finalize arg
arg = deval(arg);
self.truncate.argument
&& arg.length > self.truncate.argument
&& (arg.substr(0, self.truncate.argument - 4) + ' ...');
message.push(arg);
}
// no args? stop
if (!message.length) { return true; /* courtesy */ }
// finalize output
message = message.join(self.separator);
self.truncate.output
&& message.length + self.leaderLength + 4 > self.truncate.output
&& (message = message.substr(0, self.truncate.output - self.leaderLength - 4) + ' ...');
// exclusion
for (var i = 0; i < self.exclude.length; i++)
{
if (self.exclude[i].test(message)) { return true; }
}
// compress?
if (self.compress && window.LZString)
{
var startLength = message.length,
startTime = (new Date()).getTime();
message = LZString.compressToUTF16(message);
var endTime = (new Date()).getTime(),
endLength = message.length;
message = '{0}% {1}ms ~ {2}'.format(Math.round(endLength / startLength * 100), endTime - startTime, message);
}
// output
ISORION && !self.console
? OrionSystem.logToConsole(fwLevels[level], message + "\n")
: console.log(message);
//Bus.ask('logs', {'action': 'create', 'message': message});
return true; // courtesy
}
/** @function pad
@readonly
@desc Prepends characters to a value.
@arg {Number|String} value - Item to pad.
@arg {Number} places - Desired minimum length.
@arg {String} character - Item to pad with.
@returns {String} Padded string of length >= places.
*/
function pad(value, places, character)
{
value = value.toString();
character = character || ' ';
while (value.length < places) { value = character + value; }
return value;
}
// in development
function sanitizeJSON(input)
{
var self = arguments.callee,
output = null;
switch (dltypeof(input))
{
case 'arguments': input = [].slice.call(input);
case 'object' :
case 'jsobject' :
output = {},
returned = null;
for (var key in input)
{
input.hasOwnProperty(key)
&& (returned = self(input[key])) !== null
&& (output[key] = returned);
}
//console.log('obj', output);
break;
case 'array':
output = [];
for (var index = 0; index < input.length; index++)
{
(returned = self(input[index])) !== null
&& (output[index] = returned);
}
//console.log('obj', output);
break;
case 'string' :
case 'number' :
case 'boolean':
case 'null' : output = input; break;
default: break;
}
return output || null;
}
/** @function sanitizeParams
@readonly
@desc Converts properties to strings for safer assembly of URLs.
@arg {Object} params - Parameters object to sanitize.
@returns {Object} Sanitized parameters object
*/
function sanitizeParams(params)
{
for (var key in params)
{
typeof params[key] === 'number' && (params[key] = String(params[key]));
};
return params;
}
/** @function touch
@readonly
@desc Creates a property on an object if it doesn't exist, defaults to value given, and returns the reference.
@arg {Object} obj - Container object.
@arg {String} property - Key to create if undefined.
@arg {Mixed} [value] - Item to set key to, otherwise null.
@returns {Mixed} Reference to obj.property.
*/
function touch(obj, property, value)
{
!obj.hasOwnProperty(property) && (obj[property] = value || null);
return obj[property];
}
/** @function unlinkCallback
@readonly
@desc Removes a callback wrapper generated with {@link linkCallback}.
@arg {String|Function} callback - Generated function name or original callback.
@returns {Boolean} Success or failure.
@seec {@link linkCallback}
*/
function unlinkCallback(callback)
{
switch (typeof callback)
{
case 'string':
log(L_DEBUG, 'window.unlinkCallback', callback, 'by name');
clearTimeout(window[callback].timeout);
delete window[callback];
return true;
case 'function':
var pattern = new RegExp('^callback_');
for (key in window)
{
if (pattern.test(key) && window[key] === callback)
{
log(L_DEBUG, 'window.unlinkCallback', key, 'by reference');
clearTimeout(window[key].timeout);
delete window[key];
return true;
}
}
default: return false;
}
}