aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/classes/ElggPriorityList.js7
-rw-r--r--js/classes/ElggUser.js16
-rw-r--r--js/lib/ajax.js6
-rw-r--r--js/lib/autocomplete.js39
-rw-r--r--js/lib/configuration.js2
-rw-r--r--js/lib/elgglib.js187
-rw-r--r--js/lib/hooks.js77
-rw-r--r--js/lib/languages.js2
-rw-r--r--js/lib/security.js31
-rw-r--r--js/lib/ui.autocomplete.js14
-rw-r--r--js/lib/ui.avatar_cropper.js76
-rw-r--r--js/lib/ui.friends_picker.js (renamed from js/lib/friends_picker.js)0
-rw-r--r--js/lib/ui.js92
-rw-r--r--js/lib/ui.userpicker.js117
-rw-r--r--js/lib/ui.widgets.js12
-rw-r--r--js/lib/userpicker.js84
-rw-r--r--js/tests/ElggLanguagesTest.js2
-rw-r--r--js/tests/ElggLibTest.js59
-rw-r--r--js/tests/ElggPriorityListTest.js6
-rw-r--r--js/tests/ElggSecurityTest.js44
-rw-r--r--js/tests/README24
-rw-r--r--js/tests/jsTestDriver.conf5
22 files changed, 667 insertions, 235 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..81209ebd0 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,8 +250,35 @@ 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;
}
@@ -353,6 +380,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])
+ value = decodeURIComponent(result[2])
+ params[key] = value;
+ }
+
+ return params;
+};
+
+/**
* Returns a jQuery selector from a URL's fragement. Defaults to expecting an ID.
*
* Examples:
@@ -379,4 +505,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 ab3a8a224..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;
@@ -100,5 +115,59 @@ elgg.trigger_hook = function(name, type, params, value) {
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..ae7ba63e2 100644
--- a/js/lib/languages.js
+++ b/js/lib/languages.js
@@ -32,6 +32,8 @@ elgg.reload_all_translations = function(language) {
},
success: function(json) {
elgg.add_translation(lang, json);
+ elgg.config.languageReady = true;
+ elgg.initWhenReady();
}
});
};
diff --git a/js/lib/security.js b/js/lib/security.js
index 486347b88..61aa1cfcd 100644
--- a/js/lib/security.js
+++ b/js/lib/security.js
@@ -60,7 +60,7 @@ elgg.security.refreshToken = function() {
/**
- * 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 +70,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
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 46e418e8b..c26cbe389 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
@@ -13,23 +14,14 @@ elgg.ui.init = function () {
$('[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.ui.initDatePicker();
- }
-
- // fix for ie7 CSS issue on menu dropdown
- // open the menu when you hover over it, close when you click off of it.
- // @todo This should be possible with CSS. Anyone want to tame the beast, go for it.
- if ($.browser.msie && $.browser.version <= 7) {
- $('.elgg-menu-site > .elgg-more').live('mouseenter', elgg.ui.ie7MenuFixMouseEnter)
- }
-}
+ $('.elgg-autofocus').focus();
+};
/**
* Toggles an element based on clicking a separate element
@@ -47,7 +39,7 @@ elgg.ui.toggles = function(event) {
var target = $(this).toggleClass('elgg-state-active').attr('href');
$(target).slideToggle('medium');
-}
+};
/**
* Pops up an element based on clicking a separate element
@@ -67,7 +59,7 @@ elgg.ui.toggles = function(event) {
* @param {Object} event
* @return void
*/
-elgg.ui.popsUp = function(event) {
+elgg.ui.popupOpen = function(event) {
event.preventDefault();
event.stopPropagation();
@@ -109,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.
@@ -147,7 +139,7 @@ elgg.ui.popupClose = function(event) {
$('body').die('click', elgg.ui.popClose);
}
-}
+};
/**
* Toggles a child menu when the parent is clicked
@@ -159,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
@@ -187,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);
}
@@ -219,7 +211,7 @@ elgg.ui.initHoverMenu = function(parent) {
$(".elgg-menu-hover").fadeOut();
}
});
-}
+};
/**
* Calls a confirm() and prevents default if denied.
@@ -244,7 +236,7 @@ elgg.ui.requiresConfirmation = function(e) {
*
* @return {Object}
*/
-elgg.ui.LoginHandler = function(hook, type, params, options) {
+elgg.ui.loginHandler = function(hook, type, params, options) {
if (params.target.attr('id') == 'login-dropdown-box') {
options.my = 'right top';
options.at = 'right bottom';
@@ -265,49 +257,25 @@ elgg.ui.LoginHandler = function(hook, type, params, options) {
* @return void
*/
elgg.ui.initDatePicker = 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 date = $.datepicker.parseDate('yy-mm-dd', dateText);
- var timestamp = $.datepicker.formatDate('@', date);
- timestamp = timestamp / 1000;
-
- var id = $(this).attr('id');
- $('input[name="' + id + '"]').val(timestamp);
+ if ($('.elgg-input-date').length) {
+ $('.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);
+ }
}
- }
- });
-}
-
-/**
- * IE 7 doesn't like our site menu system CSS, so open it with JS.
- */
-elgg.ui.ie7MenuFixMouseEnter = function() {
- $('.elgg-menu-site .elgg-menu-site-more').css('display', 'block');
- $('.elgg-menu-site .elgg-more > a')
- .css('background-color', 'white')
- .css('color', '#555')
-
- $body = $('body');
- if (!$body.data('hasIe7Clear')) {
- $body.live('click', elgg.ui.ie7MenuClear);
- $body.data('hasIe7Clear', true);
+ });
}
-
-}
-
-/**
- * Close the menu when clicking on the body
- */
-elgg.ui.ie7MenuClear = function() {
- $('.elgg-menu-site .elgg-menu-site-more').css('display', 'none');
- $('.elgg-menu-site .elgg-more > a')
- .css('background-color', 'transparent')
- .css('color', 'white')
-}
+};
elgg.register_hook_handler('init', 'system', elgg.ui.init);
-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); \ 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..8287ba91c
--- /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.siblings('[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); \ No newline at end of file
diff --git a/js/lib/ui.widgets.js b/js/lib/ui.widgets.js
index fb256672a..6435d2147 100644
--- a/js/lib/ui.widgets.js
+++ b/js/lib/ui.widgets.js
@@ -65,7 +65,7 @@ elgg.ui.widgets.add = function(event) {
}
});
event.preventDefault();
-}
+};
/**
* Persist the widget's new position
@@ -96,7 +96,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
@@ -134,7 +134,7 @@ elgg.ui.widgets.remove = function(event) {
}
});
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,7 +178,7 @@ elgg.ui.widgets.saveSettings = function(event) {
}
});
event.preventDefault();
-}
+};
/**
* Make all elements have the same min-height
@@ -197,6 +197,6 @@ elgg.ui.widgets.equalHeight = function(selector) {
}
})
$(selector).css('min-height', maxHeight);
-}
+};
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 826bf21a0..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', 'term' : e.term};
- } else {
- return {'match_on[]': 'users', 'term' : e.term};
- }
-}
-
-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..a29ebf743 100644
--- a/js/tests/ElggLibTest.js
+++ b/js/tests/ElggLibTest.js
@@ -72,13 +72,58 @@ 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'],
+
+ ['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]));
+ });
+};
+
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..4f86b27c6
--- /dev/null
+++ b/js/tests/README
@@ -0,0 +1,24 @@
+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.3d.jar --port 4224
+ 3. Point a web browser at http://localhost:4224
+ 4. Run the tests: java -jar JsTestDriver-1.3.3d.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