aboutsummaryrefslogtreecommitdiff
path: root/js/lib
diff options
context:
space:
mode:
Diffstat (limited to 'js/lib')
-rw-r--r--js/lib/ajax.js10
-rw-r--r--js/lib/autocomplete.js39
-rw-r--r--js/lib/configuration.js9
-rw-r--r--js/lib/elgglib.js241
-rw-r--r--js/lib/events.js52
-rw-r--r--js/lib/hooks.js173
-rw-r--r--js/lib/languages.js29
-rw-r--r--js/lib/pageowner.js8
-rw-r--r--js/lib/prototypes.js22
-rw-r--r--js/lib/security.js63
-rw-r--r--js/lib/session.js43
-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.js218
-rw-r--r--js/lib/ui.river.js14
-rw-r--r--js/lib/ui.userpicker.js117
-rw-r--r--js/lib/ui.widgets.js57
-rw-r--r--js/lib/userpicker.js84
19 files changed, 959 insertions, 310 deletions
diff --git a/js/lib/ajax.js b/js/lib/ajax.js
index 36f76b2c3..b3f39cc42 100644
--- a/js/lib/ajax.js
+++ b/js/lib/ajax.js
@@ -187,14 +187,18 @@ 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
var custom_success = options.success || elgg.nullFunction;
options.success = function(json, two, three, four) {
- if (json.system_messages) {
- elgg.register_error(json.system_messages.errors);
+ if (json && json.system_messages) {
+ elgg.register_error(json.system_messages.error);
elgg.system_message(json.system_messages.success);
}
diff --git a/js/lib/autocomplete.js b/js/lib/autocomplete.js
deleted file mode 100644
index eb59f51aa..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_event_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 d0c852bd0..6e221c957 100644
--- a/js/lib/configuration.js
+++ b/js/lib/configuration.js
@@ -1,7 +1,10 @@
elgg.provide('elgg.config');
-elgg.config.wwwroot = '/';
-
+/**
+ * Returns the current site URL
+ *
+ * @return {String} The site URL.
+ */
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 7a329621b..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();
@@ -238,7 +238,7 @@ elgg.inherit = function(Child, Parent) {
* If the url is already absolute or protocol-relative, no change is made.
*
* elgg.normalize_url(''); // 'http://my.site.com/'
- * elgg.normalize_url('pg/dashboard'); // 'http://my.site.com/pg/dashboard'
+ * elgg.normalize_url('dashboard'); // 'http://my.site.com/dashboard'
* elgg.normalize_url('http://google.com/'); // no change
* elgg.normalize_url('//google.com/'); // no change
*
@@ -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;
}
@@ -272,7 +299,7 @@ elgg.normalize_url = function(url) {
return 'http://' + url;
}
- // 'pg/page/handler', 'mod/plugin/file.php'
+ // 'page/handler', 'mod/plugin/file.php'
else {
// trim off any leading / because the site URL is stored
// with a trailing /
@@ -296,7 +323,7 @@ elgg.system_messages = function(msgs, delay, type) {
var classes = ['elgg-message'],
messages_html = [],
appendMessage = function(msg) {
- messages_html.push('<li class="' + classes.join(' ') + '"><div class="elgg-text"><p>' + msg + '</p></div></li>');
+ messages_html.push('<li class="' + classes.join(' ') + '"><p>' + msg + '</p></li>');
},
systemMessages = $('ul.elgg-system-messages'),
i;
@@ -320,18 +347,17 @@ elgg.system_messages = function(msgs, delay, type) {
msgs.forEach(appendMessage);
- // create the system messages ul element if needed
- if (!systemMessages.length) {
- systemMessages = $('<ul class="elgg-system-messages"></ul>').appendTo('body');
+ if (type != 'error') {
+ $(messages_html.join('')).appendTo(systemMessages)
+ .animate({opacity: '1.0'}, delay).fadeOut('slow');
+ } else {
+ $(messages_html.join('')).appendTo(systemMessages);
}
-
- $(messages_html.join('')).appendTo(systemMessages)
- .animate({opacity: '1.0'}, delay).fadeOut('slow');
};
/**
* Wrapper function for system_messages. Specifies "messages" as the type of message
- * @param {String} msg The message to display
+ * @param {String} msgs The message to display
* @param {Number} delay How long to display the message (milliseconds)
*/
elgg.system_message = function(msgs, delay) {
@@ -340,8 +366,8 @@ elgg.system_message = function(msgs, delay) {
/**
* Wrapper function for system_messages. Specifies "errors" as the type of message
- * @param {String} error The error message to display
- * @param {Number} delay How long to dispaly the error message (milliseconds)
+ * @param {String} errors The error message to display
+ * @param {Number} delay How long to dispaly the error message (milliseconds)
*/
elgg.register_error = function(errors, delay) {
elgg.system_messages(errors, delay, "error");
@@ -355,4 +381,183 @@ elgg.register_error = function(errors, delay) {
*/
elgg.forward = function(url) {
location.href = elgg.normalize_url(url);
-}; \ No newline at end of file
+};
+
+/**
+ * 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:
+ * http://elgg.org/download.php returns ''
+ * http://elgg.org/download.php#id returns #id
+ * http://elgg.org/download.php#.class-name return .class-name
+ * http://elgg.org/download.php#a.class-name return a.class-name
+ *
+ * @param {String} url The URL
+ * @return {String} The selector
+ */
+elgg.getSelectorFromUrlFragment = function(url) {
+ var fragment = url.split('#')[1];
+
+ if (fragment) {
+ // this is a .class or a tag.class
+ if (fragment.indexOf('.') > -1) {
+ return fragment;
+ }
+
+ // this is an id
+ else {
+ return '#' + fragment;
+ }
+ }
+ return '';
+};
+
+/**
+ * Adds child to object[parent] array.
+ *
+ * @param {Object} object The object to add to
+ * @param {String} parent The parent array to add to.
+ * @param {Mixed} value The value
+ */
+elgg.push_to_object_array = function(object, parent, value) {
+ elgg.assertTypeOf('object', object);
+ elgg.assertTypeOf('string', parent);
+
+ if (!(object[parent] instanceof Array)) {
+ object[parent] = []
+ }
+
+ if ($.inArray(value, object[parent]) < 0) {
+ return object[parent].push(value);
+ }
+
+ return false;
+};
+
+/**
+ * Tests if object[parent] contains child
+ *
+ * @param {Object} object The object to add to
+ * @param {String} parent The parent array to add to.
+ * @param {Mixed} value The value
+ */
+elgg.is_in_object_array = function(object, parent, value) {
+ elgg.assertTypeOf('object', object);
+ elgg.assertTypeOf('string', parent);
+
+ return typeof(object[parent]) != 'undefined' && $.inArray(value, object[parent]) >= 0;
+};
+
+/**
+ * Triggers the init hook when the library is ready
+ *
+ * Current requirements:
+ * - DOM is ready
+ * - languages loaded
+ *
+ */
+elgg.initWhenReady = function() {
+ if (elgg.config.languageReady && elgg.config.domReady) {
+ elgg.trigger_hook('init', 'system');
+ elgg.trigger_hook('ready', 'system');
+ }
+};
diff --git a/js/lib/events.js b/js/lib/events.js
deleted file mode 100644
index c1aa6fd9a..000000000
--- a/js/lib/events.js
+++ /dev/null
@@ -1,52 +0,0 @@
-elgg.provide('elgg.config.events');
-
-/**
- *
- */
-elgg.register_event_handler = function(event_name, event_type, handler, priority) {
- elgg.assertTypeOf('string', event_name);
- elgg.assertTypeOf('string', event_type);
- elgg.assertTypeOf('function', handler);
-
- if (!event_name || !event_type) {
- return false;
- }
-
- var events = elgg.config.events;
-
- elgg.provide(event_name + '.' + event_type, events);
-
-
- if (!(events[event_name][event_type] instanceof elgg.ElggPriorityList)) {
- events[event_name][event_type] = new elgg.ElggPriorityList();
- }
-
- return events[event_name][event_type].insert(handler, priority);
-};
-
-/**
- *
- */
-elgg.trigger_event = function(event_name, event_type, opt_object) {
- elgg.assertTypeOf('string', event_name);
- elgg.assertTypeOf('string', event_type);
-
- var events = elgg.config.events,
- callEventHandler = function(handler) {
- return handler(event_name, event_type, opt_object) !== false;
- };
-
- elgg.provide(event_name + '.' + event_type, events);
- elgg.provide('all.' + event_type, events);
- elgg.provide(event_name + '.all', events);
- elgg.provide('all.all', events);
-
- return [
- events[event_name][event_type],
- events['all'][event_type],
- events[event_name]['all'],
- events['all']['all']
- ].every(function(handlers) {
- return !(handlers instanceof elgg.ElggPriorityList) || handlers.every(callEventHandler);
- });
-}; \ No newline at end of file
diff --git a/js/lib/hooks.js b/js/lib/hooks.js
new file mode 100644
index 000000000..5e1808e22
--- /dev/null
+++ b/js/lib/hooks.js
@@ -0,0 +1,173 @@
+/*
+ * Javascript hook interface
+ */
+
+elgg.provide('elgg.config.hooks');
+elgg.provide('elgg.config.instant_hooks');
+elgg.provide('elgg.config.triggered_hooks');
+
+/**
+ * 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
+ * @param {Number} priority Priority to call the event handler
+ * @return {Bool}
+ */
+elgg.register_hook_handler = function(name, type, handler, priority) {
+ elgg.assertTypeOf('string', name);
+ elgg.assertTypeOf('string', type);
+ elgg.assertTypeOf('function', handler);
+
+ if (!name || !type) {
+ return false;
+ }
+
+ var priorities = elgg.config.hooks;
+
+ elgg.provide(name + '.' + type, priorities);
+
+ if (!(priorities[name][type] instanceof elgg.ElggPriorityList)) {
+ 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);
+};
+
+/**
+ * Emits a hook.
+ *
+ * Loops through all registered hooks and calls the handler functions in order.
+ * 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 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)
+ * all names, specific type
+ * specific name, all types
+ * all names, all types
+ *
+ * @param {String} name Name of the hook to emit
+ * @param {String} type Type of the hook to emit
+ * @param {Object} params Optional parameters to pass to the handlers
+ * @param {Object} value Initial value of the return. Can be mangled by handlers
+ *
+ * @return {Bool}
+ */
+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;
+
+ var hooks = elgg.config.hooks,
+ tempReturnValue = null,
+ returnValue = value,
+ callHookHandler = function(handler) {
+ tempReturnValue = handler(name, type, params, value);
+ };
+
+ elgg.provide(name + '.' + type, hooks);
+ elgg.provide('all.' + type, hooks);
+ elgg.provide(name + '.all', hooks);
+ elgg.provide('all.all', hooks);
+
+ 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;
+};
+
+/**
+ * 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 e464fc0ff..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';
/**
@@ -25,15 +26,21 @@ elgg.add_translation = function(lang, translations) {
*/
elgg.reload_all_translations = function(language) {
var lang = language || elgg.get_language();
- elgg.getJSON('pg/js/languages%252f' + lang + '.js', {
- data: {
- 'viewtype': 'default',
- 'lastcache': elgg.config.lastcache
- },
- 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);
};
/**
@@ -79,11 +86,11 @@ elgg.echo = function(key, argv, language) {
return vsprintf(map[key], argv);
}
- return undefined;
+ return key;
};
elgg.config.translations.init = function() {
elgg.reload_all_translations();
};
-elgg.register_event_handler('boot', 'system', elgg.config.translations.init); \ No newline at end of file
+elgg.register_hook_handler('boot', 'system', elgg.config.translations.init); \ No newline at end of file
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/prototypes.js b/js/lib/prototypes.js
index 0f0014818..cb6184097 100644
--- a/js/lib/prototypes.js
+++ b/js/lib/prototypes.js
@@ -1,5 +1,14 @@
/**
+ * Interates through each element of an array and calls a callback function.
+ * The callback should accept the following arguments:
+ * element - The current element
+ * index - The current index
*
+ * This is different to Array.forEach in that if the callback returns false, the loop returns
+ * immediately without processing the remaining elements.
+ *
+ * @param {Function} callback
+ * @return {Bool}
*/
if (!Array.prototype.every) {
Array.prototype.every = function(callback) {
@@ -16,7 +25,16 @@ if (!Array.prototype.every) {
}
/**
+ * Interates through each element of an array and calls callback a function.
+ * The callback should accept the following arguments:
+ * element - The current element
+ * index - The current index
+ *
+ * This is different to Array.every in that the callback's return value is ignored and every
+ * element of the array will be parsed.
*
+ * @param {Function} callback
+ * @return {Void}
*/
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback) {
@@ -31,7 +49,11 @@ if (!Array.prototype.forEach) {
}
/**
+ * Left trim
*
+ * Removes a character from the left side of a string.
+ * @param {String} str The character to remove
+ * @return {String}
*/
if (!String.prototype.ltrim) {
String.prototype.ltrim = function(str) {
diff --git a/js/lib/security.js b/js/lib/security.js
index def775c05..9c12f8586 100644
--- a/js/lib/security.js
+++ b/js/lib/security.js
@@ -5,7 +5,17 @@ elgg.provide('elgg.security');
elgg.security.token = {};
-elgg.security.setToken = function(json) {
+elgg.security.tokenRefreshFailed = false;
+
+elgg.security.tokenRefreshTimer = null;
+
+/**
+ * Sets the currently active security token and updates all forms and links on the current page.
+ *
+ * @param {Object} json The json representation of a token containing __elgg_ts and __elgg_token
+ * @return {Void}
+ */
+elgg.security.setToken = function(json) {
//update the convenience object
elgg.security.token = json;
@@ -13,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);
@@ -22,19 +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) {
- elgg.security.setToken(data.output);
+ if (data && data.output.__elgg_ts && data.output.__elgg_token) {
+ elgg.security.setToken(data.output);
+ } else {
+ clearInterval(elgg.security.tokenRefreshTimer);
+ }
});
};
/**
- * 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
@@ -44,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
@@ -69,8 +100,8 @@ elgg.security.addToken = function(data) {
};
elgg.security.init = function() {
- //refresh security token every 5 minutes
- 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_event_handler('boot', 'system', elgg.security.init); \ No newline at end of file
+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 a1454aa50..a8d52733c 100644
--- a/js/lib/session.js
+++ b/js/lib/session.js
@@ -1,5 +1,5 @@
/**
- * @todo comment
+ * Provides session methods.
*/
elgg.provide('elgg.session');
@@ -8,14 +8,15 @@ elgg.provide('elgg.session');
* @param {string} name
* @param {string} value
* @param {Object} options
+ *
* {number|Date} options[expires]
* {string} options[path]
* {string} options[domain]
* {boolean} options[secure]
*
- * @return {string} The value of the cookie, if only name is specified
+ * @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()
@@ -46,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
@@ -81,6 +80,8 @@ elgg.session.cookie = function (name, value, options) {
};
/**
+ * Returns the object of the user logged in.
+ *
* @return {ElggUser} The logged in user
*/
elgg.get_logged_in_user_entity = function() {
@@ -88,6 +89,8 @@ elgg.get_logged_in_user_entity = function() {
};
/**
+ * Returns the GUID of the logged in user or 0.
+ *
* @return {number} The GUID of the logged in user
*/
elgg.get_logged_in_user_guid = function() {
@@ -96,6 +99,8 @@ elgg.get_logged_in_user_guid = function() {
};
/**
+ * Returns if a user is logged in.
+ *
* @return {boolean} Whether there is a user logged in
*/
elgg.is_logged_in = function() {
@@ -103,6 +108,8 @@ elgg.is_logged_in = function() {
};
/**
+ * Returns if the currently logged in user is an admin.
+ *
* @return {boolean} Whether there is an admin logged in
*/
elgg.is_admin_logged_in = function() {
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 3118bb408..413078b4f 100644
--- a/js/lib/ui.js
+++ b/js/lib/ui.js
@@ -1,7 +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
@@ -10,22 +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);
- $('.elgg-menu-page .elgg-menu-parent').live('click', elgg.ui.toggleMenu);
+ $('[rel=popup]').live('click', elgg.ui.popupOpen);
- $('.elgg-like-toggle').live('click', elgg.ui.toggleLikes);
+ $('.elgg-menu-page .elgg-menu-parent').live('click', elgg.ui.toggleMenu);
$('.elgg-requires-confirmation').live('click', elgg.ui.requiresConfirmation);
-}
+
+ $('.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 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
@@ -33,49 +35,123 @@ 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');
-}
+};
/**
- * Toggles a child menu when the parent is clicked
+ * Pops up an element based on clicking a separate element
+ *
+ * Set the rel="popup" on the popper and set the href to target the
+ * item you want to toggle (<a rel="popup" href="#id-of-target">)
+ *
+ * This function emits the getOptions, ui.popup hook that plugins can register for to provide custom
+ * positioning for elements. The handler is passed the following params:
+ * 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.toggleMenu = function(event) {
- $(this).siblings().slideToggle('medium');
- $(this).toggleClass('elgg-menu-closed elgg-menu-opened');
+elgg.ui.popupOpen = function(event) {
event.preventDefault();
-}
+ event.stopPropagation();
+
+ var target = elgg.getSelectorFromUrlFragment($(this).toggleClass('elgg-state-active').attr('href'));
+ var $target = $(target);
+
+ // emit a hook to allow plugins to position and control popups
+ var params = {
+ targetSelector: target,
+ target: $target,
+ source: $(this)
+ };
+
+ var options = {
+ my: 'center top',
+ at: 'center bottom',
+ of: $(this),
+ collision: 'fit fit'
+ }
+
+ options = elgg.trigger_hook('getOptions', 'ui.popup', params, options);
+
+ // allow plugins to cancel event
+ if (!options) {
+ return;
+ }
+
+ // hide if already open
+ if ($target.is(':visible')) {
+ $target.fadeOut();
+ $('body').die('click', elgg.ui.popupClose);
+ return;
+ }
+
+ $target.appendTo('body')
+ .fadeIn()
+ .position(options);
+
+ $('body')
+ .die('click', elgg.ui.popupClose)
+ .live('click', elgg.ui.popupClose);
+};
/**
- * Toggles the likes list
+ * Catches clicks that aren't in a popup and closes all popups.
+ */
+elgg.ui.popupClose = function(event) {
+ $eventTarget = $(event.target);
+ var inTarget = false;
+ var $popups = $('[rel=popup]');
+
+ // if the click event target isn't in a popup target, fade all of them out.
+ $popups.each(function(i, e) {
+ var target = elgg.getSelectorFromUrlFragment($(e).attr('href')) + ':visible';
+ var $target = $(target);
+
+ if (!$target.is(':visible')) {
+ return;
+ }
+
+ // didn't click inside the target
+ if ($eventTarget.closest(target).length > 0) {
+ inTarget = true;
+ return false;
+ }
+ });
+
+ if (!inTarget) {
+ $popups.each(function(i, e) {
+ var $e = $(e);
+ var $target = $(elgg.getSelectorFromUrlFragment($e.attr('href')) + ':visible');
+ if ($target.length > 0) {
+ $target.fadeOut();
+ $e.removeClass('elgg-state-active');
+ }
+ });
+
+ $('body').die('click', elgg.ui.popClose);
+ }
+};
+
+/**
+ * Toggles a child menu when the parent is clicked
*
* @param {Object} event
* @return void
*/
-elgg.ui.toggleLikes = function(event) {
- var $list = $(this).next(".elgg-likes-list");
- var position = $(this).position();
- var startTop = position.top;
- var stopTop = position.top - $list.height();
- if ($list.css('display') == 'none') {
- $('.elgg-likes-list').fadeOut();
-
- $list.css('top', startTop);
- $list.css('left', position.left - $list.width());
- $list.animate({opacity: "toggle", top: stopTop}, 500);
-
- $list.click(function(event) {
- $list.fadeOut();
- });
- } else {
- $list.animate({opacity: "toggle", top: startTop}, 500);
- }
+elgg.ui.toggleMenu = function(event) {
+ $(this).siblings().slideToggle('medium');
+ $(this).toggleClass('elgg-menu-closed elgg-menu-opened');
event.preventDefault();
-}
+};
/**
* Initialize the hover menu
@@ -103,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);
}
@@ -135,19 +211,83 @@ elgg.ui.initHoverMenu = function(parent) {
$(".elgg-menu-hover").fadeOut();
}
});
-}
+};
/**
* Calls a confirm() and prevents default if denied.
*
- * @param {Object} event
+ * @param {Object} e
* @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();
}
};
-elgg.register_event_handler('init', 'system', elgg.ui.init); \ No newline at end of file
+/**
+ * Repositions the login popup
+ *
+ * @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
+ *
+ * @return {Object}
+ */
+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;
+};
+
+/**
+ * Initialize the date picker
+ *
+ * Uses the class .elgg-input-date as the selector.
+ *
+ * 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.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.
+ });
+ }
+};
+
+elgg.register_hook_handler('init', 'system', elgg.ui.init);
+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 1acc22928..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_event_handler('init', 'system', elgg.ui.widgets.init);
+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 2b3d78d6b..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 past 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('pg/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_event_handler('init', 'system', elgg.userpicker.init); \ No newline at end of file