Converted CR+LF to LF in WCF.js
authorMarkus Bartz <roul@codingcorner.info>
Thu, 21 Jul 2011 15:17:09 +0000 (17:17 +0200)
committerMarkus Bartz <roul@codingcorner.info>
Thu, 21 Jul 2011 15:17:09 +0000 (17:17 +0200)
wcfsetup/install/files/js/WCF.js

index 23169e0d9183b72cdb7e29d3cf643c705e9a9fb7..b99e2cb6308d99dffcd565e4548f8105a0e207ee 100644 (file)
-/**\r
- * Class and function collection for WCF\r
- * \r
- * @author     Alexander Ebert\r
- * @copyright  2001-2011 WoltLab GmbH\r
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>\r
- */\r
-\r
-/**\r
- * Initialize WCF namespace\r
- */\r
-var WCF = {};\r
-\r
-/**\r
- * Extends jQuery with additional methods.\r
- */\r
-$.extend(true, {\r
-       /**\r
-        * Escapes an ID to work with jQuery selectors.\r
-        *\r
-        * @see         http://docs.jquery.com/Frequently_Asked_Questions#How_do_I_select_an_element_by_an_ID_that_has_characters_used_in_CSS_notation.3F\r
-        * @param       string          id\r
-        * @return      string\r
-        */\r
-       wcfEscapeID: function(id) {\r
-               return id.replace(/(:|\.)/g, '\\$1');\r
-       },\r
-       \r
-       /**\r
-        * Returns true if given ID exists within DOM.\r
-        * \r
-        * @param       string          id\r
-        * @return      boolean\r
-        */\r
-       wcfIsset: function(id) {\r
-               return !!$('#' + $.wcfEscapeID(id)).length;\r
-       }\r
-});\r
-\r
-/**\r
- * Extends jQuery's chainable methods.\r
- */\r
-$.fn.extend({\r
-       /**\r
-        * Returns tag name of current jQuery element.\r
-        * \r
-        * @returns     string\r
-        */\r
-       getTagName: function() {\r
-               return this.get(0).tagName.toLowerCase();\r
-       },\r
-       \r
-       /**\r
-        * Returns the dimensions for current element.\r
-        * \r
-        * @see         http://api.jquery.com/hidden-selector/\r
-        * @param       string          type\r
-        * @return      object\r
-        */\r
-       getDimensions: function(type) {\r
-               var dimensions = css = {};\r
-               var wasHidden = false;\r
-               \r
-               // show element to retrieve dimensions and restore them later\r
-               if (this.is(':hidden')) {\r
-                       css = {\r
-                               display: this.css('display'),\r
-                               visibility: this.css('visibility')\r
-                       };\r
-                       \r
-                       wasHidden = true;\r
-                       \r
-                       this.css({\r
-                               display: 'block',\r
-                               visibility: 'hidden'\r
-                       });\r
-               }\r
-               \r
-               switch (type) {\r
-                       case 'inner':\r
-                               dimensions = {\r
-                                       height: this.innerHeight(),\r
-                                       width: this.innerWidth()\r
-                               };\r
-                       break;\r
-                       \r
-                       case 'outer':\r
-                               dimensions = {\r
-                                       height: this.outerHeight(),\r
-                                       width: this.innerWidth()\r
-                               };\r
-                       break;\r
-                       \r
-                       default:\r
-                               dimensions = {\r
-                                       height: this.height(),\r
-                                       width: this.width()\r
-                               };\r
-                       break;\r
-               }\r
-               \r
-               // restore previous settings\r
-               if (wasHidden) {\r
-                       this.css(css);\r
-               }\r
-               \r
-               return dimensions;\r
-       },\r
-       \r
-       /**\r
-        * Returns the offsets for current element, defaults to position\r
-        * relative to document.\r
-        * \r
-        * @see         http://api.jquery.com/hidden-selector/\r
-        * @param       string          type\r
-        * @return      object\r
-        */\r
-       getOffsets: function(type) {\r
-               var offsets = css = {};\r
-               var wasHidden = false;\r
-               \r
-               // show element to retrieve dimensions and restore them later\r
-               if (this.is(':hidden')) {\r
-                       css = {\r
-                               display: this.css('display'),\r
-                               visibility: this.css('visibility')\r
-                       };\r
-                       \r
-                       wasHidden = true;\r
-                       \r
-                       this.css({\r
-                               display: 'block',\r
-                               visibility: 'hidden'\r
-                       });\r
-               }\r
-               \r
-               switch (type) {\r
-                       case 'offset':\r
-                               offsets = this.offset();\r
-                       break;\r
-                       \r
-                       case 'position':\r
-                       default:\r
-                               offsets = this.position();\r
-                       break;\r
-               }\r
-               \r
-               // restore previous settings\r
-               if (wasHidden) {\r
-                       this.css(css);\r
-               }\r
-               \r
-               return offsets;\r
-       },\r
-       \r
-       /**\r
-        * Changes element's position to 'absolute' or 'fixed' while maintaining it's\r
-        * current position relative to viewport. Optionally removes element from\r
-        * current DOM-node and moving it into body-element (useful for drag & drop)\r
-        * \r
-        * @param       boolean         rebase\r
-        * @return      object\r
-        */\r
-       makePositioned: function(position, rebase) {\r
-               if (position != 'absolute' && position != 'fixed') {\r
-                       position = 'absolute';\r
-               }\r
-               \r
-               var $currentPosition = this.getOffsets('position');\r
-               this.css({\r
-                       position: position,\r
-                       left: $currentPosition.left,\r
-                       margin: 0,\r
-                       top: $currentPosition.top\r
-               });\r
-               \r
-               if (rebase) {\r
-                       this.remove().appentTo('body');\r
-               }\r
-               \r
-               return this;\r
-       },\r
-       \r
-       /**\r
-        * Disables a form element.\r
-        * \r
-        * @return jQuery\r
-        */\r
-       disable: function() {\r
-               return this.attr('disabled', 'disabled');\r
-       },\r
-       \r
-       /**\r
-        * Enables a form element.\r
-        * \r
-        * @return      jQuery\r
-        */\r
-       enable: function() {\r
-               return this.removeAttr('disabled');\r
-       },\r
-       \r
-       /**\r
-        * Applies a grow-effect by resizing element while moving the\r
-        * element appropriately\r
-        * \r
-        * @param       object          data\r
-        * @param       object          options\r
-        * @return      jQuery\r
-        */\r
-       wcfGrow: function(data, options) {\r
-               // create temporarily element to determine dimensions\r
-               var $tempElementID = WCF.getRandomID();\r
-               $('body').append('<div id="' + $tempElementID + '" class="wcfDimensions">' + data.content + '</div>');\r
-               var $tempElement = $('#' + $tempElementID);\r
-               \r
-               // get content dimensions\r
-               var $dimensions = $tempElement.getDimensions();\r
-               \r
-               // remove temporarily element\r
-               $tempElement.empty().remove();\r
-               \r
-               // move parent element, used if applying effect on dialogs\r
-               if (!data.parent) {\r
-                       data.parent = this;\r
-               }\r
-               \r
-               // calculate values for grow-effect\r
-               var $borderHeight = parseInt(data.parent.css('borderTopWidth')) + parseInt(data.parent.css('borderBottomWidth'));\r
-               var $borderWidth = parseInt(data.parent.css('borderLeftWidth')) + parseInt(data.parent.css('borderRightWidth'));\r
-               \r
-               var $windowDimensions = $(window).getDimensions();\r
-               var $leftOffset = Math.round(($windowDimensions.width - ($dimensions.width + $borderWidth)) / 2);\r
-               var $topOffset = Math.round(($windowDimensions.height - ($dimensions.height + $borderHeight)) / 2);\r
-               \r
-               data.parent.makePositioned('fixed', false);\r
-               data.parent.animate({\r
-                       left: $leftOffset + 'px',\r
-                       top: $topOffset + 'px'\r
-               }, options);\r
-               \r
-               return this.animate({\r
-                       height: $dimensions.height,\r
-                       width: $dimensions.width\r
-               }, options);\r
-       },\r
-       \r
-       /**\r
-        * Shows an element by sliding and fading it into viewport.\r
-        * \r
-        * @param       string          direction\r
-        * @param       object          callback\r
-        * @returns     jQuery\r
-        */\r
-       wcfDropIn: function(direction, callback) {\r
-               if (!direction) direction = 'up';\r
-               \r
-               return this.show(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, 600, callback);\r
-       },\r
-       \r
-       /**\r
-        * Hides an element by sliding and fading it out the viewport.\r
-        * \r
-        * @param       string          direction\r
-        * @param       object          callback\r
-        * @returns     jQuery\r
-        */\r
-       wcfDropOut: function(direction, callback) {\r
-               if (!direction) direction = 'down';\r
-               \r
-               return this.hide(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, 600, callback);\r
-       },\r
-       \r
-       /**\r
-        * Shows an element by blinding it up.\r
-        * \r
-        * @param       string          direction\r
-        * @param       object          callback\r
-        * @returns     jQuery\r
-        */\r
-       wcfBlindIn: function(direction, callback) {\r
-               if (!direction) direction = 'vertical';\r
-               \r
-               return this.show(WCF.getEffect(this.getTagName(), 'blind'), { direction: direction }, 200, callback);\r
-       },\r
-       \r
-       /**\r
-        * Hides an element by blinding it down.\r
-        * \r
-        * @param       string          direction\r
-        * @param       object          callback\r
-        * @returns     jQuery\r
-        */\r
-       wcfBlindOut: function(direction, callback) {\r
-               if (!direction) direction = 'vertical';\r
-               \r
-               return this.hide(WCF.getEffect(this.getTagName(), 'blind'), { direction: direction }, 200, callback);\r
-       },\r
-       \r
-       /**\r
-        * Highlights an element.\r
-        * \r
-        * @param       object          options\r
-        * @param       object          callback\r
-        * @returns     jQuery\r
-        */\r
-       wcfHighlight: function(options, callback) {\r
-               return this.effect('highlight', options, 600, callback);\r
-       }\r
-});\r
-\r
-/**\r
- * WoltLab Community Framework core methods\r
- */\r
-$.extend(WCF, {\r
-       /**\r
-        * Counter for dynamic element id's\r
-        *\r
-        * @var integer\r
-        */\r
-       _idCounter: 0,\r
-       \r
-       /**\r
-        * Shows a modal dialog with a built-in AJAX-loader.\r
-        * \r
-        * @param       string          dialogID\r
-        * @param       boolean         resetDialog\r
-        */\r
-       showAJAXDialog: function(dialogID, resetDialog) {\r
-               if (!dialogID) {\r
-                       dialogID = this.getRandomID();\r
-               }\r
-               \r
-               if (!$.wcfIsset(dialogID)) {\r
-                       $('body').append($('<div id="' + dialogID + '"></div>'));\r
-               }\r
-               \r
-               var dialog = $('#' + $.wcfEscapeID(dialogID));\r
-               \r
-               if (resetDialog) {\r
-                       dialog.empty();\r
-               }\r
-               \r
-               dialog.addClass('overlayLoading');\r
-               \r
-               var dialogOptions = arguments[2] || {};\r
-               dialog.wcfAJAXDialog(dialogOptions);\r
-       },\r
-       \r
-       /**\r
-        * Shows a modal dialog.\r
-        * @param       string          dialogID\r
-        */\r
-       showDialog: function(dialogID) {\r
-               // we cannot work with a non-existant dialog, if you wish to\r
-               // load content via AJAX, see showAJAXDialog() instead\r
-               if (!$.wcfIsset(dialogID)) return;\r
-               \r
-               var $dialog = $('#' + $.wcfEscapeID(dialogID));\r
-               \r
-               var dialogOptions = arguments[2] || {};\r
-               $dialog.wcfDialog(dialogOptions);\r
-       },\r
-       \r
-       /**\r
-        * Returns a dynamically created id.\r
-        * \r
-        * @see         https://github.com/sstephenson/prototype/blob/master/src/prototype/dom/dom.js#L1789\r
-        * @return      string\r
-        */\r
-       getRandomID: function() {\r
-               var $elementID = '';\r
-               \r
-               do {\r
-                       $elementID = 'wcf' + this._idCounter++;  \r
-               }\r
-               while ($.wcfIsset($elementID));\r
-               \r
-               return $elementID;\r
-       },\r
-       \r
-       /**\r
-        * Wrapper for $.inArray which returns boolean value instead of\r
-        * index value, similar to PHP's in_array().\r
-        * \r
-        * @param       mixed           needle\r
-        * @param       array           haystack\r
-        * @return      boolean\r
-        */\r
-       inArray: function(needle, haystack) {\r
-               return ($.inArray(needle, haystack) != -1);\r
-       },\r
-       \r
-       /**\r
-        * Adjusts effect for partially supported elements.\r
-        * \r
-        * @param       object          object\r
-        * @param       string          effect\r
-        * @return      string\r
-        */\r
-       getEffect: function(tagName, effect) {\r
-               // most effects are not properly supported on table rows, use highlight instead\r
-               if (tagName == 'tr') {\r
-                       return 'highlight';\r
-               }\r
-               \r
-               return effect;\r
-       }\r
-});\r
-\r
-/**\r
- * Provides a simple call for periodical executed functions. Based upon\r
- * ideas by Prototype's PeriodicalExecuter.\r
- * \r
- * @see                https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/periodical_executer.js\r
- * @param      function                callback\r
- * @param      integer                 delay\r
- */\r
-WCF.PeriodicalExecuter = function(callback, delay) { this.init(callback, delay); };\r
-WCF.PeriodicalExecuter.prototype = {\r
-       /**\r
-        * Initializes a periodical executer.\r
-        * \r
-        * @param       function                callback\r
-        * @param       integer                 delay\r
-        */\r
-       init: function(callback, delay) {\r
-               this.callback = callback;\r
-               this.delay = delay;\r
-               this.loop = true;\r
-               \r
-               this.intervalID = setInterval($.proxy(this._execute, this), this.delay);\r
-       },\r
-       \r
-       /**\r
-        * Executes callback.\r
-        */\r
-       _execute: function() {\r
-               this.callback(this);\r
-               \r
-               if (!this.loop) {\r
-                       clearInterval(this.intervalID);\r
-               }\r
-       },\r
-       \r
-       /**\r
-        * Terminates loop.\r
-        */\r
-       stop: function() {\r
-               this.loop = false;\r
-       }\r
-};\r
-\r
-/**\r
- * Namespace for AJAXProxies\r
- */\r
-WCF.Action = {};\r
-\r
-/**\r
- * Basic implementation for AJAX-based proxyies\r
- * \r
- * @param      object          options\r
- */\r
-WCF.Action.Proxy = function(options) { this.init(options); };\r
-WCF.Action.Proxy.prototype = {\r
-       /**\r
-        * Initializes AJAXProxy.\r
-        * \r
-        * @param       object          options\r
-        */\r
-       init: function(options) {\r
-               // initialize default values\r
-               this.options = $.extend(true, {\r
-                       autoSend: false,\r
-                       data: { },\r
-                       after: null,\r
-                       init: null,\r
-                       failure: null,\r
-                       success: null,\r
-                       type: 'POST',\r
-                       url: 'index.php?action=AJAXProxy&t=' + SECURITY_TOKEN + SID_ARG_2ND\r
-               }, options);\r
-               \r
-               this.confirmationDialog = null;\r
-               this.loading = null;\r
-               \r
-               // send request immediately after initialization\r
-               if (this.options.autoSend) {\r
-                       this.sendRequest();\r
-               }\r
-       },\r
-       \r
-       /**\r
-        * Sends an AJAX request.\r
-        */\r
-       sendRequest: function() {\r
-               this._init();\r
-               \r
-               $.ajax({\r
-                       data: this.options.data,\r
-                       dataType: 'json',\r
-                       type: this.options.type,\r
-                       url: this.options.url,\r
-                       success: $.proxy(this._success, this),\r
-                       error: $.proxy(this._failure, this)\r
-               });\r
-       },\r
-       \r
-       /**\r
-        * Fires before request is send, displays global loading status.\r
-        */\r
-       _init: function() {\r
-               if ($.isFunction(this.options.init)) {\r
-                       this.options.init();\r
-               }\r
-               \r
-               $('<div id="actionProxyLoading" style="display: none;">Loading â€¦</div>').appendTo($('body'));\r
-               this.loading = $('#actionProxyLoading');\r
-               this.loading.wcfDropIn();\r
-       },\r
-       \r
-       /**\r
-        * Handles AJAX errors.\r
-        * \r
-        * @param       object          jqXHR\r
-        * @param       string          textStatus\r
-        * @param       string          errorThrown\r
-        */\r
-       _failure: function(jqXHR, textStatus, errorThrown) {\r
-               try {\r
-                       var data = $.parseJSON(jqXHR.responseText);\r
-                       \r
-                       // call child method if applicable\r
-                       if ($.isFunction(this.options.failure)) {\r
-                               this.options.failure(jqXHR, textStatus, errorThrown, data);\r
-                       }\r
-                       \r
-                       var $randomID = WCF.getRandomID();\r
-                       $('<div id="' + $randomID + '" title="HTTP/1.0 ' + jqXHR.status + ' ' + errorThrown + '"><p>Der Server antwortete: ' + data.message + '.</p></div>').wcfDialog();\r
-               }\r
-               // failed to parse JSON\r
-               catch (e) {\r
-                       var $randomID = WCF.getRandomID();\r
-                       $('<div id="' + $randomID + '" title="HTTP/1.0 ' + jqXHR.status + ' ' + errorThrown + '"><p>Der Server antwortete: ' + jqXHR.responseText + '.</p></div>').wcfDialog();\r
-               }\r
-               \r
-               this._after();\r
-       },\r
-       \r
-       /**\r
-        * Handles successful AJAX requests.\r
-        * \r
-        * @param       object          data\r
-        * @param       string          textStatus\r
-        * @param       object          jqXHR\r
-        */\r
-       _success: function(data, textStatus, jqXHR) {\r
-               // call child method if applicable\r
-               if ($.isFunction(this.options.success)) {\r
-                       this.options.success(data, textStatus, jqXHR);\r
-               }\r
-               \r
-               this._after();\r
-       },\r
-       \r
-       /**\r
-        * Fires after an AJAX request, hides global loading status.\r
-        */\r
-       _after: function() {\r
-               if ($.isFunction(this.options.after)) {\r
-                       this.options.after();\r
-               }\r
-               \r
-               this.loading.wcfDropOut('up');\r
-       },\r
-       \r
-       /**\r
-        * Sets options, MUST be used to set parameters before sending request\r
-        * if calling from child classes.\r
-        * \r
-        * @param       string          optionName\r
-        * @param       mixed           optionData\r
-        */\r
-       setOption: function(optionName, optionData) {\r
-               this.options[optionName] = optionData;\r
-       }\r
-};\r
-\r
-/**\r
- * Basic implementation for simple proxy access using bound elements.\r
- * \r
- * @param      object          options\r
- * @param      object          callbacks\r
- */\r
-WCF.Action.SimpleProxy = function(options, callbacks) { this.init(options, callbacks); };\r
-WCF.Action.SimpleProxy.prototype = {\r
-       /**\r
-        * Initializes SimpleProxy.\r
-        * \r
-        * @param       object          options\r
-        * @param       object          callbacks\r
-        */\r
-       init: function(options, callbacks) {\r
-               /**\r
-                * action-specific options\r
-                */\r
-               this.options = $.extend(true, {\r
-                       action: '',\r
-                       className: '',\r
-                       elements: null,\r
-                       eventName: 'click'\r
-               }, options);\r
-               \r
-               /**\r
-                * proxy-specific options\r
-                */\r
-               this.callbacks = $.extend(true, {\r
-                       after: null,\r
-                       failure: null,\r
-                       init: null,\r
-                       success: null\r
-               }, callbacks);\r
-               \r
-               if (!this.options.elements) return;\r
-               \r
-               // initialize proxy\r
-               this.proxy = new WCF.Action.Proxy(this.callbacks);\r
-               \r
-               // bind event listener\r
-               this.options.elements.each($.proxy(function(index, element) {\r
-                       $(element).bind(this.options.eventName, $.proxy(this._handleEvent, this));\r
-               }, this));\r
-       },\r
-       \r
-       /**\r
-        * Handles event actions.\r
-        * \r
-        * @param       object          event\r
-        */\r
-       _handleEvent: function(event) {\r
-               this.proxy.setOption('data', {\r
-                       actionName: this.options.action,\r
-                       className: this.options.className,\r
-                       objectIDs: [ $(event.target).data('objectID') ]\r
-               });\r
-               \r
-               this.proxy.sendRequest();\r
-       }\r
-};\r
-\r
-/**\r
- * Basic implementation for AJAXProxy-based deletion.\r
- * \r
- * @param      string          className\r
- * @param      jQuery          containerList\r
- */\r
-WCF.Action.Delete = function(className, containerList) { this.init(className, containerList); };\r
-WCF.Action.Delete.prototype = {\r
-       /**\r
-        * Initializes 'delete'-Proxy.\r
-        * \r
-        * @param       string          className\r
-        * @param       jQuery          containerList\r
-        */\r
-       init: function(className, containerList) {\r
-               if (!containerList.length) return;\r
-               this.containerList = containerList;\r
-               this.className = className;\r
-               \r
-               // initialize proxy\r
-               var options = {\r
-                       success: $.proxy(this._success, this)\r
-               };\r
-               this.proxy = new WCF.Action.Proxy(options);\r
-               \r
-               // bind event listener\r
-               this.containerList.each($.proxy(function(index, container) {\r
-                       $(container).find('.deleteButton').bind('click', $.proxy(this._click, this));\r
-               }, this));\r
-       },\r
-       \r
-       /**\r
-        * Sends AJAX request.\r
-        * \r
-        * @param       object          event\r
-        */\r
-       _click: function(event) {\r
-               var $target = $(event.target);\r
-               \r
-               if ($target.data('confirmMessage')) {\r
-                       if (confirm($target.data('confirmMessage'))) {\r
-                               this._sendRequest($target);\r
-                       }\r
-               }\r
-               else {\r
-                       this._sendRequest($target);\r
-               }\r
-               \r
-       },\r
-       \r
-       _sendRequest: function(object) {\r
-               this.proxy.setOption('data', {\r
-                       actionName: 'delete',\r
-                       className: this.className,\r
-                       objectIDs: [ $(object).data('objectID') ]\r
-               });\r
-               \r
-               this.proxy.sendRequest();\r
-       },\r
-       \r
-       /**\r
-        * Deletes items from containers.\r
-        * \r
-        * @param       object          data\r
-        * @param       string          textStatus\r
-        * @param       object          jqXHR\r
-        */\r
-       _success: function(data, textStatus, jqXHR) {\r
-               // remove items\r
-               this.containerList.each(function(index, container) {\r
-                       var $objectID = $(container).find('.deleteButton').data('objectID');\r
-                       if (WCF.inArray($objectID, data.objectIDs)) {\r
-                               $(container).wcfBlindOut('up', function() {\r
-                                       $(container).empty().remove();\r
-                               }, container);\r
-                       }\r
-               });\r
-       }\r
-};\r
-\r
-/**\r
- * Basic implementation for AJAXProxy-based toggle actions.\r
- * \r
- * @param      string          className\r
- * @param      jQuery          containerList\r
- */\r
-WCF.Action.Toggle = function(className, containerList) { this.init(className, containerList); };\r
-WCF.Action.Toggle.prototype = {\r
-       /**\r
-        * Initializes 'toggle'-Proxy\r
-        * \r
-        * @param       string          className\r
-        * @param       jQuery          containerList\r
-        */\r
-       init: function(className, containerList) {\r
-               if (!containerList.length) return;\r
-               this.containerList = containerList;\r
-               this.className = className;\r
-               \r
-               // initialize proxy\r
-               var options = {\r
-                       success: $.proxy(this._success, this)\r
-               };\r
-               this.proxy = new WCF.Action.Proxy(options);\r
-               \r
-               // bind event listener\r
-               this.containerList.each($.proxy(function(index, container) {\r
-                       $(container).find('.toggleButton').bind('click', $.proxy(this._click, this));\r
-               }, this));\r
-       },\r
-       \r
-       /**\r
-        * Sends AJAX request.\r
-        * \r
-        * @param       object          event\r
-        */\r
-       _click: function(event) {\r
-               this.proxy.setOption('data', {\r
-                       actionName: 'toggle',\r
-                       className: this.className,\r
-                       objectIDs: [ $(event.target).data('objectID') ]\r
-               });\r
-               \r
-               this.proxy.sendRequest();\r
-       },\r
-       \r
-       /**\r
-        * Toggles status icons.\r
-        * \r
-        * @param       object          data\r
-        * @param       string          textStatus\r
-        * @param       object          jqXHR\r
-        */\r
-       _success: function(data, textStatus, jqXHR) {\r
-               // remove items\r
-               this.containerList.each(function(index, container) {\r
-                       var $toggleButton = $(container).find('.toggleButton');\r
-                       if (WCF.inArray($toggleButton.data('objectID'), data.objectIDs)) {\r
-                               $(container).wcfHighlight();\r
-                               \r
-                               // toggle icon source\r
-                               $toggleButton.attr('src', function() {\r
-                                       if (this.src.match(/enabled(S|M|L)\.png$/)) {\r
-                                               return this.src.replace(/enabled(S|M|L)\.png$/, 'disabled$1\.png');\r
-                                       }\r
-                                       else {\r
-                                               return this.src.replace(/disabled(S|M|L)\.png$/, 'enabled$1\.png');\r
-                                       }\r
-                               });\r
-                               // toogle icon title\r
-                               $toggleButton.attr('title', function() {\r
-                                       if (this.src.match(/enabled(S|M|L)\.png$/)) {\r
-                                               return $(this).data('disableMessage');\r
-                                       }\r
-                                       else {\r
-                                               return $(this).data('enableMessage');\r
-                                       }\r
-                               });\r
-                       }\r
-               });\r
-       }\r
-};\r
-\r
-/**\r
- * Namespace for date-related functions.\r
- */\r
-WCF.Date = {};\r
-\r
-/**\r
- * Provides utility functions for date operations.\r
- */\r
-WCF.Date.Util = {\r
-       /**\r
-        * Returns UTC timestamp, if date is not given, current time will be used.\r
-        * \r
-        * @param       Date            date\r
-        * @return      integer\r
-        */\r
-       gmdate: function(date) {\r
-               var $date = (date) ? date : new Date();\r
-               \r
-               return Math.round(Date.UTC(\r
-                       $date.getUTCFullYear(),\r
-                       $date.getUTCMonth(),\r
-                       $date.getUTCDay(),\r
-                       $date.getUTCHours(),\r
-                       $date.getUTCMinutes(),\r
-                       $date.getUTCSeconds()\r
-               ) / 1000);\r
-       },\r
-       \r
-       /**\r
-        * Returns a Date object with precise offset (including timezone and local timezone).\r
-        * Parameter timestamp must be in miliseconds!\r
-        * \r
-        * @param       integer         timestamp\r
-        * @param       integer         offset\r
-        * @return      Date\r
-        */\r
-       getTimezoneDate: function(timestamp, offset) {\r
-               var $date = new Date(timestamp);\r
-               var $localOffset = $date.getTimezoneOffset() * -1 * 60000;\r
-               \r
-               return new Date((timestamp - $localOffset - offset));\r
-       }\r
-};\r
-\r
-/**\r
- * Handles relative time designations.\r
- */\r
-WCF.Date.Time = function() { this.init(); };\r
-WCF.Date.Time.prototype = {\r
-       /**\r
-        * Initializes relative datetimes.\r
-        */\r
-       init: function() {\r
-               // initialize variables\r
-               this.elements = $('time.datetime');\r
-               this.timestamp = 0;\r
-               \r
-               // calculate relative datetime on init\r
-               this._refresh();\r
-               \r
-               // re-calculate relative datetime every minute\r
-               new WCF.PeriodicalExecuter($.proxy(this._refresh, this), 60000);\r
-       },\r
-       \r
-       /**\r
-        * Refreshes relative datetime for each element.\r
-        */\r
-       _refresh: function() {\r
-               // TESTING ONLY!\r
-               var $date = new Date();\r
-               this.timestamp = ($date.getTime() - $date.getMilliseconds()) / 1000;\r
-               // TESTING ONLY!\r
-               \r
-               this.elements.each($.proxy(this._refreshElement, this));\r
-       },\r
-       \r
-       /**\r
-        * Refreshes relative datetime for current element.\r
-        * \r
-        * @param       integer         index\r
-        * @param       object          element\r
-        */\r
-       _refreshElement: function(index, element) {\r
-               if (!$(element).attr('title')) {\r
-                       $(element).attr('title', $(element).text());\r
-               }\r
-               \r
-               var $timestamp = $(element).data('timestamp');\r
-               var $date = $(element).data('date');\r
-               var $time = $(element).data('time');\r
-               var $offset = $(element).data('offset');\r
-               \r
-               // timestamp is less than 60 minutes ago (display 1 hour ago rather than 60 minutes ago)\r
-               if (this.timestamp < ($timestamp + 3540)) {\r
-                       var $minutes = Math.round((this.timestamp - $timestamp) / 60);\r
-                       $(element).text(eval(WCF.Language.get('wcf.global.date.relative.minutes')));\r
-               }\r
-               // timestamp is less than 24 hours ago\r
-               else if (this.timestamp < ($timestamp + 86400)) {\r
-                       var $hours = Math.round((this.timestamp - $timestamp) / 3600);\r
-                       $(element).text(eval(WCF.Language.get('wcf.global.date.relative.hours')));\r
-               }\r
-               // timestamp is less than a week ago\r
-               else if (this.timestamp < ($timestamp + 604800)) {\r
-                       var $days = Math.round((this.timestamp - $timestamp) / 86400);\r
-                       var $string = eval(WCF.Language.get('wcf.global.date.relative.pastDays'));\r
-               \r
-                       // get day of week\r
-                       var $dateObj = WCF.Date.Util.getTimezoneDate(($timestamp * 1000), $offset);\r
-                       var $dow = $dateObj.getDay();\r
-                       \r
-                       $(element).text($string.replace(/\%day\%/, WCF.Language.get('__days')[$dow]).replace(/\%time\%/, $time));\r
-               }\r
-               // timestamp is between ~700 million years BC and last week\r
-               else {\r
-                       var $string = WCF.Language.get('wcf.global.date.dateTimeFormat');\r
-                       $(element).text($string.replace(/\%date\%/, $date).replace(/\%time\%/, $time));\r
-               }\r
-       }\r
-};\r
-\r
-/**\r
- * Hash-like dictionary. Based upon idead from Prototype's hash\r
- * \r
- * @see        https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/hash.js\r
- */\r
-WCF.Dictionary = function() { this.init(); };\r
-WCF.Dictionary.prototype = {\r
-       /**\r
-        * Initializes a new dictionary.\r
-        */\r
-       init: function() {\r
-               this.variables = { };\r
-       },\r
-       \r
-       /**\r
-        * Adds an entry.\r
-        * \r
-        * @param       string          key\r
-        * @param       mixed           value\r
-        */\r
-       add: function(key, value) {\r
-               this.variables[key] = value;\r
-       },\r
-       \r
-       /**\r
-        * Adds a traditional object to current dataset.\r
-        * \r
-        * @param       object          object\r
-        */\r
-       addObject: function(object) {\r
-               for (var $key in object) {\r
-                       this.add($key, object[$key]);\r
-               }\r
-       },\r
-       \r
-       /**\r
-        * Adds a dictionary to current dataset.\r
-        * \r
-        * @param       object          dictionary\r
-        */\r
-       addDictionary: function(dictionary) {\r
-               dictionary.each($.proxy(function(pair) {\r
-                       this.add(pair.key, pair.value);\r
-               }, this));\r
-       },\r
-       \r
-       /**\r
-        * Retrieves the value of an entry or returns null if key is not found.\r
-        * \r
-        * @param       string          key\r
-        * @returns     mixed\r
-        */\r
-       get: function(key) {\r
-               if (this.isset(key)) {\r
-                       return this.variables[key];\r
-               }\r
-               \r
-               return null;\r
-       },\r
-       \r
-       /**\r
-        * Returns true if given key is a valid entry.\r
-        * \r
-        * @param       string          key\r
-        */\r
-       isset: function(key) {\r
-               return this.variables.hasOwnProperty(key);\r
-       },\r
-       \r
-       /**\r
-        * Removes an entry.\r
-        * \r
-        * @param       string          key\r
-        */\r
-       remove: function(key) {\r
-               delete this.variables[key];\r
-       },\r
-       \r
-       /**\r
-        * Iterates through dictionary.\r
-        * \r
-        * Usage:\r
-        *      var $hash = new WCF.Dictionary();\r
-        *      $hash.add('foo', 'bar');\r
-        *      $hash.each(function(pair) {\r
-        *              // alerts:      foo = bar\r
-        *              alert(pair.key + ' = ' + pair.value);\r
-        *      });\r
-        * \r
-        * @param       function        callback\r
-        */\r
-       each: function(callback) {\r
-               if (!$.isFunction(callback)) {\r
-                       return;\r
-               }\r
-               \r
-               for (var $key in this.variables) {\r
-                       var $value = this.variables[$key];\r
-                       var $pair = {\r
-                               key: $key,\r
-                               value: $value\r
-                       };\r
-                       \r
-                       callback($pair);\r
-               }\r
-       }\r
-};\r
-\r
-/**\r
- * Global language storage.\r
- * \r
- * @see        WCF.Dictionary\r
- */\r
-WCF.Language = {\r
-       _variables: new WCF.Dictionary(),\r
-       \r
-       /**\r
-        * @see WCF.Dictionary.addObject()\r
-        */\r
-       add: function(key, value) {\r
-               this._variables.add(key, value);\r
-       },\r
-       \r
-       /**\r
-        * @see WCF.Dictionary.addObject()\r
-        */\r
-       addObject: function(object) {\r
-               this._variables.addObject(object);\r
-       },\r
-       \r
-       /**\r
-        * Retrieves a variable.\r
-        * \r
-        * @param       string          key\r
-        * @return      mixed\r
-        */\r
-       get: function(key) {\r
-               return this._variables.get(key);\r
-       }\r
-};\r
-\r
-/**\r
- * String utilities.\r
- */\r
-WCF.String = {\r
-       /**\r
-        * Makes a string's first character uppercase\r
-        * \r
-        * @param       string          string\r
-        * @return      string\r
-        */\r
-       ucfirst: function(string) {\r
-               return string.substring(0, 1).toUpperCase() + string.substring(1);\r
-       }\r
-};\r
-\r
-/**\r
- * Basic implementation for WCF TabMenus. Use the data attributes 'active' to specify the\r
- * tab which should be shown on init. Furthermore you may specify a 'store' data-attribute\r
- * which will be filled with the currently selected tab.\r
- */\r
-WCF.TabMenu = {\r
-       /**\r
-        * Initializes all TabMenus\r
-        */\r
-       init: function() {\r
-               $('.tabMenuContainer').each(function(index, tabMenu) {\r
-                       if (!$(tabMenu).attr('id')) {\r
-                               var $randomID = WCF.getRandomID();\r
-                               $(tabMenu).attr('id', $randomID);\r
-                       }\r
-                       \r
-                       // init jQuery UI TabMenu\r
-                       $(tabMenu).wcfTabs({\r
-                               select: function(event, ui) {\r
-                                       var $panel = $(ui.panel);\r
-                                       var $container = $panel.closest('.tabMenuContainer');\r
-                                       \r
-                                       // store currently selected item\r
-                                       if ($container.data('store')) {\r
-                                               if ($.wcfIsset($container.data('store'))) {\r
-                                                       $('#' + $container.data('store')).attr('value', $panel.attr('id'));\r
-                                               }\r
-                                       }\r
-                               }\r
-                       });\r
-                       \r
-                       // display active item on init\r
-                       if ($(tabMenu).data('active')) {\r
-                               $(tabMenu).find('.tabMenuContent').each(function(index, tabMenuItem) {\r
-                                       if ($(tabMenuItem).attr('id') == $(tabMenu).data('active')) {\r
-                                               $(tabMenu).wcfTabs('select', index);\r
-                                       }\r
-                               });\r
-                       }\r
-               });\r
-       }\r
-};\r
-\r
-/**\r
- * Toggles options.\r
- * \r
- * @param      string          element\r
- * @param      array           showItems\r
- * @param      array           hideItems\r
- */\r
-WCF.ToggleOptions = function(element, showItems, hideItems) { this.init(element, showItems, hideItems); };\r
-WCF.ToggleOptions.prototype = {\r
-       /**\r
-        * target item\r
-        * \r
-        * @var jQuery\r
-        */\r
-       _element: null,\r
-       \r
-       /**\r
-        * list of items to be shown\r
-        * \r
-        * @var array\r
-        */\r
-       _showItems: [],\r
-       \r
-       /**\r
-        * list of items to be hidden\r
-        * \r
-        * @var array\r
-        */\r
-       _hideItems: [],\r
-       \r
-       /**\r
-        * Initializes option toggle.\r
-        * \r
-        * @param       string          element\r
-        * @param       array           showItems\r
-        * @param       array           hideItems\r
-        */\r
-       init: function(element, showItems, hideItems) {\r
-               this._element = $('#' + element);\r
-               this._showItems = showItems;\r
-               this._hideItems = hideItems;\r
-               \r
-               // bind event\r
-               this._element.click($.proxy(this._toggle, this));\r
-               \r
-               // execute toggle on init\r
-               this._toggle();\r
-       },\r
-       \r
-       /**\r
-        * Toggles items.\r
-        */\r
-       _toggle: function() {\r
-               if (!this._element.attr('checked')) return;\r
-               \r
-               for (var $i = 0, $length = this._showItems.length; $i < $length; $i++) {\r
-                       var $item = this._showItems[$i];\r
-                       \r
-                       $('#' + $item).show();\r
-               }\r
-               \r
-               for (var $i = 0, $length = this._hideItems.length; $i < $length; $i++) {\r
-                       var $item = this._hideItems[$i];\r
-                       \r
-                       $('#' + $item).hide();\r
-               }\r
-       }\r
-}\r
-\r
-/**\r
- * Basic implementation for WCF dialogs.\r
- */\r
-$.widget('ui.wcfDialog', $.ui.dialog, {\r
-       _init: function() {\r
-               this.options.autoOpen = true;\r
-               this.options.close = function(event, ui) {\r
-                       $(this).parent('.ui-dialog').wcfDropOut('down', $.proxy(function() {\r
-                               $(this).parent('.ui-dialog').empty().remove();\r
-                       }, this));\r
-               };\r
-               this.options.height = 'auto';\r
-               this.options.minHeight = 0;\r
-               this.options.modal = true;\r
-               this.options.width = 'auto';\r
-               \r
-               $.ui.dialog.prototype._init.apply(this, arguments);\r
-       }\r
-});\r
-\r
-/**\r
- * Basic implementation for WCF dialogs loading content\r
- * via AJAX before calling dialog itself.\r
- */\r
-$.widget('ui.wcfAJAXDialog', $.ui.dialog, {\r
-       /**\r
-        * Indicates wether callback was already executed\r
-        * \r
-        * @var boolean\r
-        */\r
-       _callbackExecuted: false,\r
-       \r
-       /**\r
-        * Initializes AJAX-request to fetch content.\r
-        */\r
-       _init: function() {\r
-               if (this.options.ajax) {\r
-                       this._loadContent();\r
-               }\r
-               \r
-               // force dialog to be placed centered\r
-               this.options.position = {\r
-                       my: 'center center',\r
-                       at: 'center center'\r
-               };\r
-               \r
-               // dialog should display a spinner-like image, thus immediately fire up dialog\r
-               this.options.autoOpen = true;\r
-               this.options.width = 'auto';\r
-               this.options.minHeight = 80;\r
-               \r
-               // disable ability to move dialog\r
-               this.options.resizable = false;\r
-               this.options.draggable = false;\r
-               \r
-               this.options.modal = true;\r
-               this.options.hide = {\r
-                       effect: 'drop',\r
-                       direction: 'down'\r
-               };\r
-               \r
-               this.options.close = function(event, ui) {\r
-                       // loading ajax content seems to block properly closing\r
-                       $(this).parent('.ui-dialog').empty().remove();\r
-               };\r
-               \r
-               if (this.options.preventClose) {\r
-                       this.options.closeOnEscape = false;\r
-               }\r
-               \r
-               $.ui.dialog.prototype._init.apply(this, arguments);\r
-               \r
-               // remove complete node instead of removing node-by-node\r
-               if (this.options.hideTitle && this.options.preventClose) {\r
-                       this.element.parent('.ui-dialog').find('div.ui-dialog-titlebar').empty().remove();\r
-               }\r
-               else {\r
-                       if (this.options.hideTitle) {\r
-                               // remove title element\r
-                               $('#ui-dialog-title-' + this.element.attr('id')).empty().remove();\r
-                       }\r
-                       \r
-                       if (this.options.preventClose) {\r
-                               // remove close-button\r
-                               this.element.parent('.ui-dialog').find('a.ui-dialog-titlebar-close').empty().remove();\r
-                       }\r
-               }\r
-       },\r
-       \r
-       /**\r
-        * Loads content via AJAX.\r
-        * \r
-        * @todo        Enforce JSON\r
-        */\r
-       _loadContent: function() {\r
-               var $type = 'GET';\r
-               if (this.options.ajax.type) {\r
-                       $type = this.options.ajax.type;\r
-                       \r
-                       if (this.options.ajax.type != 'GET' && this.options.ajax.type != 'POST') {\r
-                               $type = 'GET';\r
-                       }\r
-               }\r
-               \r
-               var $data = this.options.ajax.data || {};\r
-               \r
-               $.ajax({\r
-                       url: this.options.ajax.url,\r
-                       context: this,\r
-                       dataType: 'json',\r
-                       type: $type,\r
-                       data: $data,\r
-                       success: $.proxy(this._createDialog, this),\r
-                       error: function(transport) {\r
-                               alert(transport.responseText);\r
-                       }\r
-               });\r
-       },\r
-       \r
-       /**\r
-        * Inserts content.\r
-        * \r
-        * @param       string          data\r
-        */\r
-       _createDialog: function(data) {\r
-               data.ignoreTemplate = true;\r
-               this.element.data('responseData', data);\r
-               \r
-               this.element.wcfGrow({\r
-                       content: data.template,\r
-                       parent: this.element.parent('.ui-dialog')\r
-               }, {\r
-                       duration: 600,\r
-                       complete: $.proxy(function(data) {\r
-                               this.element.css({\r
-                                       height: 'auto'\r
-                               });\r
-                               \r
-                               // prevent double execution due to two complete-calls (two times animate)\r
-                               if (this._callbackExecuted) {\r
-                                       return;\r
-                               }\r
-                               \r
-                               this._callbackExecuted = true;\r
-                               \r
-                               this.element.removeClass('overlayLoading');\r
-                               this.element.html(this.element.data('responseData').template);\r
-                               \r
-                               if (this.options.ajax.success) {\r
-                                       this.options.ajax.success();\r
-                               }\r
-                       }, this)\r
-               });\r
-       },\r
-       \r
-       /**\r
-        * Redraws dialog, should be executed everytime content is changed.\r
-        */\r
-       redraw: function() {\r
-               var $dimensions = this.element.getDimensions();\r
-               \r
-               if ($dimensions.height > 200) {\r
-                       this.element.wcfGrow({\r
-                               content: this.element.html(),\r
-                               parent: this.element.parent('.ui-dialog')\r
-                       }, {\r
-                               duration: 600,\r
-                               complete: function() {\r
-                                       $(this).css({ height: 'auto' });\r
-                               }\r
-                       });\r
-               }\r
-       }\r
-});\r
-\r
-/**\r
- * Workaround for ids containing a dot ".", until jQuery UI devs learn\r
- * to properly escape ids ... (it took 18 months until they finally\r
- * fixed it!)\r
- * \r
- * @see        http://bugs.jqueryui.com/ticket/4681\r
- */\r
-$.widget('ui.wcfTabs', $.ui.tabs, {\r
-       _init: function() {\r
-               $.ui.dialog.prototype._init.apply(this, arguments);\r
-       },\r
-       \r
-       _sanitizeSelector: function(hash) {\r
-               return hash.replace(/([:\.])/g, '\\$1');\r
-       }\r
-});\r
-\r
-/**\r
- * Encapsulate eval() within an own function to prevent problems\r
- * with optimizing and minifiny JS.\r
- * \r
- * @param      mixed           expression\r
- * @returns    mixed\r
- */\r
-function wcfEval(expression) {\r
-       return eval(expression);\r
-}
\ No newline at end of file
+/**
+ * Class and function collection for WCF
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+
+/**
+ * Initialize WCF namespace
+ */
+var WCF = {};
+
+/**
+ * Extends jQuery with additional methods.
+ */
+$.extend(true, {
+       /**
+        * Escapes an ID to work with jQuery selectors.
+        *
+        * @see         http://docs.jquery.com/Frequently_Asked_Questions#How_do_I_select_an_element_by_an_ID_that_has_characters_used_in_CSS_notation.3F
+        * @param       string          id
+        * @return      string
+        */
+       wcfEscapeID: function(id) {
+               return id.replace(/(:|\.)/g, '\\$1');
+       },
+       
+       /**
+        * Returns true if given ID exists within DOM.
+        * 
+        * @param       string          id
+        * @return      boolean
+        */
+       wcfIsset: function(id) {
+               return !!$('#' + $.wcfEscapeID(id)).length;
+       }
+});
+
+/**
+ * Extends jQuery's chainable methods.
+ */
+$.fn.extend({
+       /**
+        * Returns tag name of current jQuery element.
+        * 
+        * @returns     string
+        */
+       getTagName: function() {
+               return this.get(0).tagName.toLowerCase();
+       },
+       
+       /**
+        * Returns the dimensions for current element.
+        * 
+        * @see         http://api.jquery.com/hidden-selector/
+        * @param       string          type
+        * @return      object
+        */
+       getDimensions: function(type) {
+               var dimensions = css = {};
+               var wasHidden = false;
+               
+               // show element to retrieve dimensions and restore them later
+               if (this.is(':hidden')) {
+                       css = {
+                               display: this.css('display'),
+                               visibility: this.css('visibility')
+                       };
+                       
+                       wasHidden = true;
+                       
+                       this.css({
+                               display: 'block',
+                               visibility: 'hidden'
+                       });
+               }
+               
+               switch (type) {
+                       case 'inner':
+                               dimensions = {
+                                       height: this.innerHeight(),
+                                       width: this.innerWidth()
+                               };
+                       break;
+                       
+                       case 'outer':
+                               dimensions = {
+                                       height: this.outerHeight(),
+                                       width: this.innerWidth()
+                               };
+                       break;
+                       
+                       default:
+                               dimensions = {
+                                       height: this.height(),
+                                       width: this.width()
+                               };
+                       break;
+               }
+               
+               // restore previous settings
+               if (wasHidden) {
+                       this.css(css);
+               }
+               
+               return dimensions;
+       },
+       
+       /**
+        * Returns the offsets for current element, defaults to position
+        * relative to document.
+        * 
+        * @see         http://api.jquery.com/hidden-selector/
+        * @param       string          type
+        * @return      object
+        */
+       getOffsets: function(type) {
+               var offsets = css = {};
+               var wasHidden = false;
+               
+               // show element to retrieve dimensions and restore them later
+               if (this.is(':hidden')) {
+                       css = {
+                               display: this.css('display'),
+                               visibility: this.css('visibility')
+                       };
+                       
+                       wasHidden = true;
+                       
+                       this.css({
+                               display: 'block',
+                               visibility: 'hidden'
+                       });
+               }
+               
+               switch (type) {
+                       case 'offset':
+                               offsets = this.offset();
+                       break;
+                       
+                       case 'position':
+                       default:
+                               offsets = this.position();
+                       break;
+               }
+               
+               // restore previous settings
+               if (wasHidden) {
+                       this.css(css);
+               }
+               
+               return offsets;
+       },
+       
+       /**
+        * Changes element's position to 'absolute' or 'fixed' while maintaining it's
+        * current position relative to viewport. Optionally removes element from
+        * current DOM-node and moving it into body-element (useful for drag & drop)
+        * 
+        * @param       boolean         rebase
+        * @return      object
+        */
+       makePositioned: function(position, rebase) {
+               if (position != 'absolute' && position != 'fixed') {
+                       position = 'absolute';
+               }
+               
+               var $currentPosition = this.getOffsets('position');
+               this.css({
+                       position: position,
+                       left: $currentPosition.left,
+                       margin: 0,
+                       top: $currentPosition.top
+               });
+               
+               if (rebase) {
+                       this.remove().appentTo('body');
+               }
+               
+               return this;
+       },
+       
+       /**
+        * Disables a form element.
+        * 
+        * @return jQuery
+        */
+       disable: function() {
+               return this.attr('disabled', 'disabled');
+       },
+       
+       /**
+        * Enables a form element.
+        * 
+        * @return      jQuery
+        */
+       enable: function() {
+               return this.removeAttr('disabled');
+       },
+       
+       /**
+        * Applies a grow-effect by resizing element while moving the
+        * element appropriately
+        * 
+        * @param       object          data
+        * @param       object          options
+        * @return      jQuery
+        */
+       wcfGrow: function(data, options) {
+               // create temporarily element to determine dimensions
+               var $tempElementID = WCF.getRandomID();
+               $('body').append('<div id="' + $tempElementID + '" class="wcfDimensions">' + data.content + '</div>');
+               var $tempElement = $('#' + $tempElementID);
+               
+               // get content dimensions
+               var $dimensions = $tempElement.getDimensions();
+               
+               // remove temporarily element
+               $tempElement.empty().remove();
+               
+               // move parent element, used if applying effect on dialogs
+               if (!data.parent) {
+                       data.parent = this;
+               }
+               
+               // calculate values for grow-effect
+               var $borderHeight = parseInt(data.parent.css('borderTopWidth')) + parseInt(data.parent.css('borderBottomWidth'));
+               var $borderWidth = parseInt(data.parent.css('borderLeftWidth')) + parseInt(data.parent.css('borderRightWidth'));
+               
+               var $windowDimensions = $(window).getDimensions();
+               var $leftOffset = Math.round(($windowDimensions.width - ($dimensions.width + $borderWidth)) / 2);
+               var $topOffset = Math.round(($windowDimensions.height - ($dimensions.height + $borderHeight)) / 2);
+               
+               data.parent.makePositioned('fixed', false);
+               data.parent.animate({
+                       left: $leftOffset + 'px',
+                       top: $topOffset + 'px'
+               }, options);
+               
+               return this.animate({
+                       height: $dimensions.height,
+                       width: $dimensions.width
+               }, options);
+       },
+       
+       /**
+        * Shows an element by sliding and fading it into viewport.
+        * 
+        * @param       string          direction
+        * @param       object          callback
+        * @returns     jQuery
+        */
+       wcfDropIn: function(direction, callback) {
+               if (!direction) direction = 'up';
+               
+               return this.show(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, 600, callback);
+       },
+       
+       /**
+        * Hides an element by sliding and fading it out the viewport.
+        * 
+        * @param       string          direction
+        * @param       object          callback
+        * @returns     jQuery
+        */
+       wcfDropOut: function(direction, callback) {
+               if (!direction) direction = 'down';
+               
+               return this.hide(WCF.getEffect(this.getTagName(), 'drop'), { direction: direction }, 600, callback);
+       },
+       
+       /**
+        * Shows an element by blinding it up.
+        * 
+        * @param       string          direction
+        * @param       object          callback
+        * @returns     jQuery
+        */
+       wcfBlindIn: function(direction, callback) {
+               if (!direction) direction = 'vertical';
+               
+               return this.show(WCF.getEffect(this.getTagName(), 'blind'), { direction: direction }, 200, callback);
+       },
+       
+       /**
+        * Hides an element by blinding it down.
+        * 
+        * @param       string          direction
+        * @param       object          callback
+        * @returns     jQuery
+        */
+       wcfBlindOut: function(direction, callback) {
+               if (!direction) direction = 'vertical';
+               
+               return this.hide(WCF.getEffect(this.getTagName(), 'blind'), { direction: direction }, 200, callback);
+       },
+       
+       /**
+        * Highlights an element.
+        * 
+        * @param       object          options
+        * @param       object          callback
+        * @returns     jQuery
+        */
+       wcfHighlight: function(options, callback) {
+               return this.effect('highlight', options, 600, callback);
+       }
+});
+
+/**
+ * WoltLab Community Framework core methods
+ */
+$.extend(WCF, {
+       /**
+        * Counter for dynamic element id's
+        *
+        * @var integer
+        */
+       _idCounter: 0,
+       
+       /**
+        * Shows a modal dialog with a built-in AJAX-loader.
+        * 
+        * @param       string          dialogID
+        * @param       boolean         resetDialog
+        */
+       showAJAXDialog: function(dialogID, resetDialog) {
+               if (!dialogID) {
+                       dialogID = this.getRandomID();
+               }
+               
+               if (!$.wcfIsset(dialogID)) {
+                       $('body').append($('<div id="' + dialogID + '"></div>'));
+               }
+               
+               var dialog = $('#' + $.wcfEscapeID(dialogID));
+               
+               if (resetDialog) {
+                       dialog.empty();
+               }
+               
+               dialog.addClass('overlayLoading');
+               
+               var dialogOptions = arguments[2] || {};
+               dialog.wcfAJAXDialog(dialogOptions);
+       },
+       
+       /**
+        * Shows a modal dialog.
+        * @param       string          dialogID
+        */
+       showDialog: function(dialogID) {
+               // we cannot work with a non-existant dialog, if you wish to
+               // load content via AJAX, see showAJAXDialog() instead
+               if (!$.wcfIsset(dialogID)) return;
+               
+               var $dialog = $('#' + $.wcfEscapeID(dialogID));
+               
+               var dialogOptions = arguments[2] || {};
+               $dialog.wcfDialog(dialogOptions);
+       },
+       
+       /**
+        * Returns a dynamically created id.
+        * 
+        * @see         https://github.com/sstephenson/prototype/blob/master/src/prototype/dom/dom.js#L1789
+        * @return      string
+        */
+       getRandomID: function() {
+               var $elementID = '';
+               
+               do {
+                       $elementID = 'wcf' + this._idCounter++;  
+               }
+               while ($.wcfIsset($elementID));
+               
+               return $elementID;
+       },
+       
+       /**
+        * Wrapper for $.inArray which returns boolean value instead of
+        * index value, similar to PHP's in_array().
+        * 
+        * @param       mixed           needle
+        * @param       array           haystack
+        * @return      boolean
+        */
+       inArray: function(needle, haystack) {
+               return ($.inArray(needle, haystack) != -1);
+       },
+       
+       /**
+        * Adjusts effect for partially supported elements.
+        * 
+        * @param       object          object
+        * @param       string          effect
+        * @return      string
+        */
+       getEffect: function(tagName, effect) {
+               // most effects are not properly supported on table rows, use highlight instead
+               if (tagName == 'tr') {
+                       return 'highlight';
+               }
+               
+               return effect;
+       }
+});
+
+/**
+ * Provides a simple call for periodical executed functions. Based upon
+ * ideas by Prototype's PeriodicalExecuter.
+ * 
+ * @see                https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/periodical_executer.js
+ * @param      function                callback
+ * @param      integer                 delay
+ */
+WCF.PeriodicalExecuter = function(callback, delay) { this.init(callback, delay); };
+WCF.PeriodicalExecuter.prototype = {
+       /**
+        * Initializes a periodical executer.
+        * 
+        * @param       function                callback
+        * @param       integer                 delay
+        */
+       init: function(callback, delay) {
+               this.callback = callback;
+               this.delay = delay;
+               this.loop = true;
+               
+               this.intervalID = setInterval($.proxy(this._execute, this), this.delay);
+       },
+       
+       /**
+        * Executes callback.
+        */
+       _execute: function() {
+               this.callback(this);
+               
+               if (!this.loop) {
+                       clearInterval(this.intervalID);
+               }
+       },
+       
+       /**
+        * Terminates loop.
+        */
+       stop: function() {
+               this.loop = false;
+       }
+};
+
+/**
+ * Namespace for AJAXProxies
+ */
+WCF.Action = {};
+
+/**
+ * Basic implementation for AJAX-based proxyies
+ * 
+ * @param      object          options
+ */
+WCF.Action.Proxy = function(options) { this.init(options); };
+WCF.Action.Proxy.prototype = {
+       /**
+        * Initializes AJAXProxy.
+        * 
+        * @param       object          options
+        */
+       init: function(options) {
+               // initialize default values
+               this.options = $.extend(true, {
+                       autoSend: false,
+                       data: { },
+                       after: null,
+                       init: null,
+                       failure: null,
+                       success: null,
+                       type: 'POST',
+                       url: 'index.php?action=AJAXProxy&t=' + SECURITY_TOKEN + SID_ARG_2ND
+               }, options);
+               
+               this.confirmationDialog = null;
+               this.loading = null;
+               
+               // send request immediately after initialization
+               if (this.options.autoSend) {
+                       this.sendRequest();
+               }
+       },
+       
+       /**
+        * Sends an AJAX request.
+        */
+       sendRequest: function() {
+               this._init();
+               
+               $.ajax({
+                       data: this.options.data,
+                       dataType: 'json',
+                       type: this.options.type,
+                       url: this.options.url,
+                       success: $.proxy(this._success, this),
+                       error: $.proxy(this._failure, this)
+               });
+       },
+       
+       /**
+        * Fires before request is send, displays global loading status.
+        */
+       _init: function() {
+               if ($.isFunction(this.options.init)) {
+                       this.options.init();
+               }
+               
+               $('<div id="actionProxyLoading" style="display: none;">Loading â€¦</div>').appendTo($('body'));
+               this.loading = $('#actionProxyLoading');
+               this.loading.wcfDropIn();
+       },
+       
+       /**
+        * Handles AJAX errors.
+        * 
+        * @param       object          jqXHR
+        * @param       string          textStatus
+        * @param       string          errorThrown
+        */
+       _failure: function(jqXHR, textStatus, errorThrown) {
+               try {
+                       var data = $.parseJSON(jqXHR.responseText);
+                       
+                       // call child method if applicable
+                       if ($.isFunction(this.options.failure)) {
+                               this.options.failure(jqXHR, textStatus, errorThrown, data);
+                       }
+                       
+                       var $randomID = WCF.getRandomID();
+                       $('<div id="' + $randomID + '" title="HTTP/1.0 ' + jqXHR.status + ' ' + errorThrown + '"><p>Der Server antwortete: ' + data.message + '.</p></div>').wcfDialog();
+               }
+               // failed to parse JSON
+               catch (e) {
+                       var $randomID = WCF.getRandomID();
+                       $('<div id="' + $randomID + '" title="HTTP/1.0 ' + jqXHR.status + ' ' + errorThrown + '"><p>Der Server antwortete: ' + jqXHR.responseText + '.</p></div>').wcfDialog();
+               }
+               
+               this._after();
+       },
+       
+       /**
+        * Handles successful AJAX requests.
+        * 
+        * @param       object          data
+        * @param       string          textStatus
+        * @param       object          jqXHR
+        */
+       _success: function(data, textStatus, jqXHR) {
+               // call child method if applicable
+               if ($.isFunction(this.options.success)) {
+                       this.options.success(data, textStatus, jqXHR);
+               }
+               
+               this._after();
+       },
+       
+       /**
+        * Fires after an AJAX request, hides global loading status.
+        */
+       _after: function() {
+               if ($.isFunction(this.options.after)) {
+                       this.options.after();
+               }
+               
+               this.loading.wcfDropOut('up');
+       },
+       
+       /**
+        * Sets options, MUST be used to set parameters before sending request
+        * if calling from child classes.
+        * 
+        * @param       string          optionName
+        * @param       mixed           optionData
+        */
+       setOption: function(optionName, optionData) {
+               this.options[optionName] = optionData;
+       }
+};
+
+/**
+ * Basic implementation for simple proxy access using bound elements.
+ * 
+ * @param      object          options
+ * @param      object          callbacks
+ */
+WCF.Action.SimpleProxy = function(options, callbacks) { this.init(options, callbacks); };
+WCF.Action.SimpleProxy.prototype = {
+       /**
+        * Initializes SimpleProxy.
+        * 
+        * @param       object          options
+        * @param       object          callbacks
+        */
+       init: function(options, callbacks) {
+               /**
+                * action-specific options
+                */
+               this.options = $.extend(true, {
+                       action: '',
+                       className: '',
+                       elements: null,
+                       eventName: 'click'
+               }, options);
+               
+               /**
+                * proxy-specific options
+                */
+               this.callbacks = $.extend(true, {
+                       after: null,
+                       failure: null,
+                       init: null,
+                       success: null
+               }, callbacks);
+               
+               if (!this.options.elements) return;
+               
+               // initialize proxy
+               this.proxy = new WCF.Action.Proxy(this.callbacks);
+               
+               // bind event listener
+               this.options.elements.each($.proxy(function(index, element) {
+                       $(element).bind(this.options.eventName, $.proxy(this._handleEvent, this));
+               }, this));
+       },
+       
+       /**
+        * Handles event actions.
+        * 
+        * @param       object          event
+        */
+       _handleEvent: function(event) {
+               this.proxy.setOption('data', {
+                       actionName: this.options.action,
+                       className: this.options.className,
+                       objectIDs: [ $(event.target).data('objectID') ]
+               });
+               
+               this.proxy.sendRequest();
+       }
+};
+
+/**
+ * Basic implementation for AJAXProxy-based deletion.
+ * 
+ * @param      string          className
+ * @param      jQuery          containerList
+ */
+WCF.Action.Delete = function(className, containerList) { this.init(className, containerList); };
+WCF.Action.Delete.prototype = {
+       /**
+        * Initializes 'delete'-Proxy.
+        * 
+        * @param       string          className
+        * @param       jQuery          containerList
+        */
+       init: function(className, containerList) {
+               if (!containerList.length) return;
+               this.containerList = containerList;
+               this.className = className;
+               
+               // initialize proxy
+               var options = {
+                       success: $.proxy(this._success, this)
+               };
+               this.proxy = new WCF.Action.Proxy(options);
+               
+               // bind event listener
+               this.containerList.each($.proxy(function(index, container) {
+                       $(container).find('.deleteButton').bind('click', $.proxy(this._click, this));
+               }, this));
+       },
+       
+       /**
+        * Sends AJAX request.
+        * 
+        * @param       object          event
+        */
+       _click: function(event) {
+               var $target = $(event.target);
+               
+               if ($target.data('confirmMessage')) {
+                       if (confirm($target.data('confirmMessage'))) {
+                               this._sendRequest($target);
+                       }
+               }
+               else {
+                       this._sendRequest($target);
+               }
+               
+       },
+       
+       _sendRequest: function(object) {
+               this.proxy.setOption('data', {
+                       actionName: 'delete',
+                       className: this.className,
+                       objectIDs: [ $(object).data('objectID') ]
+               });
+               
+               this.proxy.sendRequest();
+       },
+       
+       /**
+        * Deletes items from containers.
+        * 
+        * @param       object          data
+        * @param       string          textStatus
+        * @param       object          jqXHR
+        */
+       _success: function(data, textStatus, jqXHR) {
+               // remove items
+               this.containerList.each(function(index, container) {
+                       var $objectID = $(container).find('.deleteButton').data('objectID');
+                       if (WCF.inArray($objectID, data.objectIDs)) {
+                               $(container).wcfBlindOut('up', function() {
+                                       $(container).empty().remove();
+                               }, container);
+                       }
+               });
+       }
+};
+
+/**
+ * Basic implementation for AJAXProxy-based toggle actions.
+ * 
+ * @param      string          className
+ * @param      jQuery          containerList
+ */
+WCF.Action.Toggle = function(className, containerList) { this.init(className, containerList); };
+WCF.Action.Toggle.prototype = {
+       /**
+        * Initializes 'toggle'-Proxy
+        * 
+        * @param       string          className
+        * @param       jQuery          containerList
+        */
+       init: function(className, containerList) {
+               if (!containerList.length) return;
+               this.containerList = containerList;
+               this.className = className;
+               
+               // initialize proxy
+               var options = {
+                       success: $.proxy(this._success, this)
+               };
+               this.proxy = new WCF.Action.Proxy(options);
+               
+               // bind event listener
+               this.containerList.each($.proxy(function(index, container) {
+                       $(container).find('.toggleButton').bind('click', $.proxy(this._click, this));
+               }, this));
+       },
+       
+       /**
+        * Sends AJAX request.
+        * 
+        * @param       object          event
+        */
+       _click: function(event) {
+               this.proxy.setOption('data', {
+                       actionName: 'toggle',
+                       className: this.className,
+                       objectIDs: [ $(event.target).data('objectID') ]
+               });
+               
+               this.proxy.sendRequest();
+       },
+       
+       /**
+        * Toggles status icons.
+        * 
+        * @param       object          data
+        * @param       string          textStatus
+        * @param       object          jqXHR
+        */
+       _success: function(data, textStatus, jqXHR) {
+               // remove items
+               this.containerList.each(function(index, container) {
+                       var $toggleButton = $(container).find('.toggleButton');
+                       if (WCF.inArray($toggleButton.data('objectID'), data.objectIDs)) {
+                               $(container).wcfHighlight();
+                               
+                               // toggle icon source
+                               $toggleButton.attr('src', function() {
+                                       if (this.src.match(/enabled(S|M|L)\.png$/)) {
+                                               return this.src.replace(/enabled(S|M|L)\.png$/, 'disabled$1\.png');
+                                       }
+                                       else {
+                                               return this.src.replace(/disabled(S|M|L)\.png$/, 'enabled$1\.png');
+                                       }
+                               });
+                               // toogle icon title
+                               $toggleButton.attr('title', function() {
+                                       if (this.src.match(/enabled(S|M|L)\.png$/)) {
+                                               return $(this).data('disableMessage');
+                                       }
+                                       else {
+                                               return $(this).data('enableMessage');
+                                       }
+                               });
+                       }
+               });
+       }
+};
+
+/**
+ * Namespace for date-related functions.
+ */
+WCF.Date = {};
+
+/**
+ * Provides utility functions for date operations.
+ */
+WCF.Date.Util = {
+       /**
+        * Returns UTC timestamp, if date is not given, current time will be used.
+        * 
+        * @param       Date            date
+        * @return      integer
+        */
+       gmdate: function(date) {
+               var $date = (date) ? date : new Date();
+               
+               return Math.round(Date.UTC(
+                       $date.getUTCFullYear(),
+                       $date.getUTCMonth(),
+                       $date.getUTCDay(),
+                       $date.getUTCHours(),
+                       $date.getUTCMinutes(),
+                       $date.getUTCSeconds()
+               ) / 1000);
+       },
+       
+       /**
+        * Returns a Date object with precise offset (including timezone and local timezone).
+        * Parameter timestamp must be in miliseconds!
+        * 
+        * @param       integer         timestamp
+        * @param       integer         offset
+        * @return      Date
+        */
+       getTimezoneDate: function(timestamp, offset) {
+               var $date = new Date(timestamp);
+               var $localOffset = $date.getTimezoneOffset() * -1 * 60000;
+               
+               return new Date((timestamp - $localOffset - offset));
+       }
+};
+
+/**
+ * Handles relative time designations.
+ */
+WCF.Date.Time = function() { this.init(); };
+WCF.Date.Time.prototype = {
+       /**
+        * Initializes relative datetimes.
+        */
+       init: function() {
+               // initialize variables
+               this.elements = $('time.datetime');
+               this.timestamp = 0;
+               
+               // calculate relative datetime on init
+               this._refresh();
+               
+               // re-calculate relative datetime every minute
+               new WCF.PeriodicalExecuter($.proxy(this._refresh, this), 60000);
+       },
+       
+       /**
+        * Refreshes relative datetime for each element.
+        */
+       _refresh: function() {
+               // TESTING ONLY!
+               var $date = new Date();
+               this.timestamp = ($date.getTime() - $date.getMilliseconds()) / 1000;
+               // TESTING ONLY!
+               
+               this.elements.each($.proxy(this._refreshElement, this));
+       },
+       
+       /**
+        * Refreshes relative datetime for current element.
+        * 
+        * @param       integer         index
+        * @param       object          element
+        */
+       _refreshElement: function(index, element) {
+               if (!$(element).attr('title')) {
+                       $(element).attr('title', $(element).text());
+               }
+               
+               var $timestamp = $(element).data('timestamp');
+               var $date = $(element).data('date');
+               var $time = $(element).data('time');
+               var $offset = $(element).data('offset');
+               
+               // timestamp is less than 60 minutes ago (display 1 hour ago rather than 60 minutes ago)
+               if (this.timestamp < ($timestamp + 3540)) {
+                       var $minutes = Math.round((this.timestamp - $timestamp) / 60);
+                       $(element).text(eval(WCF.Language.get('wcf.global.date.relative.minutes')));
+               }
+               // timestamp is less than 24 hours ago
+               else if (this.timestamp < ($timestamp + 86400)) {
+                       var $hours = Math.round((this.timestamp - $timestamp) / 3600);
+                       $(element).text(eval(WCF.Language.get('wcf.global.date.relative.hours')));
+               }
+               // timestamp is less than a week ago
+               else if (this.timestamp < ($timestamp + 604800)) {
+                       var $days = Math.round((this.timestamp - $timestamp) / 86400);
+                       var $string = eval(WCF.Language.get('wcf.global.date.relative.pastDays'));
+               
+                       // get day of week
+                       var $dateObj = WCF.Date.Util.getTimezoneDate(($timestamp * 1000), $offset);
+                       var $dow = $dateObj.getDay();
+                       
+                       $(element).text($string.replace(/\%day\%/, WCF.Language.get('__days')[$dow]).replace(/\%time\%/, $time));
+               }
+               // timestamp is between ~700 million years BC and last week
+               else {
+                       var $string = WCF.Language.get('wcf.global.date.dateTimeFormat');
+                       $(element).text($string.replace(/\%date\%/, $date).replace(/\%time\%/, $time));
+               }
+       }
+};
+
+/**
+ * Hash-like dictionary. Based upon idead from Prototype's hash
+ * 
+ * @see        https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/hash.js
+ */
+WCF.Dictionary = function() { this.init(); };
+WCF.Dictionary.prototype = {
+       /**
+        * Initializes a new dictionary.
+        */
+       init: function() {
+               this.variables = { };
+       },
+       
+       /**
+        * Adds an entry.
+        * 
+        * @param       string          key
+        * @param       mixed           value
+        */
+       add: function(key, value) {
+               this.variables[key] = value;
+       },
+       
+       /**
+        * Adds a traditional object to current dataset.
+        * 
+        * @param       object          object
+        */
+       addObject: function(object) {
+               for (var $key in object) {
+                       this.add($key, object[$key]);
+               }
+       },
+       
+       /**
+        * Adds a dictionary to current dataset.
+        * 
+        * @param       object          dictionary
+        */
+       addDictionary: function(dictionary) {
+               dictionary.each($.proxy(function(pair) {
+                       this.add(pair.key, pair.value);
+               }, this));
+       },
+       
+       /**
+        * Retrieves the value of an entry or returns null if key is not found.
+        * 
+        * @param       string          key
+        * @returns     mixed
+        */
+       get: function(key) {
+               if (this.isset(key)) {
+                       return this.variables[key];
+               }
+               
+               return null;
+       },
+       
+       /**
+        * Returns true if given key is a valid entry.
+        * 
+        * @param       string          key
+        */
+       isset: function(key) {
+               return this.variables.hasOwnProperty(key);
+       },
+       
+       /**
+        * Removes an entry.
+        * 
+        * @param       string          key
+        */
+       remove: function(key) {
+               delete this.variables[key];
+       },
+       
+       /**
+        * Iterates through dictionary.
+        * 
+        * Usage:
+        *      var $hash = new WCF.Dictionary();
+        *      $hash.add('foo', 'bar');
+        *      $hash.each(function(pair) {
+        *              // alerts:      foo = bar
+        *              alert(pair.key + ' = ' + pair.value);
+        *      });
+        * 
+        * @param       function        callback
+        */
+       each: function(callback) {
+               if (!$.isFunction(callback)) {
+                       return;
+               }
+               
+               for (var $key in this.variables) {
+                       var $value = this.variables[$key];
+                       var $pair = {
+                               key: $key,
+                               value: $value
+                       };
+                       
+                       callback($pair);
+               }
+       }
+};
+
+/**
+ * Global language storage.
+ * 
+ * @see        WCF.Dictionary
+ */
+WCF.Language = {
+       _variables: new WCF.Dictionary(),
+       
+       /**
+        * @see WCF.Dictionary.addObject()
+        */
+       add: function(key, value) {
+               this._variables.add(key, value);
+       },
+       
+       /**
+        * @see WCF.Dictionary.addObject()
+        */
+       addObject: function(object) {
+               this._variables.addObject(object);
+       },
+       
+       /**
+        * Retrieves a variable.
+        * 
+        * @param       string          key
+        * @return      mixed
+        */
+       get: function(key) {
+               return this._variables.get(key);
+       }
+};
+
+/**
+ * String utilities.
+ */
+WCF.String = {
+       /**
+        * Makes a string's first character uppercase
+        * 
+        * @param       string          string
+        * @return      string
+        */
+       ucfirst: function(string) {
+               return string.substring(0, 1).toUpperCase() + string.substring(1);
+       }
+};
+
+/**
+ * Basic implementation for WCF TabMenus. Use the data attributes 'active' to specify the
+ * tab which should be shown on init. Furthermore you may specify a 'store' data-attribute
+ * which will be filled with the currently selected tab.
+ */
+WCF.TabMenu = {
+       /**
+        * Initializes all TabMenus
+        */
+       init: function() {
+               $('.tabMenuContainer').each(function(index, tabMenu) {
+                       if (!$(tabMenu).attr('id')) {
+                               var $randomID = WCF.getRandomID();
+                               $(tabMenu).attr('id', $randomID);
+                       }
+                       
+                       // init jQuery UI TabMenu
+                       $(tabMenu).wcfTabs({
+                               select: function(event, ui) {
+                                       var $panel = $(ui.panel);
+                                       var $container = $panel.closest('.tabMenuContainer');
+                                       
+                                       // store currently selected item
+                                       if ($container.data('store')) {
+                                               if ($.wcfIsset($container.data('store'))) {
+                                                       $('#' + $container.data('store')).attr('value', $panel.attr('id'));
+                                               }
+                                       }
+                               }
+                       });
+                       
+                       // display active item on init
+                       if ($(tabMenu).data('active')) {
+                               $(tabMenu).find('.tabMenuContent').each(function(index, tabMenuItem) {
+                                       if ($(tabMenuItem).attr('id') == $(tabMenu).data('active')) {
+                                               $(tabMenu).wcfTabs('select', index);
+                                       }
+                               });
+                       }
+               });
+       }
+};
+
+/**
+ * Toggles options.
+ * 
+ * @param      string          element
+ * @param      array           showItems
+ * @param      array           hideItems
+ */
+WCF.ToggleOptions = function(element, showItems, hideItems) { this.init(element, showItems, hideItems); };
+WCF.ToggleOptions.prototype = {
+       /**
+        * target item
+        * 
+        * @var jQuery
+        */
+       _element: null,
+       
+       /**
+        * list of items to be shown
+        * 
+        * @var array
+        */
+       _showItems: [],
+       
+       /**
+        * list of items to be hidden
+        * 
+        * @var array
+        */
+       _hideItems: [],
+       
+       /**
+        * Initializes option toggle.
+        * 
+        * @param       string          element
+        * @param       array           showItems
+        * @param       array           hideItems
+        */
+       init: function(element, showItems, hideItems) {
+               this._element = $('#' + element);
+               this._showItems = showItems;
+               this._hideItems = hideItems;
+               
+               // bind event
+               this._element.click($.proxy(this._toggle, this));
+               
+               // execute toggle on init
+               this._toggle();
+       },
+       
+       /**
+        * Toggles items.
+        */
+       _toggle: function() {
+               if (!this._element.attr('checked')) return;
+               
+               for (var $i = 0, $length = this._showItems.length; $i < $length; $i++) {
+                       var $item = this._showItems[$i];
+                       
+                       $('#' + $item).show();
+               }
+               
+               for (var $i = 0, $length = this._hideItems.length; $i < $length; $i++) {
+                       var $item = this._hideItems[$i];
+                       
+                       $('#' + $item).hide();
+               }
+       }
+}
+
+/**
+ * Basic implementation for WCF dialogs.
+ */
+$.widget('ui.wcfDialog', $.ui.dialog, {
+       _init: function() {
+               this.options.autoOpen = true;
+               this.options.close = function(event, ui) {
+                       $(this).parent('.ui-dialog').wcfDropOut('down', $.proxy(function() {
+                               $(this).parent('.ui-dialog').empty().remove();
+                       }, this));
+               };
+               this.options.height = 'auto';
+               this.options.minHeight = 0;
+               this.options.modal = true;
+               this.options.width = 'auto';
+               
+               $.ui.dialog.prototype._init.apply(this, arguments);
+       }
+});
+
+/**
+ * Basic implementation for WCF dialogs loading content
+ * via AJAX before calling dialog itself.
+ */
+$.widget('ui.wcfAJAXDialog', $.ui.dialog, {
+       /**
+        * Indicates wether callback was already executed
+        * 
+        * @var boolean
+        */
+       _callbackExecuted: false,
+       
+       /**
+        * Initializes AJAX-request to fetch content.
+        */
+       _init: function() {
+               if (this.options.ajax) {
+                       this._loadContent();
+               }
+               
+               // force dialog to be placed centered
+               this.options.position = {
+                       my: 'center center',
+                       at: 'center center'
+               };
+               
+               // dialog should display a spinner-like image, thus immediately fire up dialog
+               this.options.autoOpen = true;
+               this.options.width = 'auto';
+               this.options.minHeight = 80;
+               
+               // disable ability to move dialog
+               this.options.resizable = false;
+               this.options.draggable = false;
+               
+               this.options.modal = true;
+               this.options.hide = {
+                       effect: 'drop',
+                       direction: 'down'
+               };
+               
+               this.options.close = function(event, ui) {
+                       // loading ajax content seems to block properly closing
+                       $(this).parent('.ui-dialog').empty().remove();
+               };
+               
+               if (this.options.preventClose) {
+                       this.options.closeOnEscape = false;
+               }
+               
+               $.ui.dialog.prototype._init.apply(this, arguments);
+               
+               // remove complete node instead of removing node-by-node
+               if (this.options.hideTitle && this.options.preventClose) {
+                       this.element.parent('.ui-dialog').find('div.ui-dialog-titlebar').empty().remove();
+               }
+               else {
+                       if (this.options.hideTitle) {
+                               // remove title element
+                               $('#ui-dialog-title-' + this.element.attr('id')).empty().remove();
+                       }
+                       
+                       if (this.options.preventClose) {
+                               // remove close-button
+                               this.element.parent('.ui-dialog').find('a.ui-dialog-titlebar-close').empty().remove();
+                       }
+               }
+       },
+       
+       /**
+        * Loads content via AJAX.
+        * 
+        * @todo        Enforce JSON
+        */
+       _loadContent: function() {
+               var $type = 'GET';
+               if (this.options.ajax.type) {
+                       $type = this.options.ajax.type;
+                       
+                       if (this.options.ajax.type != 'GET' && this.options.ajax.type != 'POST') {
+                               $type = 'GET';
+                       }
+               }
+               
+               var $data = this.options.ajax.data || {};
+               
+               $.ajax({
+                       url: this.options.ajax.url,
+                       context: this,
+                       dataType: 'json',
+                       type: $type,
+                       data: $data,
+                       success: $.proxy(this._createDialog, this),
+                       error: function(transport) {
+                               alert(transport.responseText);
+                       }
+               });
+       },
+       
+       /**
+        * Inserts content.
+        * 
+        * @param       string          data
+        */
+       _createDialog: function(data) {
+               data.ignoreTemplate = true;
+               this.element.data('responseData', data);
+               
+               this.element.wcfGrow({
+                       content: data.template,
+                       parent: this.element.parent('.ui-dialog')
+               }, {
+                       duration: 600,
+                       complete: $.proxy(function(data) {
+                               this.element.css({
+                                       height: 'auto'
+                               });
+                               
+                               // prevent double execution due to two complete-calls (two times animate)
+                               if (this._callbackExecuted) {
+                                       return;
+                               }
+                               
+                               this._callbackExecuted = true;
+                               
+                               this.element.removeClass('overlayLoading');
+                               this.element.html(this.element.data('responseData').template);
+                               
+                               if (this.options.ajax.success) {
+                                       this.options.ajax.success();
+                               }
+                       }, this)
+               });
+       },
+       
+       /**
+        * Redraws dialog, should be executed everytime content is changed.
+        */
+       redraw: function() {
+               var $dimensions = this.element.getDimensions();
+               
+               if ($dimensions.height > 200) {
+                       this.element.wcfGrow({
+                               content: this.element.html(),
+                               parent: this.element.parent('.ui-dialog')
+                       }, {
+                               duration: 600,
+                               complete: function() {
+                                       $(this).css({ height: 'auto' });
+                               }
+                       });
+               }
+       }
+});
+
+/**
+ * Workaround for ids containing a dot ".", until jQuery UI devs learn
+ * to properly escape ids ... (it took 18 months until they finally
+ * fixed it!)
+ * 
+ * @see        http://bugs.jqueryui.com/ticket/4681
+ */
+$.widget('ui.wcfTabs', $.ui.tabs, {
+       _init: function() {
+               $.ui.dialog.prototype._init.apply(this, arguments);
+       },
+       
+       _sanitizeSelector: function(hash) {
+               return hash.replace(/([:\.])/g, '\\$1');
+       }
+});
+
+/**
+ * Encapsulate eval() within an own function to prevent problems
+ * with optimizing and minifiny JS.
+ * 
+ * @param      mixed           expression
+ * @returns    mixed
+ */
+function wcfEval(expression) {
+       return eval(expression);
+}