aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/lib/autocomplete.js31
-rw-r--r--js/lib/avatar_cropper.js50
-rw-r--r--js/lib/elgglib.js175
-rw-r--r--js/lib/hooks.js75
-rw-r--r--js/lib/security.js20
-rw-r--r--js/lib/ui.js2
-rw-r--r--js/lib/userpicker.js123
7 files changed, 393 insertions, 83 deletions
diff --git a/js/lib/autocomplete.js b/js/lib/autocomplete.js
index 917326d4f..46d72d146 100644
--- a/js/lib/autocomplete.js
+++ b/js/lib/autocomplete.js
@@ -5,35 +5,10 @@ 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);
- }
+ source: elgg.autocomplete.url, //gets set by input/autocomplete view
+ minLength: 2,
+ html: "html"
})
-
- //@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/avatar_cropper.js b/js/lib/avatar_cropper.js
new file mode 100644
index 000000000..bfd76225b
--- /dev/null
+++ b/js/lib/avatar_cropper.js
@@ -0,0 +1,50 @@
+/**
+ * Avatar cropping
+ */
+
+elgg.provide('elgg.avatarCropper');
+
+/**
+ * Register the avatar cropper.
+ */
+elgg.avatarCropper.init = function() {
+ $('#user-avatar-cropper').imgAreaSelect({
+ selectionOpacity: 0,
+ aspectRatio: '1:1',
+ onSelectEnd: elgg.avatarCropper.selectChange,
+ onSelectChange: elgg.avatarCropper.preview
+ });
+}
+
+/**
+ * Handler for changing select area.
+ */
+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
+ */
+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/elgglib.js b/js/lib/elgglib.js
index 9a372738d..0f17eeced 100644
--- a/js/lib/elgglib.js
+++ b/js/lib/elgglib.js
@@ -353,6 +353,145 @@ 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 = {
+ 'mailto': {
+ 4: "scheme",
+ 5: "user",
+ 6: "host",
+ 9: "path",
+ 12: "query",
+ 13: "fragment"
+ },
+
+ 'standard': {
+ 1: "scheme",
+ 4: "user",
+ 5: "pass",
+ 6: "host",
+ 7: "port",
+ 9: "path",
+ 12: "query",
+ 13: "fragment"
+ }
+ },
+ results = {},
+ match_keys,
+ is_mailto = false;
+
+ 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'];
+ }
+
+ for (var i in match_keys) {
+ if (matches[i]) {
+ results[match_keys[i]] = matches[i];
+ }
+ }
+
+ // 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;
+ }
+
+ for (var prop in results) {
+ new_results[prop] = results[prop];
+ }
+
+ results = new_results;
+ }
+
+ 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:
@@ -382,6 +521,42 @@ elgg.getSelectorFromUrlFragment = function(url) {
};
/**
+ * 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 (object[parent].indexOf(value) < 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' && object[parent].indexOf(value) >= 0;
+}
+
+/**
* Triggers the init hook when the library is ready
*
* Current requirements:
diff --git a/js/lib/hooks.js b/js/lib/hooks.js
index ab3a8a224..edfd28f24 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;
@@ -101,4 +116,58 @@ elgg.trigger_hook = function(name, type, params, value) {
});
return (tempReturnValue !== null) ? tempReturnValue : returnValue;
-}; \ No newline at end of file
+};
+
+/**
+ * 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/security.js b/js/lib/security.js
index 486347b88..d14ddff95 100644
--- a/js/lib/security.js
+++ b/js/lib/security.js
@@ -70,14 +70,22 @@ elgg.security.addToken = function(data) {
// 'http://example.com?data=sofar'
if (elgg.isString(data)) {
- var args = [];
- if (data) {
- args.push(data);
+ var args = {},
+ base = '';
+
+ // check for query strings
+ if (data.indexOf('?') != -1) {
+ var split = data.split('?');
+ base = split[0];
+ args = elgg.parse_str(split[1]);
+ } else {
+ base = data;
}
- 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.js b/js/lib/ui.js
index 57378a4d6..166ca16bc 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
@@ -277,6 +278,5 @@ elgg.ui.initDatePicker = function() {
});
}
-
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
diff --git a/js/lib/userpicker.js b/js/lib/userpicker.js
index 826bf21a0..ae2add53f 100644
--- a/js/lib/userpicker.js
+++ b/js/lib/userpicker.js
@@ -1,14 +1,22 @@
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() {
-
- var _this = this;
-
+
$(this).autocomplete({
source: function(request, response) {
+
var params = elgg.userpicker.getSearchParams(this);
elgg.get('livesearch', {
@@ -20,64 +28,89 @@ elgg.userpicker.init = function() {
});
},
minLength: 2,
+ html: "html",
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-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 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);
+ var guid = item.find('[name="members[]"]').val();
+ delete elgg.userpicker.userList[guid];
- // 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);
+ item.remove();
+ event.preventDefault();
+}
- $(this).val('');
- }
-};
+/**
+ * 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>";
-elgg.userpicker.removeUser = function(link, guid) {
- $(link).closest('.elgg-user-picker-entries > li').remove();
-};
+ 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;
+}
-elgg.userpicker.getSearchParams = function(e) {
- if ($(e).closest('.elgg-user-picker').find('[name=match_on]').attr('checked')) {
- return {'match_on[]': 'friends', 'term' : e.term};
+/**
+ * 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' : e.term};
+ return {'match_on[]': 'users', 'term' : obj.term};
}
}