diff options
Diffstat (limited to 'js/lib/elgglib.js')
-rw-r--r-- | js/lib/elgglib.js | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/js/lib/elgglib.js b/js/lib/elgglib.js new file mode 100644 index 000000000..af2c94000 --- /dev/null +++ b/js/lib/elgglib.js @@ -0,0 +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} 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]) + 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 ($.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'); + } +}; |