diff options
Diffstat (limited to 'js')
| -rw-r--r-- | js/classes/ElggPriorityList.js | 7 | ||||
| -rw-r--r-- | js/classes/ElggUser.js | 16 | ||||
| -rw-r--r-- | js/lib/ajax.js | 6 | ||||
| -rw-r--r-- | js/lib/autocomplete.js | 39 | ||||
| -rw-r--r-- | js/lib/configuration.js | 2 | ||||
| -rw-r--r-- | js/lib/elgglib.js | 197 | ||||
| -rw-r--r-- | js/lib/hooks.js | 99 | ||||
| -rw-r--r-- | js/lib/languages.js | 23 | ||||
| -rw-r--r-- | js/lib/pageowner.js | 8 | ||||
| -rw-r--r-- | js/lib/security.js | 68 | ||||
| -rw-r--r-- | js/lib/session.js | 32 | ||||
| -rw-r--r-- | js/lib/ui.autocomplete.js | 14 | ||||
| -rw-r--r-- | js/lib/ui.avatar_cropper.js | 76 | ||||
| -rw-r--r-- | js/lib/ui.friends_picker.js (renamed from js/lib/friends_picker.js) | 0 | ||||
| -rw-r--r-- | js/lib/ui.js | 102 | ||||
| -rw-r--r-- | js/lib/ui.river.js | 14 | ||||
| -rw-r--r-- | js/lib/ui.userpicker.js | 117 | ||||
| -rw-r--r-- | js/lib/ui.widgets.js | 55 | ||||
| -rw-r--r-- | js/lib/userpicker.js | 84 | ||||
| -rw-r--r-- | js/tests/ElggLanguagesTest.js | 2 | ||||
| -rw-r--r-- | js/tests/ElggLibTest.js | 70 | ||||
| -rw-r--r-- | js/tests/ElggPriorityListTest.js | 6 | ||||
| -rw-r--r-- | js/tests/ElggSecurityTest.js | 44 | ||||
| -rw-r--r-- | js/tests/README | 25 | ||||
| -rw-r--r-- | js/tests/jsTestDriver.conf | 5 |
25 files changed, 821 insertions, 290 deletions
diff --git a/js/classes/ElggPriorityList.js b/js/classes/ElggPriorityList.js index 831342f21..b4cec5044 100644 --- a/js/classes/ElggPriorityList.js +++ b/js/classes/ElggPriorityList.js @@ -16,7 +16,10 @@ elgg.ElggPriorityList = function() { * @return {Void} */ elgg.ElggPriorityList.prototype.insert = function(obj, opt_priority) { - var priority = parseInt(opt_priority || 500, 10); + var priority = 500; + if (arguments.length == 2 && opt_priority != undefined) { + priority = parseInt(opt_priority, 10); + } priority = Math.max(priority, 0); @@ -31,7 +34,7 @@ elgg.ElggPriorityList.prototype.insert = function(obj, opt_priority) { /** * Iterates through each element in order. * -* Unlike every, this ignores the return value of the callback. + * Unlike every, this ignores the return value of the callback. * * @param {Function} callback The callback function to pass each element through. See * Array.prototype.every() for details. diff --git a/js/classes/ElggUser.js b/js/classes/ElggUser.js index 8a7a8b7eb..b8a976fba 100644 --- a/js/classes/ElggUser.js +++ b/js/classes/ElggUser.js @@ -6,9 +6,23 @@ * @class Represents an ElggUser * @property {string} name * @property {string} username + * @property {string} language + * @property {boolean} admin */ elgg.ElggUser = function(o) { elgg.ElggEntity.call(this, o); }; -elgg.inherit(elgg.ElggUser, elgg.ElggEntity);
\ No newline at end of file +elgg.inherit(elgg.ElggUser, elgg.ElggEntity); + +/** + * Is this user an admin? + * + * @warning The admin state of the user should be checked on the server for any + * actions taken that require admin privileges. + * + * @return {boolean} + */ +elgg.ElggUser.prototype.isAdmin = function() { + return this.admin; +};
\ No newline at end of file diff --git a/js/lib/ajax.js b/js/lib/ajax.js index 6f6ae052f..b3f39cc42 100644 --- a/js/lib/ajax.js +++ b/js/lib/ajax.js @@ -187,7 +187,11 @@ elgg.action = function(action, options) { options = elgg.ajax.handleOptions(action, options); - options.data = elgg.security.addToken(options.data); + // This is a misuse of elgg.security.addToken() because it is not always a + // full query string with a ?. As such we need a special check for the tokens. + if (!elgg.isString(options.data) || options.data.indexOf('__elgg_ts') == -1) { + options.data = elgg.security.addToken(options.data); + } options.dataType = 'json'; //Always display system messages after actions diff --git a/js/lib/autocomplete.js b/js/lib/autocomplete.js deleted file mode 100644 index 917326d4f..000000000 --- a/js/lib/autocomplete.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * - */ -elgg.provide('elgg.autocomplete'); - -elgg.autocomplete.init = function() { - $('.elgg-input-autocomplete').autocomplete({ - source: elgg.autocomplete.url, //gets set by input/autocomplete - minLength: 1, - select: function(event, ui) { - var item = ui.item; - $(this).val(item.name); - - var hidden = $(this).next(); - hidden.val(item.guid); - } - }) - - //@todo This seems convoluted - .data("autocomplete")._renderItem = function(ul, item) { - switch (item.type) { - case 'user': - case 'group': - r = item.icon + item.name + ' - ' + item.desc; - break; - - default: - r = item.name + ' - ' + item.desc; - break; - } - - return $("<li/>") - .data("item.autocomplete", item) - .append(r) - .appendTo(ul); - }; -}; - -elgg.register_hook_handler('init', 'system', elgg.autocomplete.init);
\ No newline at end of file diff --git a/js/lib/configuration.js b/js/lib/configuration.js index f724a2f01..6e221c957 100644 --- a/js/lib/configuration.js +++ b/js/lib/configuration.js @@ -7,4 +7,4 @@ elgg.provide('elgg.config'); */ elgg.get_site_url = function() { return elgg.config.wwwroot; -}
\ No newline at end of file +};
\ No newline at end of file diff --git a/js/lib/elgglib.js b/js/lib/elgglib.js index f2545fb6c..a8e187f1d 100644 --- a/js/lib/elgglib.js +++ b/js/lib/elgglib.js @@ -224,8 +224,8 @@ elgg.provide = function(pkg, opt_context) { * child.foo('boo!'); // alert('boo!'); * </pre> * - * @param {Function} childCtor Child class. - * @param {Function} parentCtor Parent class. + * @param {Function} Child Child class constructor. + * @param {Function} Parent Parent class constructor. */ elgg.inherit = function(Child, Parent) { Child.prototype = new Parent(); @@ -250,13 +250,40 @@ 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)) { + 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) { + else if (url.indexOf('javascript:') === 0 || url.indexOf('mailto:') === 0 ) { return url; } @@ -320,8 +347,12 @@ elgg.system_messages = function(msgs, delay, type) { msgs.forEach(appendMessage); - $(messages_html.join('')).appendTo(systemMessages) - .animate({opacity: '1.0'}, delay).fadeOut('slow'); + if (type != 'error') { + $(messages_html.join('')).appendTo(systemMessages) + .animate({opacity: '1.0'}, delay).fadeOut('slow'); + } else { + $(messages_html.join('')).appendTo(systemMessages); + } }; /** @@ -353,6 +384,105 @@ elgg.forward = function(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: @@ -379,4 +509,55 @@ elgg.getSelectorFromUrlFragment = function(url) { } } return ''; -};
\ No newline at end of file +}; + +/** + * 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'); + } +}; diff --git a/js/lib/hooks.js b/js/lib/hooks.js index eeaffb846..5e1808e22 100644 --- a/js/lib/hooks.js +++ b/js/lib/hooks.js @@ -3,13 +3,18 @@ */ elgg.provide('elgg.config.hooks'); +elgg.provide('elgg.config.instant_hooks'); +elgg.provide('elgg.config.triggered_hooks'); /** - * Registers an hook handler with the event system. + * Registers a hook handler with the event system. * * The special keyword "all" can be used for either the name or the type or both * and means to call that handler for all of those hooks. * + * Note that handlers registering for instant hooks will be executed immediately if the instant + * hook has been previously triggered. + * * @param {String} name Name of the plugin hook to register for * @param {String} type Type of the event to register for * @param {Function} handler Handle to call @@ -33,6 +38,11 @@ elgg.register_hook_handler = function(name, type, handler, priority) { priorities[name][type] = new elgg.ElggPriorityList(); } + // call if instant and already triggered. + if (elgg.is_instant_hook(name, type) && elgg.is_triggered_hook(name, type)) { + handler(name, type, null, null); + } + return priorities[name][type].insert(handler, priority); }; @@ -43,7 +53,9 @@ elgg.register_hook_handler = function(name, type, handler, priority) { * Every handler function will always be called, regardless of the return value. * * @warning Handlers take the same 4 arguments in the same order as when calling this function. - * This is different to the PHP version! + * This is different from the PHP version! + * + * @note Instant hooks do not support params or values. * * Hooks are called in this order: * specifically registered (event_name and event_type match) @@ -62,6 +74,9 @@ elgg.trigger_hook = function(name, type, params, value) { elgg.assertTypeOf('string', name); elgg.assertTypeOf('string', type); + // mark as triggered + elgg.set_triggered_hook(name, type); + // default to true if unpassed value = value || true; @@ -77,16 +92,82 @@ elgg.trigger_hook = function(name, type, params, value) { elgg.provide(name + '.all', hooks); elgg.provide('all.all', hooks); - [ hooks[name][type], - hooks['all'][type], - hooks[name]['all'], - hooks['all']['all'] - ].every(function(handlers) { + var hooksList = []; + + if (name != 'all' && type != 'all') { + hooksList.push(hooks[name][type]); + } + + if (type != 'all') { + hooksList.push(hooks['all'][type]); + } + + if (name != 'all') { + hooksList.push(hooks[name]['all']); + } + + hooksList.push(hooks['all']['all']); + + hooksList.every(function(handlers) { if (handlers instanceof elgg.ElggPriorityList) { handlers.forEach(callHookHandler); } return true; }); - return (tempReturnValue !== null) ? tempReturnValue : returnValue; -};
\ No newline at end of file + return (tempReturnValue != null) ? tempReturnValue : returnValue; +}; + +/** + * Registers a hook as an instant hook. + * + * After being trigger once, registration of a handler to an instant hook will cause the + * handle to be executed immediately. + * + * @note Instant hooks must be triggered without params or defaults. Any params or default + * passed will *not* be passed to handlers executed upon registration. + * + * @param {String} name The hook name. + * @param {String} type The hook type. + * @return {Int} + */ +elgg.register_instant_hook = function(name, type) { + elgg.assertTypeOf('string', name); + elgg.assertTypeOf('string', type); + + return elgg.push_to_object_array(elgg.config.instant_hooks, name, type); +}; + +/** + * Is this hook registered as an instant hook? + * + * @param {String} name The hook name. + * @param {String} type The hook type. + */ +elgg.is_instant_hook = function(name, type) { + return elgg.is_in_object_array(elgg.config.instant_hooks, name, type); +}; + +/** + * Records that a hook has been triggered. + * + * @param {String} name The hook name. + * @param {String} type The hook type. + */ +elgg.set_triggered_hook = function(name, type) { + return elgg.push_to_object_array(elgg.config.triggered_hooks, name, type); +}; + +/** + * Has this hook been triggered yet? + * + * @param {String} name The hook name. + * @param {String} type The hook type. + */ +elgg.is_triggered_hook = function(name, type) { + return elgg.is_in_object_array(elgg.config.triggered_hooks, name, type); +}; + +elgg.register_instant_hook('init', 'system'); +elgg.register_instant_hook('ready', 'system'); +elgg.register_instant_hook('boot', 'system'); diff --git a/js/lib/languages.js b/js/lib/languages.js index 4cfe84968..d218cbc4f 100644 --- a/js/lib/languages.js +++ b/js/lib/languages.js @@ -4,6 +4,7 @@ */ elgg.provide('elgg.config.translations'); +// default language - required by unit tests elgg.config.language = 'en'; /** @@ -26,14 +27,20 @@ elgg.add_translation = function(lang, translations) { elgg.reload_all_translations = function(language) { var lang = language || elgg.get_language(); - elgg.getJSON('ajax/view/js/languages', { - data: { - language: lang - }, - success: function(json) { - elgg.add_translation(lang, json); - } - }); + var url, options; + url = 'ajax/view/js/languages'; + options = {data: {language: lang}}; + if (elgg.config.simplecache_enabled) { + options.data.lc = elgg.config.lastcache; + } + + options['success'] = function(json) { + elgg.add_translation(lang, json); + elgg.config.languageReady = true; + elgg.initWhenReady(); + }; + + elgg.getJSON(url, options); }; /** diff --git a/js/lib/pageowner.js b/js/lib/pageowner.js index 825898416..c695c41c3 100644 --- a/js/lib/pageowner.js +++ b/js/lib/pageowner.js @@ -6,9 +6,13 @@ */ /** - * @return {number} The GUID of the logged in user + * @return {number} The GUID of the page owner entity or 0 for no owner */ elgg.get_page_owner_guid = function() { - return elgg.page_owner.guid || 0; + if (elgg.page_owner !== undefined) { + return elgg.page_owner.guid; + } else { + return 0; + } }; diff --git a/js/lib/security.js b/js/lib/security.js index fa0a6f7ef..9c12f8586 100644 --- a/js/lib/security.js +++ b/js/lib/security.js @@ -7,6 +7,8 @@ elgg.security.token = {}; elgg.security.tokenRefreshFailed = false; +elgg.security.tokenRefreshTimer = null; + /** * Sets the currently active security token and updates all forms and links on the current page. * @@ -21,8 +23,8 @@ elgg.security.setToken = function(json) { $('[name=__elgg_ts]').val(json.__elgg_ts); $('[name=__elgg_token]').val(json.__elgg_token); - //also update all links - $('[href]').each(function() { + // also update all links that contain tokens and time stamps + $('[href*="__elgg_ts"][href*="__elgg_token"]').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); @@ -30,37 +32,23 @@ elgg.security.setToken = function(json) { }; /** - * Security tokens time out, so lets refresh those every so often. + * Security tokens time out so we refresh those every so often. * - * @todo handle error and bad return data + * @private */ elgg.security.refreshToken = function() { elgg.action('security/refreshtoken', function(data) { - - // @todo might want to move this to setToken() once http://trac.elgg.org/ticket/3127 - // is implemented. It's here right now to avoid soggy code. - if (!data || !(data.output.__elgg_ts && data.output.__elgg_token)) { - elgg.register_error(elgg.echo('js:security:token_refresh_failed', [elgg.get_site_url()])); - elgg.security.tokenRefreshFailed = true; - - // don't setToken because we refresh every 5 minutes and tokens are good for 1 - // hour by default - return; + if (data && data.output.__elgg_ts && data.output.__elgg_token) { + elgg.security.setToken(data.output); + } else { + clearInterval(elgg.security.tokenRefreshTimer); } - - // if had problems last time, let them know it's working now - if (elgg.security.tokenRefreshFailed) { - elgg.system_message(elgg.echo('js:security:token_refreshed', [elgg.get_site_url()])); - elgg.security.tokenRefreshFailed = false; - } - - elgg.security.setToken(data.output); }); }; /** - * Add elgg action tokens to an object or string (assumed to be url data) + * Add elgg action tokens to an object, URL, or query string (with a ?). * * @param {Object|string} data * @return {Object} The new data object including action tokens @@ -70,14 +58,31 @@ elgg.security.addToken = function(data) { // 'http://example.com?data=sofar' if (elgg.isString(data)) { - var args = []; - if (data) { - args.push(data); + // is this a full URL, relative URL, or just the query string? + var parts = elgg.parse_url(data), + args = {}, + base = ''; + + if (parts['host'] == undefined) { + if (data.indexOf('?') === 0) { + // query string + base = '?'; + args = elgg.parse_str(parts['query']); + } + } else { + // full or relative URL + + if (parts['query'] != undefined) { + // with query string + args = elgg.parse_str(parts['query']); + } + var split = data.split('?'); + base = split[0] + '?'; } - args.push("__elgg_ts=" + elgg.security.token.__elgg_ts); - args.push("__elgg_token=" + elgg.security.token.__elgg_token); + args["__elgg_ts"] = elgg.security.token.__elgg_ts; + args["__elgg_token"] = elgg.security.token.__elgg_token; - return args.join('&'); + return base + jQuery.param(args); } // no input! acts like a getter @@ -95,9 +100,8 @@ elgg.security.addToken = function(data) { }; elgg.security.init = function() { - //refresh security token every 5 minutes - //this is set in the js/elgg PHP view. - setInterval(elgg.security.refreshToken, elgg.security.interval); + // elgg.security.interval is set in the js/elgg PHP view. + elgg.security.tokenRefreshTimer = setInterval(elgg.security.refreshToken, elgg.security.interval); }; elgg.register_hook_handler('boot', 'system', elgg.security.init);
\ No newline at end of file diff --git a/js/lib/session.js b/js/lib/session.js index fa3d60aa9..a8d52733c 100644 --- a/js/lib/session.js +++ b/js/lib/session.js @@ -14,9 +14,9 @@ elgg.provide('elgg.session'); * {string} options[domain] * {boolean} options[secure] * - * @return {string} The value of the cookie, if only name is specified + * @return {string|undefined} The value of the cookie, if only name is specified. Undefined if no value set */ -elgg.session.cookie = function (name, value, options) { +elgg.session.cookie = function(name, value, options) { var cookies = [], cookie = [], i = 0, date, valid = true; //elgg.session.cookie() @@ -47,21 +47,19 @@ elgg.session.cookie = function (name, value, options) { } cookies.push(name + '=' + value); - - if (elgg.isNumber(options.expires)) { - if (elgg.isNumber(options.expires)) { - date = new Date(); - date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); - } else if (options.expires.toUTCString) { - date = options.expires; - } else { - valid = false; - } - - if (valid) { - cookies.push('expires=' + date.toUTCString()); - } - } + + if (options.expires) { + if (elgg.isNumber(options.expires)) { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else if (options.expires.toUTCString) { + date = options.expires; + } + + if (date) { + cookies.push('expires=' + date.toUTCString()); + } + } // CAUTION: Needed to parenthesize options.path and options.domain // in the following expressions, otherwise they evaluate to undefined diff --git a/js/lib/ui.autocomplete.js b/js/lib/ui.autocomplete.js new file mode 100644 index 000000000..46d72d146 --- /dev/null +++ b/js/lib/ui.autocomplete.js @@ -0,0 +1,14 @@ +/** + * + */ +elgg.provide('elgg.autocomplete'); + +elgg.autocomplete.init = function() { + $('.elgg-input-autocomplete').autocomplete({ + source: elgg.autocomplete.url, //gets set by input/autocomplete view + minLength: 2, + html: "html" + }) +}; + +elgg.register_hook_handler('init', 'system', elgg.autocomplete.init);
\ No newline at end of file diff --git a/js/lib/ui.avatar_cropper.js b/js/lib/ui.avatar_cropper.js new file mode 100644 index 000000000..fc32a0832 --- /dev/null +++ b/js/lib/ui.avatar_cropper.js @@ -0,0 +1,76 @@ +/** + * Avatar cropping + */ + +elgg.provide('elgg.avatarCropper'); + +/** + * Register the avatar cropper. + * + * If the hidden inputs have the coordinates from a previous cropping, begin + * the selection and preview with that displayed. + */ +elgg.avatarCropper.init = function() { + var params = { + selectionOpacity: 0, + aspectRatio: '1:1', + onSelectEnd: elgg.avatarCropper.selectChange, + onSelectChange: elgg.avatarCropper.preview + }; + + if ($('input[name=x2]').val()) { + params.x1 = $('input[name=x1]').val(); + params.x2 = $('input[name=x2]').val(); + params.y1 = $('input[name=y1]').val(); + params.y2 = $('input[name=y2]').val(); + } + + $('#user-avatar-cropper').imgAreaSelect(params); + + if ($('input[name=x2]').val()) { + var ias = $('#user-avatar-cropper').imgAreaSelect({instance: true}); + var selection = ias.getSelection(); + elgg.avatarCropper.preview($('#user-avatar-cropper'), selection); + } +}; + +/** + * Handler for changing select area. + * + * @param {Object} reference to the image + * @param {Object} imgareaselect selection object + * @return void + */ +elgg.avatarCropper.preview = function(img, selection) { + // catch for the first click on the image + if (selection.width == 0 || selection.height == 0) { + return; + } + + var origWidth = $("#user-avatar-cropper").width(); + var origHeight = $("#user-avatar-cropper").height(); + var scaleX = 100 / selection.width; + var scaleY = 100 / selection.height; + $('#user-avatar-preview > img').css({ + width: Math.round(scaleX * origWidth) + 'px', + height: Math.round(scaleY * origHeight) + 'px', + marginLeft: '-' + Math.round(scaleX * selection.x1) + 'px', + marginTop: '-' + Math.round(scaleY * selection.y1) + 'px' + }); +}; + +/** + * Handler for updating the form inputs after select ends + * + * @param {Object} reference to the image + * @param {Object} imgareaselect selection object + * @return void + */ +elgg.avatarCropper.selectChange = function(img, selection) { + $('input[name=x1]').val(selection.x1); + $('input[name=x2]').val(selection.x2); + $('input[name=y1]').val(selection.y1); + $('input[name=y2]').val(selection.y2); +}; + +elgg.register_hook_handler('init', 'system', elgg.avatarCropper.init);
\ No newline at end of file diff --git a/js/lib/friends_picker.js b/js/lib/ui.friends_picker.js index 9257c40fc..9257c40fc 100644 --- a/js/lib/friends_picker.js +++ b/js/lib/ui.friends_picker.js diff --git a/js/lib/ui.js b/js/lib/ui.js index 16f1f969c..413078b4f 100644 --- a/js/lib/ui.js +++ b/js/lib/ui.js @@ -1,6 +1,7 @@ elgg.provide('elgg.ui'); elgg.ui.init = function () { + // add user hover menus elgg.ui.initHoverMenu(); //if the user clicks a system message, make it disappear @@ -9,26 +10,24 @@ elgg.ui.init = function () { }); $('.elgg-system-messages li').animate({opacity: 0.9}, 6000); - $('.elgg-system-messages li').fadeOut('slow'); + $('.elgg-system-messages li.elgg-state-success').fadeOut('slow'); - $('.elgg-toggler').live('click', elgg.ui.toggles); + $('[rel=toggle]').live('click', elgg.ui.toggles); - $('[rel=popup]').live('click', elgg.ui.popsUp); + $('[rel=popup]').live('click', elgg.ui.popupOpen); $('.elgg-menu-page .elgg-menu-parent').live('click', elgg.ui.toggleMenu); $('.elgg-requires-confirmation').live('click', elgg.ui.requiresConfirmation); - if ($('.elgg-input-date').length) { - $('.elgg-input-date').datepicker(); - } -} + $('.elgg-autofocus').focus(); +}; /** * Toggles an element based on clicking a separate element * - * Use .elgg-toggler on the toggler element - * Set the href to target the item you want to toggle (<a class="elgg-toggler" href="#id-of-target">) + * Use rel="toggle" on the toggler element + * Set the href to target the item you want to toggle (<a rel="toggle" href="#id-of-target">) * * @param {Object} event * @return void @@ -36,10 +35,11 @@ elgg.ui.init = function () { elgg.ui.toggles = function(event) { event.preventDefault(); + // @todo might want to switch this to elgg.getSelectorFromUrlFragment(). var target = $(this).toggleClass('elgg-state-active').attr('href'); $(target).slideToggle('medium'); -} +}; /** * Pops up an element based on clicking a separate element @@ -52,14 +52,14 @@ elgg.ui.toggles = function(event) { * targetSelector: The selector used to find the popup * target: The popup jQuery element as found by the selector * source: The jquery element whose click event initiated a popup. - * + * * The return value of the function is used as the options object to .position(). * Handles can also return false to abort the default behvior and override it with their own. * * @param {Object} event * @return void */ -elgg.ui.popsUp = function(event) { +elgg.ui.popupOpen = function(event) { event.preventDefault(); event.stopPropagation(); @@ -86,7 +86,7 @@ elgg.ui.popsUp = function(event) { if (!options) { return; } - + // hide if already open if ($target.is(':visible')) { $target.fadeOut(); @@ -101,7 +101,7 @@ elgg.ui.popsUp = function(event) { $('body') .die('click', elgg.ui.popupClose) .live('click', elgg.ui.popupClose); -} +}; /** * Catches clicks that aren't in a popup and closes all popups. @@ -119,7 +119,7 @@ elgg.ui.popupClose = function(event) { if (!$target.is(':visible')) { return; } - + // didn't click inside the target if ($eventTarget.closest(target).length > 0) { inTarget = true; @@ -139,7 +139,7 @@ elgg.ui.popupClose = function(event) { $('body').die('click', elgg.ui.popClose); } -} +}; /** * Toggles a child menu when the parent is clicked @@ -151,7 +151,7 @@ elgg.ui.toggleMenu = function(event) { $(this).siblings().slideToggle('medium'); $(this).toggleClass('elgg-menu-closed elgg-menu-opened'); event.preventDefault(); -} +}; /** * Initialize the hover menu @@ -179,7 +179,7 @@ elgg.ui.initHoverMenu = function(parent) { var $hovermenu = $(this).data('hovermenu') || null; if (!$hovermenu) { - var $hovermenu = $(this).parent().find(".elgg-menu-hover"); + $hovermenu = $(this).parent().find(".elgg-menu-hover"); $(this).data('hovermenu', $hovermenu); } @@ -211,7 +211,7 @@ elgg.ui.initHoverMenu = function(parent) { $(".elgg-menu-hover").fadeOut(); } }); -} +}; /** * Calls a confirm() and prevents default if denied. @@ -220,14 +220,14 @@ elgg.ui.initHoverMenu = function(parent) { * @return void */ elgg.ui.requiresConfirmation = function(e) { - var confirmText = $(this).attr('title') || elgg.echo('question:areyousure'); + var confirmText = $(this).attr('rel') || elgg.echo('question:areyousure'); if (!confirm(confirmText)) { e.preventDefault(); } }; /** - * Repositions the likes popup + * Repositions the login popup * * @param {String} hook 'getOptions' * @param {String} type 'ui.popup' @@ -236,34 +236,58 @@ elgg.ui.requiresConfirmation = function(e) { * * @return {Object} */ -elgg.ui.likesPopupHandler = function(hook, type, params, options) { - if (params.target.hasClass('elgg-likes-list')) { - options.my = 'right bottom'; - options.at = 'left top'; +elgg.ui.loginHandler = function(hook, type, params, options) { + if (params.target.attr('id') == 'login-dropdown-box') { + options.my = 'right top'; + options.at = 'right bottom'; return options; } return null; }; /** - * Repositions the login popup + * Initialize the date picker * - * @param {String} hook 'getOptions' - * @param {String} type 'ui.popup' - * @param {Object} params An array of info about the target and source. - * @param {Object} options Options to pass to + * Uses the class .elgg-input-date as the selector. * - * @return {Object} + * If the class .elgg-input-timestamp is set on the input element, the onSelect + * method converts the date text to a unix timestamp in seconds. That value is + * stored in a hidden element indicated by the id on the input field. + * + * @return void */ -elgg.ui.LoginHandler = function(hook, type, params, options) { - if (params.target.attr('id') == 'login-dropdown-box') { - options.my = 'right top'; - options.at = 'right bottom'; - return options; +elgg.ui.initDatePicker = function() { + var loadDatePicker = function() { + $('.elgg-input-date').datepicker({ + // ISO-8601 + dateFormat: 'yy-mm-dd', + onSelect: function(dateText) { + if ($(this).is('.elgg-input-timestamp')) { + // convert to unix timestamp + var dateParts = dateText.split("-"); + var timestamp = Date.UTC(dateParts[0], dateParts[1] - 1, dateParts[2]); + timestamp = timestamp / 1000; + + var id = $(this).attr('id'); + $('input[name="' + id + '"]').val(timestamp); + } + } + }); + }; + + if ($('.elgg-input-date').length && elgg.get_language() == 'en') { + loadDatePicker(); + } else if ($('.elgg-input-date').length) { + elgg.get({ + url: elgg.config.wwwroot + 'vendors/jquery/i18n/jquery.ui.datepicker-'+ elgg.get_language() +'.js', + dataType: "script", + cache: true, + success: loadDatePicker, + error: loadDatePicker // english language is already loaded. + }); } - return null; }; elgg.register_hook_handler('init', 'system', elgg.ui.init); -elgg.register_hook_handler('getOptions', 'ui.popup', elgg.ui.likesPopupHandler); -elgg.register_hook_handler('getOptions', 'ui.popup', elgg.ui.LoginHandler);
\ No newline at end of file +elgg.register_hook_handler('init', 'system', elgg.ui.initDatePicker); +elgg.register_hook_handler('getOptions', 'ui.popup', elgg.ui.loginHandler); diff --git a/js/lib/ui.river.js b/js/lib/ui.river.js new file mode 100644 index 000000000..c103fabb3 --- /dev/null +++ b/js/lib/ui.river.js @@ -0,0 +1,14 @@ +elgg.provide('elgg.ui.river'); + +elgg.ui.river.init = function() { + $('#elgg-river-selector').change(function() { + var url = window.location.href; + if (window.location.search.length) { + url = url.substring(0, url.indexOf('?')); + } + url += '?' + $(this).val(); + elgg.forward(url); + }); +}; + +elgg.register_hook_handler('init', 'system', elgg.ui.river.init);
\ No newline at end of file diff --git a/js/lib/ui.userpicker.js b/js/lib/ui.userpicker.js new file mode 100644 index 000000000..669b84cdb --- /dev/null +++ b/js/lib/ui.userpicker.js @@ -0,0 +1,117 @@ +elgg.provide('elgg.userpicker'); + +/** + * Userpicker initialization + * + * The userpicker is an autocomplete library for selecting multiple users or + * friends. It works in concert with the view input/userpicker. + * + * @return void + */ +elgg.userpicker.init = function() { + + // binding autocomplete. + // doing this as an each so we can pass this to functions. + $('.elgg-input-user-picker').each(function() { + + $(this).autocomplete({ + source: function(request, response) { + + var params = elgg.userpicker.getSearchParams(this); + + elgg.get('livesearch', { + data: params, + dataType: 'json', + success: function(data) { + response(data); + } + }); + }, + minLength: 2, + html: "html", + select: elgg.userpicker.addUser + }) + }); + + $('.elgg-userpicker-remove').live('click', elgg.userpicker.removeUser); +}; + +/** + * Adds a user to the select user list + * + * elgg.userpicker.userList is defined in the input/userpicker view + * + * @param {Object} event + * @param {Object} ui The object returned by the autocomplete endpoint + * @return void + */ +elgg.userpicker.addUser = function(event, ui) { + var info = ui.item; + + // do not allow users to be added multiple times + if (!(info.guid in elgg.userpicker.userList)) { + elgg.userpicker.userList[info.guid] = true; + var users = $(this).siblings('.elgg-user-picker-list'); + var li = '<input type="hidden" name="members[]" value="' + info.guid + '" />'; + li += elgg.userpicker.viewUser(info); + $('<li>').html(li).appendTo(users); + } + + $(this).val(''); + event.preventDefault(); +}; + +/** + * Remove a user from the selected user list + * + * @param {Object} event + * @return void + */ +elgg.userpicker.removeUser = function(event) { + var item = $(this).closest('.elgg-user-picker-list > li'); + + var guid = item.find('[name="members[]"]').val(); + delete elgg.userpicker.userList[guid]; + + item.remove(); + event.preventDefault(); +}; + +/** + * Render the list item for insertion into the selected user list + * + * The html in this method has to remain synced with the input/userpicker view + * + * @param {Object} info The object returned by the autocomplete endpoint + * @return string + */ +elgg.userpicker.viewUser = function(info) { + + var deleteLink = "<a href='#' class='elgg-userpicker-remove'>X</a>"; + + var html = "<div class='elgg-image-block'>"; + html += "<div class='elgg-image'>" + info.icon + "</div>"; + html += "<div class='elgg-image-alt'>" + deleteLink + "</div>"; + html += "<div class='elgg-body'>" + info.name + "</div>"; + html += "</div>"; + + return html; +}; + +/** + * Get the parameters to use for autocomplete + * + * This grabs the value of the friends checkbox. + * + * @param {Object} obj Object for the autocomplete callback + * @return Object + */ +elgg.userpicker.getSearchParams = function(obj) { + if (obj.element.parent('.elgg-user-picker').find('input[name=match_on]').attr('checked')) { + return {'match_on[]': 'friends', 'term' : obj.term}; + } else { + return {'match_on[]': 'users', 'term' : obj.term}; + } +}; + +elgg.register_hook_handler('init', 'system', elgg.userpicker.init); diff --git a/js/lib/ui.widgets.js b/js/lib/ui.widgets.js index fb256672a..26020bb4b 100644 --- a/js/lib/ui.widgets.js +++ b/js/lib/ui.widgets.js @@ -15,7 +15,7 @@ elgg.ui.widgets.init = function() { $(".elgg-widgets").sortable({ items: 'div.elgg-module-widget.elgg-state-draggable', connectWith: '.elgg-widgets', - handle: 'div.elgg-head', + handle: '.elgg-widget-handle', forcePlaceholderSize: true, placeholder: 'elgg-widget-placeholder', opacity: 0.8, @@ -29,7 +29,7 @@ elgg.ui.widgets.init = function() { $('.elgg-widget-edit > form ').live('submit', elgg.ui.widgets.saveSettings); $('a.elgg-widget-collapse-button').live('click', elgg.ui.widgets.collapseToggle); - elgg.ui.widgets.equalHeight(".elgg-widgets"); + elgg.ui.widgets.setMinHeight(".elgg-widgets"); }; /** @@ -58,6 +58,7 @@ elgg.ui.widgets.add = function(event) { handler: type, owner_guid: elgg.get_page_owner_guid(), context: $("input[name='widget_context']").val(), + show_access: $("input[name='show_access']").val(), default_widgets: $("input[name='default_widgets']").val() || 0 }, success: function(json) { @@ -65,7 +66,7 @@ elgg.ui.widgets.add = function(event) { } }); event.preventDefault(); -} +}; /** * Persist the widget's new position @@ -96,7 +97,7 @@ elgg.ui.widgets.move = function(event, ui) { // @hack fixes jquery-ui/opera bug where draggable elements jump ui.item.css('top', 0); ui.item.css('left', 0); -} +}; /** * Removes a widget from the layout @@ -107,7 +108,12 @@ elgg.ui.widgets.move = function(event, ui) { * @return void */ elgg.ui.widgets.remove = function(event) { - var $widget = $(this).parent().parent(); + if (confirm(elgg.echo('deleteconfirm')) == false) { + event.preventDefault(); + return; + } + + var $widget = $(this).closest('.elgg-module-widget'); // if widget type is single instance type, enable the add buton var type = $widget.attr('class'); @@ -124,17 +130,11 @@ elgg.ui.widgets.remove = function(event) { $widget.remove(); - // elgg-widget-delete-button-<guid> - var id = $(this).attr('id'); - id = id.substr(id.indexOf('elgg-widget-delete-button-') + "elgg-widget-delete-button-".length); + // delete the widget through ajax + elgg.action($(this).attr('href')); - elgg.action('widgets/delete', { - data: { - widget_guid: id - } - }); event.preventDefault(); -} +}; /** * Toggle the collapse state of the widget @@ -146,7 +146,7 @@ elgg.ui.widgets.collapseToggle = function(event) { $(this).toggleClass('elgg-widget-collapsed'); $(this).parent().parent().find('.elgg-body').slideToggle('medium'); event.preventDefault(); -} +}; /** * Save a widget's settings @@ -178,25 +178,32 @@ elgg.ui.widgets.saveSettings = function(event) { } }); event.preventDefault(); -} +}; /** - * Make all elements have the same min-height + * Set the min-height so that all widget column bottoms are the same * * This addresses the issue of trying to drag a widget into a column that does - * not have any widgets. + * not have any widgets or many fewer widgets than other columns. * * @param {String} selector * @return void */ -elgg.ui.widgets.equalHeight = function(selector) { - var maxHeight = 0; +elgg.ui.widgets.setMinHeight = function(selector) { + var maxBottom = 0; $(selector).each(function() { - if ($(this).height() > maxHeight) { - maxHeight = $(this).height(); + var bottom = parseInt($(this).offset().top + $(this).height()); + if (bottom > maxBottom) { + maxBottom = bottom; } }) - $(selector).css('min-height', maxHeight); -} + $(selector).each(function() { + var bottom = parseInt($(this).offset().top + $(this).height()); + if (bottom < maxBottom) { + var newMinHeight = parseInt($(this).height() + (maxBottom - bottom)); + $(this).css('min-height', newMinHeight + 'px'); + } + }) +}; elgg.register_hook_handler('init', 'system', elgg.ui.widgets.init); diff --git a/js/lib/userpicker.js b/js/lib/userpicker.js deleted file mode 100644 index 475af150f..000000000 --- a/js/lib/userpicker.js +++ /dev/null @@ -1,84 +0,0 @@ -elgg.provide('elgg.userpicker'); - -elgg.userpicker.init = function() { - // binding autocomplete. - // doing this as an each so we can pass this to functions. - $('.elgg-input-user-picker').each(function() { - - var _this = this; - - $(this).autocomplete({ - source: function(request, response) { - var params = elgg.userpicker.getSearchParams(this); - - elgg.get('livesearch', { - data: params, - dataType: 'json', - success: function(data) { - response(data); - } - }); - }, - minLength: 2, - select: elgg.userpicker.addUser - }) - - //@todo This seems convoluted - .data("autocomplete")._renderItem = elgg.userpicker.formatItem; - }); -}; - -elgg.userpicker.formatItem = function(ul, item) { - switch (item.type) { - case 'user': - case 'group': - r = item.icon + item.name + ' - ' + item.desc; - break; - - default: - r = item.name + ' - ' + item.desc; - break; - } - - return $("<li/>") - .data("item.autocomplete", item) - .append(r) - .appendTo(ul); -}; - -elgg.userpicker.addUser = function(event, ui) { - var info = ui.item; - - // do not allow users to be added multiple times - if (!(info.guid in elgg.userpicker.userList)) { - elgg.userpicker.userList[info.guid] = true; - - var picker = $(this).closest('.elgg-user-picker'); - var users = picker.find('.elgg-user-picker-entries'); - var internalName = users.find('[type=hidden]').attr('name'); - - // not sure why formatted isn't. - var formatted = elgg.userpicker.formatItem(data); - - // add guid as hidden input and to list. - var li = formatted + ' <div class="delete-button"><a onclick="elgg.userpicker.removeUser(this, ' + info.guid + ')"><strong>X</strong></a></div>' - + '<input type="hidden" name="' + internalName + '" value="' + info.guid + '" />'; - $('<li>').html(li).appendTo(users); - - $(this).val(''); - } -}; - -elgg.userpicker.removeUser = function(link, guid) { - $(link).closest('.elgg-user-picker-entries > li').remove(); -}; - -elgg.userpicker.getSearchParams = function(e) { - if ($(e).closest('.elgg-user-picker').find('[name=match_on]').attr('checked')) { - return {'match_on[]': 'friends'}; - } else { - return {'match_on[]': 'users'}; - } -} - -elgg.register_hook_handler('init', 'system', elgg.userpicker.init);
\ No newline at end of file diff --git a/js/tests/ElggLanguagesTest.js b/js/tests/ElggLanguagesTest.js index 1f66fc35b..9186ff5bb 100644 --- a/js/tests/ElggLanguagesTest.js +++ b/js/tests/ElggLanguagesTest.js @@ -6,7 +6,7 @@ ElggLanguagesTest.prototype.setUp = function() { //Immediately execute some dummy "returned" javascript instead of sending //an actual ajax request $.ajax = function(settings) { - var lang = settings.data.js.split('/')[1]; + var lang = settings.data.language; elgg.config.translations[lang] = {'language':lang}; }; }; diff --git a/js/tests/ElggLibTest.js b/js/tests/ElggLibTest.js index dd0267c5c..bd39e7fb3 100644 --- a/js/tests/ElggLibTest.js +++ b/js/tests/ElggLibTest.js @@ -72,13 +72,69 @@ ElggLibTest.prototype.testNormalizeUrl = function() { elgg.config.wwwroot = "http://elgg.org/"; [ - ['', elgg.config.wwwroot], - ['test', elgg.config.wwwroot + 'test'], - ['http://google.com', 'http://google.com'], - ['//example.com', '//example.com'], - ['/page', elgg.config.wwwroot + 'page'], - ['mod/plugin/index.php', elgg.config.wwwroot + 'mod/plugin/index.php'], + ['', elgg.config.wwwroot], + ['test', elgg.config.wwwroot + 'test'], + ['http://example.com', 'http://example.com'], + ['https://example.com', 'https://example.com'], + ['http://example-time.com', 'http://example-time.com'], + ['//example.com', '//example.com'], + ['mod/my_plugin/graphics/image.jpg', elgg.config.wwwroot + 'mod/my_plugin/graphics/image.jpg'], + + ['ftp://example.com/file', 'ftp://example.com/file'], + ['mailto:brett@elgg.org', 'mailto:brett@elgg.org'], + ['javascript:alert("test")', 'javascript:alert("test")'], + ['app://endpoint', 'app://endpoint'], + + ['example.com', 'http://example.com'], + ['example.com/subpage', 'http://example.com/subpage'], + + ['page/handler', elgg.config.wwwroot + 'page/handler'], + ['page/handler?p=v&p2=v2', elgg.config.wwwroot + 'page/handler?p=v&p2=v2'], + ['mod/plugin/file.php', elgg.config.wwwroot + 'mod/plugin/file.php'], + ['mod/plugin/file.php?p=v&p2=v2', elgg.config.wwwroot + 'mod/plugin/file.php?p=v&p2=v2'], + ['rootfile.php', elgg.config.wwwroot + 'rootfile.php'], + ['rootfile.php?p=v&p2=v2', elgg.config.wwwroot + 'rootfile.php?p=v&p2=v2'], + + ['/page/handler', elgg.config.wwwroot + 'page/handler'], + ['/page/handler?p=v&p2=v2', elgg.config.wwwroot + 'page/handler?p=v&p2=v2'], + ['/mod/plugin/file.php', elgg.config.wwwroot + 'mod/plugin/file.php'], + ['/mod/plugin/file.php?p=v&p2=v2', elgg.config.wwwroot + 'mod/plugin/file.php?p=v&p2=v2'], + ['/rootfile.php', elgg.config.wwwroot + 'rootfile.php'], + ['/rootfile.php?p=v&p2=v2', elgg.config.wwwroot + 'rootfile.php?p=v&p2=v2'] + ].forEach(function(args) { assertEquals(args[1], elgg.normalize_url(args[0])); }); -};
\ No newline at end of file +}; + +ElggLibTest.prototype.testParseUrl = function() { + + [ + ["http://www.elgg.org/test/", {'scheme': 'http', 'host': 'www.elgg.org', 'path': '/test/'}], + ["https://www.elgg.org/test/", {'scheme': 'https', 'host': 'www.elgg.org', 'path': '/test/'}], + ["ftp://www.elgg.org/test/", {'scheme': 'ftp', 'host': 'www.elgg.org', 'path': '/test/'}], + ["http://elgg.org/test?val1=one&val2=two", {'scheme': 'http', 'host': 'elgg.org', 'path': '/test', 'query': 'val1=one&val2=two'}], + ["http://elgg.org:8080/", {'scheme': 'http', 'host': 'elgg.org', 'port': 8080, 'path': '/'}], + ["http://elgg.org/test#there", {'scheme': 'http', 'host': 'elgg.org', 'path': '/test', 'fragment': 'there'}], + + ["test?val=one", {'host': 'test', 'query': 'val=one'}], + ["?val=one", {'query': 'val=one'}], + + ["mailto:joe@elgg.org", {'scheme': 'mailto', 'path': 'joe@elgg.org'}], + ["javascript:load()", {'scheme': 'javascript', 'path': 'load()'}] + + ].forEach(function(args) { + assertEquals(args[1], elgg.parse_url(args[0])); + }); +}; + +ElggLibTest.prototype.testParseStr = function() { + + [ + ["A+%2B+B=A+%2B+B", {"A + B": "A + B"}] + + ].forEach(function(args) { + assertEquals(args[1], elgg.parse_str(args[0])); + }); +}; + diff --git a/js/tests/ElggPriorityListTest.js b/js/tests/ElggPriorityListTest.js index 2549e0ee0..2329a8490 100644 --- a/js/tests/ElggPriorityListTest.js +++ b/js/tests/ElggPriorityListTest.js @@ -15,7 +15,7 @@ ElggPriorityListTest.prototype.testInsert = function() { this.list.insert('bar', 501); - assertEquals('foo', this.list.priorities_[501][0]); + assertEquals('bar', this.list.priorities_[501][0]); }; ElggPriorityListTest.prototype.testInsertRespectsPriority = function() { @@ -25,9 +25,9 @@ ElggPriorityListTest.prototype.testInsertRespectsPriority = function() { this.list.insert(values[i], values[i]); } - this.list.forEach(function(elem, idx)) { + this.list.forEach(function(elem, idx) { assertEquals(elem, idx); - } + }) }; ElggPriorityListTest.prototype.testInsertHandlesDuplicatePriorities = function() { diff --git a/js/tests/ElggSecurityTest.js b/js/tests/ElggSecurityTest.js index f1111168f..107c0adbd 100644 --- a/js/tests/ElggSecurityTest.js +++ b/js/tests/ElggSecurityTest.js @@ -26,16 +26,42 @@ ElggSecurityTest.prototype.testAddTokenAcceptsObject = function() { assertEquals(expected, elgg.security.addToken(input)); }; -ElggSecurityTest.prototype.testAddTokenAcceptsString = function() { +ElggSecurityTest.prototype.testAddTokenAcceptsRelativeUrl = 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)); - + + input = "test"; + assertEquals(input + '?' + str, elgg.security.addToken(input)); +}; + +ElggSecurityTest.prototype.testAddTokenAcceptsFullUrl = function() { + var input, + str = "__elgg_ts=" + this.ts + "&__elgg_token=" + this.token; + + input = "http://elgg.org/"; + assertEquals(input + '?' + str, elgg.security.addToken(input)); +}; + +ElggSecurityTest.prototype.testAddTokenAcceptsQueryString = function() { + var input, + str = "__elgg_ts=" + this.ts + "&__elgg_token=" + this.token; + + input = "?data=sofar"; + assertEquals(input + '&' + str, elgg.security.addToken(input)); + + input = "test?data=sofar"; + assertEquals(input + '&' + str, elgg.security.addToken(input)); + + input = "http://elgg.org/?data=sofar"; + assertEquals(input + '&' + str, elgg.security.addToken(input)); +}; + +ElggSecurityTest.prototype.testAddTokenAlreadyAdded = function() { + var input, + str = "__elgg_ts=" + this.ts + "&__elgg_token=" + this.token; + + input = "http://elgg.org/?" + str + "&data=sofar"; + assertEquals(input, elgg.security.addToken(input)); }; ElggSecurityTest.prototype.testSetTokenSetsElggSecurityToken = function() { @@ -47,5 +73,3 @@ ElggSecurityTest.prototype.testSetTokenSetsElggSecurityToken = function() { elgg.security.setToken(json); assertEquals(json, elgg.security.token); }; - - diff --git a/js/tests/README b/js/tests/README new file mode 100644 index 000000000..f43c0c89d --- /dev/null +++ b/js/tests/README @@ -0,0 +1,25 @@ +Elgg JavaScript Unit Tests +-------------------------- + +Introduction +============ +Elgg uses js-test-driver to run its unit tests. Instructions on obtaining, +configuring, and using it are at http://code.google.com/p/js-test-driver/. It +supports running the test in multiple browsers and debugging using web browser +based debuggers. Visit its wiki at the Google Code site for more information. + + +Sample Usage +============ + 1. Put jar file in the base directory of Elgg + 2. Run the server: java -jar JsTestDriver-1.3.5.jar --port 4224 + 3. Point a web browser at http://localhost:4224 + 4. Click "Capture this browser" + 5. Run the tests: java -jar JsTestDriver-1.3.5.jar --config js/tests/jsTestDriver.conf --basePath . --tests all + + +Configuration Hints +=================== + * The port when running the server must be the same as listed in the + configuration file. If that port is being used, change the configuration file. + * The basePath must be the base directory of Elgg.
\ No newline at end of file diff --git a/js/tests/jsTestDriver.conf b/js/tests/jsTestDriver.conf index 1bb06e811..cc0b5d373 100644 --- a/js/tests/jsTestDriver.conf +++ b/js/tests/jsTestDriver.conf @@ -1,9 +1,10 @@ -server: http://localhost:42442 +server: http://localhost:4224 load: - - vendors/jquery/jquery-1.4.2.min.js + - vendors/jquery/jquery-1.6.4.min.js - vendors/sprintf.js - js/lib/elgglib.js + - js/lib/hooks.js - js/classes/*.js - js/lib/*.js - js/tests/*.js
\ No newline at end of file |
