diff options
Diffstat (limited to 'js/lib/elgglib.js')
| -rw-r--r-- | js/lib/elgglib.js | 883 |
1 files changed, 563 insertions, 320 deletions
diff --git a/js/lib/elgglib.js b/js/lib/elgglib.js index bed2db39d..a8e187f1d 100644 --- a/js/lib/elgglib.js +++ b/js/lib/elgglib.js @@ -1,320 +1,563 @@ -/**
- * @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.
- *
- * <pre>
- * elgg.provide('elgg.package.subpackage');
- * </pre>
- *
- * is equivalent to
- *
- * <pre>
- * elgg = elgg || {};
- * elgg.package = elgg.package || {};
- * elgg.package.subpackage = elgg.package.subpackage || {};
- * </pre>
- *
- * @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
- * <pre>
- * 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!');
- * </pre>
- *
- * @param {Function} childCtor Child class.
- * @param {Function} parentCtor Parent class.
- */
-elgg.inherit = function(Child, Parent) {
- Child.prototype = new Parent();
- Child.prototype.constructor = Child;
-};
-
-/**
- * Prepend elgg.config.wwwroot to a url if the url doesn't already have it.
- *
- * @param {String} url The url to extend
- * @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?:)?//")).test(url)) {
- return url;
- }
-
- 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_system_message', 'radius8'],
- messages_html = [],
- appendMessage = function(msg) {
- messages_html.push('<div class="' + classes.join(' ') + '"><p>' + msg + '</p></div>');
- }, i;
-
- //validate delay. Must be a positive integer.
- delay = parseInt(delay || 6000, 10);
- if (isNaN(delay) || delay <= 0) {
- delay = 6000;
- }
-
- if (type === 'error') {
- classes.push('messages_error');
- }
-
- //Handle non-arrays
- if (!elgg.isArray(msgs)) {
- msgs = [msgs];
- }
-
- msgs.forEach(appendMessage);
-
- $(messages_html.join('')).appendTo('#elgg_system_messages')
- .animate({opacity: '1.0'}, delay).fadeOut('slow');
-};
-
-/**
- * Wrapper function for system_messages. Specifies "messages" as the type of message
- * @param {String} msg 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} error 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);
-};
\ No newline at end of file +/** + * @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. + * + * <pre> + * elgg.provide('elgg.package.subpackage'); + * </pre> + * + * is equivalent to + * + * <pre> + * elgg = elgg || {}; + * elgg.package = elgg.package || {}; + * elgg.package.subpackage = elgg.package.subpackage || {}; + * </pre> + * + * @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 + * <pre> + * 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!'); + * </pre> + * + * @param {Function} Child Child class constructor. + * @param {Function} Parent Parent class constructor. + */ +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); + + validated = (function(url) { + url = elgg.parse_url(url); + if (url.scheme){ + url.scheme = url.scheme.toLowerCase(); + } + if (url.scheme == 'http' || url.scheme == 'https') { + if (!url.host) { + return false; + } + /* hostname labels may contain only alphanumeric characters, dots and hypens. */ + if (!(new RegExp("^([a-zA-Z0-9][a-zA-Z0-9\\-\\.]*)$", "i")).test(url.host) || url.host.charAt(-1) == '.') { + return false; + } + } + /* some schemas allow the host to be empty */ + if (!url.scheme || !url.host && url.scheme != 'mailto' && url.scheme != 'news' && url.scheme != 'file') { + return false; + } + return true; + })(url); + + // all normal URLs including mailto: + if (validated) { + return url; + } + + // '//example.com' (Shortcut for protocol.) + // '?query=test', #target + else if ((new RegExp("^(\\#|\\?|//)", "i")).test(url)) { + return url; + } + + // 'javascript:' + else if (url.indexOf('javascript:') === 0 || url.indexOf('mailto:') === 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('<li class="' + classes.join(' ') + '"><p>' + msg + '</p></li>'); + }, + 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); + + if (type != 'error') { + $(messages_html.join('')).appendTo(systemMessages) + .animate({opacity: '1.0'}, delay).fadeOut('slow'); + } else { + $(messages_html.join('')).appendTo(systemMessages); + } +}; + +/** + * 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 = { + 1: "scheme", + 4: "user", + 5: "pass", + 6: "host", + 7: "port", + 9: "path", + 12: "query", + 13: "fragment" + }, + results = {}; + + if (url.indexOf('mailto:') === 0) { + results['scheme'] = 'mailto'; + results['path'] = url.replace('mailto:', ''); + return results; + } + + if (url.indexOf('javascript:') === 0) { + results['scheme'] = 'javascript'; + results['path'] = url.replace('javascript:', ''); + return results; + } + + var re = new RegExp(re_str); + var matches = re.exec(url); + + for (var i in keys) { + if (matches[i]) { + results[keys[i]] = matches[i]; + } + } + + 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].replace(/\+/g, ' ')); + value = decodeURIComponent(result[2].replace(/\+/g, ' ')); + 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 ($.inArray(value, object[parent]) < 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' && $.inArray(value, object[parent]) >= 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'); + } +}; |
