-/**
- * 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);
- },
-
- /**
- * Adds thousands separators to a given number.
- *
- * @param mixed number
- * @return string
- */
- addThousandsSeparator: function(number) {
- var $numberString = String(number);
- if (number >= 1000 || number <= -1000) {
- var $negative = false;
- if (number <= -1000) {
- $negative = true;
- $numberString = $numberString.substring(1);
- }
- var $separator = WCF.Language.get('wcf.global.thousandsSeparator');
-
- if ($separator != null && $separator != '') {
- var $numElements = new Array();
- var $firstPart = $numberString.length % 3
- if ($firstPart == 0) $firstPart = 3;
- for (var $i = 0; $i < Math.ceil($numberString.length / 3); $i++) {
- if ($i == 0) $numElements.push($numberString.substring(0, $firstPart));
- else {
- var $start = (($i - 1) * 3) + $firstPart
- $numElements.push($numberString.substring($start, $start + 3));
- }
- }
- $numberString = (($negative) ? ('-') : ('')) + $numElements.join($separator);
- }
- }
-
- return $numberString;
- }
-};
-
-/**
- * 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');
- }
-});
-
-/**
- * jQuery widget implementation of the wcf pagination.
- */
-$.widget('ui.wcfPages', {
- SHOW_LINKS: 11,
- SHOW_SUB_LINKS: 20,
-
- options: {
- // vars
- activePage: 1,
- maxPage: 1,
-
- // icons
- previousIcon: RELATIVE_WCF_DIR + 'icon/previousS.png',
- previousDisabledIcon: RELATIVE_WCF_DIR + 'icon/previousDisabledS.png',
- arrowDownIcon: RELATIVE_WCF_DIR + 'icon/arrowDown.png',
- nextIcon: RELATIVE_WCF_DIR + 'icon/nextS.png',
- nextDisabledIcon: RELATIVE_WCF_DIR + 'icon/nextDisabledS.png',
-
- // language
- // we use options here instead of language variables, because the paginator is not only usable with pages
- nextPage: null,
- previousPage: null,
- },
-
- /**
- * Creates the pages widget.
- */
- _create: function() {
- if (this.options.nextPage === null) this.options.nextPage = WCF.Language.get('wcf.global.page.next');
- if (this.options.previousPage === null) this.options.previousPage = WCF.Language.get('wcf.global.page.previous');
-
- this.element.addClass('pageNavigation');
-
- this._render();
- },
-
- /**
- * Destroys the pages widget.
- */
- destroy: function() {
- $.Widget.prototype.destroy.apply(this, arguments);
-
- this.element.children().remove();
- },
-
- /**
- * Renders th pages widget.
- */
- _render: function() {
- // only render if we have more than 1 page
- if (!this.options.disabled && this.options.maxPage > 1) {
- // make sure pagination is visible
- if (this.element.hasClass('hidden')) {
- this.element.removeClass('hidden');
- }
- this.element.show();
-
- this.element.children().remove();
-
- var $pageList = $('<ul></ul>');
- this.element.append($pageList);
-
- var $previousElement = $('<li></li>');
- $pageList.append($previousElement);
-
- if (this.options.activePage > 1) {
- var $previousLink = $('<a' + ((this.options.previousPage != null) ? (' title="' + this.options.previousPage + '"') : ('')) + '></a>');
- $previousElement.append($previousLink);
- this._bindSwitchPage($previousLink, this.options.activePage - 1);
-
- var $previousImage = $('<img src="' + this.options.previousIcon + '" alt="" />');
- $previousLink.append($previousImage);
- }
- else {
- var $previousImage = $('<img src="' + this.options.previousDisabledIcon + '" alt="" />');
- $previousElement.append($previousImage);
- }
- $previousElement.addClass('skip');
-
- // add first page
- $pageList.append(this._renderLink(1));
-
- // calculate page links
- var $maxLinks = this.SHOW_LINKS - 4;
- var $linksBefore = this.options.activePage - 2;
- if ($linksBefore < 0) $linksBefore = 0;
- var $linksAfter = this.options.maxPage - (this.options.activePage + 1);
- if ($linksAfter < 0) $linksAfter = 0;
- if (this.options.activePage > 1 && this.options.activePage < this.options.maxPage) $maxLinks--;
-
- var $half = $maxLinks / 2;
- var $left = this.options.activePage;
- var $right = this.options.activePage;
- if ($left < 1) $left = 1;
- if ($right < 1) $right = 1;
- if ($right > this.options.maxPage - 1) $right = this.options.maxPage - 1;
-
- if ($linksBefore >= $half) {
- $left -= $half;
- }
- else {
- $left -= $linksBefore;
- $right += $half - $linksBefore;
- }
-
- if ($linksAfter >= $half) {
- $right += $half;
- }
- else {
- $right += $linksAfter;
- $left -= $half - $linksAfter;
- }
-
- $right = Math.ceil($right);
- $left = Math.ceil($left);
- if ($left < 1) $left = 1;
- if ($right > this.options.maxPage) $right = this.options.maxPage;
-
- // left ... links
- if ($left > 1) {
- if ($left - 1 < 2) {
- $pageList.append(this._renderLink(2));
- }
- else {
- var $leftChildren = $('<li class="children"></li>');
- $pageList.append($leftChildren);
-
- var $leftChildrenLink = $('<a>…</a>');
- $leftChildren.append($leftChildrenLink);
- $leftChildrenLink.click($.proxy(this._startInput, this));
-
- var $leftChildrenImage = $('<img src="' + this.options.arrowDownIcon + '" alt="" />');
- $leftChildrenLink.append($leftChildrenImage);
-
- var $leftChildrenInput = $('<input type="text" class="inputText" name="pageNo" />');
- $leftChildren.append($leftChildrenInput);
- $leftChildrenInput.keydown($.proxy(this._handleInput, this));
- $leftChildrenInput.keyup($.proxy(this._handleInput, this));
- $leftChildrenInput.blur($.proxy(this._stopInput, this));
-
- var $leftChildrenContainer = $('<div></div>');
- $leftChildren.append($leftChildrenContainer);
-
- var $leftChildrenList = $('<ul></u>');
- $leftChildrenContainer.append($leftChildrenList);
-
- // render sublinks
- var $k = 0;
- var $step = Math.ceil(($left - 2) / this.SHOW_SUB_LINKS);
- for (var $i = 2; $i <= $left; $i += $step) {
- $leftChildrenList.append(this._renderLink($i, ($k != 0 && $k % 4 == 0)));
- $k++;
- }
- }
- }
-
- // visible links
- for (var $i = $left + 1; $i < $right; $i++) {
- $pageList.append(this._renderLink($i));
- }
-
- // right ... links
- if ($right < this.options.maxPage) {
- if (this.options.maxPage - $right < 2) {
- $pageList.append(this._renderLink(this.options.maxPage - 1));
- }
- else {
- var $rightChildren = $('<li class="children"></li>');
- $pageList.append($rightChildren);
-
- var $rightChildrenLink = $('<a>…</a>');
- $rightChildren.append($rightChildrenLink);
- $rightChildrenLink.click($.proxy(this._startInput, this));
-
- var $rightChildrenImage = $('<img src="' + this.options.arrowDownIcon + '" alt="" />');
- $rightChildrenLink.append($rightChildrenImage);
-
- var $rightChildrenInput = $('<input type="text" class="inputText" name="pageNo" />');
- $rightChildren.append($rightChildrenInput);
- $rightChildrenInput.keydown($.proxy(this._handleInput, this));
- $rightChildrenInput.keyup($.proxy(this._handleInput, this));
- $rightChildrenInput.blur($.proxy(this._stopInput, this));
-
- var $rightChildrenContainer = $('<div></div>');
- $rightChildren.append($rightChildrenContainer);
-
- var $rightChildrenList = $('<ul></ul>');
- $rightChildrenContainer.append($rightChildrenList);
-
- // render sublinks
- var $k = 0;
- var $step = Math.ceil((this.options.maxPage - $right) / this.SHOW_SUB_LINKS);
- for (var $i = $right; $i < this.options.maxPage; $i += $step) {
- $rightChildrenList.append(this._renderLink($i, ($k != 0 && $k % 4 == 0)));
- $k++;
- }
- }
- }
-
- // add last page
- $pageList.append(this._renderLink(this.options.maxPage));
-
- // add next button
- var $nextElement = $('<li></li>');
- $pageList.append($nextElement);
-
- if (this.options.activePage < this.options.maxPage) {
- var $nextLink = $('<a title="' + ((this.options.nextPage != null) ? (' title="' + this.options.nextPage + '"') : ('')) + '"></a>');
- $nextElement.append($nextLink);
- this._bindSwitchPage($nextLink, this.options.activePage + 1);
-
- var $nextImage = $('<img src="' + this.options.nextIcon + '" alt="" />');
- $nextLink.append($nextImage);
- }
- else {
- var $nextImage = $('<img src="' + this.options.nextDisabledIcon + '" alt="" />');
- $nextElement.append($nextImage);
- }
- $nextElement.addClass('skip');
- }
- else {
- // otherwise hide the paginator if not already hidden
- this.element.hide();
- }
- },
-
- /**
- * Renders a page link
- *
- * @parameter integer page
- *
- * @return $(element)
- */
- _renderLink: function(page, lineBreak) {
- var $pageElement = $('<li></li>');
- if (lineBreak != undefined && lineBreak) {
- $pageElement.addClass('break');
- }
- if (page != this.options.activePage) {
- var $pageLink = $('<a>' + WCF.String.addThousandsSeparator(page) + '</a>');
- $pageElement.append($pageLink);
- this._bindSwitchPage($pageLink, page);
- }
- else {
- $pageElement.addClass('active');
- var $pageSubElement = $('<span>' + WCF.String.addThousandsSeparator(page) + '</span>');
- $pageElement.append($pageSubElement);
- }
-
- return $pageElement;
- },
-
- /**
- * Binds the 'click'-event for the page switching to the given element.
- *
- * @parameter $(element) element
- * @paremeter integer page
- */
- _bindSwitchPage: function(element, page) {
- var $self = this;
- element.click(function() {
- $self.switchPage(page);
- });
- },
-
- /**
- * Switches to the given page
- *
- * @parameter Event event
- * @parameter integer page
- */
- switchPage: function(page) {
- this._setOption('activePage', page);
- },
-
- /**
- * Sets the given option to the given value.
- * See the jQuery UI widget documentation for more.
- */
- _setOption: function(key, value) {
- if (key == 'activePage') {
- if (value != this.options[key] && value > 0 && value <= this.options.maxPage) {
- // you can prevent the page switching by returning false or by event.preventDefault()
- // in a shouldSwitch-callback. e.g. if an AJAX request is already running.
- var $result = this._trigger('shouldSwitch', undefined, {
- nextPage: value,
- });
-
- if ($result) {
- this.options[key] = value;
- this._render();
- this._trigger('switched', undefined, {
- activePage: value,
- });
- }
- else {
- this._trigger('notSwitched', undefined, {
- activePage: value,
- });
- }
- }
- }
- else {
- this.options[key] = value;
-
- if (key == 'disabled') {
- if (value) {
- this.element.children().remove();
- }
- else {
- this._render()
- }
- }
- else if (key == 'maxPage') {
- this._render();
- }
- }
-
- return this;
- },
-
- /**
- * Start input of pagenumber
- *
- * @parameter Event event
- */
- _startInput: function(event) {
- // hide a-tag
- var $childLink = $(event.currentTarget);
- if (!$childLink.is('a')) $childLink = $childLink.parent('a');
-
- $childLink.hide();
-
- // show input-tag
- var $childInput = $childLink.parent('li').children('input')
- .css('display', 'block')
- .val('');
-
- $childInput.focus();
- },
-
- /**
- * Stops input of pagenumber
- *
- * @parameter Event event
- */
- _stopInput: function(event) {
- // hide input-tag
- var $childInput = $(event.currentTarget);
- $childInput.css('display', 'none');
-
- // show a-tag
- var $childContainer = $childInput.parent('li')
- if ($childContainer != undefined && $childContainer != null) {
- $childContainer.children('a').show();
- }
- },
-
- /**
- * Handles input of pagenumber
- *
- * @parameter Event event
- */
- _handleInput: function(event) {
- var $ie7 = ($.browser.msie && $.browser.version == '7.0');
- if (event.type != 'keyup' || $ie7) {
- if (!$ie7 || ((event.which == 13 || event.which == 27) && event.type == 'keyup')) {
- if (event.which == 13) {
- this.switchPage(parseInt($(event.currentTarget).val()));
- }
-
- if (event.which == 13 || event.which == 27) {
- this._stopInput(event);
- event.stopPropagation();
- }
- }
- }
- }
-});
-
-/**
- * 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);
-}
+/**\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.outerWidth()\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
++ * Adds thousands separators to a given number.\r
++ * \r
++ * @param mixed number\r
++ * @return string\r
++ */\r
++ addThousandsSeparator: function(number) {\r
++ var $numberString = String(number);\r
++ if (number >= 1000 || number <= -1000) {\r
++ var $negative = false;\r
++ if (number <= -1000) {\r
++ $negative = true;\r
++ $numberString = $numberString.substring(1);\r
++ }\r
++ var $separator = WCF.Language.get('wcf.global.thousandsSeparator');\r
++ \r
++ if ($separator != null && $separator != '') {\r
++ var $numElements = new Array();\r
++ var $firstPart = $numberString.length % 3\r
++ if ($firstPart == 0) $firstPart = 3;\r
++ for (var $i = 0; $i < Math.ceil($numberString.length / 3); $i++) {\r
++ if ($i == 0) $numElements.push($numberString.substring(0, $firstPart));\r
++ else {\r
++ var $start = (($i - 1) * 3) + $firstPart\r
++ $numElements.push($numberString.substring($start, $start + 3));\r
++ }\r
++ }\r
++ $numberString = (($negative) ? ('-') : ('')) + $numElements.join($separator);\r
++ }\r
++ }\r
++ \r
++ return $numberString;\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
++ * jQuery widget implementation of the wcf pagination.\r
++ */\r
++$.widget('ui.wcfPages', {\r
++ SHOW_LINKS: 11,\r
++ SHOW_SUB_LINKS: 20,\r
++ \r
++ options: {\r
++ // vars\r
++ activePage: 1,\r
++ maxPage: 1,\r
++ \r
++ // icons\r
++ previousIcon: RELATIVE_WCF_DIR + 'icon/previousS.png',\r
++ previousDisabledIcon: RELATIVE_WCF_DIR + 'icon/previousDisabledS.png',\r
++ arrowDownIcon: RELATIVE_WCF_DIR + 'icon/arrowDown.png',\r
++ nextIcon: RELATIVE_WCF_DIR + 'icon/nextS.png',\r
++ nextDisabledIcon: RELATIVE_WCF_DIR + 'icon/nextDisabledS.png',\r
++ \r
++ // language\r
++ // we use options here instead of language variables, because the paginator is not only usable with pages\r
++ nextPage: null,\r
++ previousPage: null,\r
++ },\r
++ \r
++ /**\r
++ * Creates the pages widget.\r
++ */\r
++ _create: function() {\r
++ if (this.options.nextPage === null) this.options.nextPage = WCF.Language.get('wcf.global.page.next');\r
++ if (this.options.previousPage === null) this.options.previousPage = WCF.Language.get('wcf.global.page.previous');\r
++ \r
++ this.element.addClass('pageNavigation');\r
++ \r
++ this._render();\r
++ },\r
++ \r
++ /**\r
++ * Destroys the pages widget.\r
++ */\r
++ destroy: function() {\r
++ $.Widget.prototype.destroy.apply(this, arguments);\r
++ \r
++ this.element.children().remove();\r
++ },\r
++ \r
++ /**\r
++ * Renders th pages widget.\r
++ */\r
++ _render: function() {\r
++ // only render if we have more than 1 page\r
++ if (!this.options.disabled && this.options.maxPage > 1) {\r
++ // make sure pagination is visible\r
++ if (this.element.hasClass('hidden')) {\r
++ this.element.removeClass('hidden');\r
++ }\r
++ this.element.show();\r
++ \r
++ this.element.children().remove();\r
++ \r
++ var $pageList = $('<ul></ul>');\r
++ this.element.append($pageList);\r
++ \r
++ var $previousElement = $('<li></li>');\r
++ $pageList.append($previousElement);\r
++ \r
++ if (this.options.activePage > 1) {\r
++ var $previousLink = $('<a' + ((this.options.previousPage != null) ? (' title="' + this.options.previousPage + '"') : ('')) + '></a>');\r
++ $previousElement.append($previousLink);\r
++ this._bindSwitchPage($previousLink, this.options.activePage - 1);\r
++ \r
++ var $previousImage = $('<img src="' + this.options.previousIcon + '" alt="" />');\r
++ $previousLink.append($previousImage);\r
++ }\r
++ else {\r
++ var $previousImage = $('<img src="' + this.options.previousDisabledIcon + '" alt="" />');\r
++ $previousElement.append($previousImage);\r
++ }\r
++ $previousElement.addClass('skip');\r
++ \r
++ // add first page\r
++ $pageList.append(this._renderLink(1));\r
++ \r
++ // calculate page links\r
++ var $maxLinks = this.SHOW_LINKS - 4;\r
++ var $linksBefore = this.options.activePage - 2;\r
++ if ($linksBefore < 0) $linksBefore = 0;\r
++ var $linksAfter = this.options.maxPage - (this.options.activePage + 1);\r
++ if ($linksAfter < 0) $linksAfter = 0;\r
++ if (this.options.activePage > 1 && this.options.activePage < this.options.maxPage) $maxLinks--;\r
++ \r
++ var $half = $maxLinks / 2;\r
++ var $left = this.options.activePage;\r
++ var $right = this.options.activePage;\r
++ if ($left < 1) $left = 1;\r
++ if ($right < 1) $right = 1;\r
++ if ($right > this.options.maxPage - 1) $right = this.options.maxPage - 1;\r
++ \r
++ if ($linksBefore >= $half) {\r
++ $left -= $half;\r
++ }\r
++ else {\r
++ $left -= $linksBefore;\r
++ $right += $half - $linksBefore;\r
++ }\r
++ \r
++ if ($linksAfter >= $half) {\r
++ $right += $half;\r
++ }\r
++ else {\r
++ $right += $linksAfter;\r
++ $left -= $half - $linksAfter;\r
++ }\r
++ \r
++ $right = Math.ceil($right);\r
++ $left = Math.ceil($left);\r
++ if ($left < 1) $left = 1;\r
++ if ($right > this.options.maxPage) $right = this.options.maxPage;\r
++ \r
++ // left ... links\r
++ if ($left > 1) {\r
++ if ($left - 1 < 2) {\r
++ $pageList.append(this._renderLink(2));\r
++ }\r
++ else {\r
++ var $leftChildren = $('<li class="children"></li>');\r
++ $pageList.append($leftChildren);\r
++ \r
++ var $leftChildrenLink = $('<a>…</a>');\r
++ $leftChildren.append($leftChildrenLink);\r
++ $leftChildrenLink.click($.proxy(this._startInput, this));\r
++ \r
++ var $leftChildrenImage = $('<img src="' + this.options.arrowDownIcon + '" alt="" />');\r
++ $leftChildrenLink.append($leftChildrenImage);\r
++ \r
++ var $leftChildrenInput = $('<input type="text" class="inputText" name="pageNo" />');\r
++ $leftChildren.append($leftChildrenInput);\r
++ $leftChildrenInput.keydown($.proxy(this._handleInput, this));\r
++ $leftChildrenInput.keyup($.proxy(this._handleInput, this));\r
++ $leftChildrenInput.blur($.proxy(this._stopInput, this));\r
++ \r
++ var $leftChildrenContainer = $('<div></div>');\r
++ $leftChildren.append($leftChildrenContainer);\r
++ \r
++ var $leftChildrenList = $('<ul></u>');\r
++ $leftChildrenContainer.append($leftChildrenList);\r
++ \r
++ // render sublinks\r
++ var $k = 0;\r
++ var $step = Math.ceil(($left - 2) / this.SHOW_SUB_LINKS);\r
++ for (var $i = 2; $i <= $left; $i += $step) {\r
++ $leftChildrenList.append(this._renderLink($i, ($k != 0 && $k % 4 == 0)));\r
++ $k++;\r
++ }\r
++ }\r
++ }\r
++ \r
++ // visible links\r
++ for (var $i = $left + 1; $i < $right; $i++) {\r
++ $pageList.append(this._renderLink($i));\r
++ }\r
++ \r
++ // right ... links\r
++ if ($right < this.options.maxPage) {\r
++ if (this.options.maxPage - $right < 2) {\r
++ $pageList.append(this._renderLink(this.options.maxPage - 1));\r
++ }\r
++ else {\r
++ var $rightChildren = $('<li class="children"></li>');\r
++ $pageList.append($rightChildren);\r
++ \r
++ var $rightChildrenLink = $('<a>…</a>');\r
++ $rightChildren.append($rightChildrenLink);\r
++ $rightChildrenLink.click($.proxy(this._startInput, this));\r
++ \r
++ var $rightChildrenImage = $('<img src="' + this.options.arrowDownIcon + '" alt="" />');\r
++ $rightChildrenLink.append($rightChildrenImage);\r
++ \r
++ var $rightChildrenInput = $('<input type="text" class="inputText" name="pageNo" />');\r
++ $rightChildren.append($rightChildrenInput);\r
++ $rightChildrenInput.keydown($.proxy(this._handleInput, this));\r
++ $rightChildrenInput.keyup($.proxy(this._handleInput, this));\r
++ $rightChildrenInput.blur($.proxy(this._stopInput, this));\r
++ \r
++ var $rightChildrenContainer = $('<div></div>');\r
++ $rightChildren.append($rightChildrenContainer);\r
++ \r
++ var $rightChildrenList = $('<ul></ul>');\r
++ $rightChildrenContainer.append($rightChildrenList);\r
++ \r
++ // render sublinks\r
++ var $k = 0;\r
++ var $step = Math.ceil((this.options.maxPage - $right) / this.SHOW_SUB_LINKS);\r
++ for (var $i = $right; $i < this.options.maxPage; $i += $step) {\r
++ $rightChildrenList.append(this._renderLink($i, ($k != 0 && $k % 4 == 0)));\r
++ $k++;\r
++ }\r
++ }\r
++ }\r
++ \r
++ // add last page\r
++ $pageList.append(this._renderLink(this.options.maxPage));\r
++ \r
++ // add next button\r
++ var $nextElement = $('<li></li>');\r
++ $pageList.append($nextElement);\r
++ \r
++ if (this.options.activePage < this.options.maxPage) {\r
++ var $nextLink = $('<a title="' + ((this.options.nextPage != null) ? (' title="' + this.options.nextPage + '"') : ('')) + '"></a>');\r
++ $nextElement.append($nextLink);\r
++ this._bindSwitchPage($nextLink, this.options.activePage + 1);\r
++ \r
++ var $nextImage = $('<img src="' + this.options.nextIcon + '" alt="" />');\r
++ $nextLink.append($nextImage);\r
++ }\r
++ else {\r
++ var $nextImage = $('<img src="' + this.options.nextDisabledIcon + '" alt="" />');\r
++ $nextElement.append($nextImage);\r
++ }\r
++ $nextElement.addClass('skip');\r
++ }\r
++ else {\r
++ // otherwise hide the paginator if not already hidden\r
++ this.element.hide();\r
++ }\r
++ },\r
++ \r
++ /**\r
++ * Renders a page link\r
++ * \r
++ * @parameter integer page\r
++ * \r
++ * @return $(element)\r
++ */\r
++ _renderLink: function(page, lineBreak) {\r
++ var $pageElement = $('<li></li>');\r
++ if (lineBreak != undefined && lineBreak) {\r
++ $pageElement.addClass('break');\r
++ }\r
++ if (page != this.options.activePage) {\r
++ var $pageLink = $('<a>' + WCF.String.addThousandsSeparator(page) + '</a>'); \r
++ $pageElement.append($pageLink);\r
++ this._bindSwitchPage($pageLink, page);\r
++ }\r
++ else {\r
++ $pageElement.addClass('active');\r
++ var $pageSubElement = $('<span>' + WCF.String.addThousandsSeparator(page) + '</span>');\r
++ $pageElement.append($pageSubElement);\r
++ }\r
++ \r
++ return $pageElement;\r
++ },\r
++ \r
++ /**\r
++ * Binds the 'click'-event for the page switching to the given element.\r
++ * \r
++ * @parameter $(element) element\r
++ * @paremeter integer page\r
++ */\r
++ _bindSwitchPage: function(element, page) {\r
++ var $self = this;\r
++ element.click(function() {\r
++ $self.switchPage(page);\r
++ });\r
++ },\r
++ \r
++ /**\r
++ * Switches to the given page\r
++ * \r
++ * @parameter Event event\r
++ * @parameter integer page\r
++ */\r
++ switchPage: function(page) {\r
++ this._setOption('activePage', page);\r
++ },\r
++ \r
++ /**\r
++ * Sets the given option to the given value.\r
++ * See the jQuery UI widget documentation for more.\r
++ */\r
++ _setOption: function(key, value) {\r
++ if (key == 'activePage') {\r
++ if (value != this.options[key] && value > 0 && value <= this.options.maxPage) {\r
++ // you can prevent the page switching by returning false or by event.preventDefault()\r
++ // in a shouldSwitch-callback. e.g. if an AJAX request is already running.\r
++ var $result = this._trigger('shouldSwitch', undefined, {\r
++ nextPage: value,\r
++ });\r
++ \r
++ if ($result) {\r
++ this.options[key] = value;\r
++ this._render();\r
++ this._trigger('switched', undefined, {\r
++ activePage: value,\r
++ });\r
++ }\r
++ else {\r
++ this._trigger('notSwitched', undefined, {\r
++ activePage: value,\r
++ });\r
++ }\r
++ }\r
++ }\r
++ else {\r
++ this.options[key] = value;\r
++ \r
++ if (key == 'disabled') {\r
++ if (value) {\r
++ this.element.children().remove();\r
++ }\r
++ else {\r
++ this._render()\r
++ }\r
++ }\r
++ else if (key == 'maxPage') {\r
++ this._render();\r
++ }\r
++ }\r
++ \r
++ return this;\r
++ },\r
++ \r
++ /**\r
++ * Start input of pagenumber\r
++ * \r
++ * @parameter Event event\r
++ */\r
++ _startInput: function(event) {\r
++ // hide a-tag\r
++ var $childLink = $(event.currentTarget);\r
++ if (!$childLink.is('a')) $childLink = $childLink.parent('a');\r
++ \r
++ $childLink.hide();\r
++ \r
++ // show input-tag\r
++ var $childInput = $childLink.parent('li').children('input')\r
++ .css('display', 'block')\r
++ .val('');\r
++ \r
++ $childInput.focus();\r
++ },\r
++ \r
++ /**\r
++ * Stops input of pagenumber\r
++ * \r
++ * @parameter Event event\r
++ */\r
++ _stopInput: function(event) {\r
++ // hide input-tag\r
++ var $childInput = $(event.currentTarget);\r
++ $childInput.css('display', 'none');\r
++ \r
++ // show a-tag\r
++ var $childContainer = $childInput.parent('li')\r
++ if ($childContainer != undefined && $childContainer != null) {\r
++ $childContainer.children('a').show();\r
++ }\r
++ },\r
++ \r
++ /**\r
++ * Handles input of pagenumber\r
++ * \r
++ * @parameter Event event\r
++ */\r
++ _handleInput: function(event) {\r
++ var $ie7 = ($.browser.msie && $.browser.version == '7.0');\r
++ if (event.type != 'keyup' || $ie7) {\r
++ if (!$ie7 || ((event.which == 13 || event.which == 27) && event.type == 'keyup')) {\r
++ if (event.which == 13) {\r
++ this.switchPage(parseInt($(event.currentTarget).val()));\r
++ }\r
++ \r
++ if (event.which == 13 || event.which == 27) {\r
++ this._stopInput(event);\r
++ event.stopPropagation();\r
++ }\r
++ }\r
++ }\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
- }
++}\r