]> gitweb.fluxo.info Git - lorea/elgg.git/commitdiff
Refs #2538: Pulled in elgg JS object and unit tests
authorewinslow <ewinslow@36083f99-b078-4883-b0ff-0f9b5a30f544>
Mon, 1 Nov 2010 07:34:24 +0000 (07:34 +0000)
committerewinslow <ewinslow@36083f99-b078-4883-b0ff-0f9b5a30f544>
Mon, 1 Nov 2010 07:34:24 +0000 (07:34 +0000)
git-svn-id: http://code.elgg.org/elgg/trunk@7173 36083f99-b078-4883-b0ff-0f9b5a30f544

13 files changed:
engine/js/lib/ajax.js [new file with mode: 0644]
engine/js/lib/configuration.js [new file with mode: 0644]
engine/js/lib/elgglib.js [new file with mode: 0644]
engine/js/lib/security.js [new file with mode: 0644]
engine/js/lib/session.js [new file with mode: 0644]
engine/js/lib/ui.js [new file with mode: 0644]
engine/js/lib/ui.widgets.js [new file with mode: 0644]
engine/js/tests/ElggAjaxOptionsTest.js [new file with mode: 0644]
engine/js/tests/ElggAjaxTest.js [new file with mode: 0644]
engine/js/tests/ElggLibTest.js [new file with mode: 0644]
engine/js/tests/ElggSecurityTest.js [new file with mode: 0644]
engine/js/tests/ElggSessionTest.js [new file with mode: 0644]
jsTestDriver.conf [new file with mode: 0644]

