/**
* @namespace Singleton object for holding the Elgg javascript library
*/
var elgg = elgg || {};
/**
* Pointer to the global context
*
* @see elgg.require
* @see elgg.provide
*/
elgg.global = this;
/**
* Convenience reference to an empty function.
*
* Save memory by not generating multiple empty functions.
*/
elgg.nullFunction = function() {};
/**
* This forces an inheriting class to implement the method or
* it will throw an error.
*
* @example
* AbstractClass.prototype.toBeImplemented = elgg.abstractMethod;
*/
elgg.abstractMethod = function() {
throw new Error("Oops... you forgot to implement an abstract method!");
};
/**
* Merges two or more objects together and returns the result.
*/
elgg.extend = jQuery.extend;
/**
* Check if the value is an array.
*
* No sense in reinventing the wheel!
*
* @param {*} val
*
* @return boolean
*/
elgg.isArray = jQuery.isArray;
/**
* Check if the value is a function.
*
* No sense in reinventing the wheel!
*
* @param {*} val
*
* @return boolean
*/
elgg.isFunction = jQuery.isFunction;
/**
* Check if the value is a "plain" object (i.e., created by {} or new Object())
*
* No sense in reinventing the wheel!
*
* @param {*} val
*
* @return boolean
*/
elgg.isPlainObject = jQuery.isPlainObject;
/**
* Check if the value is a string
*
* @param {*} val
*
* @return boolean
*/
elgg.isString = function(val) {
return typeof val === 'string';
};
/**
* Check if the value is a number
*
* @param {*} val
*
* @return boolean
*/
elgg.isNumber = function(val) {
return typeof val === 'number';
};
/**
* Check if the value is an object
*
* @note This returns true for functions and arrays! If you want to return true only
* for "plain" objects (created using {} or new Object()) use elgg.isPlainObject.
*
* @param {*} val
*
* @return boolean
*/
elgg.isObject = function(val) {
return typeof val === 'object';
};
/**
* Check if the value is undefined
*
* @param {*} val
*
* @return boolean
*/
elgg.isUndefined = function(val) {
return val === undefined;
};
/**
* Check if the value is null
*
* @param {*} val
*
* @return boolean
*/
elgg.isNull = function(val) {
return val === null;
};
/**
* Check if the value is either null or undefined
*
* @param {*} val
*
* @return boolean
*/
elgg.isNullOrUndefined = function(val) {
return val == null;
};
/**
* Throw an exception of the type doesn't match
*
* @todo Might be more appropriate for debug mode only?
*/
elgg.assertTypeOf = function(type, val) {
if (typeof val !== type) {
throw new TypeError("Expecting param of " +
arguments.caller + "to be a(n) " + type + "." +
" Was actually a(n) " + typeof val + ".");
}
};
/**
* Throw an error if the required package isn't present
*
* @param {String} pkg The required package (e.g., 'elgg.package')
*/
elgg.require = function(pkg) {
elgg.assertTypeOf('string', pkg);
var parts = pkg.split('.'),
cur = elgg.global,
part, i;
for (i = 0; i < parts.length; i += 1) {
part = parts[i];
cur = cur[part];
if (elgg.isUndefined(cur)) {
throw new Error("Missing package: " + pkg);
}
}
};
/**
* Generate the skeleton for a package.
*
*
* elgg.provide('elgg.package.subpackage');
*
*
* is equivalent to
*
*
* elgg = elgg || {};
* elgg.package = elgg.package || {};
* elgg.package.subpackage = elgg.package.subpackage || {};
*
*
* @example elgg.provide('elgg.config.translations')
*
* @param {string} pkg The package name.
*/
elgg.provide = function(pkg, opt_context) {
elgg.assertTypeOf('string', pkg);
var parts = pkg.split('.'),
context = opt_context || elgg.global,
part, i;
for (i = 0; i < parts.length; i += 1) {
part = parts[i];
context[part] = context[part] || {};
context = context[part];
}
};
/**
* Inherit the prototype methods from one constructor into another.
*
* @example
*
* function ParentClass(a, b) { }
*
* ParentClass.prototype.foo = function(a) { alert(a); }
*
* function ChildClass(a, b, c) {
* //equivalent of parent::__construct(a, b); in PHP
* ParentClass.call(this, a, b);
* }
*
* elgg.inherit(ChildClass, ParentClass);
*
* var child = new ChildClass('a', 'b', 'see');
* child.foo('boo!'); // alert('boo!');
*
*
* @param {Function} childCtor Child class.
* @param {Function} parentCtor Parent class.
*/
elgg.inherit = function(Child, Parent) {
Child.prototype = new Parent();
Child.prototype.constructor = Child;
};
/**
* Converts shorthand urls to absolute urls.
*
* If the url is already absolute or protocol-relative, no change is made.
*
* elgg.normalize_url(''); // 'http://my.site.com/'
* elgg.normalize_url('dashboard'); // 'http://my.site.com/dashboard'
* elgg.normalize_url('http://google.com/'); // no change
* elgg.normalize_url('//google.com/'); // no change
*
* @param {String} url The url to normalize
* @return {String} The extended url
* @private
*/
elgg.normalize_url = function(url) {
url = url || '';
elgg.assertTypeOf('string', url);
// jslint complains if you use /regexp/ shorthand here... ?!?!
if ((new RegExp("^(https?:)?//", "i")).test(url)) {
return url;
}
// 'javascript:'
else if (url.indexOf('javascript:') === 0) {
return url;
}
// watch those double escapes in JS.
// 'install.php', 'install.php?step=step'
else if ((new RegExp("^[^\/]*\\.php(\\?.*)?$", "i")).test(url)) {
return elgg.config.wwwroot + url.ltrim('/');
}
// 'example.com', 'example.com/subpage'
else if ((new RegExp("^[^/]*\\.", "i")).test(url)) {
return 'http://' + url;
}
// 'page/handler', 'mod/plugin/file.php'
else {
// trim off any leading / because the site URL is stored
// with a trailing /
return elgg.config.wwwroot + url.ltrim('/');
}
};
/**
* Displays system messages via javascript rather than php.
*
* @param {String} msgs The message we want to display
* @param {Number} delay The amount of time to display the message in milliseconds. Defaults to 6 seconds.
* @param {String} type The type of message (typically 'error' or 'message')
* @private
*/
elgg.system_messages = function(msgs, delay, type) {
if (elgg.isUndefined(msgs)) {
return;
}
var classes = ['elgg-message'],
messages_html = [],
appendMessage = function(msg) {
messages_html.push('' + msg + '
');
},
systemMessages = $('ul.elgg-system-messages'),
i;
//validate delay. Must be a positive integer.
delay = parseInt(delay || 6000, 10);
if (isNaN(delay) || delay <= 0) {
delay = 6000;
}
//Handle non-arrays
if (!elgg.isArray(msgs)) {
msgs = [msgs];
}
if (type === 'error') {
classes.push('elgg-state-error');
} else {
classes.push('elgg-state-success');
}
msgs.forEach(appendMessage);
$(messages_html.join('')).appendTo(systemMessages)
.animate({opacity: '1.0'}, delay).fadeOut('slow');
};
/**
* Wrapper function for system_messages. Specifies "messages" as the type of message
* @param {String} msgs The message to display
* @param {Number} delay How long to display the message (milliseconds)
*/
elgg.system_message = function(msgs, delay) {
elgg.system_messages(msgs, delay, "message");
};
/**
* Wrapper function for system_messages. Specifies "errors" as the type of message
* @param {String} errors The error message to display
* @param {Number} delay How long to dispaly the error message (milliseconds)
*/
elgg.register_error = function(errors, delay) {
elgg.system_messages(errors, delay, "error");
};
/**
* Meant to mimic the php forward() function by simply redirecting the
* user to another page.
*
* @param {String} url The url to forward to
*/
elgg.forward = function(url) {
location.href = elgg.normalize_url(url);
};
/**
* Parse a URL into its parts. Mimicks http://php.net/parse_url
*
* @param {String} url The URL to parse
* @param {Int} component A component to return
* @param {Bool} expand Expand the query into an object? Else it's a string.
*
* @return {Object} The parsed URL
*/
elgg.parse_url = function(url, component, expand) {
// Adapted from http://blog.stevenlevithan.com/archives/parseuri
// which was release under the MIT
// It was modified to fix mailto: and javascript: support.
var
expand = expand || false,
component = component || false,
re_str =
// scheme (and user@ testing)
'^(?:(?![^:@]+:[^:@/]*@)([^:/?#.]+):)?(?://)?'
// possibly a user[:password]@
+ '((?:(([^:@]*)(?::([^:@]*))?)?@)?'
// host and port
+ '([^:/?#]*)(?::(\\d*))?)'
// path
+ '(((/(?:[^?#](?![^?#/]*\\.[^?#/.]+(?:[?#]|$)))*/?)?([^?#/]*))'
// query string
+ '(?:\\?([^#]*))?'
// fragment
+ '(?:#(.*))?)',
keys = {
'mailto': {
4: "scheme",
5: "user",
6: "host",
9: "path",
12: "query",
13: "fragment"
},
'standard': {
1: "scheme",
4: "user",
5: "pass",
6: "host",
7: "port",
9: "path",
12: "query",
13: "fragment"
}
},
results = {},
match_keys,
is_mailto = false;
var re = new RegExp(re_str);
var matches = re.exec(url);
// if the scheme field is undefined it means we're using a protocol
// without :// and an @. Feel free to fix this in the re if you can >:O
if (matches[1] == undefined) {
match_keys = keys['mailto'];
is_mailto = true;
} else {
match_keys = keys['standard'];
}
for (var i in match_keys) {
if (matches[i]) {
results[match_keys[i]] = matches[i];
}
}
// merge everything to path if not standard
if (is_mailto) {
var path = '',
new_results = {};
if (typeof(results['user']) != 'undefined' && typeof(results['host']) != 'undefined') {
path = results['user'] + '@' + results['host'];
delete results['user'];
delete results['host'];
} else if (typeof(results['user'])) {
path = results['user'];
delete results['user'];
} else if (typeof(results['host'])) {
path = results['host'];
delete results['host'];
}
if (typeof(results['path']) != 'undefined') {
results['path'] = path + results['path'];
} else {
results['path'] = path;
}
for (var prop in results) {
new_results[prop] = results[prop];
}
results = new_results;
}
if (expand && typeof(results['query']) != 'undefined') {
results['query'] = elgg.parse_str(results['query']);
}
if (component) {
if (typeof(results[component]) != 'undefined') {
return results[component];
} else {
return false;
}
}
return results;
}
/**
* Returns an object with key/values of the parsed query string.
*
* @param {String} string The string to parse
* @return {Object} The parsed object string
*/
elgg.parse_str = function(string) {
var params = {};
var result,
key,
value,
re = /([^&=]+)=?([^&]*)/g;
while (result = re.exec(string)) {
key = decodeURIComponent(result[1])
value = decodeURIComponent(result[2])
params[key] = value;
}
return params;
};
/**
* Returns a jQuery selector from a URL's fragement. Defaults to expecting an ID.
*
* Examples:
* http://elgg.org/download.php returns ''
* http://elgg.org/download.php#id returns #id
* http://elgg.org/download.php#.class-name return .class-name
* http://elgg.org/download.php#a.class-name return a.class-name
*
* @param {String} url The URL
* @return {String} The selector
*/
elgg.getSelectorFromUrlFragment = function(url) {
var fragment = url.split('#')[1];
if (fragment) {
// this is a .class or a tag.class
if (fragment.indexOf('.') > -1) {
return fragment;
}
// this is an id
else {
return '#' + fragment;
}
}
return '';
};
/**
* Adds child to object[parent] array.
*
* @param {Object} object The object to add to
* @param {String} parent The parent array to add to.
* @param {Mixed} value The value
*/
elgg.push_to_object_array = function(object, parent, value) {
elgg.assertTypeOf('object', object);
elgg.assertTypeOf('string', parent);
if (!(object[parent] instanceof Array)) {
object[parent] = []
}
if (object[parent].indexOf(value) < 0) {
return object[parent].push(value);
}
return false;
}
/**
* Tests if object[parent] contains child
*
* @param {Object} object The object to add to
* @param {String} parent The parent array to add to.
* @param {Mixed} value The value
*/
elgg.is_in_object_array = function(object, parent, value) {
elgg.assertTypeOf('object', object);
elgg.assertTypeOf('string', parent);
return typeof(object[parent]) != 'undefined' && object[parent].indexOf(value) >= 0;
}
/**
* Triggers the init hook when the library is ready
*
* Current requirements:
* - DOM is ready
* - languages loaded
*
*/
elgg.initWhenReady = function() {
if (elgg.config.languageReady && elgg.config.domReady) {
elgg.trigger_hook('init', 'system');
elgg.trigger_hook('ready', 'system');
}
}