From 441c9e85dcb824dba2a44657a31fa29ad71a4ee1 Mon Sep 17 00:00:00 2001 From: ewinslow Date: Tue, 2 Nov 2010 16:06:53 +0000 Subject: Refs #2538: Moved js directory to elgg root. git-svn-id: http://code.elgg.org/elgg/trunk@7189 36083f99-b078-4883-b0ff-0f9b5a30f544 --- js/classes/ElggEntity.js | 20 ++++ js/classes/ElggPriorityList.js | 60 ++++++++++ js/classes/ElggUser.js | 14 +++ js/lib/ajax.js | 252 +++++++++++++++++++++++++++++++++++++++ js/lib/configuration.js | 3 + js/lib/elgglib.js | 187 +++++++++++++++++++++++++++++ js/lib/events.js | 65 ++++++++++ js/lib/languages.js | 83 +++++++++++++ js/lib/security.js | 70 +++++++++++ js/lib/session.js | 116 ++++++++++++++++++ js/lib/ui.js | 121 +++++++++++++++++++ js/lib/ui.widgets.js | 131 ++++++++++++++++++++ js/tests/ElggAjaxOptionsTest.js | 62 ++++++++++ js/tests/ElggAjaxTest.js | 59 +++++++++ js/tests/ElggEventsTest.js | 28 +++++ js/tests/ElggLanguagesTest.js | 45 +++++++ js/tests/ElggLibTest.js | 88 ++++++++++++++ js/tests/ElggPriorityListTest.js | 47 ++++++++ js/tests/ElggSecurityTest.js | 51 ++++++++ js/tests/ElggSessionTest.js | 36 ++++++ 20 files changed, 1538 insertions(+) create mode 100644 js/classes/ElggEntity.js create mode 100644 js/classes/ElggPriorityList.js create mode 100644 js/classes/ElggUser.js create mode 100644 js/lib/ajax.js create mode 100644 js/lib/configuration.js create mode 100644 js/lib/elgglib.js create mode 100644 js/lib/events.js create mode 100644 js/lib/languages.js create mode 100644 js/lib/security.js create mode 100644 js/lib/session.js create mode 100644 js/lib/ui.js create mode 100644 js/lib/ui.widgets.js create mode 100644 js/tests/ElggAjaxOptionsTest.js create mode 100644 js/tests/ElggAjaxTest.js create mode 100644 js/tests/ElggEventsTest.js create mode 100644 js/tests/ElggLanguagesTest.js create mode 100644 js/tests/ElggLibTest.js create mode 100644 js/tests/ElggPriorityListTest.js create mode 100644 js/tests/ElggSecurityTest.js create mode 100644 js/tests/ElggSessionTest.js (limited to 'js') diff --git a/js/classes/ElggEntity.js b/js/classes/ElggEntity.js new file mode 100644 index 000000000..9461a463f --- /dev/null +++ b/js/classes/ElggEntity.js @@ -0,0 +1,20 @@ +/** + * Create a new ElggEntity + * + * @class Represents an ElggEntity + * @property {number} guid + * @property {string} type + * @property {string} subtype + * @property {number} owner_guid + * @property {number} site_guid + * @property {number} container_guid + * @property {number} access_id + * @property {number} time_created + * @property {number} time_updated + * @property {number} last_action + * @property {string} enabled + * + */ +elgg.ElggEntity = function(o) { + $.extend(this, o); +}; \ No newline at end of file diff --git a/js/classes/ElggPriorityList.js b/js/classes/ElggPriorityList.js new file mode 100644 index 000000000..521fbbb64 --- /dev/null +++ b/js/classes/ElggPriorityList.js @@ -0,0 +1,60 @@ +elgg.ElggPriorityList = function() { + this.length = 0; + this.priorities_ = []; +}; + +elgg.ElggPriorityList.prototype.insert = function(obj, opt_priority) { + if (opt_priority == undefined) { + opt_priority = 500; + } + + opt_priority = parseInt(opt_priority); + if (opt_priority < 0) { + opt_priority = 0; + } + + if (this.priorities_[opt_priority] == undefined) { + this.priorities_[opt_priority] = []; + } + + this.priorities_[opt_priority].push(obj); + this.length++; +}; + +elgg.ElggPriorityList.prototype.forEach = function(callback) { + elgg.assertTypeOf('function', callback); + + var index = 0; + for (var p in this.priorities_) { + var elems = this.priorities_[p]; + for (var i in elems) { + callback(elems[i], index); + index++; + } + } +}; + +elgg.ElggPriorityList.prototype.every = function(callback) { + elgg.assertTypeOf('function', callback); + + var index = 0; + for (var p in this.priorities_) { + var elems = this.priorities_[p]; + for (var i in elems) { + if (!callback(elems[i], index)) { + return false; + }; + } + } + + return true; +}; + +elgg.ElggPriorityList.prototype.remove = function(obj) { + this.priorities_.forEach(function(elems, priority) { + var index; + while ((index = elems.indexOf(obj)) != -1) { + elems.splice(index, 1); + } + }); +}; \ No newline at end of file diff --git a/js/classes/ElggUser.js b/js/classes/ElggUser.js new file mode 100644 index 000000000..8a7a8b7eb --- /dev/null +++ b/js/classes/ElggUser.js @@ -0,0 +1,14 @@ +/** + * Create a new ElggUser + * + * @param {Object} o + * @extends ElggEntity + * @class Represents an ElggUser + * @property {string} name + * @property {string} username + */ +elgg.ElggUser = function(o) { + elgg.ElggEntity.call(this, o); +}; + +elgg.inherit(elgg.ElggUser, elgg.ElggEntity); \ No newline at end of file diff --git a/js/lib/ajax.js b/js/lib/ajax.js new file mode 100644 index 000000000..184fd0da3 --- /dev/null +++ b/js/lib/ajax.js @@ -0,0 +1,252 @@ +elgg.provide('elgg.ajax'); + +/** + * @author Evan Winslow + * Provides a bunch of useful shortcut functions for making ajax calls + */ + +/** + * Wrapper function for jQuery.ajax which ensures that the url being called + * is relative to the elgg site root. + * + * You would most likely use elgg.get or elgg.post, rather than this function + * + * @param {string} url Optionally specify the url as the first argument + * @param {Object} options Optional. {@see jQuery#ajax} + * @return {XmlHttpRequest} + */ +elgg.ajax = function(url, options) { + options = elgg.ajax.handleOptions(url, options); + + options.url = elgg.normalize_url(options.url); + return $.ajax(options); +}; +/** + * @const + */ +elgg.ajax.SUCCESS = 0; + +/** + * @const + */ +elgg.ajax.ERROR = -1; + +/** + * Handle optional arguments and return the resulting options object + * + * @param url + * @param options + * @return {Object} + * @private + */ +elgg.ajax.handleOptions = function(url, options) { + //elgg.ajax('example/file.php', {...}); + if(typeof url == 'string') { + options = options || {}; + + //elgg.ajax({...}); + } else { + options = url || {}; + url = options.url; + } + + var data_only = true; + + //elgg.ajax('example/file.php', function() {...}); + if (typeof options == 'function') { + data_only = false; + options = {success: options}; + } + + //elgg.ajax('example/file.php', {data:{...}}); + if(options.data) { + data_only = false; + } else { + for (var member in options) { + //elgg.ajax('example/file.php', {callback:function(){...}}); + if(typeof options[member] == 'function') { + data_only = false; + } + } + } + + //elgg.ajax('example/file.php', {notdata:notfunc}); + if (data_only) { + var data = options; + options = {data: data}; + } + + if (url) { + options.url = url; + } + + return options; +}; + +/** + * Wrapper function for elgg.ajax which forces the request type to 'get.' + * + * @param {string} url Optionally specify the url as the first argument + * @param {Object} options {@see jQuery#ajax} + * @return {XmlHttpRequest} + */ +elgg.get = function(url, options) { + options = elgg.ajax.handleOptions(url, options); + + options.type = 'get'; + return elgg.ajax(options); +}; + +/** + * Wrapper function for elgg.get which forces the dataType to 'json.' + * + * @param {string} url Optionally specify the url as the first argument + * @param {Object} options {@see jQuery#ajax} + * @return {XmlHttpRequest} + */ +elgg.getJSON = function(url, options) { + options = elgg.ajax.handleOptions(url, options); + + options.dataType = 'json'; + return elgg.get(options); +}; + +/** + * Wrapper function for elgg.ajax which forces the request type to 'post.' + * + * @param {string} url Optionally specify the url as the first argument + * @param {Object} options {@see jQuery#ajax} + * @return {XmlHttpRequest} + */ +elgg.post = function(url, options) { + options = elgg.ajax.handleOptions(url, options); + + options.type = 'post'; + return elgg.ajax(options); +}; + +/** + * Perform an action via ajax + * + * @example Usage 1: + * At its simplest, only the action name is required (and anything more than the + * action name will be invalid). + *
+ * elgg.action('name/of/action');
+ * 
+ * Note that it will *not* love you if you specify the full url as the action + * (i.e. elgg.yoursite.com/action/name/of/action), but why would you want to do + * that anyway, when you can just specify the action name? + * + * @example Usage 2: + * If you want to pass some data along with it, use the second parameter + *
+ * elgg.action('friend/add', { friend: some_guid });
+ * 
+ * + * @example Usage 3: + * Of course, you will have no control over what happens when the request + * completes if you do it like that, so there's also the most verbose method + *
+ * elgg.action('friend/add', {
+ *     data: {
+ *         friend: some_guid
+ *     },
+ *     success: function(json) {
+ *         //do something
+ *     },
+ * }
+ * 
+ * You can pass any of your favorite $.ajax arguments into this second parameter. + * + * Note: If you intend to use the second field in the "verbose" way, you must + * specify a callback method or the data parameter. If you do not, elgg.action + * will think you mean to send the second parameter as data. + * + * @param {String} action The action to call. + * @param {Object} options {@see jQuery#ajax} + * @return {XMLHttpRequest} + */ +elgg.action = function(action, options) { + if(!action) { + throw new TypeError("action must be specified"); + } else if (typeof action != 'string') { + throw new TypeError("action must be a string"); + } + + options = elgg.ajax.handleOptions('action/' + action, options); + + options.data = elgg.security.addToken(options.data); + options.dataType = 'json'; + + //Always display system messages after actions + var custom_success = options.success || function(){}; + options.success = function(json, two, three, four) { + if (json.system_messages) { + elgg.register_error(json.system_messages.errors); + elgg.system_message(json.system_messages.messages); + } + custom_success(json, two, three, four); + }; + + return elgg.post(options); +}; + +/** + * Make an API call + * + * @example Usage: + *
+ * elgg.api('system.api.list', {
+ *     success: function(data) {
+ *         console.log(data);
+ *     }
+ * });
+ * 
+ * + * @param {String} method The API method to be called + * @param {Object} options {@see jQuery#ajax} + * @return {XmlHttpRequest} + */ +elgg.api = function(method, options) { + if (!method) { + throw new TypeError("method must be specified"); + } else if (typeof method != 'string') { + throw new TypeError("method must be a string"); + } + + var defaults = { + dataType: 'json', + data: {} + }; + + options = elgg.ajax.handleOptions(method, options); + options = $.extend(defaults, options); + + options.url = 'services/api/rest/' + options.dataType + '/'; + options.data.method = method; + + return elgg.ajax(options); +}; + +/** + * @param {string} selector a jQuery selector + * @param {Function} complete A function to execute when the refresh is done + * @return {XMLHttpRequest} + */ +elgg.refresh = function(selector, complete) { + $(selector).html('
'); + return $(selector).load(location.href + ' ' + selector + ' > *', complete); +}; + +/** + * @param {string} selector a jQuery selector (usually an #id) + * @param {number} interval The refresh interval in seconds + * @param {Function} complete A function to execute when the refresh is done + * @return {number} The interval identifier + */ +elgg.feed = function(selector, interval, complete) { + return setInterval(function() { + elgg.refresh(selector, complete); + }, interval); +}; \ No newline at end of file diff --git a/js/lib/configuration.js b/js/lib/configuration.js new file mode 100644 index 000000000..8ed326116 --- /dev/null +++ b/js/lib/configuration.js @@ -0,0 +1,3 @@ +elgg.provide('elgg.config'); + +elgg.config.wwwroot = '/'; \ No newline at end of file diff --git a/js/lib/elgglib.js b/js/lib/elgglib.js new file mode 100644 index 000000000..32dbb1ec3 --- /dev/null +++ b/js/lib/elgglib.js @@ -0,0 +1,187 @@ +/** + * + * + */ + +/** + * @namespace Namespace for elgg javascript functions + */ +var elgg = elgg || {}; + +elgg.assertTypeOf = function(type, param) { + if (typeof param !== type) { + throw new TypeError("Expecting param to be a(n) " + type + ". Was a(n) " + typeof param + "."); + } +}; + +/** + * Pointer to the global context + * {@see elgg.require} and {@see elgg.provide} + */ +elgg.global = this; + +/** + * 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; + + for (var i = 0; i < parts.length; i++) { + part = parts[i]; + cur = cur[part]; + if(typeof cur == 'undefined') { + 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) { + elgg.assertTypeOf('string', pkg); + + var parts = pkg.split('.'), + cur = elgg.global, + part; + + for (var i = 0; i < parts.length; i++) { + part = parts[i]; + cur[part] = cur[part] || {}; + cur = cur[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; +}; + +/** + * 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); + + if(/(^(https?:)?\/\/)/.test(url)) { + return url; + } + + return elgg.config.wwwroot + url; +}; + +/** + * 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 (msgs == undefined) { + return; + } + + //validate delay. Must be a positive integer. + delay = parseInt(delay); + if (isNaN(delay) || delay <= 0) { + delay = 6000; + } + + classes = ['elgg_system_message', 'radius8']; + if (type == 'error') { + classes.push('messages_error'); + } + + //Handle non-arrays + if (msgs.constructor.toString().indexOf("Array") == -1) { + msgs = [msgs]; + } + + var messages_html = [];f + + for (var i in msgs) { + messages_html.push('

' + msgs[i] + '

'); + } + + $(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 diff --git a/js/lib/events.js b/js/lib/events.js new file mode 100644 index 000000000..358dd6280 --- /dev/null +++ b/js/lib/events.js @@ -0,0 +1,65 @@ +elgg.provide('elgg.config.events'); +elgg.provide('elgg.config.events.all'); +elgg.provide('elgg.config.events.all.all'); + +elgg.register_event_handler = function(event, type, callback, priority) { + elgg.assertTypeOf('string', event); + elgg.assertTypeOf('string', event); + elgg.assertTypeOf('function', callback); + + if (!event || !type) { + return false; + } + + elgg.provide('elgg.config.events.' + event + '.' + type); + + var events = elgg.config.events; + + if (!(events[event][type] instanceof elgg.ElggPriorityList)) { + events[event][type] = new elgg.ElggPriorityList(); + } + + return events[event][type].insert(callback, priority); +}; + +elgg.trigger_event = function(event, type, object) { + elgg.assertTypeOf('string', event); + elgg.assertTypeOf('string', event); + + elgg.provide('elgg.config.events.' + event + '.' + type); + elgg.provide('elgg.config.events.all.' + type); + elgg.provide('elgg.config.events.' + event + '.all'); + elgg.provide('elgg.config.events.all.all'); + + var events = elgg.config.events; + + var callEventHandler = function(handler) { + return handler(event, type, object) !== false; + }; + + if (events[event][type] instanceof elgg.ElggPriorityList) { + if (!events[event][type].every(callEventHandler)) { + return false; + } + } + + if (events['all'][type] instanceof elgg.ElggPriorityList) { + if (!events['all'][type].every(callEventHandler)) { + return false; + } + } + + if (events[event]['all'] instanceof elgg.ElggPriorityList) { + if (!events[event]['all'].every(callEventHandler)) { + return false; + } + } + + if (events['all']['all'] instanceof elgg.ElggPriorityList) { + if (!events['all']['all'].every(callEventHandler)) { + return false; + } + } + + return true; +}; \ No newline at end of file diff --git a/js/lib/languages.js b/js/lib/languages.js new file mode 100644 index 000000000..3231cf77d --- /dev/null +++ b/js/lib/languages.js @@ -0,0 +1,83 @@ +/** + * Provides language-related functionality + */ +elgg.provide('elgg.config.translations'); + +elgg.config.language = 'en'; + +elgg.add_translation = function(lang, translations) { + elgg.provide('elgg.config.translations.' + lang); + + $.extend(elgg.config.translations[lang], translations); +} + +/** + * Load the translations for the given language. + * + * If no language is specified, the default language is used. + * @param {string} language + * @return {XMLHttpRequest} + */ +elgg.reload_all_translations = function(language) { + var lang = language || elgg.get_language(); + elgg.getJSON('_css/js.php', { + data: { + 'js': 'languages/'+lang, + 'viewtype': 'default', + 'lastcache': elgg.config.lastcache + }, + success: function(json) { + elgg.add_translation(lang, json); + } + }); +}; + +/** + * Get the current language + * @return {String} + */ +elgg.get_language = function() { + var user = elgg.get_loggedin_user(); + + if (user && user.language) { + return user.language; + } + + return elgg.config.language; +}; + +/** + * Translates a string + * + * @param {String} key The string to translate + * @param {String} language The language to display it in + * @return {String} The translation + */ +elgg.echo = function(key, language) { + var translations, + dlang = elgg.get_language(); + + language = language || dlang; + + translations = elgg.config.translations[language]; + if (translations && translations[key]) { + return translations[key]; + } + + if (language == dlang) { + return undefined; + } + + translations = elgg.config.translations[dlang]; + if (translations && translations[key]) { + return translations[key]; + } + + return undefined; +}; + +elgg.config.translations.init = function() { + elgg.reload_all_translations(); +}; + +elgg.register_event_handler('boot', 'system', elgg.config.translations.init); \ No newline at end of file diff --git a/js/lib/security.js b/js/lib/security.js new file mode 100644 index 000000000..bdd762560 --- /dev/null +++ b/js/lib/security.js @@ -0,0 +1,70 @@ +/** + * Hold security-related data here + */ +elgg.provide('elgg.security'); + +elgg.security.token = {}; + +elgg.security.setToken = function(json) { + //update the convenience object + elgg.security.token = json; + + //also update all forms + $('[name=__elgg_ts]').val(json.__elgg_ts); + $('[name=__elgg_token]').val(json.__elgg_token); + + //also update all links + $('[href]').each(function() { + this.href = this.href + .replace(/__elgg_ts=\d*/, '__elgg_ts=' + json.__elgg_ts) + .replace(/__elgg_token=[0-9a-f]*/, '__elgg_token=' + json.__elgg_token); + }); +}; + +/** + * Security tokens time out, so lets refresh those every so often + * @todo handle error and bad return data + */ +elgg.security.refreshToken = function() { + elgg.action('ajax/securitytoken', function(data) { + elgg.security.setToken(data.output); + }); +}; + + +/** + * Add elgg action tokens to an object or string (assumed to be url data) + * + * @param {Object|string} data + * @return {Object} The new data object including action tokens + * @private + */ +elgg.security.addToken = function(data) { + + //addToken('data=sofar') + if (typeof data == 'string') { + var args = []; + if(data) { + args.push(data); + } + args.push("__elgg_ts=" + elgg.security.token.__elgg_ts); + args.push("__elgg_token=" + elgg.security.token.__elgg_token) + + return args.join('&'); + } + + //addToken({...}) + if (typeof data == 'object' || typeof data == 'undefined') { + return $.extend(data, elgg.security.token); + } + + //addToken(???) + throw new TypeError("elgg.security.addToken not implemented for " + (typeof data) + "s"); +}; + +elgg.security.init = function() { + //refresh security token every 5 minutes + setInterval(elgg.security.refreshToken, elgg.security.interval); +}; + +elgg.register_event_handler('boot', 'system', elgg.security.init); \ No newline at end of file diff --git a/js/lib/session.js b/js/lib/session.js new file mode 100644 index 000000000..227c607eb --- /dev/null +++ b/js/lib/session.js @@ -0,0 +1,116 @@ +/** + * @todo comment + */ +elgg.provide('elgg.session'); + +/** + * Helper function for setting cookies + * @param {string} name + * @param {string} value + * @param {Object} options + * {number|Date} options[expires] + * {string} options[path] + * {string} options[domain] + * {boolean} options[secure] + * + * @return {string} The value of the cookie, if only name is specified + */ +elgg.session.cookie = function(name, value, options) { + //elgg.session.cookie() + if(typeof name == 'undefined') { + return document.cookie; + } + + //elgg.session.cookie(name) + if (typeof value == 'undefined') { + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]).split('='); + if (cookie[0] == name) { + return decodeURIComponent(cookie[1]); + } + } + } + return undefined; + } + + // elgg.session.cookie(name, value[, opts]) + var cookies = []; + + options = options || {}; + + if (value === null) { + value = ''; + options.expires = -1; + } + + cookies.push(name + '=' + value); + + if (typeof options.expires == 'number') { + var date, valid = true; + + if (typeof options.expires == 'number') { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else if(options.expires.toUTCString) { + date = options.expires; + } else { + valid = false; + } + + valid ? cookies.push('expires=' + date.toUTCString()) : 0; + } + + // CAUTION: Needed to parenthesize options.path and options.domain + // in the following expressions, otherwise they evaluate to undefined + // in the packed version for some reason. + if (options.path) { + cookies.push('path=' + (options.path)); + } + + if (options.domain) { + cookies.push('domain=' + (options.domain)); + } + + if (options.secure) { + cookies.push('secure'); + } + + document.cookie = cookies.join('; '); +}; + +/** + * @return {ElggUser} The logged in user + */ +elgg.get_loggedin_user = function() { + return elgg.session.user; +}; + +/** + * @return {number} The GUID of the logged in user + */ +elgg.get_loggedin_userid = function() { + var user = elgg.get_loggedin_user(); + return user ? user.guid : 0; +}; + +/** + * @return {boolean} Whether there is a user logged in + */ +elgg.isloggedin = function() { + return (elgg.get_loggedin_user() instanceof elgg.ElggUser); +}; + +/** + * @return {boolean} Whether there is an admin logged in + */ +elgg.isadminloggedin = function() { + var user = elgg.get_loggedin_user(); + return (user instanceof ElggUser) && user.isAdmin(); +}; + +/** + * @deprecated Use elgg.session.cookie instead + */ +$.cookie = elgg.session.cookie; \ No newline at end of file diff --git a/js/lib/ui.js b/js/lib/ui.js new file mode 100644 index 000000000..4a9c64e70 --- /dev/null +++ b/js/lib/ui.js @@ -0,0 +1,121 @@ +elgg.provide('elgg.ui'); + +elgg.ui.init = function () { + //if the user clicks a system message, make it disappear + $('.elgg_system_message').live('click', function() { + $(this).stop().fadeOut('fast'); + }); + + $('a.collapsibleboxlink').click(elgg.ui.toggleCollapsibleBox); + + // set-up hover class for dragged widgets + var cols = [ + "#rightcolumn_widgets", + "#middlecolumn_widgets", + "#leftcolumn_widgets" + ].join(','); + + $(cols).droppable({ + accept: ".draggable_widget", + hoverClass: 'droppable-hover' + }); +}; + +// reusable generic hidden panel +elgg.ui.toggleCollapsibleBox = function () { + $(this.parentNode.parentNode).children(".collapsible_box").slideToggle("fast"); + return false; +}; + +//define some helper jquery plugins +(function($) { + + // ELGG TOOLBAR MENU + $.fn.elgg_topbardropdownmenu = function(options) { + var defaults = { + speed: 350 + }; + + options = $.extend(defaults, options || {}); + + this.each(function() { + + var root = this, zIndex = 5000; + + function getSubnav(ele) { + if (ele.nodeName.toLowerCase() == 'li') { + var subnav = $('> ul', ele); + return subnav.length ? subnav[0] : null; + } else { + return ele; + } + } + + function getActuator(ele) { + if (ele.nodeName.toLowerCase() == 'ul') { + return $(ele).parents('li')[0]; + } else { + return ele; + } + } + + function hide() { + var subnav = getSubnav(this); + if (!subnav) { + return; + } + + $.data(subnav, 'cancelHide', false); + setTimeout(function() { + if (!$.data(subnav, 'cancelHide')) { + $(subnav).slideUp(100); + } + }, 250); + } + + function show() { + var subnav = getSubnav(this); + if (!subnav) { + return; + } + + $.data(subnav, 'cancelHide', true); + + $(subnav).css({zIndex: zIndex++}).slideDown(options.speed); + + if (this.nodeName.toLowerCase() == 'ul') { + var li = getActuator(this); + $(li).addClass('hover'); + $('> a', li).addClass('hover'); + } + } + + $('ul, li', this).hover(show, hide); + $('li', this).hover( + function() { $(this).addClass('hover'); $('> a', this).addClass('hover'); }, + function() { $(this).removeClass('hover'); $('> a', this).removeClass('hover'); } + ); + + }); + }; + + //Make delimited list + $.fn.makeDelimitedList = function(elementAttribute) { + + var delimitedListArray = []; + var listDelimiter = "::"; + + // Loop over each element in the stack and add the elementAttribute to the array + this.each(function(e) { + var listElement = $(this); + // Add the attribute value to our values array + delimitedListArray[delimitedListArray.length] = listElement.attr(elementAttribute); + } + ); + + // Return value list by joining the array + return(delimitedListArray.join(listDelimiter)); + }; +})(jQuery); + +elgg.register_event_handler('init', 'system', elgg.ui.init); \ No newline at end of file diff --git a/js/lib/ui.widgets.js b/js/lib/ui.widgets.js new file mode 100644 index 000000000..1e3163709 --- /dev/null +++ b/js/lib/ui.widgets.js @@ -0,0 +1,131 @@ +elgg.provide('elgg.ui.widgets'); + +elgg.ui.widgets.init = function() { + // COLLAPSABLE WIDGETS (on Dashboard & Profile pages) + $('a.toggle_box_contents').live('click', elgg.ui.widgets.toggleContent); + $('a.toggle_box_edit_panel').live('click', elgg.ui.widgets.toggleEditPanel); + $('a.toggle_customise_edit_panel').live('click', elgg.ui.widgets.toggleCustomizeEditPanel); + + // WIDGET GALLERY EDIT PANEL + // Sortable widgets + var els = [ + '#leftcolumn_widgets', + '#middlecolumn_widgets', + '#rightcolumn_widgets', + '#widget_picker_gallery' + ].join(','); + + $(els).sortable({ + items: '.draggable_widget', + handle: '.drag_handle', + forcePlaceholderSize: true, + placeholder: 'ui-state-highlight', + cursor: 'move', + opacity: 0.9, + appendTo: 'body', + connectWith: els, + stop: function(e,ui) { + // refresh list before updating hidden fields with new widget order + $(this).sortable("refresh"); + + var widgetNamesLeft = outputWidgetList('#leftcolumn_widgets'); + var widgetNamesMiddle = outputWidgetList('#middlecolumn_widgets'); + var widgetNamesRight = outputWidgetList('#rightcolumn_widgets'); + + $('#debugField1').val(widgetNamesLeft); + $('#debugField2').val(widgetNamesMiddle); + $('#debugField3').val(widgetNamesRight); + } + }); + + // bind more info buttons - called when new widgets are created + elgg.ui.widgets.moreinfo(); +}; + +//List active widgets for each page column +elgg.ui.widgets.outputList = function(forElement) { + return( $("input[name='handler'], input[name='guid']", forElement ).makeDelimitedList("value") ); +}; + +//Read each widgets collapsed/expanded state from cookie and apply +elgg.ui.widgets.state = function(forWidget) { + + var thisWidgetState = elgg.session.cookie(forWidget); + + if (thisWidgetState == 'collapsed') { + forWidget = "#" + forWidget; + $(forWidget).find("div.collapsable_box_content").hide(); + $(forWidget).find("a.toggle_box_contents").html('+'); + $(forWidget).find("a.toggle_box_edit_panel").fadeOut('medium'); + } +}; + +//More info tooltip in widget gallery edit panel +elgg.ui.widgets.moreinfo = function() { + $("img.more_info").hover(function(e) { + var widgetdescription = $("input[name='description']", this.parentNode.parentNode.parentNode).val(); + $("body").append("

"+ widgetdescription +"

"); + + if (e.pageX < 900) { + $("#widget_moreinfo") + .css("top",(e.pageY + 10) + "px") + .css("left",(e.pageX + 10) + "px") + .fadeIn("medium"); + } else { + $("#widget_moreinfo") + .css("top",(e.pageY + 10) + "px") + .css("left",(e.pageX - 210) + "px") + .fadeIn("medium"); + } + }, function() { + $("#widget_moreinfo").remove(); + }); +}; + +//Toggle widgets contents and save to a cookie +elgg.ui.widgets.toggleContent = function(e) { + var targetContent = $('div.collapsable_box_content', this.parentNode.parentNode); + if (targetContent.css('display') == 'none') { + targetContent.slideDown(400); + $(this).html('-'); + $(this.parentNode).children(".toggle_box_edit_panel").fadeIn('medium'); + + // set cookie for widget panel open-state + var thisWidgetName = $(this.parentNode.parentNode.parentNode).attr('id'); + $.cookie(thisWidgetName, 'expanded', { expires: 365 }); + + } else { + targetContent.slideUp(400); + $(this).html('+'); + $(this.parentNode).children(".toggle_box_edit_panel").fadeOut('medium'); + // make sure edit pane is closed + $(this.parentNode.parentNode).children(".collapsable_box_editpanel").hide(); + + // set cookie for widget panel closed-state + var thisWidgetName = $(this.parentNode.parentNode.parentNode).attr('id'); + $.cookie(thisWidgetName, 'collapsed', { expires: 365 }); + } + return false; +}; + +// toggle widget box edit panel +elgg.ui.widgets.toggleEditPanel = function () { + $(this.parentNode.parentNode).children(".collapsable_box_editpanel").slideToggle("fast"); + return false; +}; + +// toggle customise edit panel +elgg.ui.widgets.toggleCustomizeEditPanel = function () { + $('#customise_editpanel').slideToggle("fast"); + return false; +}; + +/** + * @deprecated Use elgg.ui.widgets.* + */ +var toggleContent = elgg.ui.widgets.toggleContent, + widget_moreinfo = elgg.ui.widgets.moreinfo, + widget_state = elgg.ui.widgets.state, + outputWidgetList = elgg.ui.widgets.outputList; + +elgg.register_event_handler('init', 'system', elgg.ui.widgets.init); \ No newline at end of file diff --git a/js/tests/ElggAjaxOptionsTest.js b/js/tests/ElggAjaxOptionsTest.js new file mode 100644 index 000000000..8a2b7f574 --- /dev/null +++ b/js/tests/ElggAjaxOptionsTest.js @@ -0,0 +1,62 @@ +/** + * Tests elgg.ajax.handleOptions() with all of the possible valid inputs + */ +ElggAjaxOptionsTest = TestCase("ElggAjaxOptionsTest"); + +ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsNoArgs = function() { + assertNotUndefined(elgg.ajax.handleOptions()); +}; + +ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsUrl = function() { + var url = 'url', + result = elgg.ajax.handleOptions(url); + + assertEquals(url, result.url); +}; + +ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsDataOnly = function() { + var options = {}, + result = elgg.ajax.handleOptions(options); + + assertEquals(options, result.data); +}; + +ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsOptions = function() { + var options = {data:{arg:1}}, + result = elgg.ajax.handleOptions(options); + + assertEquals(options, result); + + function func() {} + options = {success: func}; + result = elgg.ajax.handleOptions(options); + + assertEquals(options, result); +}; + +ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsUrlThenDataOnly = function() { + var url = 'url', + options = {arg:1}, + result = elgg.ajax.handleOptions(url, options); + + assertEquals(url, result.url); + assertEquals(options, result.data); +}; + +ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsUrlThenSuccessOnly = function() { + var url = 'url', + success = function() {}, + result = elgg.ajax.handleOptions(url, success); + + assertEquals(url, result.url); + assertEquals(success, result.success); +}; + +ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsUrlThenOptions = function() { + var url = 'url', + options = {data:{arg:1}}, + result = elgg.ajax.handleOptions(url, options); + + assertEquals(url, result.url); + assertEquals(options.data, result.data); +}; \ No newline at end of file diff --git a/js/tests/ElggAjaxTest.js b/js/tests/ElggAjaxTest.js new file mode 100644 index 000000000..1fa5daca5 --- /dev/null +++ b/js/tests/ElggAjaxTest.js @@ -0,0 +1,59 @@ +/** + * Makes sure that each of the helper ajax functions ends up calling $.ajax + * with the right options. + */ +ElggAjaxTest = TestCase("ElggAjaxTest"); + +ElggAjaxTest.prototype.setUp = function() { + + this.wwwroot = elgg.config.wwwroot; + this.ajax = $.ajax; + + elgg.config.wwwroot = 'http://www.elgg.org/'; + + $.ajax = function(options) { + return options; + }; +}; + +ElggAjaxTest.prototype.tearDown = function() { + $.ajax = this.ajax; + elgg.config.wwwroot = this.wwwroot; +}; + +ElggAjaxTest.prototype.testElggAjax = function() { + assertEquals(elgg.config.wwwroot, elgg.ajax().url); +}; + +ElggAjaxTest.prototype.testElggGet = function() { + assertEquals('get', elgg.get().type); +}; + +ElggAjaxTest.prototype.testElggGetJSON = function() { + assertEquals('json', elgg.getJSON().dataType); +}; + +ElggAjaxTest.prototype.testElggPost = function() { + assertEquals('post', elgg.post().type); +}; + +ElggAjaxTest.prototype.testElggAction = function() { + assertException(function() { elgg.action(); }); + assertException(function() { elgg.action({}); }); + + var result = elgg.action('action'); + assertEquals('post', result.type); + assertEquals('json', result.dataType); + assertEquals(elgg.config.wwwroot + 'action/action', result.url); + assertEquals(elgg.security.token.__elgg_ts, result.data.__elgg_ts); +}; + +ElggAjaxTest.prototype.testElggAPI = function() { + assertException(function() { elgg.api(); }); + assertException(function() { elgg.api({}); }); + + var result = elgg.api('method'); + assertEquals('json', result.dataType); + assertEquals('method', result.data.method); + assertEquals(elgg.config.wwwroot + 'services/api/rest/json/', result.url); +}; diff --git a/js/tests/ElggEventsTest.js b/js/tests/ElggEventsTest.js new file mode 100644 index 000000000..cc30e8418 --- /dev/null +++ b/js/tests/ElggEventsTest.js @@ -0,0 +1,28 @@ +ElggEventsTest = TestCase("ElggEventsTest"); + +ElggEventsTest.prototype.setUp = function() { + elgg.config.events = {}; + elgg.provide('elgg.config.events.all.all'); +}; + +ElggEventsTest.prototype.testEventHandlersMustBeFunctions = function() { + assertException(function() { elgg.register_event_handler('str', 'str', 'oops'); }); +}; + +ElggEventsTest.prototype.testReturnValueDefaultsToTrue = function() { + assertTrue(elgg.trigger_event('fee', 'fum')); + + elgg.register_event_handler('fee', 'fum', function() {}); + assertTrue(elgg.trigger_event('fee', 'fum')); +}; + +ElggEventsTest.prototype.testCanGlomEventsWithAll = function() { + elgg.register_event_handler('all', 'bar', function() { throw new Error(); }); + assertException("all,bar", function() { elgg.trigger_event('foo', 'bar'); }); + + elgg.register_event_handler('foo', 'all', function() { throw new Error(); }); + assertException("foo,all", function() { elgg.trigger_event('foo', 'baz'); }); + + elgg.register_event_handler('all', 'all', function() { throw new Error(); }); + assertException("all,all", function() { elgg.trigger_event('pinky', 'winky'); }); +}; \ No newline at end of file diff --git a/js/tests/ElggLanguagesTest.js b/js/tests/ElggLanguagesTest.js new file mode 100644 index 000000000..950d5d3b8 --- /dev/null +++ b/js/tests/ElggLanguagesTest.js @@ -0,0 +1,45 @@ +ElggLanguagesTest = TestCase("ElggLanguagesTest"); + +ElggLanguagesTest.prototype.setUp = function() { + this.ajax = $.ajax; + + //Immediately execute some dummy "returned" javascript instead of sending + //an actual ajax request + $.ajax = function(settings) { + var lang = settings.data.js.split('/')[1]; + elgg.config.translations[lang] = {'language':lang}; + }; +}; + +ElggLanguagesTest.prototype.tearDown = function() { + $.ajax = this.ajax; + + //clear translations + elgg.config.translations['en'] = undefined; + elgg.config.translations['aa'] = undefined; +}; + +ElggLanguagesTest.prototype.testLoadTranslations = function() { + assertUndefined(elgg.config.translations['en']); + assertUndefined(elgg.config.translations['aa']); + + elgg.reload_all_translations(); + elgg.reload_all_translations('aa'); + + assertNotUndefined(elgg.config.translations['en']['language']); + assertNotUndefined(elgg.config.translations['aa']['language']); +}; + +ElggLanguagesTest.prototype.testElggEchoTranslates = function() { + elgg.reload_all_translations('en'); + elgg.reload_all_translations('aa'); + + assertEquals('en', elgg.echo('language')); + assertEquals('aa', elgg.echo('language', 'aa')); +}; + +ElggLanguagesTest.prototype.testElggEchoFallsBackToDefaultLanguage = function() { + elgg.reload_all_translations('en'); + assertEquals('en', elgg.echo('language', 'aa')); +}; + diff --git a/js/tests/ElggLibTest.js b/js/tests/ElggLibTest.js new file mode 100644 index 000000000..035b60325 --- /dev/null +++ b/js/tests/ElggLibTest.js @@ -0,0 +1,88 @@ +/** + * Test basic elgg library functions + */ +ElggLibTest = TestCase("ElggLibTest"); + +ElggLibTest.prototype.testGlobal = function() { + assertTrue(window === elgg.global); +}; + +ElggLibTest.prototype.testAssertTypeOf = function() { + var noexceptions = [ + ['string', ''], + ['object', {}], + ['boolean', true], + ['boolean', false], + ['undefined', undefined], + ['number', 0], + ['function', function() {}], + ]; + + for (var i in noexceptions) { + assertNoException(function() { + elgg.assertTypeOf.apply(elgg, noexceptions[i]); + }); + } + + var exceptions = [ + ['function', {}], + ['object', function() {}], + ]; + + for (var i in exceptions) { + assertException(function() { + elgg.assertTypeOf.apply(elgg, exceptions[i]); + }); + } +}; + +ElggLibTest.prototype.testProvide = function() { + elgg.provide('foo.bar.baz'); + + assertNotUndefined(foo); + assertNotUndefined(foo.bar); + assertNotUndefined(foo.bar.baz); + + var str = foo.bar.baz.oof = "don't overwrite me"; + + elgg.provide('foo.bar.baz'); + + assertEquals(str, foo.bar.baz.oof); +}; + +ElggLibTest.prototype.testRequire = function() { + /* Try requiring bogus input */ + assertException(function(){ elgg.require(''); }); + assertException(function(){ elgg.require('garbage'); }); + assertException(function(){ elgg.require('gar.ba.ge'); }); + + assertNoException(function(){ elgg.require('jQuery'); }); + assertNoException(function(){ elgg.require('elgg'); }); + assertNoException(function(){ elgg.require('elgg.config'); }); + assertNoException(function(){ elgg.require('elgg.security'); }); +}; + +ElggLibTest.prototype.testInherit = function() { + function Base() {} + function Child() {} + + elgg.inherit(Child, Base); + + assertInstanceOf(Base, new Child()); + assertEquals(Child, Child.prototype.constructor); +}; + +ElggLibTest.prototype.testExtendUrl = function() { + elgg.config.wwwroot = "http://elgg.org/"; + + var inputs = [ + [elgg.config.wwwroot, ''], + [elgg.config.wwwroot + 'pg/test', 'pg/test'], + ['http://google.com', 'http://google.com'], + ['//example.com', '//example.com'], + ]; + + for (var i in inputs) { + assertEquals(inputs[i][0], elgg.normalize_url(inputs[i][1])); + } +}; \ No newline at end of file diff --git a/js/tests/ElggPriorityListTest.js b/js/tests/ElggPriorityListTest.js new file mode 100644 index 000000000..2549e0ee0 --- /dev/null +++ b/js/tests/ElggPriorityListTest.js @@ -0,0 +1,47 @@ +ElggPriorityListTest = TestCase("ElggPriorityListTest"); + +ElggPriorityListTest.prototype.setUp = function() { + this.list = new elgg.ElggPriorityList(); +}; + +ElggPriorityListTest.prototype.tearDown = function() { + this.list = null; +}; + +ElggPriorityListTest.prototype.testInsert = function() { + this.list.insert('foo'); + + assertEquals('foo', this.list.priorities_[500][0]); + + this.list.insert('bar', 501); + + assertEquals('foo', this.list.priorities_[501][0]); +}; + +ElggPriorityListTest.prototype.testInsertRespectsPriority = function() { + var values = [5, 4, 3, 2, 1, 0]; + + for (var i in values) { + this.list.insert(values[i], values[i]); + } + + this.list.forEach(function(elem, idx)) { + assertEquals(elem, idx); + } +}; + +ElggPriorityListTest.prototype.testInsertHandlesDuplicatePriorities = function() { + values = [0, 1, 2, 3, 4, 5, 6, 7, 8 , 9]; + + for (var i in values) { + this.list.insert(values[i], values[i]/3); + } + + this.list.forEach(function(elem, idx) { + assertEquals(elem, idx); + }); +}; + +ElggPriorityListTest.prototype.testEveryDefaultsToTrue = function() { + assertTrue(this.list.every(function() {})); +}; \ No newline at end of file diff --git a/js/tests/ElggSecurityTest.js b/js/tests/ElggSecurityTest.js new file mode 100644 index 000000000..4324f5671 --- /dev/null +++ b/js/tests/ElggSecurityTest.js @@ -0,0 +1,51 @@ +ElggSecurityTest = TestCase("ElggSecurityTest"); + +ElggSecurityTest.prototype.setUp = function() { + //fill with fake, but reasonable, values for testing + this.ts = elgg.security.token.__elgg_ts = 12345; + this.token = elgg.security.token.__elgg_token = 'abcdef'; +}; + +ElggSecurityTest.prototype.testAddTokenAcceptsUndefined = function() { + var input, + expected = { + __elgg_ts: this.ts, + __elgg_token: this.token + }; + + assertEquals(expected, elgg.security.addToken(input)); +}; + +ElggSecurityTest.prototype.testAddTokenAcceptsObject = function() { + var input = {}, + expected = { + __elgg_ts: this.ts, + __elgg_token: this.token + }; + + assertEquals(expected, elgg.security.addToken(input)); +}; + +ElggSecurityTest.prototype.testAddTokenAcceptsString = function() { + var input, + str = "__elgg_ts=" + this.ts + "&__elgg_token=" + this.token; + + input = ""; + assertEquals(str, elgg.security.addToken(input)); + + input = "data=sofar"; + assertEquals(input+'&'+str, elgg.security.addToken(input)); + +}; + +ElggSecurityTest.prototype.testSetTokenSetsElggSecurityToken = function() { + var json = { + __elgg_ts: 4567, + __elgg_token: 'abcdef' + }; + + elgg.security.setToken(json); + assertEquals(json, elgg.security.token); +}; + + diff --git a/js/tests/ElggSessionTest.js b/js/tests/ElggSessionTest.js new file mode 100644 index 000000000..0245e9e90 --- /dev/null +++ b/js/tests/ElggSessionTest.js @@ -0,0 +1,36 @@ +ElggSessionTest = TestCase("ElggSessionTest"); + +ElggSessionTest.prototype.testGetCookie = function() { + assertEquals(document.cookie, elgg.session.cookie()); +}; + +ElggSessionTest.prototype.testGetCookieKey = function() { + document.cookie = "name=value"; + assertEquals('value', elgg.session.cookie('name')); + + document.cookie = "name=value2"; + assertEquals('value2', elgg.session.cookie('name')); + + document.cookie = "name=value"; + document.cookie = "name2=value2"; + assertEquals('value', elgg.session.cookie('name')); + assertEquals('value2', elgg.session.cookie('name2')); +}; + +ElggSessionTest.prototype.testSetCookieKey = function() { + elgg.session.cookie('name', 'value'); + assertEquals('value', elgg.session.cookie('name')); + + elgg.session.cookie('name', 'value2'); + assertEquals('value2', elgg.session.cookie('name')); + + elgg.session.cookie('name', 'value'); + elgg.session.cookie('name2', 'value2'); + assertEquals('value', elgg.session.cookie('name')); + assertEquals('value2', elgg.session.cookie('name2')); + + elgg.session.cookie('name', null); + elgg.session.cookie('name2', null); + assertUndefined(elgg.session.cookie('name')); + assertUndefined(elgg.session.cookie('name2')); +}; \ No newline at end of file -- cgit v1.2.3