diff --git a/engine/js/lib/ajax.js b/engine/js/lib/ajax.js
new file mode 100644 (file)
index 0000000..bce0d31
--- /dev/null
@@ -0,0 +1,252 @@
+elgg.provide('elgg.ajax');\r
+\r
+/**\r
+ * @author Evan Winslow\r
+ * Provides a bunch of useful shortcut functions for making ajax calls\r
+ */\r
+\r
+/**\r
+ * Wrapper function for jQuery.ajax which ensures that the url being called\r
+ * is relative to the elgg site root.\r
+ * \r
+ * You would most likely use elgg.get or elgg.post, rather than this function\r
+ * \r
+ * @param {string} url Optionally specify the url as the first argument\r
+ * @param {Object} options Optional. {@see jQuery#ajax}\r
+ * @return {XmlHttpRequest}\r
+ */\r
+elgg.ajax = function(url, options) {\r
+       options = elgg.ajax.handleOptions(url, options);\r
+       \r
+       options.url = elgg.extendUrl(options.url);\r
+       return $.ajax(options);\r
+};\r
+/**\r
+ * @const\r
+ */\r
+elgg.ajax.SUCCESS = 0;\r
+\r
+/**\r
+ * @const\r
+ */\r
+elgg.ajax.ERROR = -1;\r
+\r
+/**\r
+ * Handle optional arguments and return the resulting options object\r
+ * \r
+ * @param url\r
+ * @param options\r
+ * @return {Object}\r
+ * @private\r
+ */\r
+elgg.ajax.handleOptions = function(url, options) {\r
+       //elgg.ajax('example/file.php', {...});\r
+       if(typeof url == 'string') {\r
+               options = options || {};\r
+       \r
+       //elgg.ajax({...});\r
+       } else {\r
+               options = url || {};\r
+               url = options.url;\r
+       }\r
+       \r
+       var data_only = true;\r
+\r
+       //elgg.ajax('example/file.php', function() {...});\r
+       if (typeof options == 'function') {\r
+               data_only = false;\r
+               options = {success: options};\r
+       }\r
+       \r
+       //elgg.ajax('example/file.php', {data:{...}});\r
+       if(options.data) {\r
+               data_only = false;\r
+       } else {\r
+               for (var member in options) {\r
+                       //elgg.ajax('example/file.php', {callback:function(){...}});\r
+                       if(typeof options[member] == 'function') {\r
+                               data_only = false;\r
+                       }\r
+               }\r
+       }\r
+\r
+       //elgg.ajax('example/file.php', {notdata:notfunc});\r
+       if (data_only) {\r
+               var data = options;\r
+               options = {data: data};\r
+       }\r
+       \r
+       if (url) {\r
+               options.url = url;\r
+       }\r
+       \r
+       return options;\r
+};\r
+\r
+/**\r
+ * Wrapper function for elgg.ajax which forces the request type to 'get.'\r
+ * \r
+ * @param {string} url Optionally specify the url as the first argument\r
+ * @param {Object} options {@see jQuery#ajax}\r
+ * @return {XmlHttpRequest}\r
+ */\r
+elgg.get = function(url, options) {\r
+       options = elgg.ajax.handleOptions(url, options);\r
+       \r
+       options.type = 'get';\r
+       return elgg.ajax(options);\r
+};\r
+\r
+/**\r
+ * Wrapper function for elgg.get which forces the dataType to 'json.'\r
+ * \r
+ * @param {string} url Optionally specify the url as the first argument\r
+ * @param {Object} options {@see jQuery#ajax}\r
+ * @return {XmlHttpRequest}\r
+ */\r
+elgg.getJSON = function(url, options) {\r
+       options = elgg.ajax.handleOptions(url, options);\r
+       \r
+       options.dataType = 'json';\r
+       return elgg.get(options);\r
+};\r
+\r
+/**\r
+ * Wrapper function for elgg.ajax which forces the request type to 'post.'\r
+ * \r
+ * @param {string} url Optionally specify the url as the first argument\r
+ * @param {Object} options {@see jQuery#ajax}\r
+ * @return {XmlHttpRequest}\r
+ */\r
+elgg.post = function(url, options) {\r
+       options = elgg.ajax.handleOptions(url, options);\r
+       \r
+       options.type = 'post';\r
+       return elgg.ajax(options);\r
+};\r
+\r
+/**\r
+ * Perform an action via ajax\r
+ * \r
+ * @example Usage 1:\r
+ * At its simplest, only the action name is required (and anything more than the\r
+ * action name will be invalid).\r
+ * <pre>\r
+ * elgg.action('name/of/action');\r
+ * </pre>\r
+ * Note that it will *not* love you if you specify the full url as the action\r
+ * (i.e. elgg.yoursite.com/action/name/of/action), but why would you want to do\r
+ * that anyway, when you can just specify the action name?\r
+ * \r
+ * @example Usage 2:\r
+ * If you want to pass some data along with it, use the second parameter\r
+ * <pre>\r
+ * elgg.action('friend/add', { friend: some_guid });\r
+ * </pre>\r
+ * \r
+ * @example Usage 3:\r
+ * Of course, you will have no control over what happens when the request\r
+ * completes if you do it like that, so there's also the most verbose method\r
+ * <pre>\r
+ * elgg.action('friend/add', {\r
+ *     data: {\r
+ *         friend: some_guid\r
+ *     },\r
+ *     success: function(json) {\r
+ *         //do something\r
+ *     },\r
+ * }\r
+ * </pre>\r
+ * You can pass any of your favorite $.ajax arguments into this second parameter.\r
+ * \r
+ * Note: If you intend to use the second field in the "verbose" way, you must\r
+ * specify a callback method or the data parameter.  If you do not, elgg.action\r
+ * will think you mean to send the second parameter as data.\r
+ * \r
+ * @param {String} action The action to call.\r
+ * @param {Object} options {@see jQuery#ajax}\r
+ * @return {XMLHttpRequest}\r
+ */\r
+elgg.action = function(action, options) {\r
+       if(!action) {\r
+               throw new TypeError("action must be specified");\r
+       } else if (typeof action != 'string') {\r
+               throw new TypeError("action must be a string");\r
+       }\r
+       \r
+       options = elgg.ajax.handleOptions('action/' + action, options);\r
+       \r
+       options.data = elgg.security.addToken(options.data);\r
+       options.dataType = 'json';\r
+       \r
+       //Always display system messages after actions\r
+       var custom_success = options.success || function(){};\r
+       options.success = function(json, two, three, four) {\r
+               if (json.system_messages) {\r
+                       elgg.register_error(json.system_messages.errors);\r
+                       elgg.system_message(json.system_messages.messages);\r
+               }\r
+               custom_success(json, two, three, four);\r
+       };\r
+       \r
+       return elgg.post(options);\r
+};\r
+\r
+/**\r
+ * Make an API call\r
+ * \r
+ * @example Usage:\r
+ * <pre>\r
+ * elgg.api('system.api.list', {\r
+ *     success: function(data) {\r
+ *         console.log(data);\r
+ *     }\r
+ * });\r
+ * </pre>\r
+ * \r
+ * @param {String} method The API method to be called\r
+ * @param {Object} options {@see jQuery#ajax}\r
+ * @return {XmlHttpRequest}\r
+ */\r
+elgg.api = function(method, options) {\r
+       if (!method) {\r
+               throw new TypeError("method must be specified");\r
+       } else if (typeof method != 'string') {\r
+               throw new TypeError("method must be a string");\r
+       }\r
+       \r
+       var defaults = {\r
+               dataType: 'json',\r
+               data: {}\r
+       };\r
+       \r
+       options = elgg.ajax.handleOptions(method, options);\r
+       options = $.extend(defaults, options);\r
+       \r
+       options.url = 'services/api/rest/' + options.dataType + '/';\r
+       options.data.method = method;\r
+       \r
+       return elgg.ajax(options);\r
+};\r
+\r
+/**\r
+ * @param {string} selector a jQuery selector\r
+ * @param {Function} complete A function to execute when the refresh is done\r
+ * @return {XMLHttpRequest}\r
+ */\r
+elgg.refresh = function(selector, complete) {\r
+       $(selector).html('<div align="center" class="ajax_loader"></div>');\r
+       return $(selector).load(location.href + ' ' + selector + ' > *', complete);\r
+};\r
+\r
+/**\r
+ * @param {string} selector a jQuery selector (usually an #id)\r
+ * @param {number} interval The refresh interval in seconds\r
+ * @param {Function} complete A function to execute when the refresh is done\r
+ * @return {number} The interval identifier\r
+ */\r
+elgg.feed = function(selector, interval, complete) {\r
+       return setInterval(function() {\r
+               elgg.refresh(selector, complete);\r
+       }, interval);\r
+};
\ No newline at end of file
diff --git a/engine/js/lib/configuration.js b/engine/js/lib/configuration.js
new file mode 100644 (file)
index 0000000..8ed3261
--- /dev/null
@@ -0,0 +1,3 @@
+elgg.provide('elgg.config');
+
+elgg.config.wwwroot = '/';
\ No newline at end of file
diff --git a/engine/js/lib/elgglib.js b/engine/js/lib/elgglib.js
new file mode 100644 (file)
index 0000000..c0ce69f
--- /dev/null
@@ -0,0 +1,165 @@
+/**\r
+ * @author Evan Winslow\r
+ * \r
+ * $Id: elgglib.js 76 2010-07-17 02:08:02Z evan.b.winslow $\r
+ */\r
+\r
+/**\r
+ * @namespace Namespace for elgg javascript functions\r
+ */\r
+var elgg = elgg || {};\r
+\r
+elgg.init = function() {\r
+       //if the user clicks a system message, make it disappear\r
+       $('.elgg_system_message').live('click', function() {\r
+               $(this).stop().fadeOut('fast');\r
+       });\r
+};\r
+\r
+/**\r
+ * Pointer to the global context\r
+ * {@see elgg.require} and {@see elgg.provide}\r
+ */\r
+elgg.global = this;\r
+\r
+/**\r
+ * Throw an error if the required package isn't present\r
+ * \r
+ * @param {String} pkg The required package (e.g., 'elgg.package')\r
+ */\r
+elgg.require = function(pkg) {\r
+       var parts = pkg.split('.'),\r
+               cur = elgg.global,\r
+               part;\r
+\r
+       for (var i = 0; i < parts.length; i++) {\r
+               part = parts[i];\r
+               cur = cur[part];\r
+               if(typeof cur == 'undefined') {\r
+                       throw new Error("Missing package: " + pkg);\r
+               }\r
+       }\r
+};\r
+\r
+/**\r
+ * Generate the skeleton for a package.\r
+ * \r
+ * <pre>\r
+ * elgg.provide('elgg.package.subpackage');\r
+ * </pre>\r
+ * \r
+ * is equivalent to\r
+ * \r
+ * <pre>\r
+ * elgg = elgg || {};\r
+ * elgg.package = elgg.package || {};\r
+ * elgg.package.subpackage = elgg.package.subpackage || {};\r
+ * </pre>\r
+ */\r
+elgg.provide = function(pkg) {\r
+       var parts = pkg.split('.'),\r
+               cur = elgg.global,\r
+               part;\r
+       \r
+       for (var i = 0; i < parts.length; i++) {\r
+               part = parts[i];\r
+               cur[part] = cur[part] || {};\r
+               cur = cur[part];\r
+       }\r
+};\r
+\r
+/**\r
+ * Prepend elgg.config.wwwroot to a url if the url doesn't already have it.\r
+ * \r
+ * @param {String} url The url to extend\r
+ * @return {String} The extended url\r
+ * @private\r
+ */\r
+elgg.extendUrl = function(url) {\r
+       url = url || '';\r
+       if(url.indexOf(elgg.config.wwwroot) == -1) {\r
+               url = elgg.config.wwwroot + url;\r
+       }\r
+       \r
+       return url;\r
+};\r
+\r
+/**\r
+ * Displays system messages via javascript rather than php.\r
+ * \r
+ * @param {String} msgs The message we want to display\r
+ * @param {Number} delay The amount of time to display the message in milliseconds. Defaults to 6 seconds.\r
+ * @param {String} type The type of message (typically 'error' or 'message')\r
+ * @private\r
+ */\r
+elgg.system_messages = function(msgs, delay, type) {\r
+       if (msgs == undefined) {\r
+               return;\r
+       }\r
+       \r
+       //validate delay.  Must be a positive integer. \r
+       delay = parseInt(delay);\r
+       if (isNaN(delay) || delay <= 0) {\r
+               delay = 6000;\r
+       }\r
+       \r
+       var messages_class = 'messages';\r
+       if (type == 'error') {\r
+               messages_class = 'messages_error';\r
+       }\r
+\r
+       //Handle non-arrays\r
+       if (msgs.constructor.toString().indexOf("Array") == -1) {\r
+               msgs = [msgs];\r
+       }\r
+       \r
+       var messages_html = '<div class="' + messages_class + '">' \r
+               + '<span class="closeMessages">'\r
+                       + '<a href="#">' \r
+                               + elgg.echo('systemmessages:dismiss')\r
+                       + '</a>'\r
+               + '</span>'\r
+               + '<p>' + msgs.join('</p><p>') + '</p>'\r
+       + '</div>';\r
+       \r
+       $(messages_html).insertAfter('#layout_header').click(function () {\r
+               $(this).stop().fadeOut('slow');\r
+               return false;\r
+       }).show().animate({opacity:'1.0'},delay).fadeOut('slow');\r
+};\r
+\r
+/**\r
+ * Wrapper function for system_messages. Specifies "messages" as the type of message\r
+ * @param {String} msg The message to display\r
+ * @param {Number} delay How long to display the message (milliseconds)\r
+ */\r
+elgg.system_message = function(msgs, delay) {\r
+       elgg.system_messages(msgs, delay, "message");\r
+};\r
+\r
+/**\r
+ * Wrapper function for system_messages.  Specifies "errors" as the type of message\r
+ * @param {String} error The error message to display\r
+ * @param {Number} delay How long to dispaly the error message (milliseconds)\r
+ */\r
+elgg.register_error = function(errors, delay) {\r
+       elgg.system_messages(errors, delay, "error");\r
+};\r
+\r
+/**\r
+ * Meant to mimic the php forward() function by simply redirecting the\r
+ * user to another page.\r
+ * \r
+ * @param {String} url The url to forward to\r
+ */\r
+elgg.forward = function(url) {\r
+       location.href = elgg.extendUrl(url);\r
+};\r
+\r
+/**\r
+ * Initialise Elgg\r
+ * @todo How should plugins, etc. initialize themselves?\r
+ */\r
+$(function() {\r
+       elgg.init();\r
+});\r
diff --git a/engine/js/lib/security.js b/engine/js/lib/security.js
new file mode 100644 (file)
index 0000000..f449411
--- /dev/null
@@ -0,0 +1,72 @@
+/**\r
+ * Hold security-related data here\r
+ */\r
+elgg.provide('elgg.security');\r
+\r
+elgg.security.token = {};\r
+\r
+elgg.security.init = function() {\r
+       //refresh security token every 5 minutes\r
+       setInterval(elgg.security.refreshToken, elgg.security.interval);\r
+};\r
+\r
+elgg.security.setToken = function(json) {\r
+       //update the convenience object\r
+       elgg.security.token = json;\r
+       \r
+       //also update all forms\r
+       $('[name=__elgg_ts]').val(json.__elgg_ts);\r
+       $('[name=__elgg_token]').val(json.__elgg_token);\r
+       \r
+       //also update all links\r
+       $('[href]').each(function() {\r
+               this.href = this.href\r
+                       .replace(/__elgg_ts=\d*/, '__elgg_ts=' + json.__elgg_ts)\r
+                       .replace(/__elgg_token=[0-9a-f]*/, '__elgg_token=' + json.__elgg_token);\r
+       });\r
+};\r
+\r
+/**\r
+ * Security tokens time out, so lets refresh those every so often\r
+ * @todo handle error and bad return data\r
+ */\r
+elgg.security.refreshToken = function() {\r
+       elgg.action('ajax/securitytoken', function(data) {\r
+               elgg.security.setToken(data.output);\r
+       });\r
+};\r
+\r
+\r
+/**\r
+ * Add elgg action tokens to an object or string (assumed to be url data)\r
+ * \r
+ * @param {Object|string} data\r
+ * @return {Object} The new data object including action tokens\r
+ * @private\r
+ */\r
+elgg.security.addToken = function(data) {\r
+\r
+       //addToken('data=sofar')\r
+       if (typeof data == 'string') {\r
+               var args = [];\r
+               if(data) {\r
+                       args.push(data);\r
+               }\r
+               args.push("__elgg_ts=" + elgg.security.token.__elgg_ts);\r
+               args.push("__elgg_token=" + elgg.security.token.__elgg_token)\r
+               \r
+               return args.join('&');\r
+       }\r
+       \r
+       //addToken({...})\r
+       if (typeof data == 'object' || typeof data == 'undefined') {\r
+               return $.extend(data, elgg.security.token);\r
+       }\r
+\r
+       //addToken(???)\r
+       throw new TypeError("elgg.security.addToken not implemented for " + (typeof data) + "s");\r
+};\r
+\r
+$(function() {\r
+       elgg.security.init();\r
+});
\ No newline at end of file
diff --git a/engine/js/lib/session.js b/engine/js/lib/session.js
new file mode 100644 (file)
index 0000000..446dbfa
--- /dev/null
@@ -0,0 +1,86 @@
+/**\r
+ * @todo comment\r
+ */\r
+elgg.provide('elgg.session');\r
+\r
+/**\r
+ * Helper function for setting cookies\r
+ * @param {string} name\r
+ * @param {string} value\r
+ * @param {Object} options\r
+ *  {number|Date} options[expires]\r
+ *     {string} options[path]\r
+ *     {string} options[domain]\r
+ *     {boolean} options[secure]\r
+ * \r
+ * @return {string} The value of the cookie, if only name is specified\r
+ */\r
+elgg.session.cookie = function(name, value, options) {\r
+       //elgg.session.cookie()\r
+       if(typeof name == 'undefined') {\r
+               return document.cookie;\r
+       }\r
+       \r
+       //elgg.session.cookie(name)\r
+       if (typeof value == 'undefined') {\r
+               if (document.cookie && document.cookie != '') {\r
+                       var cookies = document.cookie.split(';');\r
+                       for (var i = 0; i < cookies.length; i++) {\r
+                               var cookie = jQuery.trim(cookies[i]).split('=');\r
+                               if (cookie[0] == name) {\r
+                                       return decodeURIComponent(cookie[1]);\r
+                               }\r
+                       }\r
+               }\r
+               return undefined;\r
+       }\r
+       \r
+       // elgg.session.cookie(name, value[, opts])\r
+       var cookies = [];\r
+\r
+       options = options || {};\r
+       \r
+       if (value === null) {\r
+               value = '';\r
+               options.expires = -1;\r
+       }\r
+       \r
+       cookies.push(name + '=' + value);\r
+       \r
+       if (typeof options.expires == 'number') {\r
+               var date, valid = true;\r
+               \r
+               if (typeof options.expires == 'number') {\r
+                       date = new Date();\r
+                       date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));\r
+               } else if(options.expires.toUTCString) {\r
+                       date = options.expires;\r
+               } else {\r
+                       valid = false;\r
+               }\r
+               \r
+               valid ? cookies.push('expires=' + date.toUTCString()) : 0;\r
+       }\r
+       \r
+       // CAUTION: Needed to parenthesize options.path and options.domain\r
+       // in the following expressions, otherwise they evaluate to undefined\r
+       // in the packed version for some reason.\r
+       if (options.path) {\r
+               cookies.push('path=' + (options.path));\r
+       }\r
+\r
+       if (options.domain) {\r
+               cookies.push('domain=' + (options.domain));\r
+       }\r
+       \r
+       if (options.secure) {\r
+               cookies.push('secure');\r
+       }\r
+       \r
+       document.cookie = cookies.join('; ');\r
+};\r
+\r
+/**\r
+ * @deprecated Use elgg.session.cookie instead\r
+ */\r
+$.cookie = elgg.session.cookie;
\ No newline at end of file
diff --git a/engine/js/lib/ui.js b/engine/js/lib/ui.js
new file mode 100644 (file)
index 0000000..b584d66
--- /dev/null
@@ -0,0 +1,118 @@
+elgg.provide('elgg.ui');\r
+\r
+elgg.ui.init = function () {\r
+       $('a.collapsibleboxlink').click(elgg.ui.toggleCollapsibleBox);\r
+\r
+       // set-up hover class for dragged widgets\r
+       var cols = [\r
+               "#rightcolumn_widgets",\r
+               "#middlecolumn_widgets",\r
+               "#leftcolumn_widgets"\r
+       ].join(',');\r
+       \r
+       $(cols).droppable({\r
+               accept: ".draggable_widget",\r
+               hoverClass: 'droppable-hover'\r
+       });\r
+};\r
+\r
+// reusable generic hidden panel\r
+elgg.ui.toggleCollapsibleBox = function () {\r
+       $(this.parentNode.parentNode).children(".collapsible_box").slideToggle("fast");\r
+       return false;\r
+};\r
+\r
+//define some helper jquery plugins\r
+(function($) {\r
+       \r
+       // ELGG TOOLBAR MENU\r
+       $.fn.elgg_topbardropdownmenu = function(options) {\r
+               var defaults = {\r
+                       speed: 350\r
+               };\r
+               \r
+               options = $.extend(defaults, options || {});\r
+       \r
+               this.each(function() {\r
+               \r
+                       var root = this, zIndex = 5000;\r
+               \r
+                       function getSubnav(ele) {\r
+                               if (ele.nodeName.toLowerCase() == 'li') {\r
+                                       var subnav = $('> ul', ele);\r
+                                       return subnav.length ? subnav[0] : null;\r
+                               } else {\r
+                                       return ele;\r
+                               }\r
+                       }\r
+               \r
+                       function getActuator(ele) {\r
+                               if (ele.nodeName.toLowerCase() == 'ul') {\r
+                                       return $(ele).parents('li')[0];\r
+                               } else {\r
+                                       return ele;\r
+                               }\r
+                       }\r
+               \r
+                       function hide() {\r
+                               var subnav = getSubnav(this);\r
+                               if (!subnav) {\r
+                                       return;\r
+                               }\r
+                       \r
+                               $.data(subnav, 'cancelHide', false);\r
+                               setTimeout(function() {\r
+                                       if (!$.data(subnav, 'cancelHide')) {\r
+                                               $(subnav).slideUp(100);\r
+                                       }\r
+                               }, 250);\r
+                       }\r
+               \r
+                       function show() {\r
+                               var subnav = getSubnav(this);\r
+                               if (!subnav) {\r
+                                       return;\r
+                               }\r
+                               \r
+                               $.data(subnav, 'cancelHide', true);\r
+                               \r
+                               $(subnav).css({zIndex: zIndex++}).slideDown(options.speed);\r
+                               \r
+                               if (this.nodeName.toLowerCase() == 'ul') {\r
+                                       var li = getActuator(this);\r
+                                       $(li).addClass('hover');\r
+                                       $('> a', li).addClass('hover');\r
+                               }\r
+                       }\r
+               \r
+                       $('ul, li', this).hover(show, hide);\r
+                       $('li', this).hover(\r
+                               function() { $(this).addClass('hover'); $('> a', this).addClass('hover'); },\r
+                               function() { $(this).removeClass('hover'); $('> a', this).removeClass('hover'); }\r
+                       );\r
+               \r
+               });\r
+       };\r
+       \r
+       //Make delimited list\r
+       $.fn.makeDelimitedList = function(elementAttribute) {\r
+       \r
+               var delimitedListArray = [];\r
+               var listDelimiter = "::";\r
+       \r
+               // Loop over each element in the stack and add the elementAttribute to the array\r
+               this.each(function(e) {\r
+                               var listElement = $(this);\r
+                               // Add the attribute value to our values array\r
+                               delimitedListArray[delimitedListArray.length] = listElement.attr(elementAttribute);\r
+                       }\r
+               );\r
+       \r
+               // Return value list by joining the array\r
+               return(delimitedListArray.join(listDelimiter));\r
+       };\r
+})(jQuery);\r
+\r
+$(function() {\r
+       elgg.ui.init();\r
+});
\ No newline at end of file
diff --git a/engine/js/lib/ui.widgets.js b/engine/js/lib/ui.widgets.js
new file mode 100644 (file)
index 0000000..02a6d0e
--- /dev/null
@@ -0,0 +1,133 @@
+elgg.provide('elgg.ui.widgets');\r
+\r
+elgg.ui.widgets.init = function() {\r
+       // COLLAPSABLE WIDGETS (on Dashboard & Profile pages)\r
+       $('a.toggle_box_contents').live('click', elgg.ui.widgets.toggleContent);\r
+       $('a.toggle_box_edit_panel').live('click', elgg.ui.widgets.toggleEditPanel);\r
+       $('a.toggle_customise_edit_panel').live('click', elgg.ui.widgets.toggleCustomizeEditPanel);\r
+       \r
+       // WIDGET GALLERY EDIT PANEL\r
+       // Sortable widgets\r
+       var els = [\r
+               '#leftcolumn_widgets',\r
+               '#middlecolumn_widgets',\r
+               '#rightcolumn_widgets',\r
+               '#widget_picker_gallery'\r
+       ].join(',');\r
+       \r
+       $(els).sortable({\r
+               items: '.draggable_widget',\r
+               handle: '.drag_handle',\r
+               forcePlaceholderSize: true,\r
+               placeholder: 'ui-state-highlight',\r
+               cursor: 'move',\r
+               opacity: 0.9,\r
+               appendTo: 'body',\r
+               connectWith: els,\r
+               stop: function(e,ui) {\r
+                       // refresh list before updating hidden fields with new widget order\r
+                       $(this).sortable("refresh");\r
+\r
+                       var widgetNamesLeft = outputWidgetList('#leftcolumn_widgets');\r
+                       var widgetNamesMiddle = outputWidgetList('#middlecolumn_widgets');\r
+                       var widgetNamesRight = outputWidgetList('#rightcolumn_widgets');\r
+\r
+                       $('#debugField1').val(widgetNamesLeft);\r
+                       $('#debugField2').val(widgetNamesMiddle);\r
+                       $('#debugField3').val(widgetNamesRight);\r
+               }\r
+       });\r
+\r
+       // bind more info buttons - called when new widgets are created\r
+       elgg.ui.widgets.moreinfo();\r
+};\r
+\r
+//List active widgets for each page column\r
+elgg.ui.widgets.outputList = function(forElement) {\r
+       return( $("input[name='handler'], input[name='guid']", forElement ).makeDelimitedList("value") );\r
+};\r
+\r
+//Read each widgets collapsed/expanded state from cookie and apply\r
+elgg.ui.widgets.state = function(forWidget) {\r
+\r
+       var thisWidgetState = elgg.session.cookie(forWidget);\r
+\r
+       if (thisWidgetState == 'collapsed') {\r
+               forWidget = "#" + forWidget;\r
+               $(forWidget).find("div.collapsable_box_content").hide();\r
+               $(forWidget).find("a.toggle_box_contents").html('+');\r
+               $(forWidget).find("a.toggle_box_edit_panel").fadeOut('medium');\r
+       }\r
+};\r
+\r
+//More info tooltip in widget gallery edit panel\r
+elgg.ui.widgets.moreinfo = function() {\r
+       $("img.more_info").hover(function(e) {\r
+               var widgetdescription = $("input[name='description']", this.parentNode.parentNode.parentNode).val();\r
+               $("body").append("<p id='widget_moreinfo'><b>"+ widgetdescription +" </b></p>");\r
+\r
+               if (e.pageX < 900) {\r
+                       $("#widget_moreinfo")\r
+                               .css("top",(e.pageY + 10) + "px")\r
+                               .css("left",(e.pageX + 10) + "px")\r
+                               .fadeIn("medium");\r
+               } else {\r
+                       $("#widget_moreinfo")\r
+                               .css("top",(e.pageY + 10) + "px")\r
+                               .css("left",(e.pageX - 210) + "px")\r
+                               .fadeIn("medium");\r
+               }\r
+       }, function() {\r
+               $("#widget_moreinfo").remove();\r
+       });\r
+};\r
+\r
+//Toggle widgets contents and save to a cookie\r
+elgg.ui.widgets.toggleContent = function(e) {\r
+       var targetContent = $('div.collapsable_box_content', this.parentNode.parentNode);\r
+       if (targetContent.css('display') == 'none') {\r
+               targetContent.slideDown(400);\r
+               $(this).html('-');\r
+               $(this.parentNode).children(".toggle_box_edit_panel").fadeIn('medium');\r
+\r
+               // set cookie for widget panel open-state\r
+               var thisWidgetName = $(this.parentNode.parentNode.parentNode).attr('id');\r
+               $.cookie(thisWidgetName, 'expanded', { expires: 365 });\r
+\r
+       } else {\r
+               targetContent.slideUp(400);\r
+               $(this).html('+');\r
+               $(this.parentNode).children(".toggle_box_edit_panel").fadeOut('medium');\r
+               // make sure edit pane is closed\r
+               $(this.parentNode.parentNode).children(".collapsable_box_editpanel").hide();\r
+\r
+               // set cookie for widget panel closed-state\r
+               var thisWidgetName = $(this.parentNode.parentNode.parentNode).attr('id');\r
+               $.cookie(thisWidgetName, 'collapsed', { expires: 365 });\r
+       }\r
+       return false;\r
+};\r
+\r
+// toggle widget box edit panel\r
+elgg.ui.widgets.toggleEditPanel = function () {\r
+       $(this.parentNode.parentNode).children(".collapsable_box_editpanel").slideToggle("fast");\r
+       return false;\r
+};\r
+\r
+// toggle customise edit panel\r
+elgg.ui.widgets.toggleCustomizeEditPanel = function () {\r
+       $('#customise_editpanel').slideToggle("fast");\r
+       return false;\r
+};\r
+\r
+/**\r
+ * @deprecated Use elgg.ui.widgets.*\r
+ */\r
+var toggleContent =    elgg.ui.widgets.toggleContent,\r
+    widget_moreinfo =  elgg.ui.widgets.moreinfo,\r
+    widget_state =     elgg.ui.widgets.state,\r
+    outputWidgetList = elgg.ui.widgets.outputList;\r
+\r
+$(function() {\r
+       elgg.ui.widgets.init();\r
+});\r
diff --git a/engine/js/tests/ElggAjaxOptionsTest.js b/engine/js/tests/ElggAjaxOptionsTest.js
new file mode 100644 (file)
index 0000000..1f6f251
--- /dev/null
@@ -0,0 +1,60 @@
+ElggAjaxOptionsTest = TestCase("ElggAjaxOptionsTest");\r
+\r
+ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsNoArgs = function() {\r
+       assertNotUndefined(elgg.ajax.handleOptions());\r
+       \r
+};\r
+\r
+ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsUrl = function() {\r
+       var url = 'url',\r
+               result = elgg.ajax.handleOptions(url);\r
+       \r
+       assertEquals(url, result.url);\r
+};\r
+\r
+ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsDataOnly = function() {\r
+       var options = {},\r
+               result = elgg.ajax.handleOptions(options);\r
+       \r
+       assertEquals(options, result.data);\r
+};\r
+\r
+ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsOptions = function() {\r
+       var options = {data:{arg:1}},\r
+               result = elgg.ajax.handleOptions(options);\r
+       \r
+       assertEquals(options, result);\r
+       \r
+       function func() {}\r
+       options = {success: func};\r
+       result = elgg.ajax.handleOptions(options);\r
+       \r
+       assertEquals(options, result);\r
+};\r
+\r
+ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsUrlThenDataOnly = function() {\r
+       var url = 'url',\r
+               options = {arg:1},\r
+               result = elgg.ajax.handleOptions(url, options);\r
+       \r
+       assertEquals(url, result.url);\r
+       assertEquals(options, result.data);\r
+};\r
+\r
+ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsUrlThenSuccessOnly = function() {\r
+       var url = 'url',\r
+       success = function() {},\r
+       result = elgg.ajax.handleOptions(url, success);\r
+       \r
+       assertEquals(url, result.url);\r
+       assertEquals(success, result.success);\r
+};\r
+\r
+ElggAjaxOptionsTest.prototype.testHandleOptionsAcceptsUrlThenOptions = function() {\r
+       var url = 'url',\r
+       options = {data:{arg:1}},\r
+       result = elgg.ajax.handleOptions(url, options);\r
+       \r
+       assertEquals(url, result.url);\r
+       assertEquals(options.data, result.data);\r
+};
\ No newline at end of file
diff --git a/engine/js/tests/ElggAjaxTest.js b/engine/js/tests/ElggAjaxTest.js
new file mode 100644 (file)
index 0000000..1fa5dac
--- /dev/null
@@ -0,0 +1,59 @@
+/**\r
+ * Makes sure that each of the helper ajax functions ends up calling $.ajax\r
+ * with the right options.\r
+ */\r
+ElggAjaxTest = TestCase("ElggAjaxTest");\r
+\r
+ElggAjaxTest.prototype.setUp = function() {\r
+       \r
+       this.wwwroot = elgg.config.wwwroot;\r
+       this.ajax = $.ajax;\r
+       \r
+       elgg.config.wwwroot = 'http://www.elgg.org/';\r
+       \r
+       $.ajax = function(options) {\r
+               return options;\r
+       };\r
+};\r
+\r
+ElggAjaxTest.prototype.tearDown = function() {\r
+       $.ajax = this.ajax;\r
+       elgg.config.wwwroot = this.wwwroot;\r
+};\r
+\r
+ElggAjaxTest.prototype.testElggAjax = function() {\r
+       assertEquals(elgg.config.wwwroot, elgg.ajax().url);\r
+};\r
+\r
+ElggAjaxTest.prototype.testElggGet = function() {\r
+       assertEquals('get', elgg.get().type);\r
+};\r
+\r
+ElggAjaxTest.prototype.testElggGetJSON = function() {\r
+       assertEquals('json', elgg.getJSON().dataType);\r
+};\r
+\r
+ElggAjaxTest.prototype.testElggPost = function() {\r
+       assertEquals('post', elgg.post().type);\r
+};\r
+\r
+ElggAjaxTest.prototype.testElggAction = function() {\r
+       assertException(function() { elgg.action(); });\r
+       assertException(function() { elgg.action({}); });\r
+       \r
+       var result = elgg.action('action');\r
+       assertEquals('post', result.type);\r
+       assertEquals('json', result.dataType);\r
+       assertEquals(elgg.config.wwwroot + 'action/action', result.url);\r
+       assertEquals(elgg.security.token.__elgg_ts, result.data.__elgg_ts);\r
+};\r
+\r
+ElggAjaxTest.prototype.testElggAPI = function() {\r
+       assertException(function() { elgg.api(); });\r
+       assertException(function() { elgg.api({}); });\r
+       \r
+       var result = elgg.api('method');\r
+       assertEquals('json', result.dataType);\r
+       assertEquals('method', result.data.method);\r
+       assertEquals(elgg.config.wwwroot + 'services/api/rest/json/', result.url);\r
+};\r
diff --git a/engine/js/tests/ElggLibTest.js b/engine/js/tests/ElggLibTest.js
new file mode 100644 (file)
index 0000000..1cd1b13
--- /dev/null
@@ -0,0 +1,49 @@
+ElggLibTest = TestCase("ElggLibTest");\r
+\r
+ElggLibTest.prototype.testGlobal = function() {\r
+       assertTrue(window === elgg.global);\r
+};\r
+\r
+ElggLibTest.prototype.testProvide = function() {\r
+       elgg.provide('foo.bar.baz');\r
+       \r
+       assertNotUndefined(foo);\r
+       assertNotUndefined(foo.bar);\r
+       assertNotUndefined(foo.bar.baz);\r
+       \r
+       var str = foo.bar.baz.oof = "don't overwrite me";\r
+       \r
+       elgg.provide('foo.bar.baz');\r
+       \r
+       assertEquals(str, foo.bar.baz.oof);\r
+};\r
+\r
+ElggLibTest.prototype.testRequire = function() {\r
+       /* Try requiring bogus input */\r
+       assertException(function(){ elgg.require(''); });\r
+       assertException(function(){ elgg.require('garbage'); });\r
+       assertException(function(){ elgg.require('gar.ba.ge'); });\r
+\r
+       assertNoException(function(){ elgg.require('jQuery'); });\r
+       assertNoException(function(){ elgg.require('elgg'); });\r
+       assertNoException(function(){ elgg.require('elgg.config'); });\r
+       assertNoException(function(){ elgg.require('elgg.security'); });\r
+};\r
+\r
+ElggLibTest.prototype.testExtendUrl = function() {\r
+       var url;\r
+       elgg.config.wwwroot = "http://www.elgg.org/";\r
+       \r
+       url = '';\r
+       assertEquals(elgg.config.wwwroot, elgg.extendUrl(url));\r
+       \r
+       url = 'pg/test';\r
+       assertEquals('http://www.elgg.org/pg/test', elgg.extendUrl(url));\r
+};\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
diff --git a/engine/js/tests/ElggSecurityTest.js b/engine/js/tests/ElggSecurityTest.js
new file mode 100644 (file)
index 0000000..2b497b8
--- /dev/null
@@ -0,0 +1,51 @@
+ElggSecurityTest = TestCase("ElggSecurityTest");\r
+\r
+ElggSecurityTest.prototype.setUp = function() {\r
+       //fill with fake, but reasonable, values for testing\r
+       this.ts = elgg.security.token.__elgg_ts = 12345;\r
+       this.token = elgg.security.token.__elgg_token = 'abcdef';\r
+};\r
+\r
+ElggSecurityTest.prototype.testAddTokenAcceptsUndefined = function() {\r
+       var input,\r
+               expected = {\r
+                               __elgg_ts: this.ts,\r
+                               __elgg_token: this.token\r
+               };\r
+       \r
+       assertEquals(expected, elgg.security.addToken(input));\r
+};\r
+\r
+ElggSecurityTest.prototype.testAddTokenAcceptsObject = function() {\r
+       var input = {},\r
+               expected = {\r
+                       __elgg_ts: this.ts,\r
+                       __elgg_token: this.token\r
+               };\r
+       \r
+       assertEquals(expected, elgg.security.addToken(input));\r
+};\r
+\r
+ElggSecurityTest.prototype.testAddTokenAcceptsString = function() {\r
+       var input,\r
+               str = "__elgg_ts=" + this.ts + "&__elgg_token=" + this.token;\r
+       \r
+       input = "";\r
+       assertEquals(str, elgg.security.addToken(input));\r
+       \r
+       input = "data=sofar";\r
+       assertEquals(input+'&'+str, elgg.security.addToken(input));\r
+       \r
+};\r
+\r
+ElggSecurityTest.prototype.testSetTokenSetsElggSecurityToken = function() {\r
+       var json = {\r
+               __elgg_ts: 4567,\r
+               __elgg_token: 'abcdef'\r
+       };\r
+       \r
+       elgg.security.setToken(json);\r
+       assertEquals(json, elgg.security.token);\r
+};\r
+\r
+\r
diff --git a/engine/js/tests/ElggSessionTest.js b/engine/js/tests/ElggSessionTest.js
new file mode 100644 (file)
index 0000000..0245e9e
--- /dev/null
@@ -0,0 +1,36 @@
+ElggSessionTest = TestCase("ElggSessionTest");\r
+\r
+ElggSessionTest.prototype.testGetCookie = function() {\r
+       assertEquals(document.cookie, elgg.session.cookie());\r
+};\r
+\r
+ElggSessionTest.prototype.testGetCookieKey = function() {\r
+       document.cookie = "name=value";\r
+       assertEquals('value', elgg.session.cookie('name'));\r
+       \r
+       document.cookie = "name=value2";\r
+       assertEquals('value2', elgg.session.cookie('name'));\r
+       \r
+       document.cookie = "name=value";\r
+       document.cookie = "name2=value2";\r
+       assertEquals('value', elgg.session.cookie('name'));\r
+       assertEquals('value2', elgg.session.cookie('name2'));\r
+};\r
+\r
+ElggSessionTest.prototype.testSetCookieKey = function() {\r
+       elgg.session.cookie('name', 'value');\r
+       assertEquals('value', elgg.session.cookie('name'));\r
+\r
+       elgg.session.cookie('name', 'value2');\r
+       assertEquals('value2', elgg.session.cookie('name'));\r
+       \r
+       elgg.session.cookie('name', 'value');\r
+       elgg.session.cookie('name2', 'value2');\r
+       assertEquals('value', elgg.session.cookie('name'));\r
+       assertEquals('value2', elgg.session.cookie('name2'));\r
+       \r
+       elgg.session.cookie('name', null);\r
+       elgg.session.cookie('name2', null);\r
+       assertUndefined(elgg.session.cookie('name'));\r
+       assertUndefined(elgg.session.cookie('name2'));\r
+};
\ No newline at end of file
diff --git a/jsTestDriver.conf b/jsTestDriver.conf
new file mode 100644 (file)
index 0000000..06b6e05
--- /dev/null
@@ -0,0 +1,7 @@
+server: http://localhost:4321\r
+\r
+load:\r
+ - vendors/jquery/jquery-1.4.2.min.js\r
+ - engine/js/lib/elgglib.js\r
+ - engine/js/lib/*.js\r
+ - engine/js/tests/*.js
\ No newline at end of file