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/configuration.js2
-rw-r--r--js/lib/elgglib.js131
-rw-r--r--js/lib/hooks.js10
-rw-r--r--js/lib/languages.js25
-rw-r--r--js/lib/pageowner.js8
-rw-r--r--js/lib/security.js62
-rw-r--r--js/lib/session.js32
-rw-r--r--js/lib/ui.autocomplete.js (renamed from js/lib/autocomplete.js)0
-rw-r--r--js/lib/ui.avatar_cropper.js (renamed from js/lib/avatar_cropper.js)36
-rw-r--r--js/lib/ui.friends_picker.js (renamed from js/lib/friends_picker.js)0
-rw-r--r--js/lib/ui.js73
-rw-r--r--js/lib/ui.river.js14
-rw-r--r--js/lib/ui.userpicker.js (renamed from js/lib/userpicker.js)16
-rw-r--r--js/lib/ui.widgets.js55
-rw-r--r--js/tests/ElggLanguagesTest.js2
-rw-r--r--js/tests/ElggLibTest.js70
-rw-r--r--js/tests/ElggPriorityListTest.js6
-rw-r--r--js/tests/ElggSecurityTest.js44
-rw-r--r--js/tests/README25
-rw-r--r--js/tests/jsTestDriver.conf5
23 files changed, 412 insertions, 233 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/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 0f17eeced..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);
+ }
};
/**
@@ -357,7 +388,7 @@ elgg.forward = function(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.
+ * @param {Bool} expand Expand the query into an object? Else it's a string.
*
* @return {Object} The parsed URL
*/
@@ -383,16 +414,6 @@ elgg.parse_url = function(url, component, expand) {
// fragment
+ '(?:#(.*))?)',
keys = {
- 'mailto': {
- 4: "scheme",
- 5: "user",
- 6: "host",
- 9: "path",
- 12: "query",
- 13: "fragment"
- },
-
- 'standard': {
1: "scheme",
4: "user",
5: "pass",
@@ -401,58 +422,28 @@ elgg.parse_url = function(url, component, expand) {
9: "path",
12: "query",
13: "fragment"
- }
},
- results = {},
- match_keys,
- is_mailto = false;
+ results = {};
- var re = new RegExp(re_str);
- var matches = re.exec(url);
-
- // if the scheme field is undefined it means we're using a protocol
- // without :// and an @. Feel free to fix this in the re if you can >:O
- if (matches[1] == undefined) {
- match_keys = keys['mailto'];
- is_mailto = true;
- } else {
- match_keys = keys['standard'];
+ if (url.indexOf('mailto:') === 0) {
+ results['scheme'] = 'mailto';
+ results['path'] = url.replace('mailto:', '');
+ return results;
}
- for (var i in match_keys) {
- if (matches[i]) {
- results[match_keys[i]] = matches[i];
- }
+ if (url.indexOf('javascript:') === 0) {
+ results['scheme'] = 'javascript';
+ results['path'] = url.replace('javascript:', '');
+ return results;
}
- // merge everything to path if not standard
- if (is_mailto) {
- var path = '',
- new_results = {};
-
- if (typeof(results['user']) != 'undefined' && typeof(results['host']) != 'undefined') {
- path = results['user'] + '@' + results['host'];
- delete results['user'];
- delete results['host'];
- } else if (typeof(results['user'])) {
- path = results['user'];
- delete results['user'];
- } else if (typeof(results['host'])) {
- path = results['host'];
- delete results['host'];
- }
-
- if (typeof(results['path']) != 'undefined') {
- results['path'] = path + results['path'];
- } else {
- results['path'] = path;
- }
+ var re = new RegExp(re_str);
+ var matches = re.exec(url);
- for (var prop in results) {
- new_results[prop] = results[prop];
+ for (var i in keys) {
+ if (matches[i]) {
+ results[keys[i]] = matches[i];
}
-
- results = new_results;
}
if (expand && typeof(results['query']) != 'undefined') {
@@ -467,7 +458,7 @@ elgg.parse_url = function(url, component, expand) {
}
}
return results;
-}
+};
/**
* Returns an object with key/values of the parsed query string.
@@ -483,8 +474,8 @@ elgg.parse_str = function(string) {
re = /([^&=]+)=?([^&]*)/g;
while (result = re.exec(string)) {
- key = decodeURIComponent(result[1])
- value = decodeURIComponent(result[2])
+ key = decodeURIComponent(result[1].replace(/\+/g, ' '));
+ value = decodeURIComponent(result[2].replace(/\+/g, ' '));
params[key] = value;
}
@@ -535,12 +526,12 @@ elgg.push_to_object_array = function(object, parent, value) {
object[parent] = []
}
- if (object[parent].indexOf(value) < 0) {
+ if ($.inArray(value, object[parent]) < 0) {
return object[parent].push(value);
}
return false;
-}
+};
/**
* Tests if object[parent] contains child
@@ -553,8 +544,8 @@ elgg.is_in_object_array = function(object, parent, value) {
elgg.assertTypeOf('object', object);
elgg.assertTypeOf('string', parent);
- return typeof(object[parent]) != 'undefined' && object[parent].indexOf(value) >= 0;
-}
+ return typeof(object[parent]) != 'undefined' && $.inArray(value, object[parent]) >= 0;
+};
/**
* Triggers the init hook when the library is ready
@@ -569,4 +560,4 @@ elgg.initWhenReady = function() {
elgg.trigger_hook('init', 'system');
elgg.trigger_hook('ready', 'system');
}
-} \ No newline at end of file
+};
diff --git a/js/lib/hooks.js b/js/lib/hooks.js
index edfd28f24..5e1808e22 100644
--- a/js/lib/hooks.js
+++ b/js/lib/hooks.js
@@ -115,7 +115,7 @@ elgg.trigger_hook = function(name, type, params, value) {
return true;
});
- return (tempReturnValue !== null) ? tempReturnValue : returnValue;
+ return (tempReturnValue != null) ? tempReturnValue : returnValue;
};
/**
@@ -136,7 +136,7 @@ elgg.register_instant_hook = function(name, type) {
elgg.assertTypeOf('string', type);
return elgg.push_to_object_array(elgg.config.instant_hooks, name, type);
-}
+};
/**
* Is this hook registered as an instant hook?
@@ -146,7 +146,7 @@ elgg.register_instant_hook = function(name, 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.
@@ -156,7 +156,7 @@ elgg.is_instant_hook = function(name, 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?
@@ -166,7 +166,7 @@ elgg.set_triggered_hook = function(name, 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');
diff --git a/js/lib/languages.js b/js/lib/languages.js
index ae7ba63e2..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,16 +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);
- elgg.config.languageReady = true;
- elgg.initWhenReady();
- }
- });
+ 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 d14ddff95..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.
*
@@ -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 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;
+ if (data && data.output.__elgg_ts && data.output.__elgg_token) {
+ elgg.security.setToken(data.output);
+ } else {
+ clearInterval(elgg.security.tokenRefreshTimer);
}
-
- 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,22 +58,31 @@ elgg.security.addToken = function(data) {
// 'http://example.com?data=sofar'
if (elgg.isString(data)) {
- var args = {},
+ // 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
- // check for query strings
- if (data.indexOf('?') != -1) {
+ if (parts['query'] != undefined) {
+ // with query string
+ args = elgg.parse_str(parts['query']);
+ }
var split = data.split('?');
- base = split[0];
- args = elgg.parse_str(split[1]);
- } else {
- base = data;
+ base = split[0] + '?';
}
-
args["__elgg_ts"] = elgg.security.token.__elgg_ts;
args["__elgg_token"] = elgg.security.token.__elgg_token;
- return base + '?' + jQuery.param(args);
+ return base + jQuery.param(args);
}
// no input! acts like a getter
@@ -103,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/autocomplete.js b/js/lib/ui.autocomplete.js
index 46d72d146..46d72d146 100644
--- a/js/lib/autocomplete.js
+++ b/js/lib/ui.autocomplete.js
diff --git a/js/lib/avatar_cropper.js b/js/lib/ui.avatar_cropper.js
index bfd76225b..fc32a0832 100644
--- a/js/lib/avatar_cropper.js
+++ b/js/lib/ui.avatar_cropper.js
@@ -6,18 +6,40 @@ 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() {
- $('#user-avatar-cropper').imgAreaSelect({
+ 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
@@ -35,16 +57,20 @@ elgg.avatarCropper.preview = function(img, selection) {
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 166ca16bc..413078b4f 100644
--- a/js/lib/ui.js
+++ b/js/lib/ui.js
@@ -10,22 +10,18 @@ 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');
$('[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);
$('.elgg-autofocus').focus();
-
- if ($('.elgg-input-date').length) {
- elgg.ui.initDatePicker();
- }
-}
+};
/**
* Toggles an element based on clicking a separate element
@@ -43,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
@@ -63,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();
@@ -105,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.
@@ -143,7 +139,7 @@ elgg.ui.popupClose = function(event) {
$('body').die('click', elgg.ui.popClose);
}
-}
+};
/**
* Toggles a child menu when the parent is clicked
@@ -155,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
@@ -183,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);
}
@@ -215,7 +211,7 @@ elgg.ui.initHoverMenu = function(parent) {
$(".elgg-menu-hover").fadeOut();
}
});
-}
+};
/**
* Calls a confirm() and prevents default if denied.
@@ -240,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';
@@ -261,22 +257,37 @@ 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);
+ 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.
+ });
+ }
+};
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);
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/userpicker.js b/js/lib/ui.userpicker.js
index ae2add53f..669b84cdb 100644
--- a/js/lib/userpicker.js
+++ b/js/lib/ui.userpicker.js
@@ -34,7 +34,7 @@ elgg.userpicker.init = function() {
});
$('.elgg-userpicker-remove').live('click', elgg.userpicker.removeUser);
-}
+};
/**
* Adds a user to the select user list
@@ -59,7 +59,7 @@ elgg.userpicker.addUser = function(event, ui) {
$(this).val('');
event.preventDefault();
-}
+};
/**
* Remove a user from the selected user list
@@ -75,7 +75,7 @@ elgg.userpicker.removeUser = function(event) {
item.remove();
event.preventDefault();
-}
+};
/**
* Render the list item for insertion into the selected user list
@@ -93,10 +93,10 @@ elgg.userpicker.viewUser = function(info) {
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";
+ html += "</div>";
return html;
-}
+};
/**
* Get the parameters to use for autocomplete
@@ -107,11 +107,11 @@ elgg.userpicker.viewUser = function(info) {
* @return Object
*/
elgg.userpicker.getSearchParams = function(obj) {
- if (obj.element.siblings('[name=match_on]').attr('checked')) {
+ 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); \ No newline at end of file
+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/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