Commit | Line | Data |
---|---|---|
e368b357 AE |
1 | "use strict"; |
2 | ||
158bd3ca | 3 | /** |
e919d1d3 | 4 | * Class and function collection for WCF. |
158bd3ca | 5 | * |
95d8093a AE |
6 | * Major Contributors: Markus Bartz, Tim Duesterhus, Matthias Schmidt and Marcel Werk |
7 | * | |
8 | * @author Alexander Ebert | |
c839bd49 | 9 | * @copyright 2001-2018 WoltLab GmbH |
158bd3ca TD |
10 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
11 | */ | |
12 | ||
e93829d0 AE |
13 | (function() { |
14 | // store original implementation | |
15 | var $jQueryData = jQuery.fn.data; | |
9f959ced | 16 | |
e93829d0 AE |
17 | /** |
18 | * Override jQuery.fn.data() to support custom 'ID' suffix which will | |
19 | * be translated to '-id' at runtime. | |
20 | * | |
21 | * @see jQuery.fn.data() | |
22 | */ | |
23 | jQuery.fn.data = function(key, value) { | |
1f4f33dd AE |
24 | if (key) { |
25 | switch (typeof key) { | |
26 | case 'object': | |
27 | for (var $key in key) { | |
28 | if ($key.match(/ID$/)) { | |
29 | var $value = key[$key]; | |
30 | delete key[$key]; | |
31 | ||
32 | $key = $key.replace(/ID$/, '-id'); | |
33 | key[$key] = $value; | |
34 | } | |
35 | } | |
36 | ||
37 | arguments[0] = key; | |
38 | break; | |
39 | ||
40 | case 'string': | |
41 | if (key.match(/ID$/)) { | |
42 | arguments[0] = key.replace(/ID$/, '-id'); | |
43 | } | |
44 | break; | |
0f0590c2 | 45 | } |
e93829d0 | 46 | } |
9f959ced | 47 | |
e93829d0 | 48 | // call jQuery's own data method |
1c93e6e7 AE |
49 | var $data = $jQueryData.apply(this, arguments); |
50 | ||
51 | // handle .data() call without arguments | |
52 | if (key === undefined) { | |
53 | for (var $key in $data) { | |
54 | if ($key.match(/Id$/)) { | |
696930b9 | 55 | $data[$key.replace(/Id$/, 'ID')] = $data[$key]; |
1c93e6e7 AE |
56 | delete $data[$key]; |
57 | } | |
58 | } | |
59 | } | |
60 | ||
61 | return $data; | |
f4126129 | 62 | }; |
e919d1d3 | 63 | |
0af61800 TD |
64 | // provide a sane window.console implementation |
65 | if (!window.console) window.console = { }; | |
66 | var consoleProperties = [ "log",/* "debug",*/ "info", "warn", "exception", "assert", "dir", "dirxml", "trace", "group", "groupEnd", "groupCollapsed", "profile", "profileEnd", "count", "clear", "time", "timeEnd", "timeStamp", "table", "error" ]; | |
67 | for (var i = 0; i < consoleProperties.length; i++) { | |
68 | if (typeof (console[consoleProperties[i]]) === 'undefined') { | |
89be55b6 | 69 | console[consoleProperties[i]] = function () { }; |
0af61800 | 70 | } |
e919d1d3 | 71 | } |
0af61800 TD |
72 | |
73 | if (typeof(console.debug) === 'undefined') { | |
e919d1d3 AE |
74 | // forward console.debug to console.log (IE9) |
75 | console.debug = function(string) { console.log(string); }; | |
76 | } | |
e93829d0 AE |
77 | })(); |
78 | ||
d5717b23 AE |
79 | /** |
80 | * Adds a Fisher-Yates shuffle algorithm for arrays. | |
81 | * | |
82 | * @see http://stackoverflow.com/a/2450976 | |
83 | */ | |
01a570df | 84 | window.shuffle = function(array) { |
d5717b23 AE |
85 | var currentIndex = array.length, temporaryValue, randomIndex; |
86 | ||
87 | // While there remain elements to shuffle... | |
88 | while (0 !== currentIndex) { | |
89 | // Pick a remaining element... | |
90 | randomIndex = Math.floor(Math.random() * currentIndex); | |
91 | currentIndex -= 1; | |
92 | ||
93 | // And swap it with the current element. | |
94 | temporaryValue = array[currentIndex]; | |
95 | array[currentIndex] = array[randomIndex]; | |
96 | array[randomIndex] = temporaryValue; | |
97 | } | |
98 | ||
99 | return this; | |
100 | }; | |
101 | ||
b108eaa5 | 102 | /** |
eb69a2e9 AE |
103 | * User-Agent based browser detection and touch detection. |
104 | */ | |
c05492aa | 105 | (function(jQuery) { |
eb69a2e9 AE |
106 | var ua = navigator.userAgent.toLowerCase(); |
107 | var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || | |
108 | /(webkit)[ \/]([\w.]+)/.exec( ua ) || | |
109 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || | |
110 | /(msie) ([\w.]+)/.exec( ua ) || | |
111 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || | |
112 | []; | |
113 | ||
114 | var matched = { | |
115 | browser: match[ 1 ] || "", | |
116 | version: match[ 2 ] || "0" | |
117 | }; | |
e368b357 | 118 | var browser = {}; |
eb69a2e9 AE |
119 | |
120 | if ( matched.browser ) { | |
121 | browser[ matched.browser ] = true; | |
122 | browser.version = matched.version; | |
123 | } | |
124 | ||
125 | // Chrome is Webkit, but Webkit is also Safari. | |
126 | if ( browser.chrome ) { | |
127 | browser.webkit = true; | |
128 | } else if ( browser.webkit ) { | |
129 | browser.safari = true; | |
130 | } | |
131 | ||
60f1af30 AE |
132 | jQuery.browser = jQuery.browser || { }; |
133 | jQuery.browser = $.extend(jQuery.browser, browser); | |
555561be | 134 | jQuery.browser.touch = (!!('ontouchstart' in window) || (!!('msMaxTouchPoints' in window.navigator) && window.navigator.msMaxTouchPoints > 0)); |
9899046c AE |
135 | |
136 | // detect smartphones | |
f7461bbb | 137 | jQuery.browser.smartphone = ($('html').css('caption-side') == 'bottom'); |
e3f96cf0 | 138 | |
d66c3c5b AE |
139 | // properly detect IE11 |
140 | if (jQuery.browser.mozilla && ua.match(/trident/)) { | |
141 | jQuery.browser.mozilla = false; | |
142 | jQuery.browser.msie = true; | |
143 | } | |
144 | ||
145 | // detect iOS devices | |
146 | jQuery.browser.iOS = /\((ipad|iphone|ipod);/.test(ua); | |
147 | if (jQuery.browser.iOS) { | |
148 | $('html').addClass('iOS'); | |
149 | } | |
150 | ||
151 | // dectect Android | |
152 | jQuery.browser.android = (ua.indexOf('android') !== -1); | |
153 | ||
60c1c067 AE |
154 | // allow plugins to detect the used editor, value should be the same as the $.browser.<editorName> key |
155 | jQuery.browser.editor = 'redactor'; | |
156 | ||
157 | // CKEditor support (removed in WCF 2.1), do NOT remove this variable for the sake for compatibility | |
158 | jQuery.browser.ckeditor = false; | |
159 | ||
160 | // Redactor support | |
161 | jQuery.browser.redactor = true; | |
c05492aa AE |
162 | |
163 | // work-around for zoom bug on iOS when using .focus() | |
164 | if (jQuery.browser.iOS) { | |
de3110b2 AE |
165 | jQuery.fn.focus = function(data, fn) { |
166 | return arguments.length > 0 ? this.on('focus', null, data, fn) : this.trigger('focus'); | |
c05492aa AE |
167 | }; |
168 | } | |
169 | })(jQuery); | |
49d0d797 | 170 | |
158bd3ca TD |
171 | /** |
172 | * Initialize WCF namespace | |
173 | */ | |
46badb6a TD |
174 | // non strict equals by intent |
175 | if (window.WCF == null) window.WCF = { }; | |
158bd3ca TD |
176 | |
177 | /** | |
178 | * Extends jQuery with additional methods. | |
179 | */ | |
180 | $.extend(true, { | |
a009a689 | 181 | /** |
acf25f0f | 182 | * Removes the given value from the given array and returns the array. |
a009a689 MS |
183 | * |
184 | * @param array array | |
185 | * @param mixed element | |
186 | * @return array | |
187 | */ | |
188 | removeArrayValue: function(array, value) { | |
189 | return $.grep(array, function(element, index) { | |
190 | return value !== element; | |
191 | }); | |
192 | }, | |
193 | ||
158bd3ca TD |
194 | /** |
195 | * Escapes an ID to work with jQuery selectors. | |
7b608580 | 196 | * |
158bd3ca TD |
197 | * @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 |
198 | * @param string id | |
199 | * @return string | |
200 | */ | |
201 | wcfEscapeID: function(id) { | |
202 | return id.replace(/(:|\.)/g, '\\$1'); | |
203 | }, | |
204 | ||
205 | /** | |
206 | * Returns true if given ID exists within DOM. | |
207 | * | |
208 | * @param string id | |
209 | * @return boolean | |
210 | */ | |
211 | wcfIsset: function(id) { | |
212 | return !!$('#' + $.wcfEscapeID(id)).length; | |
2b4d2743 | 213 | }, |
9f959ced | 214 | |
2b4d2743 AE |
215 | /** |
216 | * Returns the length of an object. | |
217 | * | |
218 | * @param object targetObject | |
219 | * @return integer | |
220 | */ | |
221 | getLength: function(targetObject) { | |
222 | var $length = 0; | |
9f959ced | 223 | |
2b4d2743 AE |
224 | for (var $key in targetObject) { |
225 | if (targetObject.hasOwnProperty($key)) { | |
226 | $length++; | |
227 | } | |
228 | } | |
7b608580 | 229 | |
2b4d2743 | 230 | return $length; |
158bd3ca TD |
231 | } |
232 | }); | |
233 | ||
234 | /** | |
235 | * Extends jQuery's chainable methods. | |
236 | */ | |
237 | $.fn.extend({ | |
238 | /** | |
404c9abe | 239 | * Returns tag name of first jQuery element. |
158bd3ca TD |
240 | * |
241 | * @returns string | |
242 | */ | |
243 | getTagName: function() { | |
d487dd36 | 244 | return (this.length) ? this.get(0).tagName.toLowerCase() : ''; |
158bd3ca TD |
245 | }, |
246 | ||
247 | /** | |
248 | * Returns the dimensions for current element. | |
249 | * | |
250 | * @see http://api.jquery.com/hidden-selector/ | |
251 | * @param string type | |
252 | * @return object | |
253 | */ | |
254 | getDimensions: function(type) { | |
e368b357 AE |
255 | var css = { }; |
256 | var dimensions = { }; | |
158bd3ca TD |
257 | var wasHidden = false; |
258 | ||
259 | // show element to retrieve dimensions and restore them later | |
260 | if (this.is(':hidden')) { | |
69057ad5 | 261 | css = WCF.getInlineCSS(this); |
158bd3ca TD |
262 | |
263 | wasHidden = true; | |
264 | ||
265 | this.css({ | |
266 | display: 'block', | |
267 | visibility: 'hidden' | |
268 | }); | |
269 | } | |
270 | ||
271 | switch (type) { | |
272 | case 'inner': | |
273 | dimensions = { | |
274 | height: this.innerHeight(), | |
275 | width: this.innerWidth() | |
276 | }; | |
277 | break; | |
278 | ||
279 | case 'outer': | |
280 | dimensions = { | |
281 | height: this.outerHeight(), | |
282 | width: this.outerWidth() | |
283 | }; | |
284 | break; | |
285 | ||
286 | default: | |
287 | dimensions = { | |
288 | height: this.height(), | |
289 | width: this.width() | |
290 | }; | |
291 | break; | |
292 | } | |
293 | ||
294 | // restore previous settings | |
295 | if (wasHidden) { | |
69057ad5 | 296 | WCF.revertInlineCSS(this, css, [ 'display', 'visibility' ]); |
158bd3ca TD |
297 | } |
298 | ||
299 | return dimensions; | |
300 | }, | |
301 | ||
302 | /** | |
303 | * Returns the offsets for current element, defaults to position | |
304 | * relative to document. | |
305 | * | |
306 | * @see http://api.jquery.com/hidden-selector/ | |
307 | * @param string type | |
308 | * @return object | |
309 | */ | |
310 | getOffsets: function(type) { | |
e368b357 AE |
311 | var css = { }; |
312 | var offsets = { }; | |
158bd3ca TD |
313 | var wasHidden = false; |
314 | ||
315 | // show element to retrieve dimensions and restore them later | |
316 | if (this.is(':hidden')) { | |
69057ad5 | 317 | css = WCF.getInlineCSS(this); |
158bd3ca TD |
318 | wasHidden = true; |
319 | ||
320 | this.css({ | |
321 | display: 'block', | |
322 | visibility: 'hidden' | |
323 | }); | |
324 | } | |
325 | ||
326 | switch (type) { | |
327 | case 'offset': | |
328 | offsets = this.offset(); | |
329 | break; | |
330 | ||
331 | case 'position': | |
332 | default: | |
333 | offsets = this.position(); | |
334 | break; | |
335 | } | |
336 | ||
337 | // restore previous settings | |
338 | if (wasHidden) { | |
69057ad5 | 339 | WCF.revertInlineCSS(this, css, [ 'display', 'visibility' ]); |
158bd3ca TD |
340 | } |
341 | ||
342 | return offsets; | |
343 | }, | |
344 | ||
345 | /** | |
346 | * Changes element's position to 'absolute' or 'fixed' while maintaining it's | |
347 | * current position relative to viewport. Optionally removes element from | |
348 | * current DOM-node and moving it into body-element (useful for drag & drop) | |
349 | * | |
350 | * @param boolean rebase | |
351 | * @return object | |
352 | */ | |
353 | makePositioned: function(position, rebase) { | |
354 | if (position != 'absolute' && position != 'fixed') { | |
355 | position = 'absolute'; | |
356 | } | |
357 | ||
358 | var $currentPosition = this.getOffsets('position'); | |
359 | this.css({ | |
360 | position: position, | |
361 | left: $currentPosition.left, | |
362 | margin: 0, | |
363 | top: $currentPosition.top | |
364 | }); | |
365 | ||
366 | if (rebase) { | |
367 | this.remove().appentTo('body'); | |
368 | } | |
369 | ||
370 | return this; | |
371 | }, | |
372 | ||
373 | /** | |
374 | * Disables a form element. | |
375 | * | |
06355ec3 | 376 | * @return jQuery |
158bd3ca TD |
377 | */ |
378 | disable: function() { | |
379 | return this.attr('disabled', 'disabled'); | |
380 | }, | |
381 | ||
382 | /** | |
383 | * Enables a form element. | |
384 | * | |
385 | * @return jQuery | |
386 | */ | |
387 | enable: function() { | |
388 | return this.removeAttr('disabled'); | |
389 | }, | |
9f959ced | 390 | |
b3991cb3 AE |
391 | /** |
392 | * Returns the element's id. If none is set, a random unique | |
393 | * ID will be assigned. | |
394 | * | |
395 | * @return string | |
396 | */ | |
397 | wcfIdentify: function() { | |
68173711 | 398 | return window.bc_wcfDomUtil.identify(this[0]); |
b3991cb3 | 399 | }, |
158bd3ca | 400 | |
0bfde690 AE |
401 | /** |
402 | * Returns the caret position of current element. If the element | |
403 | * does not equal input[type=text], input[type=password] or | |
404 | * textarea, -1 is returned. | |
405 | * | |
406 | * @return integer | |
407 | */ | |
408 | getCaret: function() { | |
404c9abe | 409 | if (this.is('input')) { |
0bfde690 AE |
410 | if (this.attr('type') != 'text' && this.attr('type') != 'password') { |
411 | return -1; | |
412 | } | |
413 | } | |
404c9abe | 414 | else if (!this.is('textarea')) { |
0bfde690 AE |
415 | return -1; |
416 | } | |
417 | ||
418 | var $position = 0; | |
419 | var $element = this.get(0); | |
420 | if (document.selection) { // IE 8 | |
421 | // set focus to enable caret on this element | |
422 | this.focus(); | |
423 | ||
424 | var $selection = document.selection.createRange(); | |
425 | $selection.moveStart('character', -this.val().length); | |
426 | $position = $selection.text.length; | |
427 | } | |
428 | else if ($element.selectionStart || $element.selectionStart == '0') { // Opera, Chrome, Firefox, Safari, IE 9+ | |
429 | $position = parseInt($element.selectionStart); | |
430 | } | |
431 | ||
432 | return $position; | |
433 | }, | |
434 | ||
f73ff47d TD |
435 | /** |
436 | * Sets the caret position of current element. If the element | |
437 | * does not equal input[type=text], input[type=password] or | |
438 | * textarea, false is returned. | |
439 | * | |
440 | * @param integer position | |
441 | * @return boolean | |
442 | */ | |
443 | setCaret: function (position) { | |
404c9abe | 444 | if (this.is('input')) { |
f73ff47d TD |
445 | if (this.attr('type') != 'text' && this.attr('type') != 'password') { |
446 | return false; | |
447 | } | |
448 | } | |
404c9abe | 449 | else if (!this.is('textarea')) { |
f73ff47d TD |
450 | return false; |
451 | } | |
452 | ||
453 | var $element = this.get(0); | |
454 | ||
455 | // set focus to enable caret on this element | |
456 | this.focus(); | |
457 | if (document.selection) { // IE 8 | |
458 | var $selection = document.selection.createRange(); | |
459 | $selection.moveStart('character', position); | |
460 | $selection.moveEnd('character', 0); | |
461 | $selection.select(); | |
462 | } | |
463 | else if ($element.selectionStart || $element.selectionStart == '0') { // Opera, Chrome, Firefox, Safari, IE 9+ | |
464 | $element.selectionStart = position; | |
465 | $element.selectionEnd = position; | |
466 | } | |
467 | ||
468 | return true; | |
469 | }, | |
470 | ||
158bd3ca TD |
471 | /** |
472 | * Shows an element by sliding and fading it into viewport. | |
473 | * | |
474 | * @param string direction | |
475 | * @param object callback | |
453aced6 | 476 | * @param integer duration |
158bd3ca TD |
477 | * @returns jQuery |
478 | */ | |
453aced6 | 479 | wcfDropIn: function(direction, callback, duration) { |
158bd3ca | 480 | if (!direction) direction = 'up'; |
453aced6 | 481 | if (!duration || !parseInt(duration)) duration = 200; |
158bd3ca | 482 | |
404c9abe | 483 | return this.show(WCF.getEffect(this, 'drop'), { direction: direction }, duration, callback); |
158bd3ca TD |
484 | }, |
485 | ||
486 | /** | |
487 | * Hides an element by sliding and fading it out the viewport. | |
488 | * | |
489 | * @param string direction | |
490 | * @param object callback | |
453aced6 | 491 | * @param integer duration |
158bd3ca TD |
492 | * @returns jQuery |
493 | */ | |
453aced6 | 494 | wcfDropOut: function(direction, callback, duration) { |
158bd3ca | 495 | if (!direction) direction = 'down'; |
453aced6 | 496 | if (!duration || !parseInt(duration)) duration = 200; |
158bd3ca | 497 | |
404c9abe | 498 | return this.hide(WCF.getEffect(this, 'drop'), { direction: direction }, duration, callback); |
158bd3ca TD |
499 | }, |
500 | ||
501 | /** | |
502 | * Shows an element by blinding it up. | |
503 | * | |
504 | * @param string direction | |
505 | * @param object callback | |
78e4d558 | 506 | * @param integer duration |
158bd3ca TD |
507 | * @returns jQuery |
508 | */ | |
78e4d558 | 509 | wcfBlindIn: function(direction, callback, duration) { |
158bd3ca | 510 | if (!direction) direction = 'vertical'; |
78e4d558 | 511 | if (!duration || !parseInt(duration)) duration = 200; |
158bd3ca | 512 | |
404c9abe | 513 | return this.show(WCF.getEffect(this, 'blind'), { direction: direction }, duration, callback); |
158bd3ca TD |
514 | }, |
515 | ||
516 | /** | |
517 | * Hides an element by blinding it down. | |
518 | * | |
519 | * @param string direction | |
520 | * @param object callback | |
78e4d558 | 521 | * @param integer duration |
158bd3ca TD |
522 | * @returns jQuery |
523 | */ | |
78e4d558 | 524 | wcfBlindOut: function(direction, callback, duration) { |
158bd3ca | 525 | if (!direction) direction = 'vertical'; |
78e4d558 | 526 | if (!duration || !parseInt(duration)) duration = 200; |
158bd3ca | 527 | |
404c9abe | 528 | return this.hide(WCF.getEffect(this, 'blind'), { direction: direction }, duration, callback); |
158bd3ca TD |
529 | }, |
530 | ||
531 | /** | |
532 | * Highlights an element. | |
533 | * | |
534 | * @param object options | |
535 | * @param object callback | |
536 | * @returns jQuery | |
537 | */ | |
538 | wcfHighlight: function(options, callback) { | |
539 | return this.effect('highlight', options, 600, callback); | |
25763f41 | 540 | }, |
9f959ced | 541 | |
25763f41 AE |
542 | /** |
543 | * Shows an element by fading it in. | |
544 | * | |
545 | * @param object callback | |
546 | * @param integer duration | |
547 | * @returns jQuery | |
548 | */ | |
549 | wcfFadeIn: function(callback, duration) { | |
550 | if (!duration || !parseInt(duration)) duration = 200; | |
551 | ||
404c9abe | 552 | return this.show(WCF.getEffect(this, 'fade'), { }, duration, callback); |
25763f41 | 553 | }, |
9f959ced | 554 | |
25763f41 AE |
555 | /** |
556 | * Hides an element by fading it out. | |
557 | * | |
558 | * @param object callback | |
559 | * @param integer duration | |
560 | * @returns jQuery | |
561 | */ | |
562 | wcfFadeOut: function(callback, duration) { | |
563 | if (!duration || !parseInt(duration)) duration = 200; | |
564 | ||
404c9abe | 565 | return this.hide(WCF.getEffect(this, 'fade'), { }, duration, callback); |
d12b7e5c AE |
566 | }, |
567 | ||
568 | /** | |
569 | * Returns a CSS property as raw number. | |
570 | * | |
571 | * @param string property | |
572 | */ | |
573 | cssAsNumber: function(property) { | |
574 | if (this.length) { | |
575 | var $property = this.css(property); | |
576 | if ($property !== undefined) { | |
577 | return parseInt($property.replace(/px$/, '')); | |
578 | } | |
579 | } | |
580 | ||
581 | return 0; | |
b7f79b30 TD |
582 | }, |
583 | /** | |
584 | * @deprecated Use perfectScrollbar directly. | |
585 | * | |
586 | * This is taken from the jQuery adaptor of perfect scrollbar. | |
587 | * Copyright (c) 2015 Hyunje Alex Jun and other contributors | |
588 | * Licensed under the MIT License | |
589 | */ | |
590 | perfectScrollbar: function (settingOrCommand) { | |
591 | var ps = require('perfect-scrollbar'); | |
592 | ||
593 | return this.each(function () { | |
594 | if (typeof settingOrCommand === 'object' || | |
595 | typeof settingOrCommand === 'undefined') { | |
596 | // If it's an object or none, initialize. | |
597 | var settings = settingOrCommand; | |
598 | if (!$(this).data('psID')) | |
599 | ps.initialize(this, settings); | |
600 | } else { | |
601 | // Unless, it may be a command. | |
602 | var command = settingOrCommand; | |
603 | ||
604 | if (command === 'update') { | |
605 | ps.update(this); | |
606 | } else if (command === 'destroy') { | |
607 | ps.destroy(this); | |
608 | } | |
609 | } | |
610 | ||
611 | return jQuery(this); | |
612 | }); | |
158bd3ca TD |
613 | } |
614 | }); | |
615 | ||
616 | /** | |
e71525e4 | 617 | * WoltLab Suite Core methods |
158bd3ca TD |
618 | */ |
619 | $.extend(WCF, { | |
5a1b4042 AE |
620 | /** |
621 | * count of active dialogs | |
622 | * @var integer | |
623 | */ | |
624 | activeDialogs: 0, | |
625 | ||
158bd3ca | 626 | /** |
4da866ad | 627 | * Counter for dynamic element ids |
7b608580 | 628 | * |
158bd3ca TD |
629 | * @var integer |
630 | */ | |
631 | _idCounter: 0, | |
9f959ced | 632 | |
158bd3ca TD |
633 | /** |
634 | * Returns a dynamically created id. | |
635 | * | |
a9e6c5a7 | 636 | * @see https://github.com/sstephenson/prototype/blob/5e5cfff7c2c253eaf415c279f9083b4650cd4506/src/prototype/dom/dom.js#L1789 |
158bd3ca TD |
637 | * @return string |
638 | */ | |
639 | getRandomID: function() { | |
68173711 | 640 | return window.bc_wcfDomUtil.getUniqueId(); |
158bd3ca TD |
641 | }, |
642 | ||
643 | /** | |
644 | * Wrapper for $.inArray which returns boolean value instead of | |
645 | * index value, similar to PHP's in_array(). | |
646 | * | |
647 | * @param mixed needle | |
648 | * @param array haystack | |
649 | * @return boolean | |
650 | */ | |
651 | inArray: function(needle, haystack) { | |
652 | return ($.inArray(needle, haystack) != -1); | |
653 | }, | |
654 | ||
655 | /** | |
656 | * Adjusts effect for partially supported elements. | |
657 | * | |
404c9abe | 658 | * @param jQuery object |
158bd3ca TD |
659 | * @param string effect |
660 | * @return string | |
661 | */ | |
404c9abe | 662 | getEffect: function(object, effect) { |
158bd3ca | 663 | // most effects are not properly supported on table rows, use highlight instead |
404c9abe | 664 | if (object.is('tr')) { |
158bd3ca TD |
665 | return 'highlight'; |
666 | } | |
667 | ||
668 | return effect; | |
69057ad5 AE |
669 | }, |
670 | ||
671 | /** | |
672 | * Returns inline CSS for given element. | |
673 | * | |
674 | * @param jQuery element | |
675 | * @return object | |
676 | */ | |
677 | getInlineCSS: function(element) { | |
678 | var $inlineStyles = { }; | |
679 | var $style = element.attr('style'); | |
680 | ||
681 | // no style tag given or empty | |
682 | if (!$style) { | |
683 | return { }; | |
684 | } | |
685 | ||
686 | $style = $style.split(';'); | |
687 | for (var $i = 0, $length = $style.length; $i < $length; $i++) { | |
688 | var $fragment = $.trim($style[$i]); | |
689 | if ($fragment == '') { | |
690 | continue; | |
691 | } | |
692 | ||
693 | $fragment = $fragment.split(':'); | |
694 | $inlineStyles[$.trim($fragment[0])] = $.trim($fragment[1]); | |
695 | } | |
696 | ||
697 | return $inlineStyles; | |
698 | }, | |
699 | ||
700 | /** | |
701 | * Reverts inline CSS or negates a previously set property. | |
702 | * | |
703 | * @param jQuery element | |
704 | * @param object inlineCSS | |
705 | * @param array<string> targetProperties | |
706 | */ | |
707 | revertInlineCSS: function(element, inlineCSS, targetProperties) { | |
708 | for (var $i = 0, $length = targetProperties.length; $i < $length; $i++) { | |
709 | var $property = targetProperties[$i]; | |
710 | ||
711 | // revert inline CSS | |
712 | if (inlineCSS[$property]) { | |
713 | element.css($property, inlineCSS[$property]); | |
714 | } | |
715 | else { | |
716 | // negate inline CSS | |
717 | element.css($property, ''); | |
718 | } | |
719 | } | |
996dd9e0 AE |
720 | }, |
721 | ||
722 | /** | |
58d7e8f8 | 723 | * @deprecated Use WoltLabSuite/Core/Core.getUuid(). |
996dd9e0 AE |
724 | */ |
725 | getUUID: function() { | |
726 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { | |
727 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); | |
728 | return v.toString(16); | |
729 | }); | |
b2cc4656 AE |
730 | }, |
731 | ||
732 | /** | |
733 | * Converts a base64 encoded file into a native Blob. | |
734 | * | |
735 | * @param string base64data | |
736 | * @param string contentType | |
737 | * @param integer sliceSize | |
738 | * @return Blob | |
739 | */ | |
740 | base64toBlob: function(base64data, contentType, sliceSize) { | |
741 | contentType = contentType || ''; | |
742 | sliceSize = sliceSize || 512; | |
743 | ||
744 | var $byteCharacters = atob(base64data); | |
745 | var $byteArrays = [ ]; | |
746 | ||
747 | for (var $offset = 0; $offset < $byteCharacters.length; $offset += sliceSize) { | |
748 | var $slice = $byteCharacters.slice($offset, $offset + sliceSize); | |
749 | ||
750 | var $byteNumbers = new Array($slice.length); | |
751 | for (var $i = 0; $i < $slice.length; $i++) { | |
752 | $byteNumbers[$i] = $slice.charCodeAt($i); | |
753 | } | |
754 | ||
755 | var $byteArray = new Uint8Array($byteNumbers); | |
756 | $byteArrays.push($byteArray); | |
757 | } | |
758 | ||
759 | return new Blob($byteArrays, { type: contentType }); | |
9eec0471 AE |
760 | }, |
761 | ||
762 | /** | |
763 | * Converts legacy URLs to the URL schema used by WCF 2.1. | |
764 | * | |
765 | * @param string url | |
766 | * @return string | |
767 | */ | |
768 | convertLegacyURL: function(url) { | |
9eec0471 AE |
769 | return url.replace(/^index\.php\/(.*?)\/\?/, function(match, controller) { |
770 | var $parts = controller.split(/([A-Z][a-z0-9]+)/); | |
771 | var $controller = ''; | |
772 | for (var $i = 0, $length = $parts.length; $i < $length; $i++) { | |
773 | var $part = $parts[$i].trim(); | |
774 | if ($part.length) { | |
775 | if ($controller.length) $controller += '-'; | |
776 | $controller += $part.toLowerCase(); | |
777 | } | |
778 | } | |
779 | ||
780 | return 'index.php?' + $controller + '/&'; | |
781 | }); | |
158bd3ca TD |
782 | } |
783 | }); | |
784 | ||
b8b58a7e AE |
785 | /** |
786 | * Browser related functions. | |
787 | */ | |
788 | WCF.Browser = { | |
789 | /** | |
790 | * determines if browser is chrome | |
791 | * @var boolean | |
792 | */ | |
793 | _isChrome: null, | |
794 | ||
795 | /** | |
796 | * Returns true, if browser is Chrome, Chromium or using GoogleFrame for Internet Explorer. | |
797 | * | |
798 | * @return boolean | |
799 | */ | |
800 | isChrome: function() { | |
801 | if (this._isChrome === null) { | |
802 | this._isChrome = false; | |
803 | if (/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())) { | |
804 | this._isChrome = true; | |
805 | } | |
806 | } | |
807 | ||
808 | return this._isChrome; | |
809 | } | |
810 | }; | |
811 | ||
0d6ea23f | 812 | /** |
184a8d6d | 813 | * Dropdown API |
d788c437 | 814 | * |
e71525e4 | 815 | * @deprecated 3.0 - please use `Ui/SimpleDropdown` instead |
184a8d6d AE |
816 | */ |
817 | WCF.Dropdown = { | |
289b4a48 | 818 | /** |
4bbf6ff1 | 819 | * Initializes dropdowns. |
289b4a48 | 820 | */ |
4bbf6ff1 | 821 | init: function(api) { |
d788c437 | 822 | window.bc_wcfSimpleDropdown.initAll(); |
184a8d6d AE |
823 | }, |
824 | ||
3ef8dee9 AE |
825 | /** |
826 | * Initializes a dropdown. | |
827 | * | |
828 | * @param jQuery button | |
829 | * @param boolean isLazyInitialization | |
830 | */ | |
831 | initDropdown: function(button, isLazyInitialization) { | |
d788c437 | 832 | window.bc_wcfSimpleDropdown.init(button[0], isLazyInitialization); |
3ef8dee9 AE |
833 | }, |
834 | ||
a203335d MS |
835 | /** |
836 | * Removes the dropdown with the given container id. | |
837 | * | |
838 | * @param string containerID | |
839 | */ | |
840 | removeDropdown: function(containerID) { | |
5575edd9 | 841 | window.bc_wcfSimpleDropdown.destroy(containerID); |
a203335d MS |
842 | }, |
843 | ||
38d131ce AE |
844 | /** |
845 | * Initializes a dropdown fragment which behaves like a usual dropdown | |
846 | * but is not controlled by a trigger element. | |
847 | * | |
848 | * @param jQuery dropdown | |
849 | * @param jQuery dropdownMenu | |
850 | */ | |
851 | initDropdownFragment: function(dropdown, dropdownMenu) { | |
d788c437 | 852 | window.bc_wcfSimpleDropdown.initFragment(dropdown[0], dropdownMenu[0]); |
38d131ce AE |
853 | }, |
854 | ||
184a8d6d AE |
855 | /** |
856 | * Registers a callback notified upon dropdown state change. | |
857 | * | |
858 | * @param string identifier | |
859 | * @var object callback | |
860 | */ | |
861 | registerCallback: function(identifier, callback) { | |
d788c437 | 862 | window.bc_wcfSimpleDropdown.registerCallback(identifier, callback); |
184a8d6d AE |
863 | }, |
864 | ||
865 | /** | |
866 | * Toggles a dropdown. | |
867 | * | |
868 | * @param object event | |
3ef8dee9 | 869 | * @param string targetID |
184a8d6d | 870 | */ |
3ef8dee9 | 871 | _toggle: function(event, targetID) { |
d788c437 | 872 | window.bc_wcfSimpleDropdown._toggle(event, targetID); |
184a8d6d AE |
873 | }, |
874 | ||
3ef8dee9 AE |
875 | /** |
876 | * Toggles a dropdown. | |
877 | * | |
878 | * @param string containerID | |
879 | */ | |
880 | toggleDropdown: function(containerID) { | |
d788c437 | 881 | window.bc_wcfSimpleDropdown._toggle(null, containerID); |
3ef8dee9 AE |
882 | }, |
883 | ||
884 | /** | |
885 | * Returns dropdown by container id. | |
886 | * | |
887 | * @param string containerID | |
888 | * @return jQuery | |
889 | */ | |
890 | getDropdown: function(containerID) { | |
d788c437 | 891 | var dropdown = window.bc_wcfSimpleDropdown.getDropdown(containerID); |
3ef8dee9 | 892 | |
4bbf6ff1 | 893 | return (dropdown) ? $(dropdown) : null; |
3ef8dee9 AE |
894 | }, |
895 | ||
896 | /** | |
897 | * Returns dropdown menu by container id. | |
898 | * | |
899 | * @param string containerID | |
900 | * @return jQuery | |
901 | */ | |
902 | getDropdownMenu: function(containerID) { | |
d788c437 | 903 | var menu = window.bc_wcfSimpleDropdown.getDropdownMenu(containerID); |
3ef8dee9 | 904 | |
4bbf6ff1 | 905 | return (menu) ? $(menu) : null; |
3ef8dee9 AE |
906 | }, |
907 | ||
908 | /** | |
909 | * Sets alignment for given container id. | |
910 | * | |
911 | * @param string containerID | |
912 | */ | |
913 | setAlignmentByID: function(containerID) { | |
d788c437 | 914 | window.bc_wcfSimpleDropdown.setAlignmentById(containerID); |
3ef8dee9 AE |
915 | }, |
916 | ||
71662ae8 AE |
917 | /** |
918 | * Sets alignment for dropdown. | |
919 | * | |
920 | * @param jQuery dropdown | |
921 | * @param jQuery dropdownMenu | |
922 | */ | |
923 | setAlignment: function(dropdown, dropdownMenu) { | |
d788c437 | 924 | window.bc_wcfSimpleDropdown.setAlignment(dropdown[0], dropdownMenu[0]); |
71662ae8 AE |
925 | }, |
926 | ||
184a8d6d AE |
927 | /** |
928 | * Closes all dropdowns. | |
929 | */ | |
930 | _closeAll: function() { | |
d788c437 | 931 | window.bc_wcfSimpleDropdown.closeAll(); |
184a8d6d AE |
932 | }, |
933 | ||
934 | /** | |
935 | * Closes a dropdown without notifying callbacks. | |
936 | * | |
937 | * @param string containerID | |
938 | */ | |
939 | close: function(containerID) { | |
d788c437 | 940 | window.bc_wcfSimpleDropdown.close(containerID); |
927616d2 AE |
941 | }, |
942 | ||
943 | /** | |
944 | * Destroies an existing dropdown menu. | |
945 | * | |
946 | * @param string containerID | |
947 | * @return boolean | |
948 | */ | |
949 | destroy: function(containerID) { | |
d788c437 | 950 | window.bc_wcfSimpleDropdown.destroy(containerID); |
184a8d6d AE |
951 | } |
952 | }; | |
5be96f42 | 953 | |
0d42c1c1 AE |
954 | /** |
955 | * Namespace for interactive dropdowns. | |
956 | */ | |
957 | WCF.Dropdown.Interactive = { }; | |
958 | ||
f5e3a61b | 959 | if (COMPILER_TARGET_DEFAULT) { |
0d42c1c1 | 960 | /** |
f5e3a61b | 961 | * General interface to create and manage interactive dropdowns. |
0d42c1c1 | 962 | */ |
f5e3a61b AE |
963 | WCF.Dropdown.Interactive.Handler = { |
964 | /** | |
965 | * global container for interactive dropdowns | |
966 | * @var jQuery | |
967 | */ | |
968 | _dropdownContainer: null, | |
0d42c1c1 | 969 | |
f5e3a61b AE |
970 | /** |
971 | * list of dropdown instances by identifier | |
972 | * @var object<WCF.Dropdown.Interactive.Instance> | |
973 | */ | |
974 | _dropdownMenus: {}, | |
0d42c1c1 | 975 | |
f5e3a61b AE |
976 | /** |
977 | * Creates a new interactive dropdown instance. | |
978 | * | |
979 | * @param jQuery triggerElement | |
980 | * @param string identifier | |
981 | * @param object options | |
982 | * @return WCF.Dropdown.Interactive.Instance | |
983 | */ | |
984 | create: function (triggerElement, identifier, options) { | |
985 | if (this._dropdownContainer === null) { | |
986 | this._dropdownContainer = $('<div class="dropdownMenuContainer" />').appendTo(document.body); | |
987 | WCF.CloseOverlayHandler.addCallback('WCF.Dropdown.Interactive.Handler', $.proxy(this.closeAll, this)); | |
988 | } | |
0d42c1c1 | 989 | |
f5e3a61b AE |
990 | var $instance = new WCF.Dropdown.Interactive.Instance(this._dropdownContainer, triggerElement, identifier, options); |
991 | this._dropdownMenus[identifier] = $instance; | |
992 | ||
993 | return $instance; | |
994 | }, | |
0d42c1c1 | 995 | |
f5e3a61b AE |
996 | /** |
997 | * Opens an interactive dropdown, returns false if identifier is unknown. | |
998 | * | |
999 | * @param string identifier | |
1000 | * @return boolean | |
1001 | */ | |
1002 | open: function (identifier) { | |
1003 | if (this._dropdownMenus[identifier]) { | |
1004 | this._dropdownMenus[identifier].open(); | |
1005 | ||
1006 | return true; | |
1007 | } | |
0d42c1c1 | 1008 | |
f5e3a61b AE |
1009 | return false; |
1010 | }, | |
0d42c1c1 | 1011 | |
f5e3a61b AE |
1012 | /** |
1013 | * Closes an interactive dropdown, returns false if identifier is unknown. | |
1014 | * | |
1015 | * @param string identifier | |
1016 | * @return boolean | |
1017 | */ | |
1018 | close: function (identifier) { | |
1019 | if (this._dropdownMenus[identifier]) { | |
1020 | this._dropdownMenus[identifier].close(); | |
1021 | ||
1022 | return true; | |
1023 | } | |
1024 | ||
1025 | return false; | |
1026 | }, | |
1027 | ||
1028 | /** | |
1029 | * Closes all interactive dropdowns. | |
1030 | */ | |
1031 | closeAll: function () { | |
1032 | for (var instance in this._dropdownMenus) { | |
1033 | if (this._dropdownMenus.hasOwnProperty(instance)) { | |
1034 | this._dropdownMenus[instance].close(); | |
1035 | } | |
1036 | } | |
1037 | }, | |
1038 | ||
1039 | getOpenDropdown: function () { | |
1040 | for (var instance in this._dropdownMenus) { | |
1041 | if (this._dropdownMenus.hasOwnProperty(instance)) { | |
1042 | if (this._dropdownMenus[instance].isOpen()) { | |
1043 | return this._dropdownMenus[instance]; | |
1044 | } | |
1045 | } | |
1046 | } | |
1047 | ||
1048 | return null; | |
1049 | }, | |
1050 | ||
1051 | /** | |
1052 | * Returns the dropdown with given identifier or `undefined` if no such dropdown exists. | |
1053 | * | |
1054 | * @param string identifier | |
1055 | * @return {WCF.Dropdown.Interactive.Instance?} | |
1056 | */ | |
1057 | getDropdown: function (identifier) { | |
1058 | return this._dropdownMenus[identifier]; | |
1059 | } | |
1060 | }; | |
0d42c1c1 AE |
1061 | |
1062 | /** | |
f5e3a61b AE |
1063 | * Represents and manages a single interactive dropdown instance. |
1064 | * | |
1065 | * @param jQuery dropdownContainer | |
1066 | * @param jQuery triggerElement | |
1067 | * @param string identifier | |
1068 | * @param object options | |
0d42c1c1 | 1069 | */ |
f5e3a61b AE |
1070 | WCF.Dropdown.Interactive.Instance = Class.extend({ |
1071 | /** | |
1072 | * dropdown container | |
1073 | * @var jQuery | |
1074 | */ | |
1075 | _container: null, | |
1076 | ||
1077 | /** | |
1078 | * inner item list | |
1079 | * @var jQuery | |
1080 | */ | |
1081 | _itemList: null, | |
1082 | ||
1083 | /** | |
1084 | * header link list | |
1085 | * @var jQuery | |
1086 | */ | |
1087 | _linkList: null, | |
1088 | ||
1089 | /** | |
1090 | * option list | |
1091 | * @var object | |
1092 | */ | |
1093 | _options: {}, | |
1094 | ||
1095 | /** | |
1096 | * arrow pointer | |
1097 | * @var jQuery | |
1098 | */ | |
1099 | _pointer: null, | |
1100 | ||
1101 | /** | |
1102 | * trigger element | |
1103 | * @var jQuery | |
1104 | */ | |
1105 | _triggerElement: null, | |
1106 | ||
1107 | /** | |
1108 | * Represents and manages a single interactive dropdown instance. | |
1109 | * | |
1110 | * @param jQuery dropdownContainer | |
1111 | * @param jQuery triggerElement | |
1112 | * @param string identifier | |
1113 | * @param object options | |
1114 | */ | |
1115 | init: function (dropdownContainer, triggerElement, identifier, options) { | |
1116 | this._options = options || {}; | |
1117 | this._triggerElement = triggerElement; | |
1118 | ||
1119 | var $itemContainer = null; | |
1120 | if (options.staticDropdown === true) { | |
1121 | this._container = this._triggerElement.find('.interactiveDropdownStatic:eq(0)').data('source', identifier).click(function (event) { | |
1122 | event.stopPropagation(); | |
1123 | }); | |
390f13f5 | 1124 | } |
f5e3a61b AE |
1125 | else { |
1126 | this._container = $('<div class="interactiveDropdown" data-source="' + identifier + '" />').click(function (event) { | |
1127 | event.stopPropagation(); | |
1128 | }); | |
1129 | ||
1130 | var $header = $('<div class="interactiveDropdownHeader" />').appendTo(this._container); | |
1131 | $('<span class="interactiveDropdownTitle">' + options.title + '</span>').appendTo($header); | |
1132 | this._linkList = $('<ul class="interactiveDropdownLinks inlineList"></ul>').appendTo($header); | |
1133 | ||
1134 | $itemContainer = $('<div class="interactiveDropdownItemsContainer" />').appendTo(this._container); | |
1135 | this._itemList = $('<ul class="interactiveDropdownItems" />').appendTo($itemContainer); | |
1136 | ||
1137 | $('<a href="' + options.showAllLink + '" class="interactiveDropdownShowAll">' + WCF.Language.get('wcf.user.panel.showAll') + '</a>').appendTo(this._container); | |
1138 | } | |
1139 | ||
1140 | this._pointer = $('<span class="elementPointer"><span /></span>').appendTo(this._container); | |
1141 | ||
1142 | require(['Environment'], (function (Environment) { | |
1143 | if (Environment.platform() === 'desktop') { | |
1144 | if ($itemContainer !== null) { | |
1145 | // use jQuery scrollbar on desktop, mobile browsers have a similar display built-in | |
1146 | $itemContainer.perfectScrollbar({ | |
1147 | suppressScrollX: true | |
1148 | }); | |
1149 | } | |
78cadceb | 1150 | } |
f5e3a61b AE |
1151 | }).bind(this)); |
1152 | ||
1153 | this._container.appendTo(dropdownContainer); | |
1154 | }, | |
1155 | ||
1156 | /** | |
1157 | * Returns the dropdown container. | |
1158 | * | |
1159 | * @return jQuery | |
1160 | */ | |
1161 | getContainer: function () { | |
1162 | return this._container; | |
1163 | }, | |
1164 | ||
1165 | /** | |
1166 | * Returns the inner item list. | |
1167 | * | |
1168 | * @return jQuery | |
1169 | */ | |
1170 | getItemList: function () { | |
1171 | return this._itemList; | |
1172 | }, | |
1173 | ||
1174 | /** | |
1175 | * Returns the header link list. | |
1176 | * | |
1177 | * @return jQuery | |
1178 | */ | |
1179 | getLinkList: function () { | |
1180 | return this._linkList; | |
1181 | }, | |
1182 | ||
1183 | /** | |
1184 | * Opens the dropdown. | |
1185 | */ | |
1186 | open: function () { | |
1187 | WCF.Dropdown._closeAll(); | |
1188 | ||
1189 | this._triggerElement.addClass('open'); | |
1190 | this._container.addClass('open'); | |
1191 | ||
1192 | WCF.System.Event.fireEvent('com.woltlab.wcf.Search', 'close'); | |
1193 | ||
1194 | this.render(); | |
1195 | }, | |
1196 | ||
1197 | /** | |
1198 | * Closes the dropdown | |
1199 | */ | |
1200 | close: function () { | |
1201 | this._triggerElement.removeClass('open'); | |
1202 | this._container.removeClass('open'); | |
1203 | }, | |
1204 | ||
1205 | /** | |
1206 | * Returns true if dropdown instance is visible. | |
1207 | * | |
1208 | * @returns {boolean} | |
1209 | */ | |
1210 | isOpen: function () { | |
1211 | return this._triggerElement.hasClass('open'); | |
1212 | }, | |
1213 | ||
1214 | /** | |
1215 | * Toggles the dropdown state, returns true if dropdown is open afterwards, else false. | |
1216 | * | |
1217 | * @return boolean | |
1218 | */ | |
1219 | toggle: function () { | |
1220 | if (this._container.hasClass('open')) { | |
1221 | this.close(); | |
1222 | ||
1223 | return false; | |
78cadceb | 1224 | } |
f5e3a61b AE |
1225 | else { |
1226 | WCF.Dropdown.Interactive.Handler.closeAll(); | |
1227 | ||
1228 | this.open(); | |
1229 | ||
1230 | return true; | |
1231 | } | |
1232 | }, | |
78cadceb | 1233 | |
f5e3a61b AE |
1234 | /** |
1235 | * Resets the inner item list and closes the dropdown. | |
1236 | */ | |
1237 | resetItems: function () { | |
1238 | this._itemList.empty(); | |
1239 | ||
1240 | this.close(); | |
1241 | }, | |
1242 | ||
1243 | /** | |
1244 | * Renders the dropdown. | |
1245 | */ | |
1246 | render: function () { | |
1247 | require(['Ui/Alignment', 'Ui/Screen'], (function (UiAlignment, UiScreen) { | |
1248 | if (UiScreen.is('screen-lg')) { | |
1249 | UiAlignment.set(this._container[0], this._triggerElement[0], { | |
1250 | horizontal: 'right', | |
1251 | pointer: true | |
1252 | }); | |
1253 | } | |
1254 | else { | |
1255 | this._container.css({ | |
1256 | bottom: '', | |
1257 | left: '', | |
1258 | right: '', | |
1259 | top: elById('pageHeaderPanel').clientHeight + 'px' | |
1260 | }); | |
1261 | } | |
1262 | }).bind(this)); | |
1263 | }, | |
1264 | ||
1265 | /** | |
1266 | * Rebuilds the desktop scrollbar. | |
1267 | */ | |
1268 | rebuildScrollbar: function () { | |
1269 | require(['Environment'], function (Environment) { | |
1270 | if (Environment.platform() === 'desktop') { | |
1271 | var $itemContainer = this._itemList.parent(); | |
1272 | ||
1273 | // do NOT use 'update', seems to be broken | |
1274 | $itemContainer.perfectScrollbar('destroy'); | |
1275 | $itemContainer.perfectScrollbar({ | |
1276 | suppressScrollX: true | |
1277 | }); | |
1278 | } | |
1279 | }.bind(this)); | |
1280 | } | |
1281 | }); | |
5b48b7d0 MS |
1282 | |
1283 | /** | |
f5e3a61b | 1284 | * Clipboard API |
5b48b7d0 | 1285 | * |
f5e3a61b | 1286 | * @deprecated 3.0 - please use `WoltLabSuite/Core/Controller/Clipboard` instead |
5b48b7d0 | 1287 | */ |
f5e3a61b AE |
1288 | WCF.Clipboard = { |
1289 | /** | |
1290 | * Initializes the clipboard API. | |
1291 | * | |
1292 | * @param string page | |
1293 | * @param integer hasMarkedItems | |
1294 | * @param object actionObjects | |
1295 | * @param integer pageObjectID | |
1296 | */ | |
1297 | init: function(page, hasMarkedItems, actionObjects, pageObjectID) { | |
1298 | require(['EventHandler', 'WoltLabSuite/Core/Controller/Clipboard'], function(EventHandler, ControllerClipboard) { | |
1299 | ControllerClipboard.setup({ | |
1300 | hasMarkedItems: (hasMarkedItems > 0), | |
1301 | pageClassName: page, | |
1302 | pageObjectId: pageObjectID | |
1303 | }); | |
1304 | ||
1305 | for (var type in actionObjects) { | |
1306 | if (actionObjects.hasOwnProperty(type)) { | |
1307 | (function (type) { | |
1308 | EventHandler.add('com.woltlab.wcf.clipboard', type, function (data) { | |
1309 | // only consider events if the action has been executed | |
1310 | if (data.responseData === null) { | |
1311 | return; | |
1312 | } | |
1313 | ||
1314 | if (actionObjects[type].hasOwnProperty(data.responseData.actionName)) { | |
1315 | actionObjects[type][data.responseData.actionName].triggerEffect(data.responseData.objectIDs); | |
1316 | } | |
1317 | }); | |
1318 | })(type); | |
1319 | } | |
1320 | } | |
1321 | }); | |
1322 | }, | |
1323 | ||
1324 | /** | |
1325 | * Reloads the list of marked items. | |
1326 | */ | |
1327 | reload: function() { | |
1328 | require(['WoltLabSuite/Core/Controller/Clipboard'], function(ControllerClipboard) { | |
1329 | ControllerClipboard.reload(); | |
1330 | }); | |
1331 | } | |
1332 | }; | |
1333 | } | |
1334 | else { | |
1335 | WCF.Dropdown.Interactive.Handler = { | |
1336 | _dropdownContainer: {}, | |
1337 | _dropdownMenus: {}, | |
1338 | create: function() {}, | |
1339 | open: function() {}, | |
1340 | close: function() {}, | |
1341 | closeAll: function() {}, | |
1342 | getOpenDropdown: function() {}, | |
1343 | getDropdown: function() {} | |
1344 | }; | |
1345 | ||
1346 | WCF.Dropdown.Interactive.Instance = Class.extend({ | |
1347 | _container: {}, | |
1348 | _itemList: {}, | |
1349 | _linkList: {}, | |
1350 | _options: {}, | |
1351 | _pointer: {}, | |
1352 | _triggerElement: {}, | |
1353 | init: function() {}, | |
1354 | getContainer: function() {}, | |
1355 | getItemList: function() {}, | |
1356 | getLinkList: function() {}, | |
1357 | open: function() {}, | |
1358 | close: function() {}, | |
1359 | isOpen: function() {}, | |
1360 | toggle: function() {}, | |
1361 | resetItems: function() {}, | |
1362 | render: function() {}, | |
1363 | rebuildScrollbar: function() {} | |
1364 | }); | |
1365 | ||
1366 | WCF.Clipboard = { | |
1367 | init: function() {}, | |
1368 | reload: function() {} | |
1369 | }; | |
1370 | } | |
0d42c1c1 AE |
1371 | |
1372 | /** | |
f5e3a61b | 1373 | * @deprecated Use WoltLabSuite/Core/Timer/Repeating |
0d42c1c1 | 1374 | */ |
f5e3a61b | 1375 | WCF.PeriodicalExecuter = Class.extend({ |
0d42c1c1 | 1376 | /** |
f5e3a61b AE |
1377 | * callback for each execution cycle |
1378 | * @var object | |
0d42c1c1 | 1379 | */ |
f5e3a61b | 1380 | _callback: null, |
0d42c1c1 AE |
1381 | |
1382 | /** | |
f5e3a61b AE |
1383 | * interval |
1384 | * @var integer | |
0d42c1c1 | 1385 | */ |
f5e3a61b | 1386 | _delay: 0, |
0d42c1c1 AE |
1387 | |
1388 | /** | |
f5e3a61b AE |
1389 | * interval id |
1390 | * @var integer | |
0d42c1c1 | 1391 | */ |
f5e3a61b | 1392 | _intervalID: null, |
0d42c1c1 | 1393 | |
28c5a05a | 1394 | /** |
f5e3a61b AE |
1395 | * execution state |
1396 | * @var boolean | |
28c5a05a | 1397 | */ |
f5e3a61b | 1398 | _isExecuting: false, |
28c5a05a | 1399 | |
0d42c1c1 | 1400 | /** |
f5e3a61b AE |
1401 | * Initializes a periodical executer. |
1402 | * | |
1403 | * @param function callback | |
1404 | * @param integer delay | |
0d42c1c1 | 1405 | */ |
f5e3a61b AE |
1406 | init: function(callback, delay) { |
1407 | if (!$.isFunction(callback)) { | |
1408 | console.debug('[WCF.PeriodicalExecuter] Given callback is invalid, aborting.'); | |
1409 | return; | |
1410 | } | |
1411 | ||
1412 | this._callback = callback; | |
1413 | this._interval = delay; | |
1414 | this.resume(); | |
1415 | }, | |
0d42c1c1 AE |
1416 | |
1417 | /** | |
f5e3a61b | 1418 | * Executes callback. |
0d42c1c1 | 1419 | */ |
f5e3a61b AE |
1420 | _execute: function() { |
1421 | if (!this._isExecuting) { | |
1422 | try { | |
1423 | this._isExecuting = true; | |
1424 | this._callback(this); | |
1425 | this._isExecuting = false; | |
1426 | } | |
1427 | catch (e) { | |
1428 | this._isExecuting = false; | |
1429 | throw e; | |
1430 | } | |
1431 | } | |
1432 | }, | |
158bd3ca TD |
1433 | |
1434 | /** | |
1435 | * Terminates loop. | |
1436 | */ | |
1437 | stop: function() { | |
ac37b8fe AE |
1438 | if (!this._intervalID) { |
1439 | return; | |
1440 | } | |
1441 | ||
1442 | clearInterval(this._intervalID); | |
ff34ee1f AE |
1443 | }, |
1444 | ||
1445 | /** | |
1446 | * Resumes the interval-based callback execution. | |
032b6044 AE |
1447 | * |
1448 | * @deprecated 2.1 - use restart() instead | |
ff34ee1f AE |
1449 | */ |
1450 | resume: function() { | |
032b6044 AE |
1451 | this.restart(); |
1452 | }, | |
1453 | ||
1454 | /** | |
1455 | * Restarts the interval-based callback execution. | |
1456 | */ | |
1457 | restart: function() { | |
ff34ee1f AE |
1458 | if (this._intervalID) { |
1459 | this.stop(); | |
1460 | } | |
1461 | ||
1462 | this._intervalID = setInterval($.proxy(this._execute, this), this._interval); | |
032b6044 AE |
1463 | }, |
1464 | ||
1465 | /** | |
1466 | * Sets the interval and restarts the interval. | |
1467 | * | |
1468 | * @param integer interval | |
1469 | */ | |
1470 | setInterval: function(interval) { | |
1471 | this._interval = interval; | |
1472 | ||
1473 | this.restart(); | |
158bd3ca | 1474 | } |
39e27190 | 1475 | }); |
158bd3ca TD |
1476 | |
1477 | /** | |
093162cc | 1478 | * Handler for loading overlays |
79ba2d03 | 1479 | * |
58d7e8f8 | 1480 | * @deprecated 3.0 - Please use WoltLabSuite/Core/Ajax/Status |
158bd3ca | 1481 | */ |
093162cc | 1482 | WCF.LoadingOverlayHandler = { |
b3991cb3 | 1483 | /** |
093162cc | 1484 | * Adds one loading-request and shows the loading overlay if nessercery |
b3991cb3 | 1485 | */ |
093162cc | 1486 | show: function() { |
58d7e8f8 | 1487 | require(['WoltLabSuite/Core/Ajax/Status'], function(AjaxStatus) { |
79ba2d03 AE |
1488 | AjaxStatus.show(); |
1489 | }); | |
093162cc | 1490 | }, |
9f959ced | 1491 | |
b3991cb3 | 1492 | /** |
093162cc MK |
1493 | * Removes one loading-request and hides loading overlay if there're no more pending requests |
1494 | */ | |
1495 | hide: function() { | |
58d7e8f8 | 1496 | require(['WoltLabSuite/Core/Ajax/Status'], function(AjaxStatus) { |
79ba2d03 AE |
1497 | AjaxStatus.hide(); |
1498 | }); | |
4dbe4dc2 MK |
1499 | }, |
1500 | ||
1501 | /** | |
1502 | * Updates a icon to/from spinner | |
1503 | * | |
1504 | * @param jQuery target | |
1505 | * @pram boolean loading | |
1506 | */ | |
1507 | updateIcon: function(target, loading) { | |
1508 | var $method = (loading === undefined || loading ? 'addClass' : 'removeClass'); | |
1509 | ||
ca8bfa53 | 1510 | target.find('.icon')[$method]('fa-spinner'); |
4dbe4dc2 | 1511 | if (target.hasClass('icon')) { |
ca8bfa53 | 1512 | target[$method]('fa-spinner'); |
4dbe4dc2 | 1513 | } |
093162cc MK |
1514 | } |
1515 | }; | |
1516 | ||
1517 | /** | |
1518 | * Namespace for AJAXProxies | |
1519 | */ | |
1520 | WCF.Action = {}; | |
1521 | ||
1522 | /** | |
1523 | * Basic implementation for AJAX-based proxyies | |
1524 | * | |
58d7e8f8 | 1525 | * @deprecated 3.0 - please use `WoltLabSuite/Core/Ajax.api()` instead |
bbb6abab | 1526 | * |
093162cc MK |
1527 | * @param object options |
1528 | */ | |
1529 | WCF.Action.Proxy = Class.extend({ | |
bbb6abab | 1530 | _ajaxRequest: null, |
9ee50dd7 | 1531 | |
158bd3ca TD |
1532 | /** |
1533 | * Initializes AJAXProxy. | |
1534 | * | |
1535 | * @param object options | |
1536 | */ | |
1537 | init: function(options) { | |
bbb6abab AE |
1538 | this._ajaxRequest = null; |
1539 | ||
1540 | options = $.extend(true, { | |
158bd3ca TD |
1541 | autoSend: false, |
1542 | data: { }, | |
944d7f97 | 1543 | dataType: 'json', |
158bd3ca TD |
1544 | after: null, |
1545 | init: null, | |
889cdd4c | 1546 | jsonp: 'callback', |
7965fc49 | 1547 | async: true, |
158bd3ca | 1548 | failure: null, |
324e8301 | 1549 | showLoadingOverlay: true, |
158bd3ca | 1550 | success: null, |
88432fe0 | 1551 | suppressErrors: false, |
158bd3ca | 1552 | type: 'POST', |
8186015c | 1553 | url: 'index.php?ajax-proxy/&t=' + SECURITY_TOKEN, |
9ee50dd7 MK |
1554 | aborted: null, |
1555 | autoAbortPrevious: false | |
158bd3ca TD |
1556 | }, options); |
1557 | ||
bbb6abab AE |
1558 | if (options.dataType === 'jsonp') { |
1559 | require(['AjaxJsonp'], function(AjaxJsonp) { | |
1560 | AjaxJsonp.send(options.url, options.success, options.failure, { | |
1561 | parameterName: options.jsonp | |
1562 | }); | |
1563 | }); | |
1564 | } | |
1565 | else { | |
1566 | require(['AjaxRequest'], (function(AjaxRequest) { | |
1567 | this._ajaxRequest = new AjaxRequest({ | |
1568 | data: options.data, | |
1569 | type: options.type, | |
1570 | url: options.url, | |
bb282d8b | 1571 | withCredentials: (options.url === 'index.php?ajax-proxy/&t=' + SECURITY_TOKEN), |
716c05a4 | 1572 | responseType: (options.dataType === 'json' ? 'application/json' : ''), |
bbb6abab AE |
1573 | |
1574 | autoAbort: options.autoAbortPrevious, | |
1575 | ignoreError: options.suppressErrors, | |
e8781200 | 1576 | silent: !options.showLoadingOverlay, |
bbb6abab AE |
1577 | |
1578 | failure: options.failure, | |
1579 | finalize: options.after, | |
1580 | success: options.success | |
1581 | }); | |
1582 | ||
1583 | if (options.autoSend) { | |
1584 | this._ajaxRequest.sendRequest(); | |
1585 | } | |
1586 | }).bind(this)); | |
158bd3ca TD |
1587 | } |
1588 | }, | |
1589 | ||
1590 | /** | |
1591 | * Sends an AJAX request. | |
9ee50dd7 MK |
1592 | * |
1593 | * @param abortPrevious boolean | |
158bd3ca | 1594 | */ |
9ee50dd7 | 1595 | sendRequest: function(abortPrevious) { |
d8f608b0 AE |
1596 | require(['AjaxRequest'], (function(AjaxRequest) { |
1597 | if (this._ajaxRequest !== null) { | |
1598 | this._ajaxRequest.sendRequest(abortPrevious); | |
1599 | } | |
1600 | }).bind(this)); | |
9ee50dd7 MK |
1601 | }, |
1602 | ||
1603 | /** | |
1604 | * Aborts the previous request | |
1605 | */ | |
1606 | abortPrevious: function() { | |
d8f608b0 AE |
1607 | require(['AjaxRequest'], (function(AjaxRequest) { |
1608 | if (this._ajaxRequest !== null) { | |
1609 | this._ajaxRequest.abortPrevious(); | |
1610 | } | |
1611 | }).bind(this)); | |
158bd3ca TD |
1612 | }, |
1613 | ||
158bd3ca TD |
1614 | /** |
1615 | * Sets options, MUST be used to set parameters before sending request | |
1616 | * if calling from child classes. | |
1617 | * | |
1618 | * @param string optionName | |
1619 | * @param mixed optionData | |
1620 | */ | |
1621 | setOption: function(optionName, optionData) { | |
d8f608b0 AE |
1622 | require(['AjaxRequest'], (function(AjaxRequest) { |
1623 | if (this._ajaxRequest !== null) { | |
1624 | this._ajaxRequest.setOption(optionName, optionData); | |
1625 | } | |
1626 | }).bind(this)); | |
1627 | }, | |
1628 | ||
1629 | // legacy methods, no longer supported | |
1630 | showLoadingOverlayOnce: function() {}, | |
1631 | suppressErrors: function() {}, | |
1632 | _failure: function(jqXHR, textStatus, errorThrown) {}, | |
1633 | _success: function(data, textStatus, jqXHR) {}, | |
1634 | _after: function() {} | |
39e27190 | 1635 | }); |
158bd3ca TD |
1636 | |
1637 | /** | |
1638 | * Basic implementation for simple proxy access using bound elements. | |
1639 | * | |
1640 | * @param object options | |
1641 | * @param object callbacks | |
1642 | */ | |
39e27190 | 1643 | WCF.Action.SimpleProxy = Class.extend({ |
158bd3ca TD |
1644 | /** |
1645 | * Initializes SimpleProxy. | |
1646 | * | |
1647 | * @param object options | |
1648 | * @param object callbacks | |
1649 | */ | |
1650 | init: function(options, callbacks) { | |
1651 | /** | |
1652 | * action-specific options | |
1653 | */ | |
1654 | this.options = $.extend(true, { | |
1655 | action: '', | |
1656 | className: '', | |
1657 | elements: null, | |
1658 | eventName: 'click' | |
1659 | }, options); | |
1660 | ||
1661 | /** | |
1662 | * proxy-specific options | |
1663 | */ | |
1664 | this.callbacks = $.extend(true, { | |
1665 | after: null, | |
1666 | failure: null, | |
1667 | init: null, | |
1668 | success: null | |
1669 | }, callbacks); | |
1670 | ||
1671 | if (!this.options.elements) return; | |
1672 | ||
1673 | // initialize proxy | |
1674 | this.proxy = new WCF.Action.Proxy(this.callbacks); | |
1675 | ||
1676 | // bind event listener | |
1677 | this.options.elements.each($.proxy(function(index, element) { | |
1678 | $(element).bind(this.options.eventName, $.proxy(this._handleEvent, this)); | |
1679 | }, this)); | |
1680 | }, | |
1681 | ||
1682 | /** | |
1683 | * Handles event actions. | |
1684 | * | |
1685 | * @param object event | |
1686 | */ | |
1687 | _handleEvent: function(event) { | |
1688 | this.proxy.setOption('data', { | |
1689 | actionName: this.options.action, | |
1690 | className: this.options.className, | |
1691 | objectIDs: [ $(event.target).data('objectID') ] | |
1692 | }); | |
1693 | ||
1694 | this.proxy.sendRequest(); | |
1695 | } | |
39e27190 | 1696 | }); |
158bd3ca | 1697 | |
f5e3a61b | 1698 | if (COMPILER_TARGET_DEFAULT) { |
158bd3ca | 1699 | /** |
f5e3a61b AE |
1700 | * Basic implementation for AJAXProxy-based deletion. |
1701 | * | |
1702 | * @param string className | |
1703 | * @param string containerSelector | |
1704 | * @param string buttonSelector | |
158bd3ca | 1705 | */ |
f5e3a61b AE |
1706 | WCF.Action.Delete = Class.extend({ |
1707 | /** | |
1708 | * delete button selector | |
1709 | * @var string | |
1710 | */ | |
1711 | _buttonSelector: '', | |
9d4465dd | 1712 | |
f5e3a61b AE |
1713 | /** |
1714 | * callback function called prior to triggering the delete effect | |
1715 | * @var function | |
1716 | */ | |
1717 | _callback: null, | |
158bd3ca | 1718 | |
f5e3a61b AE |
1719 | /** |
1720 | * action class name | |
1721 | * @var string | |
1722 | */ | |
1723 | _className: '', | |
d371330f | 1724 | |
f5e3a61b AE |
1725 | /** |
1726 | * container selector | |
1727 | * @var string | |
1728 | */ | |
1729 | _containerSelector: '', | |
1730 | ||
1731 | /** | |
1732 | * list of known container ids | |
1733 | * @var array<string> | |
1734 | */ | |
1735 | _containers: [], | |
1736 | ||
1737 | /** | |
1738 | * Initializes 'delete'-Proxy. | |
1739 | * | |
1740 | * @param string className | |
1741 | * @param string containerSelector | |
1742 | * @param string buttonSelector | |
1743 | */ | |
1744 | init: function (className, containerSelector, buttonSelector) { | |
1745 | this._containerSelector = containerSelector; | |
1746 | this._className = className; | |
1747 | this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsDeleteButton'; | |
1748 | this._callback = null; | |
d371330f | 1749 | |
f5e3a61b AE |
1750 | this.proxy = new WCF.Action.Proxy({ |
1751 | success: $.proxy(this._success, this) | |
1752 | }); | |
1753 | ||
1754 | this._initElements(); | |
1755 | ||
1756 | WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Delete' + this._className.hashCode(), $.proxy(this._initElements, this)); | |
1757 | }, | |
1758 | ||
1759 | /** | |
1760 | * Initializes available element containers. | |
1761 | */ | |
1762 | _initElements: function () { | |
1763 | $(this._containerSelector).each((function (index, container) { | |
1764 | var $container = $(container); | |
1765 | var $containerID = $container.wcfIdentify(); | |
4ab96099 | 1766 | |
f5e3a61b AE |
1767 | if (!WCF.inArray($containerID, this._containers)) { |
1768 | var $deleteButton = $container.find(this._buttonSelector); | |
1769 | ||
1770 | if ($deleteButton.length) { | |
1771 | this._containers.push($containerID); | |
1772 | $deleteButton.click($.proxy(this._click, this)); | |
1773 | } | |
4ab96099 | 1774 | } |
f5e3a61b AE |
1775 | }).bind(this)); |
1776 | }, | |
1777 | ||
1778 | /** | |
1779 | * Sends AJAX request. | |
1780 | * | |
1781 | * @param object event | |
1782 | */ | |
1783 | _click: function (event) { | |
1784 | var $target = $(event.currentTarget); | |
1785 | event.preventDefault(); | |
1786 | ||
1787 | if ($target.data('confirmMessageHtml') || $target.data('confirmMessage')) { | |
1788 | WCF.System.Confirmation.show($target.data('confirmMessageHtml') ? $target.data('confirmMessageHtml') : $target.data('confirmMessage'), $.proxy(this._execute, this), {target: $target}, undefined, $target.data('confirmMessageHtml') ? true : false); | |
d371330f | 1789 | } |
f5e3a61b AE |
1790 | else { |
1791 | WCF.LoadingOverlayHandler.updateIcon($target); | |
1792 | this._sendRequest($target); | |
1793 | } | |
1794 | }, | |
158bd3ca | 1795 | |
f5e3a61b AE |
1796 | /** |
1797 | * Is called if the delete effect has been triggered on the given element. | |
1798 | * | |
1799 | * @param jQuery element | |
1800 | */ | |
1801 | _didTriggerEffect: function (element) { | |
1802 | // does nothing | |
1803 | }, | |
158bd3ca | 1804 | |
f5e3a61b AE |
1805 | /** |
1806 | * Executes deletion. | |
1807 | * | |
1808 | * @param string action | |
1809 | * @param object parameters | |
1810 | */ | |
1811 | _execute: function (action, parameters) { | |
1812 | if (action === 'cancel') { | |
1813 | return; | |
1814 | } | |
1815 | ||
1816 | WCF.LoadingOverlayHandler.updateIcon(parameters.target); | |
1817 | this._sendRequest(parameters.target); | |
1818 | }, | |
158bd3ca | 1819 | |
f5e3a61b AE |
1820 | /** |
1821 | * Sends the request | |
1822 | * | |
1823 | * @param jQuery object | |
1824 | */ | |
1825 | _sendRequest: function (object) { | |
1826 | this.proxy.setOption('data', { | |
1827 | actionName: 'delete', | |
1828 | className: this._className, | |
1829 | interfaceName: 'wcf\\data\\IDeleteAction', | |
1830 | objectIDs: [$(object).data('objectID')] | |
1831 | }); | |
1832 | ||
1833 | this.proxy.sendRequest(); | |
1834 | }, | |
ee9e4388 | 1835 | |
f5e3a61b AE |
1836 | /** |
1837 | * Deletes items from containers. | |
1838 | * | |
1839 | * @param object data | |
1840 | * @param string textStatus | |
1841 | * @param object jqXHR | |
1842 | */ | |
1843 | _success: function (data, textStatus, jqXHR) { | |
1844 | if (this._callback) { | |
1845 | this._callback(data.objectIDs); | |
1846 | } | |
1847 | ||
1848 | this.triggerEffect(data.objectIDs); | |
1849 | }, | |
ee9e4388 | 1850 | |
f5e3a61b AE |
1851 | /** |
1852 | * Sets a callback function called prior to triggering the delete effect. | |
1853 | * | |
1854 | * @param {function} callback | |
1855 | */ | |
1856 | setCallback: function (callback) { | |
1857 | if (typeof callback !== 'function') { | |
1858 | throw new TypeError("[WCF.Action.Delete] Expected a valid callback for '" + this._className + "'."); | |
1859 | } | |
1860 | ||
1861 | this._callback = callback; | |
1862 | }, | |
1863 | ||
1864 | /** | |
1865 | * Triggers the delete effect for the objects with the given ids. | |
1866 | * | |
1867 | * @param array objectIDs | |
1868 | */ | |
1869 | triggerEffect: function (objectIDs) { | |
1870 | for (var $index in this._containers) { | |
1871 | var $container = $('#' + this._containers[$index]); | |
1872 | var $button = $container.find(this._buttonSelector); | |
1873 | if (WCF.inArray($button.data('objectID'), objectIDs)) { | |
1874 | var self = this; | |
1875 | $container.wcfBlindOut('up', function () { | |
1876 | var $container = $(this).remove(); | |
1877 | self._containers.splice(self._containers.indexOf($container.wcfIdentify()), 1); | |
1878 | self._didTriggerEffect($container); | |
1879 | ||
1880 | if ($button.data('eventName')) { | |
1881 | WCF.System.Event.fireEvent('com.woltlab.wcf.action.delete', $button.data('eventName'), { | |
1882 | button: $button, | |
1883 | container: $container | |
1884 | }); | |
1885 | } | |
1886 | }); | |
1887 | } | |
1888 | } | |
1889 | } | |
1890 | }); | |
ee9e4388 | 1891 | |
da27d58a | 1892 | /** |
f5e3a61b AE |
1893 | * Basic implementation for deletion of nested elements. |
1894 | * | |
1895 | * The implementation requires the nested elements to be grouped as numbered lists | |
1896 | * (ol lists). The child elements of the deleted elements are moved to the parent | |
1897 | * element of the deleted element. | |
1898 | * | |
1899 | * @see WCF.Action.Delete | |
da27d58a | 1900 | */ |
f5e3a61b AE |
1901 | WCF.Action.NestedDelete = WCF.Action.Delete.extend({ |
1902 | /** | |
1903 | * @see WCF.Action.Delete.triggerEffect() | |
1904 | */ | |
1905 | triggerEffect: function (objectIDs) { | |
1906 | for (var $index in this._containers) { | |
1907 | var $container = $('#' + this._containers[$index]); | |
1908 | if (WCF.inArray($container.find(this._buttonSelector).data('objectID'), objectIDs)) { | |
1909 | // move children up | |
1910 | if ($container.has('ol').has('li').length) { | |
1911 | if ($container.is(':only-child')) { | |
1912 | $container.parent().replaceWith($container.find('> ol')); | |
1913 | } | |
1914 | else { | |
1915 | $container.replaceWith($container.find('> ol > li')); | |
1916 | } | |
1917 | ||
1918 | this._containers.splice(this._containers.indexOf($container.wcfIdentify()), 1); | |
1919 | this._didTriggerEffect($container); | |
1920 | } | |
1921 | else { | |
1922 | var self = this; | |
1923 | $container.wcfBlindOut('up', function () { | |
1924 | $(this).remove(); | |
1925 | self._containers.splice(self._containers.indexOf($(this).wcfIdentify()), 1); | |
1926 | self._didTriggerEffect($(this)); | |
31bec02c AE |
1927 | }); |
1928 | } | |
f5e3a61b | 1929 | } |
158bd3ca | 1930 | } |
d371330f | 1931 | } |
f5e3a61b AE |
1932 | }); |
1933 | ||
1934 | /** | |
1935 | * Basic implementation for AJAXProxy-based toggle actions. | |
1936 | * | |
1937 | * @param string className | |
1938 | * @param jQuery containerList | |
1939 | * @param string buttonSelector | |
1940 | */ | |
1941 | WCF.Action.Toggle = Class.extend({ | |
1942 | /** | |
1943 | * toogle button selector | |
1944 | * @var string | |
1945 | */ | |
1946 | _buttonSelector: '.jsToggleButton', | |
1947 | ||
1948 | /** | |
1949 | * action class name | |
1950 | * @var string | |
1951 | */ | |
1952 | _className: '', | |
1953 | ||
1954 | /** | |
1955 | * container selector | |
1956 | * @var string | |
1957 | */ | |
1958 | _containerSelector: '', | |
1959 | ||
1960 | /** | |
1961 | * list of known container ids | |
1962 | * @var array<string> | |
1963 | */ | |
1964 | _containers: [], | |
1965 | ||
1966 | /** | |
1967 | * Initializes 'toggle'-Proxy | |
1968 | * | |
1969 | * @param string className | |
1970 | * @param string containerSelector | |
1971 | * @param string buttonSelector | |
1972 | */ | |
1973 | init: function (className, containerSelector, buttonSelector) { | |
1974 | this._containerSelector = containerSelector; | |
1975 | this._className = className; | |
1976 | this._buttonSelector = (buttonSelector) ? buttonSelector : '.jsToggleButton'; | |
1977 | this._containers = []; | |
1978 | ||
1979 | // initialize proxy | |
1980 | var options = { | |
1981 | success: $.proxy(this._success, this) | |
1982 | }; | |
1983 | this.proxy = new WCF.Action.Proxy(options); | |
1984 | ||
1985 | // bind event listener | |
1986 | this._initElements(); | |
1987 | WCF.DOMNodeInsertedHandler.addCallback('WCF.Action.Toggle' + this._className.hashCode(), $.proxy(this._initElements, this)); | |
1988 | }, | |
1989 | ||
1990 | /** | |
1991 | * Initializes available element containers. | |
1992 | */ | |
1993 | _initElements: function () { | |
1994 | $(this._containerSelector).each($.proxy(function (index, container) { | |
1995 | var $container = $(container); | |
1996 | var $containerID = $container.wcfIdentify(); | |
1997 | ||
1998 | if (!WCF.inArray($containerID, this._containers)) { | |
1999 | this._containers.push($containerID); | |
2000 | $container.find(this._buttonSelector).click($.proxy(this._click, this)); | |
8a52f0a4 | 2001 | } |
f5e3a61b AE |
2002 | }, this)); |
2003 | }, | |
2004 | ||
2005 | /** | |
2006 | * Sends AJAX request. | |
2007 | * | |
2008 | * @param object event | |
2009 | */ | |
2010 | _click: function (event) { | |
2011 | var $target = $(event.currentTarget); | |
2012 | event.preventDefault(); | |
2013 | ||
2014 | if ($target.data('confirmMessageHtml') || $target.data('confirmMessage')) { | |
2015 | WCF.System.Confirmation.show($target.data('confirmMessageHtml') ? $target.data('confirmMessageHtml') : $target.data('confirmMessage'), $.proxy(this._execute, this), {target: $target}, undefined, $target.data('confirmMessageHtml') ? true : false); | |
2016 | } | |
2017 | else { | |
2018 | WCF.LoadingOverlayHandler.updateIcon($target); | |
2019 | this._sendRequest($target); | |
2020 | } | |
2021 | }, | |
2022 | ||
2023 | /** | |
2024 | * Executes toggeling. | |
2025 | * | |
2026 | * @param string action | |
2027 | * @param object parameters | |
2028 | */ | |
2029 | _execute: function (action, parameters) { | |
2030 | if (action === 'cancel') { | |
2031 | return; | |
2032 | } | |
2033 | ||
2034 | WCF.LoadingOverlayHandler.updateIcon(parameters.target); | |
2035 | this._sendRequest(parameters.target); | |
2036 | }, | |
2037 | ||
2038 | _sendRequest: function (object) { | |
2039 | this.proxy.setOption('data', { | |
2040 | actionName: 'toggle', | |
2041 | className: this._className, | |
2042 | interfaceName: 'wcf\\data\\IToggleAction', | |
2043 | objectIDs: [$(object).data('objectID')] | |
2044 | }); | |
2045 | ||
2046 | this.proxy.sendRequest(); | |
2047 | }, | |
2048 | ||
2049 | /** | |
2050 | * Toggles status icons. | |
2051 | * | |
2052 | * @param object data | |
2053 | * @param string textStatus | |
2054 | * @param object jqXHR | |
2055 | */ | |
2056 | _success: function (data, textStatus, jqXHR) { | |
2057 | this.triggerEffect(data.objectIDs); | |
2058 | }, | |
2059 | ||
2060 | /** | |
2061 | * Triggers the toggle effect for the objects with the given ids. | |
2062 | * | |
2063 | * @param array objectIDs | |
2064 | */ | |
2065 | triggerEffect: function (objectIDs) { | |
2066 | for (var $index in this._containers) { | |
2067 | var $container = $('#' + this._containers[$index]); | |
2068 | var $toggleButton = $container.find(this._buttonSelector); | |
2069 | if (WCF.inArray($toggleButton.data('objectID'), objectIDs)) { | |
2070 | $container.wcfHighlight(); | |
2071 | this._toggleButton($container, $toggleButton); | |
8a52f0a4 MS |
2072 | } |
2073 | } | |
f5e3a61b AE |
2074 | }, |
2075 | ||
2076 | /** | |
1615fc2e | 2077 | * Triggers the toggle effect on a button |
f5e3a61b AE |
2078 | * |
2079 | * @param jQuery $container | |
2080 | * @param jQuery $toggleButton | |
2081 | */ | |
2082 | _toggleButton: function ($container, $toggleButton) { | |
2083 | var $newTitle = ''; | |
2084 | ||
2085 | // toggle icon source | |
2086 | WCF.LoadingOverlayHandler.updateIcon($toggleButton, false); | |
2087 | if ($toggleButton.hasClass('fa-square-o')) { | |
2088 | $toggleButton.removeClass('fa-square-o').addClass('fa-check-square-o'); | |
2089 | $newTitle = ($toggleButton.data('disableTitle') ? $toggleButton.data('disableTitle') : WCF.Language.get('wcf.global.button.disable')); | |
2090 | $toggleButton.attr('title', $newTitle); | |
2091 | } | |
2092 | else { | |
2093 | $toggleButton.removeClass('fa-check-square-o').addClass('fa-square-o'); | |
2094 | $newTitle = ($toggleButton.data('enableTitle') ? $toggleButton.data('enableTitle') : WCF.Language.get('wcf.global.button.enable')); | |
2095 | $toggleButton.attr('title', $newTitle); | |
2096 | } | |
2097 | ||
2098 | // toggle css class | |
2099 | $container.toggleClass('disabled'); | |
8a52f0a4 | 2100 | } |
f5e3a61b AE |
2101 | }); |
2102 | } | |
2103 | else { | |
2104 | WCF.Action.Delete = Class.extend({ | |
2105 | _buttonSelector: "", | |
2106 | _callback: {}, | |
2107 | _className: "", | |
2108 | _containerSelector: "", | |
2109 | _containers: {}, | |
2110 | init: function() {}, | |
2111 | _initElements: function() {}, | |
2112 | _click: function() {}, | |
2113 | _didTriggerEffect: function() {}, | |
2114 | _execute: function() {}, | |
2115 | _sendRequest: function() {}, | |
2116 | _success: function() {}, | |
2117 | setCallback: function() {}, | |
2118 | triggerEffect: function() {} | |
2119 | }); | |
2120 | ||
2121 | WCF.Action.NestedDelete = WCF.Action.Delete.extend({ | |
2122 | triggerEffect: function() {}, | |
2123 | _buttonSelector: "", | |
2124 | _callback: {}, | |
2125 | _className: "", | |
2126 | _containerSelector: "", | |
2127 | _containers: {}, | |
2128 | init: function() {}, | |
2129 | _initElements: function() {}, | |
2130 | _click: function() {}, | |
2131 | _didTriggerEffect: function() {}, | |
2132 | _execute: function() {}, | |
2133 | _sendRequest: function() {}, | |
2134 | _success: function() {}, | |
2135 | setCallback: function() {} | |
2136 | }); | |
2137 | ||
2138 | WCF.Action.Toggle = Class.extend({ | |
2139 | _buttonSelector: "", | |
2140 | _className: "", | |
2141 | _containerSelector: "", | |
2142 | _containers: {}, | |
2143 | init: function() {}, | |
2144 | _initElements: function() {}, | |
2145 | _click: function() {}, | |
2146 | _execute: function() {}, | |
2147 | _sendRequest: function() {}, | |
2148 | _success: function() {}, | |
2149 | triggerEffect: function() {}, | |
2150 | _toggleButton: function() {} | |
2151 | }); | |
2152 | } | |
8a52f0a4 | 2153 | |
158bd3ca | 2154 | /** |
1615fc2e | 2155 | * Executes provided callback if scroll threshold is reached. Usable to determine |
f5e3a61b | 2156 | * if user reached the bottom of an element to load new elements on the fly. |
158bd3ca | 2157 | * |
f5e3a61b AE |
2158 | * If you do not provide a value for 'reference' and 'target' it will assume you're |
2159 | * monitoring page scrolls, otherwise a valid jQuery selector must be provided for both. | |
2160 | * | |
2161 | * @param integer threshold | |
2162 | * @param object callback | |
2163 | * @param string reference | |
2164 | * @param string target | |
158bd3ca | 2165 | */ |
f5e3a61b | 2166 | WCF.Action.Scroll = Class.extend({ |
9d4465dd | 2167 | /** |
f5e3a61b AE |
2168 | * callback used once threshold is reached |
2169 | * @var object | |
9d4465dd | 2170 | */ |
f5e3a61b | 2171 | _callback: null, |
9d4465dd | 2172 | |
5dd8bc73 | 2173 | /** |
f5e3a61b AE |
2174 | * reference object |
2175 | * @var jQuery | |
5dd8bc73 | 2176 | */ |
f5e3a61b | 2177 | _reference: null, |
5dd8bc73 MK |
2178 | |
2179 | /** | |
f5e3a61b AE |
2180 | * target object |
2181 | * @var jQuery | |
5dd8bc73 | 2182 | */ |
f5e3a61b | 2183 | _target: null, |
5dd8bc73 MK |
2184 | |
2185 | /** | |
f5e3a61b AE |
2186 | * threshold value |
2187 | * @var integer | |
5dd8bc73 | 2188 | */ |
f5e3a61b | 2189 | _threshold: 0, |
5dd8bc73 | 2190 | |
158bd3ca | 2191 | /** |
f5e3a61b | 2192 | * Initializes a new WCF.Action.Scroll object. |
158bd3ca | 2193 | * |
f5e3a61b AE |
2194 | * @param integer threshold |
2195 | * @param object callback | |
2196 | * @param string reference | |
2197 | * @param string target | |
158bd3ca | 2198 | */ |
f5e3a61b AE |
2199 | init: function(threshold, callback, reference, target) { |
2200 | this._threshold = parseInt(threshold); | |
2201 | if (this._threshold === 0) { | |
2202 | console.debug("[WCF.Action.Scroll] Given threshold is invalid, aborting."); | |
2203 | return; | |
2204 | } | |
32a9e8a4 | 2205 | |
f5e3a61b AE |
2206 | if ($.isFunction(callback)) this._callback = callback; |
2207 | if (this._callback === null) { | |
2208 | console.debug("[WCF.Action.Scroll] Given callback is invalid, aborting."); | |
2209 | return; | |
2210 | } | |
158bd3ca | 2211 | |
f5e3a61b AE |
2212 | // bind element references |
2213 | this._reference = $((reference) ? reference : window); | |
2214 | this._target = $((target) ? target : document); | |
2215 | ||
2216 | // watch for scroll event | |
2217 | this.start(); | |
2218 | ||
2219 | // check if browser navigated back and jumped to offset before JavaScript was loaded | |
2220 | this._scroll(); | |
5dd8bc73 MK |
2221 | }, |
2222 | ||
2223 | /** | |
f5e3a61b | 2224 | * Calculates if threshold is reached and notifies callback. |
aa4fb64e AE |
2225 | */ |
2226 | _scroll: function() { | |
2227 | var $targetHeight = this._target.height(); | |
2228 | var $topOffset = this._reference.scrollTop(); | |
2229 | var $referenceHeight = this._reference.height(); | |
2230 | ||
2231 | // calculate if defined threshold is visible | |
2232 | if (($targetHeight - ($referenceHeight + $topOffset)) < this._threshold) { | |
2233 | this._callback(this); | |
2234 | } | |
2235 | }, | |
2236 | ||
2237 | /** | |
2238 | * Enables scroll monitoring, may be used to resume. | |
2239 | */ | |
2240 | start: function() { | |
2241 | this._reference.on('scroll', $.proxy(this._scroll, this)); | |
221fce41 | 2242 | }, |
aa4fb64e AE |
2243 | |
2244 | /** | |
2245 | * Disables scroll monitoring, e.g. no more elements loadable. | |
2246 | */ | |
2247 | stop: function() { | |
2248 | this._reference.off('scroll'); | |
2249 | } | |
2250 | }); | |
2251 | ||
158bd3ca TD |
2252 | /** |
2253 | * Namespace for date-related functions. | |
2254 | */ | |
2255 | WCF.Date = {}; | |
2256 | ||
81f55d8f AE |
2257 | /** |
2258 | * Provides a date picker for date input fields. | |
5a961a44 | 2259 | * |
e71525e4 | 2260 | * @deprecated 3.0 - no longer required |
81f55d8f | 2261 | */ |
5a961a44 | 2262 | WCF.Date.Picker = { init: function() {} }; |
81f55d8f | 2263 | |
158bd3ca TD |
2264 | /** |
2265 | * Provides utility functions for date operations. | |
5a961a44 | 2266 | * |
e71525e4 | 2267 | * @deprecated 3.0 - use `DateUtil` instead |
158bd3ca TD |
2268 | */ |
2269 | WCF.Date.Util = { | |
2270 | /** | |
2271 | * Returns UTC timestamp, if date is not given, current time will be used. | |
2272 | * | |
2273 | * @param Date date | |
2274 | * @return integer | |
5a961a44 | 2275 | * |
e71525e4 | 2276 | * @deprecated 3.0 - use `DateUtil::gmdate()` instead |
158bd3ca TD |
2277 | */ |
2278 | gmdate: function(date) { | |
2279 | var $date = (date) ? date : new Date(); | |
2280 | ||
2281 | return Math.round(Date.UTC( | |
2282 | $date.getUTCFullYear(), | |
2283 | $date.getUTCMonth(), | |
2284 | $date.getUTCDay(), | |
2285 | $date.getUTCHours(), | |
2286 | $date.getUTCMinutes(), | |
2287 | $date.getUTCSeconds() | |
2288 | ) / 1000); | |
2289 | }, | |
2290 | ||
2291 | /** | |
2292 | * Returns a Date object with precise offset (including timezone and local timezone). | |
1615fc2e | 2293 | * Parameters timestamp and offset must be in milliseconds! |
158bd3ca TD |
2294 | * |
2295 | * @param integer timestamp | |
2296 | * @param integer offset | |
2297 | * @return Date | |
5a961a44 | 2298 | * |
e71525e4 | 2299 | * @deprecated 3.0 - use `DateUtil::getTimezoneDate()` instead |
158bd3ca TD |
2300 | */ |
2301 | getTimezoneDate: function(timestamp, offset) { | |
2302 | var $date = new Date(timestamp); | |
88ff183f | 2303 | var $localOffset = $date.getTimezoneOffset() * 60000; |
158bd3ca | 2304 | |
88ff183f | 2305 | return new Date((timestamp + $localOffset + offset)); |
158bd3ca TD |
2306 | } |
2307 | }; | |
2308 | ||
158bd3ca TD |
2309 | /** |
2310 | * Hash-like dictionary. Based upon idead from Prototype's hash | |
2311 | * | |
2312 | * @see https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/hash.js | |
2313 | */ | |
39e27190 | 2314 | WCF.Dictionary = Class.extend({ |
264e3f79 AE |
2315 | /** |
2316 | * list of variables | |
2317 | * @var object | |
2318 | */ | |
2319 | _variables: { }, | |
2320 | ||
158bd3ca TD |
2321 | /** |
2322 | * Initializes a new dictionary. | |
2323 | */ | |
83428b7f AE |
2324 | init: function() { |
2325 | this._variables = { }; | |
2326 | }, | |
158bd3ca TD |
2327 | |
2328 | /** | |
2329 | * Adds an entry. | |
2330 | * | |
2331 | * @param string key | |
2332 | * @param mixed value | |
2333 | */ | |
2334 | add: function(key, value) { | |
264e3f79 | 2335 | this._variables[key] = value; |
158bd3ca TD |
2336 | }, |
2337 | ||
2338 | /** | |
2339 | * Adds a traditional object to current dataset. | |
2340 | * | |
2341 | * @param object object | |
2342 | */ | |
2343 | addObject: function(object) { | |
2344 | for (var $key in object) { | |
2345 | this.add($key, object[$key]); | |
2346 | } | |
2347 | }, | |
2348 | ||
2349 | /** | |
2350 | * Adds a dictionary to current dataset. | |
2351 | * | |
2352 | * @param object dictionary | |
2353 | */ | |
2354 | addDictionary: function(dictionary) { | |
2355 | dictionary.each($.proxy(function(pair) { | |
2356 | this.add(pair.key, pair.value); | |
2357 | }, this)); | |
2358 | }, | |
2359 | ||
2360 | /** | |
2361 | * Retrieves the value of an entry or returns null if key is not found. | |
2362 | * | |
2363 | * @param string key | |
2364 | * @returns mixed | |
2365 | */ | |
2366 | get: function(key) { | |
2367 | if (this.isset(key)) { | |
264e3f79 | 2368 | return this._variables[key]; |
158bd3ca TD |
2369 | } |
2370 | ||
2371 | return null; | |
2372 | }, | |
2373 | ||
2374 | /** | |
2375 | * Returns true if given key is a valid entry. | |
2376 | * | |
2377 | * @param string key | |
2378 | */ | |
2379 | isset: function(key) { | |
264e3f79 | 2380 | return this._variables.hasOwnProperty(key); |
158bd3ca TD |
2381 | }, |
2382 | ||
2383 | /** | |
2384 | * Removes an entry. | |
2385 | * | |
2386 | * @param string key | |
2387 | */ | |
2388 | remove: function(key) { | |
264e3f79 | 2389 | delete this._variables[key]; |
158bd3ca TD |
2390 | }, |
2391 | ||
2392 | /** | |
2393 | * Iterates through dictionary. | |
2394 | * | |
2395 | * Usage: | |
2396 | * var $hash = new WCF.Dictionary(); | |
2397 | * $hash.add('foo', 'bar'); | |
2398 | * $hash.each(function(pair) { | |
2399 | * // alerts: foo = bar | |
2400 | * alert(pair.key + ' = ' + pair.value); | |
2401 | * }); | |
2402 | * | |
2403 | * @param function callback | |
2404 | */ | |
2405 | each: function(callback) { | |
2406 | if (!$.isFunction(callback)) { | |
2407 | return; | |
2408 | } | |
2409 | ||
264e3f79 AE |
2410 | for (var $key in this._variables) { |
2411 | var $value = this._variables[$key]; | |
158bd3ca TD |
2412 | var $pair = { |
2413 | key: $key, | |
2414 | value: $value | |
2415 | }; | |
2416 | ||
2417 | callback($pair); | |
2418 | } | |
264e3f79 AE |
2419 | }, |
2420 | ||
2421 | /** | |
2422 | * Returns the amount of items. | |
2423 | * | |
2424 | * @return integer | |
2425 | */ | |
2426 | count: function() { | |
2427 | return $.getLength(this._variables); | |
2428 | }, | |
2429 | ||
2430 | /** | |
28410a97 | 2431 | * Returns true if dictionary is empty. |
264e3f79 AE |
2432 | * |
2433 | * @return integer | |
2434 | */ | |
2435 | isEmpty: function() { | |
2436 | return !this.count(); | |
158bd3ca | 2437 | } |
39e27190 | 2438 | }); |
158bd3ca | 2439 | |
46badb6a TD |
2440 | // non strict equals by intent |
2441 | if (window.WCF.Language == null) { | |
2442 | /** | |
58d7e8f8 | 2443 | * @deprecated Use WoltLabSuite/Core/Language |
46badb6a TD |
2444 | */ |
2445 | WCF.Language = { | |
2446 | add: function(key, value) { | |
7eff88e3 | 2447 | require(['Language'], function(Language) { |
46badb6a TD |
2448 | Language.add(key, value); |
2449 | }); | |
2450 | }, | |
2451 | addObject: function(object) { | |
7eff88e3 | 2452 | require(['Language'], function(Language) { |
46badb6a TD |
2453 | Language.addObject(object); |
2454 | }); | |
2455 | }, | |
2456 | get: function(key, parameters) { | |
2457 | // This cannot be sanely provided as a compatibility wrapper. | |
2458 | throw new Error('Call to deprecated WCF.Language.get("' + key + '")'); | |
2459 | } | |
2460 | }; | |
2461 | } | |
f2e420cc | 2462 | |
52560d9e TD |
2463 | /** |
2464 | * Number utilities. | |
58d7e8f8 | 2465 | * @deprecated Use WoltLabSuite/Core/NumberUtil |
52560d9e TD |
2466 | */ |
2467 | WCF.Number = { | |
2468 | /** | |
7e969bc7 | 2469 | * Rounds a number to a given number of decimal places. Defaults to 0. |
52560d9e TD |
2470 | * |
2471 | * @param number number | |
7e969bc7 | 2472 | * @param decimalPlaces number of decimal places |
52560d9e TD |
2473 | * @return number |
2474 | */ | |
7e969bc7 TD |
2475 | round: function (number, decimalPlaces) { |
2476 | decimalPlaces = Math.pow(10, (decimalPlaces || 0)); | |
52560d9e | 2477 | |
7e969bc7 | 2478 | return Math.round(number * decimalPlaces) / decimalPlaces; |
52560d9e | 2479 | } |
f4126129 | 2480 | }; |
52560d9e | 2481 | |
158bd3ca TD |
2482 | /** |
2483 | * String utilities. | |
58d7e8f8 | 2484 | * @deprecated Use WoltLabSuite/Core/StringUtil |
158bd3ca TD |
2485 | */ |
2486 | WCF.String = { | |
52560d9e TD |
2487 | /** |
2488 | * Adds thousands separators to a given number. | |
2489 | * | |
2aebf1f5 | 2490 | * @see http://stackoverflow.com/a/6502556/782822 |
52560d9e TD |
2491 | * @param mixed number |
2492 | * @return string | |
2493 | */ | |
2494 | addThousandsSeparator: function(number) { | |
955082b0 | 2495 | return String(number).replace(/(^-?\d{1,3}|\d{3})(?=(?:\d{3})+(?:$|\.))/g, '$1' + WCF.Language.get('wcf.global.thousandsSeparator')); |
52560d9e TD |
2496 | }, |
2497 | ||
158bd3ca TD |
2498 | /** |
2499 | * Escapes special HTML-characters within a string | |
2500 | * | |
2501 | * @param string string | |
2502 | * @return string | |
2503 | */ | |
2504 | escapeHTML: function (string) { | |
bf3df436 | 2505 | return String(string).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>'); |
158bd3ca TD |
2506 | }, |
2507 | ||
2508 | /** | |
2509 | * Escapes a String to work with RegExp. | |
7b608580 | 2510 | * |
158bd3ca TD |
2511 | * @see https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/regexp.js#L25 |
2512 | * @param string string | |
2513 | * @return string | |
2514 | */ | |
2515 | escapeRegExp: function(string) { | |
bf3df436 | 2516 | return String(string).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); |
158bd3ca TD |
2517 | }, |
2518 | ||
2519 | /** | |
52560d9e | 2520 | * Rounds number to given count of floating point digits, localizes decimal-point and inserts thousands-separators |
158bd3ca | 2521 | * |
52560d9e | 2522 | * @param mixed number |
158bd3ca TD |
2523 | * @return string |
2524 | */ | |
7e969bc7 TD |
2525 | formatNumeric: function(number, decimalPlaces) { |
2526 | number = String(WCF.Number.round(number, decimalPlaces || 2)); | |
efdaa080 | 2527 | var numberParts = number.split('.'); |
eb9ae99c TD |
2528 | |
2529 | number = this.addThousandsSeparator(numberParts[0]); | |
2530 | if (numberParts.length > 1) number += WCF.Language.get('wcf.global.decimalPoint') + numberParts[1]; | |
52560d9e | 2531 | |
7e969bc7 TD |
2532 | number = number.replace('-', '\u2212'); |
2533 | ||
2534 | return number; | |
158bd3ca TD |
2535 | }, |
2536 | ||
7a17b105 MS |
2537 | /** |
2538 | * Makes a string's first character lowercase | |
2539 | * | |
2540 | * @param string string | |
2541 | * @return string | |
2542 | */ | |
2543 | lcfirst: function(string) { | |
bf3df436 | 2544 | return String(string).substring(0, 1).toLowerCase() + string.substring(1); |
7a17b105 MS |
2545 | }, |
2546 | ||
158bd3ca | 2547 | /** |
52560d9e | 2548 | * Makes a string's first character uppercase |
158bd3ca | 2549 | * |
52560d9e | 2550 | * @param string string |
158bd3ca TD |
2551 | * @return string |
2552 | */ | |
52560d9e | 2553 | ucfirst: function(string) { |
bf3df436 | 2554 | return String(string).substring(0, 1).toUpperCase() + string.substring(1); |
cc7db7fa AE |
2555 | }, |
2556 | ||
2557 | /** | |
2558 | * Unescapes special HTML-characters within a string | |
2559 | * | |
2560 | * @param string string | |
2561 | * @return string | |
2562 | */ | |
2563 | unescapeHTML: function (string) { | |
2564 | return String(string).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>'); | |
158bd3ca TD |
2565 | } |
2566 | }; | |
2567 | ||
2568 | /** | |
2569 | * Basic implementation for WCF TabMenus. Use the data attributes 'active' to specify the | |
2570 | * tab which should be shown on init. Furthermore you may specify a 'store' data-attribute | |
2571 | * which will be filled with the currently selected tab. | |
2572 | */ | |
2573 | WCF.TabMenu = { | |
2574 | /** | |
2575 | * Initializes all TabMenus | |
2576 | */ | |
2577 | init: function() { | |
58d7e8f8 | 2578 | require(['WoltLabSuite/Core/Ui/TabMenu'], function(UiTabMenu) { |
477b8397 | 2579 | UiTabMenu.setup(); |
4bbf6ff1 | 2580 | }); |
da804832 AE |
2581 | }, |
2582 | ||
1eda8a97 MS |
2583 | /** |
2584 | * Reloads the tab menus. | |
2585 | */ | |
2586 | reload: function() { | |
1eda8a97 | 2587 | this.init(); |
158bd3ca TD |
2588 | } |
2589 | }; | |
2590 | ||
2591 | /** | |
9f959ced MS |
2592 | * Templates that may be fetched more than once with different variables. |
2593 | * Based upon ideas from Prototype's template. | |
158bd3ca TD |
2594 | * |
2595 | * Usage: | |
2596 | * var myTemplate = new WCF.Template('{$hello} World'); | |
2597 | * myTemplate.fetch({ hello: 'Hi' }); // Hi World | |
2598 | * myTemplate.fetch({ hello: 'Hello' }); // Hello World | |
2599 | * | |
2600 | * my2ndTemplate = new WCF.Template('{@$html}{$html}'); | |
2601 | * my2ndTemplate.fetch({ html: '<b>Test</b>' }); // <b>Test</b><b>Test</b> | |
9f959ced | 2602 | * |
158bd3ca TD |
2603 | * var my3rdTemplate = new WCF.Template('You can use {literal}{$variable}{/literal}-Tags here'); |
2604 | * my3rdTemplate.fetch({ variable: 'Not shown' }); // You can use {$variable}-Tags here | |
2605 | * | |
158bd3ca TD |
2606 | * @param template template-content |
2607 | * @see https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/template.js | |
2608 | */ | |
39e27190 | 2609 | WCF.Template = Class.extend({ |
158bd3ca TD |
2610 | /** |
2611 | * Prepares template | |
2612 | * | |
2613 | * @param $template template-content | |
2614 | */ | |
955082b0 | 2615 | init: function(template) { |
35a7384e | 2616 | var $literals = new WCF.Dictionary(); |
66a11454 | 2617 | var $tagID = 0; |
35a7384e | 2618 | |
ec591c4f TD |
2619 | // escape \ and ' and newlines |
2620 | template = template.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/(\r\n|\n|\r)/g, '\\n'); | |
158bd3ca TD |
2621 | |
2622 | // save literal-tags | |
ec591c4f | 2623 | template = template.replace(/\{literal\}(.*?)\{\/literal\}/g, $.proxy(function(match) { |
158bd3ca TD |
2624 | // hopefully no one uses this string in one of his templates |
2625 | var id = '@@@@@@@@@@@'+Math.random()+'@@@@@@@@@@@'; | |
35a7384e | 2626 | $literals.add(id, match.replace(/\{\/?literal\}/g, '')); |
158bd3ca TD |
2627 | |
2628 | return id; | |
2629 | }, this)); | |
158bd3ca | 2630 | |
16872914 TD |
2631 | // remove comments |
2632 | template = template.replace(/\{\*.*?\*\}/g, ''); | |
2633 | ||
ec591c4f | 2634 | var parseParameterList = function(parameterString) { |
6c9ce157 TD |
2635 | var $chars = parameterString.split(''); |
2636 | var $parameters = { }; | |
2637 | var $inName = true; | |
2638 | var $name = ''; | |
2639 | var $value = ''; | |
a446eaa4 TD |
2640 | var $doubleQuoted = false; |
2641 | var $singleQuoted = false; | |
6c9ce157 | 2642 | var $escaped = false; |
ec591c4f | 2643 | |
6c9ce157 TD |
2644 | for (var $i = 0, $max = $chars.length; $i < $max; $i++) { |
2645 | var $char = $chars[$i]; | |
89be55b6 | 2646 | if ($inName && $char != '=' && $char != ' ') $name += $char; |
6c9ce157 TD |
2647 | else if ($inName && $char == '=') { |
2648 | $inName = false; | |
a446eaa4 TD |
2649 | $singleQuoted = false; |
2650 | $doubleQuoted = false; | |
6c9ce157 TD |
2651 | $escaped = false; |
2652 | } | |
a446eaa4 | 2653 | else if (!$inName && !$singleQuoted && !$doubleQuoted && $char == ' ') { |
6c9ce157 TD |
2654 | $inName = true; |
2655 | $parameters[$name] = $value; | |
2656 | $value = $name = ''; | |
2657 | } | |
a446eaa4 TD |
2658 | else if (!$inName && $singleQuoted && !$escaped && $char == "'") { |
2659 | $singleQuoted = false; | |
6c9ce157 TD |
2660 | $value += $char; |
2661 | } | |
a446eaa4 TD |
2662 | else if (!$inName && !$singleQuoted && !$doubleQuoted && $char == "'") { |
2663 | $singleQuoted = true; | |
6c9ce157 TD |
2664 | $value += $char; |
2665 | } | |
a446eaa4 TD |
2666 | else if (!$inName && $doubleQuoted && !$escaped && $char == '"') { |
2667 | $doubleQuoted = false; | |
2668 | $value += $char; | |
2669 | } | |
2670 | else if (!$inName && !$singleQuoted && !$doubleQuoted && $char == '"') { | |
2671 | $doubleQuoted = true; | |
2672 | $value += $char; | |
2673 | } | |
2674 | else if (!$inName && ($doubleQuoted || $singleQuoted) && !$escaped && $char == '\\') { | |
6c9ce157 TD |
2675 | $escaped = true; |
2676 | $value += $char; | |
2677 | } | |
2678 | else if (!$inName) { | |
2679 | $escaped = false; | |
2680 | $value += $char; | |
2681 | } | |
2682 | } | |
2683 | $parameters[$name] = $value; | |
2684 | ||
a446eaa4 | 2685 | if ($doubleQuoted || $singleQuoted || $escaped) throw new Error('Syntax error in parameterList: "' + parameterString + '"'); |
6c9ce157 TD |
2686 | |
2687 | return $parameters; | |
2688 | }; | |
2689 | ||
ec591c4f TD |
2690 | var unescape = function(string) { |
2691 | return string.replace(/\\n/g, "\n").replace(/\\\\/g, '\\').replace(/\\'/g, "'"); | |
2692 | }; | |
2693 | ||
4a107b66 | 2694 | template = template.replace(/\{(\$[^\}]+?)\}/g, function(_, content) { |
7e0fb8e4 | 2695 | content = unescape(content.replace(/\$([^.\[\(\)\]\s]+)/g, "(v['$1'])")); |
955082b0 | 2696 | |
35a7384e | 2697 | return "' + WCF.String.escapeHTML(" + content + ") + '"; |
ec591c4f TD |
2698 | }) |
2699 | // Numeric Variable | |
4a107b66 | 2700 | .replace(/\{#(\$[^\}]+?)\}/g, function(_, content) { |
7e0fb8e4 | 2701 | content = unescape(content.replace(/\$([^.\[\(\)\]\s]+)/g, "(v['$1'])")); |
955082b0 | 2702 | |
35a7384e | 2703 | return "' + WCF.String.formatNumeric(" + content + ") + '"; |
ec591c4f TD |
2704 | }) |
2705 | // Variable without escaping | |
4a107b66 | 2706 | .replace(/\{@(\$[^\}]+?)\}/g, function(_, content) { |
7e0fb8e4 | 2707 | content = unescape(content.replace(/\$([^.\[\(\)\]\s]+)/g, "(v['$1'])")); |
955082b0 | 2708 | |
35a7384e | 2709 | return "' + " + content + " + '"; |
35a7384e | 2710 | }) |
4650d4be | 2711 | // {lang}foo{/lang} |
084c1d0d | 2712 | .replace(/\{lang\}(.+?)\{\/lang\}/g, function(_, content) { |
b60882f1 | 2713 | return "' + WCF.Language.get('" + content + "', v) + '"; |
4650d4be | 2714 | }) |
faea55f2 TD |
2715 | // {include} |
2716 | .replace(/\{include (.+?)\}/g, function(_, content) { | |
2717 | content = content.replace(/\\\\/g, '\\').replace(/\\'/g, "'"); | |
2718 | var $parameters = parseParameterList(content); | |
2719 | ||
2720 | if (typeof $parameters['file'] === 'undefined') throw new Error('Missing file attribute in include-tag'); | |
2721 | ||
2722 | $parameters['file'] = $parameters['file'].replace(/\$([^.\[\(\)\]\s]+)/g, "(v.$1)"); | |
2723 | ||
2724 | return "' + " + $parameters['file'] + ".fetch(v) + '"; | |
2725 | }) | |
ec591c4f TD |
2726 | // {if} |
2727 | .replace(/\{if (.+?)\}/g, function(_, content) { | |
7e0fb8e4 | 2728 | content = unescape(content.replace(/\$([^.\[\(\)\]\s]+)/g, "(v['$1'])")); |
0f5aa615 | 2729 | |
ec591c4f TD |
2730 | return "';\n" + |
2731 | "if (" + content + ") {\n" + | |
2732 | " $output += '"; | |
35a7384e | 2733 | }) |
ec591c4f TD |
2734 | // {elseif} |
2735 | .replace(/\{else ?if (.+?)\}/g, function(_, content) { | |
7e0fb8e4 | 2736 | content = unescape(content.replace(/\$([^.\[\(\)\]\s]+)/g, "(v['$1'])")); |
ec591c4f TD |
2737 | |
2738 | return "';\n" + | |
2739 | "}\n" + | |
2740 | "else if (" + content + ") {\n" + | |
2741 | " $output += '"; | |
2742 | }) | |
2743 | // {implode} | |
2744 | .replace(/\{implode (.+?)\}/g, function(_, content) { | |
66a11454 | 2745 | $tagID++; |
6c9ce157 TD |
2746 | |
2747 | content = content.replace(/\\\\/g, '\\').replace(/\\'/g, "'"); | |
2748 | var $parameters = parseParameterList(content); | |
2749 | ||
2750 | if (typeof $parameters['from'] === 'undefined') throw new Error('Missing from attribute in implode-tag'); | |
2751 | if (typeof $parameters['item'] === 'undefined') throw new Error('Missing item attribute in implode-tag'); | |
2752 | if (typeof $parameters['glue'] === 'undefined') $parameters['glue'] = "', '"; | |
ec591c4f | 2753 | |
7e0fb8e4 | 2754 | $parameters['from'] = $parameters['from'].replace(/\$([^.\[\(\)\]\s]+)/g, "(v.$1)"); |
6c9ce157 | 2755 | |
ec591c4f TD |
2756 | return "';\n"+ |
2757 | "var $implode_" + $tagID + " = false;\n" + | |
2758 | "for ($implodeKey_" + $tagID + " in " + $parameters['from'] + ") {\n" + | |
2759 | " v[" + $parameters['item'] + "] = " + $parameters['from'] + "[$implodeKey_" + $tagID + "];\n" + | |
2760 | (typeof $parameters['key'] !== 'undefined' ? " v[" + $parameters['key'] + "] = $implodeKey_" + $tagID + ";\n" : "") + | |
2761 | " if ($implode_" + $tagID + ") $output += " + $parameters['glue'] + ";\n" + | |
2762 | " $implode_" + $tagID + " = true;\n" + | |
2763 | " $output += '"; | |
66a11454 | 2764 | }) |
ec591c4f TD |
2765 | // {foreach} |
2766 | .replace(/\{foreach (.+?)\}/g, function(_, content) { | |
66a11454 TD |
2767 | $tagID++; |
2768 | ||
2769 | content = content.replace(/\\\\/g, '\\').replace(/\\'/g, "'"); | |
2770 | var $parameters = parseParameterList(content); | |
2771 | ||
2772 | if (typeof $parameters['from'] === 'undefined') throw new Error('Missing from attribute in foreach-tag'); | |
2773 | if (typeof $parameters['item'] === 'undefined') throw new Error('Missing item attribute in foreach-tag'); | |
7e0fb8e4 | 2774 | $parameters['from'] = $parameters['from'].replace(/\$([^.\[\(\)\]\s]+)/g, "(v.$1)"); |
66a11454 | 2775 | |
ec591c4f TD |
2776 | return "';\n" + |
2777 | "$foreach_"+$tagID+" = false;\n" + | |
2778 | "for ($foreachKey_" + $tagID + " in " + $parameters['from'] + ") {\n" + | |
2779 | " $foreach_"+$tagID+" = true;\n" + | |
2780 | " break;\n" + | |
2781 | "}\n" + | |
2782 | "if ($foreach_"+$tagID+") {\n" + | |
2783 | " for ($foreachKey_" + $tagID + " in " + $parameters['from'] + ") {\n" + | |
2784 | " v[" + $parameters['item'] + "] = " + $parameters['from'] + "[$foreachKey_" + $tagID + "];\n" + | |
2785 | (typeof $parameters['key'] !== 'undefined' ? " v[" + $parameters['key'] + "] = $foreachKey_" + $tagID + ";\n" : "") + | |
2786 | " $output += '"; | |
6c9ce157 | 2787 | }) |
ec591c4f TD |
2788 | // {foreachelse} |
2789 | .replace(/\{foreachelse\}/g, | |
2790 | "';\n" + | |
2791 | " }\n" + | |
2792 | "}\n" + | |
2793 | "else {\n" + | |
2794 | " {\n" + | |
2795 | " $output += '" | |
2796 | ) | |
2797 | // {/foreach} | |
2798 | .replace(/\{\/foreach\}/g, | |
2799 | "';\n" + | |
2800 | " }\n" + | |
2801 | "}\n" + | |
2802 | "$output += '" | |
2803 | ) | |
2804 | // {else} | |
2805 | .replace(/\{else\}/g, | |
2806 | "';\n" + | |
2807 | "}\n" + | |
2808 | "else {\n" + | |
2809 | " $output += '" | |
2810 | ) | |
2811 | // {/if} and {/implode} | |
2812 | .replace(/\{\/(if|implode)\}/g, | |
2813 | "';\n" + | |
2814 | "}\n" + | |
2815 | "$output += '" | |
2816 | ); | |
2817 | ||
2818 | // call callback | |
71b79d8a TD |
2819 | for (var key in WCF.Template.callbacks) { |
2820 | template = WCF.Template.callbacks[key](template); | |
158bd3ca TD |
2821 | } |
2822 | ||
2823 | // insert delimiter tags | |
35a7384e | 2824 | template = template.replace('{ldelim}', '{').replace('{rdelim}', '}'); |
158bd3ca | 2825 | |
ec591c4f | 2826 | $literals.each(function(pair) { |
35a7384e | 2827 | template = template.replace(pair.key, pair.value); |
158bd3ca TD |
2828 | }); |
2829 | ||
35a7384e | 2830 | template = "$output += '" + template + "';"; |
4a107b66 | 2831 | |
a2067610 | 2832 | try { |
2d65b290 | 2833 | this.fetch = new Function("v", "v = window.$.extend({}, v, { __wcf: window.WCF, __window: window }); var $output = ''; " + template + ' return $output;'); |
a2067610 TD |
2834 | } |
2835 | catch (e) { | |
2836 | console.debug("var $output = ''; " + template + ' return $output;'); | |
2837 | throw e; | |
2838 | } | |
158bd3ca TD |
2839 | }, |
2840 | ||
2841 | /** | |
35a7384e | 2842 | * Fetches the template with the given variables. |
7b608580 | 2843 | * |
955082b0 TD |
2844 | * @param v variables to insert |
2845 | * @return parsed template | |
158bd3ca | 2846 | */ |
520d73f1 | 2847 | fetch: function(v) { |
35a7384e | 2848 | // this will be replaced in the init function |
158bd3ca | 2849 | } |
39e27190 | 2850 | }); |
158bd3ca TD |
2851 | |
2852 | /** | |
71b79d8a | 2853 | * Array of callbacks that will be called after parsing the included tags. Only applies to Templates compiled after the callback was added. |
158bd3ca | 2854 | * |
71b79d8a | 2855 | * @var array<Function> |
158bd3ca | 2856 | */ |
71b79d8a | 2857 | WCF.Template.callbacks = [ ]; |
158bd3ca TD |
2858 | |
2859 | /** | |
2860 | * Toggles options. | |
2861 | * | |
2862 | * @param string element | |
2863 | * @param array showItems | |
2864 | * @param array hideItems | |
14c5ff9e | 2865 | * @param function callback |
158bd3ca | 2866 | */ |
39e27190 | 2867 | WCF.ToggleOptions = Class.extend({ |
158bd3ca TD |
2868 | /** |
2869 | * target item | |
2870 | * | |
2871 | * @var jQuery | |
2872 | */ | |
2873 | _element: null, | |
2874 | ||
2875 | /** | |
2876 | * list of items to be shown | |
2877 | * | |
2878 | * @var array | |
2879 | */ | |
2880 | _showItems: [], | |
2881 | ||
2882 | /** | |
2883 | * list of items to be hidden | |
2884 | * | |
2885 | * @var array | |
2886 | */ | |
2887 | _hideItems: [], | |
14c5ff9e DR |
2888 | |
2889 | /** | |
2890 | * callback after options were toggled | |
2891 | * | |
2892 | * @var function | |
2893 | */ | |
2894 | _callback: null, | |
158bd3ca TD |
2895 | |
2896 | /** | |
2897 | * Initializes option toggle. | |
2898 | * | |
2899 | * @param string element | |
2900 | * @param array showItems | |
2901 | * @param array hideItems | |
14c5ff9e | 2902 | * @param function callback |
158bd3ca | 2903 | */ |
14c5ff9e | 2904 | init: function(element, showItems, hideItems, callback) { |
158bd3ca TD |
2905 | this._element = $('#' + element); |
2906 | this._showItems = showItems; | |
2907 | this._hideItems = hideItems; | |
14c5ff9e DR |
2908 | if (callback !== undefined) { |
2909 | this._callback = callback; | |
2910 | } | |
158bd3ca TD |
2911 | |
2912 | // bind event | |
2913 | this._element.click($.proxy(this._toggle, this)); | |
2914 | ||
2915 | // execute toggle on init | |
2916 | this._toggle(); | |
2917 | }, | |
2918 | ||
2919 | /** | |
2920 | * Toggles items. | |
2921 | */ | |
2922 | _toggle: function() { | |
1a50aae0 | 2923 | if (!this._element.prop('checked')) return; |
158bd3ca TD |
2924 | |
2925 | for (var $i = 0, $length = this._showItems.length; $i < $length; $i++) { | |
2926 | var $item = this._showItems[$i]; | |
2927 | ||
2928 | $('#' + $item).show(); | |
2929 | } | |
2930 | ||
2931 | for (var $i = 0, $length = this._hideItems.length; $i < $length; $i++) { | |
2932 | var $item = this._hideItems[$i]; | |
2933 | ||
2934 | $('#' + $item).hide(); | |
2935 | } | |
14c5ff9e DR |
2936 | |
2937 | if (this._callback !== null) { | |
e42f6447 | 2938 | this._callback(); |
14c5ff9e | 2939 | } |
158bd3ca | 2940 | } |
39e27190 | 2941 | }); |
158bd3ca | 2942 | |
1c746fe3 AE |
2943 | /** |
2944 | * Namespace for all kind of collapsible containers. | |
2945 | */ | |
2946 | WCF.Collapsible = {}; | |
2947 | ||
2948 | /** | |
2949 | * Simple implementation for collapsible content, neither does it | |
2950 | * store its state nor does it allow AJAX callbacks to fetch content. | |
2951 | */ | |
2952 | WCF.Collapsible.Simple = { | |
2953 | /** | |
2954 | * Initializes collapsibles. | |
2955 | */ | |
2956 | init: function() { | |
b29fbb53 | 2957 | $('.jsCollapsible').each($.proxy(function(index, button) { |
1c746fe3 AE |
2958 | this._initButton(button); |
2959 | }, this)); | |
2960 | }, | |
2961 | ||
2962 | /** | |
2963 | * Binds an event listener on all buttons triggering the collapsible. | |
2964 | * | |
2965 | * @param object button | |
2966 | */ | |
2967 | _initButton: function(button) { | |
2968 | var $button = $(button); | |
2969 | var $isOpen = $button.data('isOpen'); | |
2970 | ||
2971 | if (!$isOpen) { | |
2972 | // hide container on init | |
ec161ab2 | 2973 | $('#' + $button.data('collapsibleContainer')).hide(); |
1c746fe3 AE |
2974 | } |
2975 | ||
2976 | $button.click($.proxy(this._toggle, this)); | |
2977 | }, | |
2978 | ||
2979 | /** | |
2980 | * Toggles collapsible containers on click. | |
2981 | * | |
2982 | * @param object event | |
2983 | */ | |
2984 | _toggle: function(event) { | |
ec161ab2 | 2985 | var $button = $(event.currentTarget); |
1c746fe3 AE |
2986 | var $isOpen = $button.data('isOpen'); |
2987 | var $target = $('#' + $.wcfEscapeID($button.data('collapsibleContainer'))); | |
2988 | ||
2989 | if ($isOpen) { | |
2990 | $target.stop().wcfBlindOut('vertical', $.proxy(function() { | |
556973c1 | 2991 | this._toggleImage($button); |
1c746fe3 AE |
2992 | }, this)); |
2993 | $isOpen = false; | |
2994 | } | |
2995 | else { | |
2996 | $target.stop().wcfBlindIn('vertical', $.proxy(function() { | |
556973c1 | 2997 | this._toggleImage($button); |
1c746fe3 AE |
2998 | }, this)); |
2999 | $isOpen = true; | |
3000 | } | |
3001 | ||
3002 | $button.data('isOpen', $isOpen); | |
3003 | ||
3004 | // suppress event | |
3005 | event.stopPropagation(); | |
3006 | return false; | |
3007 | }, | |
3008 | ||
3009 | /** | |
3010 | * Toggles image of target button. | |
3011 | * | |
3012 | * @param jQuery button | |
1c746fe3 | 3013 | */ |
556973c1 MW |
3014 | _toggleImage: function(button) { |
3015 | var $icon = button.find('span.icon'); | |
3016 | if (button.data('isOpen')) { | |
ca8bfa53 | 3017 | $icon.removeClass('fa-chevron-right').addClass('fa-chevron-down'); |
556973c1 MW |
3018 | } |
3019 | else { | |
ca8bfa53 | 3020 | $icon.removeClass('fa-chevron-down').addClass('fa-chevron-right'); |
1c746fe3 | 3021 | } |
1c746fe3 AE |
3022 | } |
3023 | }; | |
3024 | ||
878d0d80 AE |
3025 | /** |
3026 | * Basic implementation for collapsible containers with AJAX support. Results for open | |
3027 | * and closed state will be cached. | |
9f959ced | 3028 | * |
878d0d80 AE |
3029 | * @param string className |
3030 | */ | |
3031 | WCF.Collapsible.Remote = Class.extend({ | |
3032 | /** | |
3033 | * class name | |
3034 | * @var string | |
3035 | */ | |
3036 | _className: '', | |
9f959ced | 3037 | |
878d0d80 AE |
3038 | /** |
3039 | * list of active containers | |
3040 | * @var object | |
3041 | */ | |
3042 | _containers: {}, | |
9f959ced | 3043 | |
878d0d80 AE |
3044 | /** |
3045 | * container meta data | |
3046 | * @var object | |
3047 | */ | |
3048 | _containerData: {}, | |
9f959ced | 3049 | |
878d0d80 AE |
3050 | /** |
3051 | * action proxy | |
3052 | * @var WCF.Action.Proxy | |
3053 | */ | |
3054 | _proxy: null, | |
9f959ced | 3055 | |
878d0d80 AE |
3056 | /** |
3057 | * Initializes the controller for collapsible containers with AJAX support. | |
3058 | * | |
3059 | * @param string className | |
3060 | */ | |
3061 | init: function(className) { | |
3062 | this._className = className; | |
878d0d80 AE |
3063 | this._proxy = new WCF.Action.Proxy({ |
3064 | success: $.proxy(this._success, this) | |
3065 | }); | |
3066 | ||
3067 | // initialize each container | |
0959ca1d AE |
3068 | this._init(); |
3069 | ||
3070 | WCF.DOMNodeInsertedHandler.addCallback('WCF.Collapsible.Remote', $.proxy(this._init, this)); | |
3071 | }, | |
3072 | ||
3073 | /** | |
3074 | * Initializes a collapsible container. | |
3075 | * | |
3076 | * @param string containerID | |
3077 | */ | |
3078 | _init: function(containerID) { | |
3079 | this._getContainers().each($.proxy(function(index, container) { | |
878d0d80 | 3080 | var $container = $(container); |
c4b3ae32 | 3081 | var $containerID = $container.wcfIdentify(); |
878d0d80 | 3082 | |
0959ca1d AE |
3083 | if (this._containers[$containerID] === undefined) { |
3084 | this._containers[$containerID] = $container; | |
3085 | ||
3086 | this._initContainer($containerID); | |
3087 | } | |
878d0d80 AE |
3088 | }, this)); |
3089 | }, | |
3090 | ||
c4b3ae32 AE |
3091 | /** |
3092 | * Initializes a collapsible container. | |
3093 | * | |
3094 | * @param string containerID | |
3095 | */ | |
8805f7bf | 3096 | _initContainer: function(containerID) { |
77c43423 MW |
3097 | var $target = this._getTarget(containerID); |
3098 | var $buttonContainer = this._getButtonContainer(containerID); | |
3099 | var $button = this._createButton(containerID, $buttonContainer); | |
3100 | ||
3101 | // store container meta data | |
3102 | this._containerData[containerID] = { | |
3103 | button: $button, | |
3104 | buttonContainer: $buttonContainer, | |
c4b3ae32 | 3105 | isOpen: this._containers[containerID].data('isOpen'), |
77c43423 MW |
3106 | target: $target |
3107 | }; | |
2851eadd MS |
3108 | |
3109 | // add 'jsCollapsed' CSS class | |
3110 | if (!this._containers[containerID].data('isOpen')) { | |
3111 | $('#' + containerID).addClass('jsCollapsed'); | |
3112 | } | |
77c43423 MW |
3113 | }, |
3114 | ||
878d0d80 AE |
3115 | /** |
3116 | * Returns a collection of collapsible containers. | |
3117 | * | |
3118 | * @return jQuery | |
3119 | */ | |
3120 | _getContainers: function() { }, | |
3121 | ||
3122 | /** | |
3123 | * Returns the target element for current collapsible container. | |
3124 | * | |
3125 | * @param integer containerID | |
3126 | * @return jQuery | |
3127 | */ | |
3128 | _getTarget: function(containerID) { }, | |
3129 | ||
3130 | /** | |
3131 | * Returns the button container for current collapsible container. | |
3132 | * | |
3133 | * @param integer containerID | |
3134 | * @return jQuery | |
3135 | */ | |
3136 | _getButtonContainer: function(containerID) { }, | |
3137 | ||
3138 | /** | |
3139 | * Creates the toggle button. | |
3140 | * | |
3141 | * @param integer containerID | |
3142 | * @param jQuery buttonContainer | |
3143 | */ | |
3144 | _createButton: function(containerID, buttonContainer) { | |
76cce184 AE |
3145 | var $button = elBySel('.jsStaticCollapsibleButton', buttonContainer[0]); |
3146 | if ($button !== null && $button.parentNode === buttonContainer[0]) { | |
3147 | $button.classList.remove('jsStaticCollapsibleButton'); | |
3148 | $button = $($button); | |
3149 | } | |
3150 | else { | |
3151 | $button = $('<span class="collapsibleButton jsTooltip pointer icon icon16 fa-chevron-down" title="' + WCF.Language.get('wcf.global.button.collapsible') + '">').prependTo(buttonContainer); | |
3152 | } | |
3153 | ||
878d0d80 | 3154 | $button.data('containerID', containerID).click($.proxy(this._toggleContainer, this)); |
9f959ced | 3155 | |
03812bbc | 3156 | return $button; |
878d0d80 AE |
3157 | }, |
3158 | ||
3159 | /** | |
3160 | * Toggles a container. | |
3161 | * | |
3162 | * @param object event | |
3163 | */ | |
3164 | _toggleContainer: function(event) { | |
b8a3ccb7 | 3165 | var $button = $(event.currentTarget); |
878d0d80 | 3166 | var $containerID = $button.data('containerID'); |
f4126129 | 3167 | var $isOpen = this._containerData[$containerID].isOpen; |
878d0d80 AE |
3168 | var $state = ($isOpen) ? 'open' : 'close'; |
3169 | var $newState = ($isOpen) ? 'close' : 'open'; | |
3170 | ||
878d0d80 AE |
3171 | // fetch content state via AJAX |
3172 | this._proxy.setOption('data', { | |
c4b3ae32 | 3173 | actionName: 'loadContainer', |
878d0d80 | 3174 | className: this._className, |
c05528d8 | 3175 | interfaceName: 'wcf\\data\\ILoadableContainerAction', |
c4b3ae32 | 3176 | objectIDs: [ this._getObjectID($containerID) ], |
88a85f47 | 3177 | parameters: $.extend(true, { |
878d0d80 AE |
3178 | containerID: $containerID, |
3179 | currentState: $state, | |
a083bb38 | 3180 | newState: $newState |
88a85f47 | 3181 | }, this._getAdditionalParameters($containerID)) |
878d0d80 AE |
3182 | }); |
3183 | this._proxy.sendRequest(); | |
9f959ced | 3184 | |
2851eadd MS |
3185 | // toogle 'jsCollapsed' CSS class |
3186 | $('#' + $containerID).toggleClass('jsCollapsed'); | |
3187 | ||
03812bbc | 3188 | // set spinner for current button |
556973c1 | 3189 | // this._exchangeIcon($button); |
03812bbc | 3190 | }, |
c4b3ae32 AE |
3191 | |
3192 | /** | |
3193 | * Exchanges button icon. | |
3194 | * | |
3195 | * @param jQuery button | |
3196 | * @param string newIcon | |
3197 | */ | |
3198 | _exchangeIcon: function(button, newIcon) { | |
556973c1 | 3199 | newIcon = newIcon || 'spinner'; |
ca8bfa53 | 3200 | button.removeClass('fa-chevron-down fa-chevron-right fa-spinner').addClass('fa-' + newIcon); |
878d0d80 AE |
3201 | }, |
3202 | ||
3203 | /** | |
3204 | * Returns the object id for current container. | |
3205 | * | |
3206 | * @param integer containerID | |
3207 | * @return integer | |
3208 | */ | |
eac3b734 MS |
3209 | _getObjectID: function(containerID) { |
3210 | return $('#' + containerID).data('objectID'); | |
3211 | }, | |
878d0d80 | 3212 | |
88a85f47 MW |
3213 | /** |
3214 | * Returns additional parameters. | |
3215 | * | |
3216 | * @param integer containerID | |
3217 | * @return object | |
3218 | */ | |
3219 | _getAdditionalParameters: function(containerID) { | |
3220 | return {}; | |
3221 | }, | |
3222 | ||
77c43423 MW |
3223 | /** |
3224 | * Updates container content. | |
3225 | * | |
3226 | * @param integer containerID | |
3227 | * @param string newContent | |
3228 | * @param string newState | |
3229 | */ | |
3230 | _updateContent: function(containerID, newContent, newState) { | |
3231 | this._containerData[containerID].target.html(newContent); | |
3232 | }, | |
3233 | ||
878d0d80 | 3234 | /** |
1615fc2e | 3235 | * Sets content upon successful AJAX request. |
878d0d80 AE |
3236 | * |
3237 | * @param object data | |
3238 | * @param string textStatus | |
3239 | * @param jQuery jqXHR | |
3240 | */ | |
3241 | _success: function(data, textStatus, jqXHR) { | |
3242 | // validate container id | |
3243 | if (!data.returnValues.containerID) return; | |
3244 | var $containerID = data.returnValues.containerID; | |
3245 | ||
3246 | // check if container id is known | |
3247 | if (!this._containers[$containerID]) return; | |
3248 | ||
3249 | // update content storage | |
3250 | this._containerData[$containerID].isOpen = (data.returnValues.isOpen) ? true : false; | |
9fd51bd2 | 3251 | var $newState = (data.returnValues.isOpen) ? 'open' : 'close'; |
878d0d80 AE |
3252 | |
3253 | // update container content | |
b418da85 | 3254 | this._updateContent($containerID, $.trim(data.returnValues.content), $newState); |
b8a3ccb7 MW |
3255 | |
3256 | // update icon | |
0f4fcdf1 | 3257 | //this._exchangeIcon(this._containerData[$containerID].button, (data.returnValues.isOpen ? 'chevron-down' : 'chevron-right')); |
c4b3ae32 AE |
3258 | } |
3259 | }); | |
3260 | ||
3261 | /** | |
3262 | * Basic implementation for collapsible containers with AJAX support. Requires collapsible | |
3263 | * content to be available in DOM already, if you want to load content on the fly use | |
3264 | * WCF.Collapsible.Remote instead. | |
3265 | */ | |
3266 | WCF.Collapsible.SimpleRemote = WCF.Collapsible.Remote.extend({ | |
3267 | /** | |
3268 | * Initializes an AJAX-based collapsible handler. | |
3269 | * | |
3270 | * @param string className | |
3271 | */ | |
3272 | init: function(className) { | |
3273 | this._super(className); | |
3274 | ||
3275 | // override settings for action proxy | |
3276 | this._proxy = new WCF.Action.Proxy({ | |
3277 | showLoadingOverlay: false | |
3278 | }); | |
3279 | }, | |
3280 | ||
f19a9976 AE |
3281 | /** |
3282 | * @see WCF.Collapsible.Remote._initContainer() | |
3283 | */ | |
3284 | _initContainer: function(containerID) { | |
3285 | this._super(containerID); | |
3286 | ||
3287 | // hide container on init if applicable | |
3288 | if (!this._containerData[containerID].isOpen) { | |
3289 | this._containerData[containerID].target.hide(); | |
143c5f6d | 3290 | this._exchangeIcon(this._containerData[containerID].button, 'chevron-right'); |
f19a9976 AE |
3291 | } |
3292 | }, | |
3293 | ||
c4b3ae32 AE |
3294 | /** |
3295 | * Toggles container visibility. | |
3296 | * | |
3297 | * @param object event | |
3298 | */ | |
3299 | _toggleContainer: function(event) { | |
3300 | var $button = $(event.currentTarget); | |
3301 | var $containerID = $button.data('containerID'); | |
3302 | var $isOpen = this._containerData[$containerID].isOpen; | |
3303 | var $currentState = ($isOpen) ? 'open' : 'close'; | |
3304 | var $newState = ($isOpen) ? 'close' : 'open'; | |
3305 | ||
3306 | this._proxy.setOption('data', { | |
3307 | actionName: 'toggleContainer', | |
3308 | className: this._className, | |
c05528d8 | 3309 | interfaceName: 'wcf\\data\\IToggleContainerAction', |
c4b3ae32 | 3310 | objectIDs: [ this._getObjectID($containerID) ], |
11c6026b | 3311 | parameters: $.extend(true, { |
c4b3ae32 AE |
3312 | containerID: $containerID, |
3313 | currentState: $currentState, | |
3314 | newState: $newState | |
11c6026b | 3315 | }, this._getAdditionalParameters($containerID)) |
c4b3ae32 AE |
3316 | }); |
3317 | this._proxy.sendRequest(); | |
3318 | ||
3319 | // exchange icon | |
143c5f6d | 3320 | this._exchangeIcon(this._containerData[$containerID].button, ($newState === 'open' ? 'chevron-down' : 'chevron-right')); |
6ea6d38b AE |
3321 | |
3322 | // toggle container | |
3323 | if ($newState === 'open') { | |
3324 | this._containerData[$containerID].target.show(); | |
3325 | } | |
3326 | else { | |
3327 | this._containerData[$containerID].target.hide(); | |
3328 | } | |
3329 | ||
2851eadd MS |
3330 | // toogle 'jsCollapsed' CSS class |
3331 | $('#' + $containerID).toggleClass('jsCollapsed'); | |
3332 | ||
6ea6d38b AE |
3333 | // update container data |
3334 | this._containerData[$containerID].isOpen = ($newState === 'open' ? true : false); | |
878d0d80 AE |
3335 | } |
3336 | }); | |
9abd2c89 | 3337 | |
fb969237 TD |
3338 | /** |
3339 | * Holds userdata of the current user | |
1ea26041 MS |
3340 | * |
3341 | * @deprecated use WCF/WoltLab/User | |
fb969237 TD |
3342 | */ |
3343 | WCF.User = { | |
3344 | /** | |
a99ebd63 | 3345 | * id of the active user |
fb969237 TD |
3346 | * @var integer |
3347 | */ | |
3348 | userID: 0, | |
3349 | ||
3350 | /** | |
9f959ced | 3351 | * name of the active user |
fb969237 TD |
3352 | * @var string |
3353 | */ | |
3354 | username: '', | |
3355 | ||
3356 | /** | |
3357 | * Initializes userdata | |
3358 | * | |
3359 | * @param integer userID | |
3360 | * @param string username | |
3361 | */ | |
3362 | init: function(userID, username) { | |
3363 | this.userID = userID; | |
3364 | this.username = username; | |
3365 | } | |
3366 | }; | |
3367 | ||
f84920f4 MW |
3368 | /** |
3369 | * Namespace for effect-related functions. | |
3370 | */ | |
3371 | WCF.Effect = {}; | |
3372 | ||
80e49fec AE |
3373 | /** |
3374 | * Scrolls to a specific element offset, optionally handling menu height. | |
3375 | */ | |
3376 | WCF.Effect.Scroll = Class.extend({ | |
3377 | /** | |
3378 | * Scrolls to a specific element offset. | |
3379 | * | |
3380 | * @param jQuery element | |
3381 | * @param boolean excludeMenuHeight | |
aa86d700 | 3382 | * @param boolean disableAnimation |
80e49fec AE |
3383 | * @return boolean |
3384 | */ | |
aa86d700 | 3385 | scrollTo: function(element, excludeMenuHeight, disableAnimation) { |
80e49fec AE |
3386 | if (!element.length) { |
3387 | return true; | |
3388 | } | |
3389 | ||
db316c8f | 3390 | var $elementOffset = element.getOffsets('offset').top; |
80e49fec AE |
3391 | var $documentHeight = $(document).height(); |
3392 | var $windowHeight = $(window).height(); | |
3393 | ||
80e49fec AE |
3394 | if ($elementOffset > $documentHeight - $windowHeight) { |
3395 | $elementOffset = $documentHeight - $windowHeight; | |
3396 | if ($elementOffset < 0) { | |
3397 | $elementOffset = 0; | |
3398 | } | |
3399 | } | |
3400 | ||
aa86d700 AE |
3401 | if (disableAnimation === true) { |
3402 | $('html,body').scrollTop($elementOffset); | |
3403 | } | |
3404 | else { | |
3405 | $('html,body').animate({ scrollTop: $elementOffset }, 400, function (x, t, b, c, d) { | |
3406 | return -c * ( ( t = t / d - 1 ) * t * t * t - 1) + b; | |
3407 | }); | |
3408 | } | |
80e49fec AE |
3409 | |
3410 | return false; | |
3411 | } | |
3412 | }); | |
3413 | ||
b3991cb3 AE |
3414 | /** |
3415 | * Handles clicks outside an overlay, hitting body-tag through bubbling. | |
3416 | * | |
3417 | * You should always remove callbacks before disposing the attached element, | |
3418 | * preventing errors from blocking the iteration. Furthermore you should | |
3419 | * always handle clicks on your overlay's container and return 'false' to | |
3420 | * prevent bubbling. | |
d788c437 | 3421 | * |
e71525e4 | 3422 | * @deprecated 3.0 - please use `Ui/CloseOverlay` instead |
b3991cb3 AE |
3423 | */ |
3424 | WCF.CloseOverlayHandler = { | |
b3991cb3 AE |
3425 | /** |
3426 | * Adds a new callback. | |
3427 | * | |
3428 | * @param string identifier | |
3429 | * @param object callback | |
3430 | */ | |
3431 | addCallback: function(identifier, callback) { | |
477b8397 MS |
3432 | require(['Ui/CloseOverlay'], function(UiCloseOverlay) { |
3433 | UiCloseOverlay.add(identifier, callback); | |
d788c437 | 3434 | }); |
b3991cb3 | 3435 | }, |
9f959ced | 3436 | |
b3991cb3 AE |
3437 | /** |
3438 | * Removes a callback from list. | |
3439 | * | |
3440 | * @param string identifier | |
3441 | */ | |
3442 | removeCallback: function(identifier) { | |
477b8397 MS |
3443 | require(['Ui/CloseOverlay'], function(UiCloseOverlay) { |
3444 | UiCloseOverlay.remove(identifier); | |
d788c437 | 3445 | }); |
b3991cb3 | 3446 | }, |
9f959ced | 3447 | |
d12b7e5c AE |
3448 | /** |
3449 | * Triggers the callbacks programmatically. | |
3450 | */ | |
3451 | forceExecution: function() { | |
477b8397 MS |
3452 | require(['Ui/CloseOverlay'], function(UiCloseOverlay) { |
3453 | UiCloseOverlay.execute(); | |
b3991cb3 AE |
3454 | }); |
3455 | } | |
3456 | }; | |
3457 | ||
9c1e5045 | 3458 | /** |
58d7e8f8 | 3459 | * @deprecated Use WoltLabSuite/Core/Dom/Change/Listener |
9c1e5045 AE |
3460 | */ |
3461 | WCF.DOMNodeInsertedHandler = { | |
9c1e5045 | 3462 | addCallback: function(identifier, callback) { |
58d7e8f8 | 3463 | require(['WoltLabSuite/Core/Dom/Change/Listener'], function (ChangeListener) { |
8fab2bd2 TD |
3464 | ChangeListener.add('__legacy__', callback); |
3465 | }); | |
9c1e5045 | 3466 | }, |
d371330f | 3467 | _executeCallbacks: function() { |
58d7e8f8 | 3468 | require(['WoltLabSuite/Core/Dom/Change/Listener'], function (ChangeListener) { |
8fab2bd2 TD |
3469 | ChangeListener.trigger(); |
3470 | }); | |
597e2774 | 3471 | }, |
42d7d2cc | 3472 | execute: function() { |
d371330f | 3473 | this._executeCallbacks(); |
9c1e5045 AE |
3474 | } |
3475 | }; | |
3476 | ||
809e4170 MS |
3477 | /** |
3478 | * Notifies objects once a DOM node was removed. | |
3479 | */ | |
3480 | WCF.DOMNodeRemovedHandler = { | |
3481 | /** | |
3482 | * list of callbacks | |
3483 | * @var WCF.Dictionary | |
3484 | */ | |
3485 | _callbacks: new WCF.Dictionary(), | |
9f959ced | 3486 | |
809e4170 MS |
3487 | /** |
3488 | * prevent infinite loop if a callback manipulates DOM | |
3489 | * @var boolean | |
3490 | */ | |
3491 | _isExecuting: false, | |
9f959ced | 3492 | |
809e4170 MS |
3493 | /** |
3494 | * indicates that overlay handler is listening to DOMNodeRemoved events on body-tag | |
3495 | * @var boolean | |
3496 | */ | |
3497 | _isListening: false, | |
9f959ced | 3498 | |
809e4170 MS |
3499 | /** |
3500 | * Adds a new callback. | |
3501 | * | |
3502 | * @param string identifier | |
3503 | * @param object callback | |
3504 | */ | |
3505 | addCallback: function(identifier, callback) { | |
3506 | this._bindListener(); | |
9f959ced | 3507 | |
809e4170 MS |
3508 | if (this._callbacks.isset(identifier)) { |
3509 | console.debug("[WCF.DOMNodeRemovedHandler] identifier '" + identifier + "' is already bound to a callback"); | |
3510 | return false; | |
3511 | } | |
9f959ced | 3512 | |
809e4170 MS |
3513 | this._callbacks.add(identifier, callback); |
3514 | }, | |
9f959ced | 3515 | |
809e4170 MS |
3516 | /** |
3517 | * Removes a callback from list. | |
3518 | * | |
3519 | * @param string identifier | |
3520 | */ | |
3521 | removeCallback: function(identifier) { | |
3522 | if (this._callbacks.isset(identifier)) { | |
3523 | this._callbacks.remove(identifier); | |
3524 | } | |
3525 | }, | |
9f959ced | 3526 | |
809e4170 MS |
3527 | /** |
3528 | * Binds click event handler. | |
3529 | */ | |
3530 | _bindListener: function() { | |
3531 | if (this._isListening) return; | |
9f959ced | 3532 | |
f934cc57 AE |
3533 | if (window.MutationObserver) { |
3534 | var $mutationObserver = new MutationObserver((function(mutations) { | |
3535 | var $triggerEvent = false; | |
3536 | ||
3537 | mutations.forEach((function(mutation) { | |
3538 | if (mutation.removedNodes.length) { | |
3539 | $triggerEvent = true; | |
3540 | } | |
3541 | }).bind(this)); | |
3542 | ||
3543 | if ($triggerEvent) { | |
3544 | this._executeCallbacks({ }); | |
3545 | } | |
3546 | }).bind(this)); | |
3547 | ||
3548 | $mutationObserver.observe(document.body, { | |
3549 | childList: true, | |
3550 | subtree: true | |
3551 | }); | |
3552 | } | |
3553 | else { | |
3554 | $(document).bind('DOMNodeRemoved', $.proxy(this._executeCallbacks, this)); | |
3555 | } | |
9f959ced | 3556 | |
809e4170 MS |
3557 | this._isListening = true; |
3558 | }, | |
9f959ced | 3559 | |
809e4170 MS |
3560 | /** |
3561 | * Executes callbacks if a DOM node is removed. | |
3562 | */ | |
3563 | _executeCallbacks: function(event) { | |
3564 | if (this._isExecuting) return; | |
9f959ced | 3565 | |
809e4170 MS |
3566 | // do not track events while executing callbacks |
3567 | this._isExecuting = true; | |
3568 | ||
3569 | this._callbacks.each(function(pair) { | |
3570 | // execute callback | |
3571 | pair.value(event); | |
3572 | }); | |
3573 | ||
3574 | // enable listener again | |
3575 | this._isExecuting = false; | |
3576 | } | |
3577 | }; | |
3578 | ||
0adad627 AE |
3579 | /** |
3580 | * Namespace for option handlers. | |
3581 | */ | |
3582 | WCF.Option = { }; | |
3583 | ||
f5e3a61b | 3584 | if (COMPILER_TARGET_DEFAULT) { |
0adad627 | 3585 | /** |
f5e3a61b | 3586 | * Handles option selection. |
0adad627 | 3587 | */ |
f5e3a61b AE |
3588 | WCF.Option.Handler = Class.extend({ |
3589 | /** | |
3590 | * Initializes the WCF.Option.Handler class. | |
3591 | */ | |
3592 | init: function () { | |
3593 | this._initOptions(); | |
3594 | ||
3595 | WCF.DOMNodeInsertedHandler.addCallback('WCF.Option.Handler', $.proxy(this._initOptions, this)); | |
3596 | }, | |
0adad627 | 3597 | |
f5e3a61b AE |
3598 | /** |
3599 | * Initializes all options. | |
3600 | */ | |
3601 | _initOptions: function () { | |
3602 | $('.jsEnablesOptions').each($.proxy(this._initOption, this)); | |
3603 | }, | |
0adad627 | 3604 | |
f5e3a61b AE |
3605 | /** |
3606 | * Initializes an option. | |
3607 | * | |
3608 | * @param integer index | |
3609 | * @param object option | |
3610 | */ | |
3611 | _initOption: function (index, option) { | |
3612 | // execute action on init | |
3613 | this._change(option); | |
3614 | ||
3615 | // bind event listener | |
3616 | $(option).change($.proxy(this._handleChange, this)); | |
3617 | }, | |
0adad627 | 3618 | |
f5e3a61b AE |
3619 | /** |
3620 | * Applies whenever an option is changed. | |
3621 | * | |
3622 | * @param object event | |
3623 | */ | |
3624 | _handleChange: function (event) { | |
3625 | this._change($(event.target)); | |
3626 | }, | |
0adad627 | 3627 | |
f5e3a61b AE |
3628 | /** |
3629 | * Enables or disables options on option value change. | |
3630 | * | |
3631 | * @param object option | |
3632 | */ | |
3633 | _change: function (option) { | |
3634 | option = $(option); | |
3635 | ||
3636 | var disableOptions = eval(option.data('disableOptions')); | |
3637 | var enableOptions = eval(option.data('enableOptions')); | |
3638 | ||
3639 | // determine action by type | |
3640 | switch (option.getTagName()) { | |
3641 | case 'input': | |
3642 | switch (option.attr('type')) { | |
3643 | case 'checkbox': | |
3644 | this._execute(option.prop('checked'), disableOptions, enableOptions); | |
3645 | break; | |
3646 | ||
3647 | case 'radio': | |
3648 | if (option.prop('checked')) { | |
3649 | var isActive = true; | |
3650 | if (option.data('isBoolean') && option.val() != 1) { | |
3651 | isActive = false; | |
3652 | } | |
3653 | ||
3654 | this._execute(isActive, disableOptions, enableOptions); | |
3655 | } | |
3656 | break; | |
3657 | } | |
0adad627 | 3658 | break; |
f5e3a61b AE |
3659 | |
3660 | case 'select': | |
3661 | var $value = option.val(); | |
3662 | var relevantDisableOptions = []; | |
3663 | var relevantEnableOptions = []; | |
0adad627 | 3664 | |
f5e3a61b AE |
3665 | if (disableOptions.length > 0) { |
3666 | for (var $index in disableOptions) { | |
3667 | var $item = disableOptions[$index]; | |
3668 | ||
3669 | if ($item.value == $value) { | |
3670 | relevantDisableOptions.push($item.option); | |
04488ea8 | 3671 | } |
f5e3a61b AE |
3672 | else { |
3673 | relevantEnableOptions.push($item.option); | |
3674 | } | |
3675 | } | |
3676 | } | |
3677 | ||
3678 | if (enableOptions.length > 0) { | |
3679 | for (var $index in enableOptions) { | |
3680 | var $item = enableOptions[$index]; | |
04488ea8 | 3681 | |
f5e3a61b AE |
3682 | if ($item.value == $value) { |
3683 | relevantEnableOptions.push($item.option); | |
3684 | } | |
3685 | else { | |
3686 | relevantDisableOptions.push($item.option); | |
3687 | } | |
0adad627 | 3688 | } |
f5e3a61b AE |
3689 | } |
3690 | ||
3691 | this._execute(true, relevantDisableOptions, relevantEnableOptions); | |
0adad627 | 3692 | break; |
f5e3a61b AE |
3693 | } |
3694 | }, | |
3695 | ||
3696 | /** | |
3697 | * Enables or disables options. | |
3698 | * | |
3699 | * @param boolean isActive | |
3700 | * @param array disableOptions | |
3701 | * @param array enableOptions | |
3702 | */ | |
3703 | _execute: function (isActive, disableOptions, enableOptions) { | |
3704 | if (disableOptions.length > 0) { | |
3705 | for (var $i = 0, $size = disableOptions.length; $i < $size; $i++) { | |
3706 | var $target = disableOptions[$i]; | |
3707 | if ($.wcfIsset($target)) { | |
3708 | this._enableOption($target, !isActive); | |
3709 | } | |
3710 | else { | |
3711 | var $dl = $('.' + $target + 'Input'); | |
3712 | if ($dl.length) { | |
3713 | this._enableOptions($dl.children('dd').find('input, select, textarea'), !isActive); | |
0adad627 AE |
3714 | } |
3715 | } | |
3716 | } | |
f5e3a61b AE |
3717 | } |
3718 | ||
3719 | if (enableOptions.length > 0) { | |
3720 | for (var $i = 0, $size = enableOptions.length; $i < $size; $i++) { | |
3721 | var $target = enableOptions[$i]; | |
3722 | if ($.wcfIsset($target)) { | |
3723 | this._enableOption($target, isActive); | |
3724 | } | |
3725 | else { | |
3726 | var $dl = $('.' + $target + 'Input'); | |
3727 | if ($dl.length) { | |
3728 | this._enableOptions($dl.children('dd').find('input, select, textarea'), isActive); | |
0adad627 AE |
3729 | } |
3730 | } | |
3731 | } | |
f5e3a61b AE |
3732 | } |
3733 | }, | |
3734 | ||
3735 | /** | |
3736 | * Enables/Disables an option. | |
3737 | * | |
3738 | * @param string target | |
3739 | * @param boolean enable | |
3740 | */ | |
3741 | _enableOption: function (target, enable) { | |
3742 | this._enableOptionElement($('#' + $.wcfEscapeID(target)), enable); | |
3743 | }, | |
3744 | ||
3745 | /** | |
3746 | * Enables/Disables an option element. | |
3747 | * | |
3748 | * @param string target | |
3749 | * @param boolean enable | |
3750 | */ | |
3751 | _enableOptionElement: function (element, enable) { | |
3752 | element = $(element); | |
3753 | var $tagName = element.getTagName(); | |
3754 | ||
3755 | if ($tagName == 'select' || ($tagName == 'input' && (element.attr('type') == 'checkbox' || element.attr('type') == 'file' || element.attr('type') == 'radio'))) { | |
8ae41018 AE |
3756 | if ($tagName === 'input' && element[0].type === 'radio') { |
3757 | if (!element[0].checked) { | |
3758 | if (enable) element.enable(); | |
3759 | else element.disable(); | |
3760 | } | |
3761 | else { | |
3762 | // Skip active radio buttons, this preserves the value on submit, | |
3763 | // while the user is still unable to move the selection to the other, | |
3764 | // now disabled options. | |
3765 | } | |
3766 | } | |
3767 | else { | |
3768 | if (enable) element.enable(); | |
3769 | else element.disable(); | |
3770 | } | |
0adad627 | 3771 | |
f5e3a61b | 3772 | if (element.parents('.optionTypeBoolean:eq(0)')) { |
c98ebfb3 MS |
3773 | // escape dots so that they are not recognized as class selectors |
3774 | var elementId = element.wcfIdentify().replace(/\./g, "\\."); | |
3775 | ||
3776 | var noElement = $('#' + elementId + '_no'); | |
f5e3a61b AE |
3777 | if (enable) noElement.enable(); |
3778 | else noElement.disable(); | |
c98ebfb3 MS |
3779 | |
3780 | var neverElement = $('#' + elementId + '_never'); | |
3781 | if (neverElement.length) { | |
3782 | if (enable) neverElement.enable(); | |
3783 | else neverElement.disable(); | |
3784 | } | |
cab5eb24 | 3785 | } |
0adad627 | 3786 | } |
f5e3a61b AE |
3787 | else { |
3788 | if (enable) element.removeAttr('readonly'); | |
3789 | else element.attr('readonly', true); | |
3790 | } | |
3791 | ||
3792 | if (enable) { | |
3793 | element.closest('dl').removeClass('disabled'); | |
3794 | } | |
3795 | else { | |
3796 | element.closest('dl').addClass('disabled'); | |
3797 | } | |
3798 | }, | |
0adad627 | 3799 | |
f5e3a61b AE |
3800 | /** |
3801 | * Enables/Disables an option consisting of multiple form elements. | |
3802 | * | |
3803 | * @param string target | |
3804 | * @param boolean enable | |
3805 | */ | |
3806 | _enableOptions: function (targets, enable) { | |
3807 | for (var $i = 0, $length = targets.length; $i < $length; $i++) { | |
3808 | this._enableOptionElement(targets[$i], enable); | |
0adad627 AE |
3809 | } |
3810 | } | |
f5e3a61b AE |
3811 | }); |
3812 | } | |
3813 | else { | |
3814 | WCF.Option.Handler = Class.extend({ | |
3815 | init: function() {}, | |
3816 | _initOptions: function() {}, | |
3817 | _initOption: function() {}, | |
3818 | _handleChange: function() {}, | |
3819 | _change: function() {}, | |
3820 | _execute: function() {}, | |
3821 | _enableOption: function() {}, | |
3822 | _enableOptionElement: function() {}, | |
3823 | _enableOptions: function() {} | |
3824 | }); | |
3825 | } | |
3826 | ||
3827 | WCF.PageVisibilityHandler = { | |
0adad627 | 3828 | /** |
f5e3a61b AE |
3829 | * list of callbacks |
3830 | * @var WCF.Dictionary | |
0adad627 | 3831 | */ |
f5e3a61b | 3832 | _callbacks: new WCF.Dictionary(), |
cab5eb24 MS |
3833 | |
3834 | /** | |
f5e3a61b AE |
3835 | * indicates that event listeners are bound |
3836 | * @var boolean | |
03fcd560 AE |
3837 | */ |
3838 | _isListening: false, | |
3839 | ||
3840 | /** | |
3841 | * name of window's hidden property | |
3842 | * @var string | |
3843 | */ | |
3844 | _hiddenFieldName: '', | |
3845 | ||
3846 | /** | |
3847 | * Adds a new callback. | |
3848 | * | |
3849 | * @param string identifier | |
3850 | * @param object callback | |
3851 | */ | |
3852 | addCallback: function(identifier, callback) { | |
3853 | this._bindListener(); | |
3854 | ||
3855 | if (this._callbacks.isset(identifier)) { | |
3856 | console.debug("[WCF.PageVisibilityHandler] identifier '" + identifier + "' is already bound to a callback"); | |
3857 | return false; | |
3858 | } | |
3859 | ||
3860 | this._callbacks.add(identifier, callback); | |
3861 | }, | |
3862 | ||
3863 | /** | |
3864 | * Removes a callback from list. | |
3865 | * | |
3866 | * @param string identifier | |
3867 | */ | |
3868 | removeCallback: function(identifier) { | |
3869 | if (this._callbacks.isset(identifier)) { | |
3870 | this._callbacks.remove(identifier); | |
3871 | } | |
3872 | }, | |
3873 | ||
3874 | /** | |
3875 | * Binds click event handler. | |
3876 | */ | |
3877 | _bindListener: function() { | |
3878 | if (this._isListening) return; | |
3879 | ||
3880 | var $eventName = null; | |
3881 | if (typeof document.hidden !== "undefined") { | |
3882 | this._hiddenFieldName = "hidden"; | |
3883 | $eventName = "visibilitychange"; | |
3884 | } | |
3885 | else if (typeof document.mozHidden !== "undefined") { | |
3886 | this._hiddenFieldName = "mozHidden"; | |
3887 | $eventName = "mozvisibilitychange"; | |
3888 | } | |
3889 | else if (typeof document.msHidden !== "undefined") { | |
3890 | this._hiddenFieldName = "msHidden"; | |
3891 | $eventName = "msvisibilitychange"; | |
3892 | } | |
3893 | else if (typeof document.webkitHidden !== "undefined") { | |
3894 | this._hiddenFieldName = "webkitHidden"; | |
3895 | $eventName = "webkitvisibilitychange"; | |
3896 | } | |
3897 | ||
3898 | if ($eventName === null) { | |
3899 | console.debug("[WCF.PageVisibilityHandler] This browser does not support the page visibility API."); | |
3900 | } | |
3901 | else { | |
3902 | $(document).on($eventName, $.proxy(this._executeCallbacks, this)); | |
3903 | } | |
3904 | ||
3905 | this._isListening = true; | |
3906 | }, | |
3907 | ||
3908 | /** | |
3909 | * Executes callbacks if page is hidden/visible again. | |
3910 | */ | |
3911 | _executeCallbacks: function(event) { | |
3912 | if (this._isExecuting) return; | |
3913 | ||
3914 | // do not track events while executing callbacks | |
3915 | this._isExecuting = true; | |
3916 | ||
3917 | var $state = document[this._hiddenFieldName]; | |
3918 | this._callbacks.each(function(pair) { | |
3919 | // execute callback | |
3920 | pair.value($state); | |
3921 | }); | |
3922 | ||
3923 | // enable listener again | |
3924 | this._isExecuting = false; | |
3925 | } | |
3926 | }; | |
3927 | ||
809e4170 MS |
3928 | /** |
3929 | * Namespace for table related classes. | |
3930 | */ | |
dc6755d0 | 3931 | WCF.Table = { }; |
809e4170 MS |
3932 | |
3933 | /** | |
3934 | * Handles empty tables which can be used in combination with WCF.Action.Proxy. | |
3935 | */ | |
3936 | WCF.Table.EmptyTableHandler = Class.extend({ | |
3937 | /** | |
3938 | * handler options | |
3939 | * @var object | |
3940 | */ | |
3941 | _options: {}, | |
3942 | ||
3943 | /** | |
3944 | * class name of the relevant rows | |
3945 | * @var string | |
3946 | */ | |
3947 | _rowClassName: '', | |
3948 | ||
3949 | /** | |
1615fc2e | 3950 | * Initializes a new WCF.Table.EmptyTableHandler object. |
809e4170 | 3951 | * |
809e4170 | 3952 | * @param jQuery tableContainer |
e0ee2eff | 3953 | * @param string rowClassName |
809e4170 MS |
3954 | * @param object options |
3955 | */ | |
e0ee2eff | 3956 | init: function(tableContainer, rowClassName, options) { |
809e4170 MS |
3957 | this._rowClassName = rowClassName; |
3958 | this._tableContainer = tableContainer; | |
3959 | ||
3960 | this._options = $.extend(true, { | |
3961 | emptyMessage: null, | |
c0bd9c8a | 3962 | messageType: 'info', |
809e4170 | 3963 | refreshPage: false, |
55b070f6 AE |
3964 | updatePageNumber: false, |
3965 | isTable: (this._tableContainer.find('table').length !== 0) | |
809e4170 MS |
3966 | }, options || { }); |
3967 | ||
3968 | WCF.DOMNodeRemovedHandler.addCallback('WCF.Table.EmptyTableHandler.' + rowClassName, $.proxy(this._remove, this)); | |
3969 | }, | |
3970 | ||
3971 | /** | |
dc6755d0 MS |
3972 | * Returns the current number of table rows. |
3973 | * | |
3974 | * @return integer | |
809e4170 | 3975 | */ |
dc6755d0 | 3976 | _getRowCount: function() { |
55b070f6 | 3977 | return this._tableContainer.find((this._options.isTable ? 'table tr.' : '.tabularList .') + this._rowClassName).length; |
dc6755d0 MS |
3978 | }, |
3979 | ||
3980 | /** | |
3981 | * Handles an empty table. | |
3982 | */ | |
3983 | _handleEmptyTable: function() { | |
3984 | if (this._options.emptyMessage) { | |
3985 | // insert message | |
3986 | this._tableContainer.replaceWith($('<p />').addClass(this._options.messageType).text(this._options.emptyMessage)); | |
3987 | } | |
3988 | else if (this._options.refreshPage) { | |
3989 | // refresh page | |
3990 | if (this._options.updatePageNumber) { | |
3991 | // calculate the new page number | |
3992 | var pageNumberURLComponents = window.location.href.match(/(\?|&)pageNo=(\d+)/g); | |
3993 | if (pageNumberURLComponents) { | |
3994 | var currentPageNumber = pageNumberURLComponents[pageNumberURLComponents.length - 1].match(/\d+/g); | |
3995 | if (this._options.updatePageNumber > 0) { | |
3996 | currentPageNumber++; | |
809e4170 MS |
3997 | } |
3998 | else { | |
dc6755d0 | 3999 | currentPageNumber--; |
809e4170 | 4000 | } |
dc6755d0 MS |
4001 | |
4002 | window.location = window.location.href.replace(pageNumberURLComponents[pageNumberURLComponents.length - 1], pageNumberURLComponents[pageNumberURLComponents.length - 1][0] + 'pageNo=' + currentPageNumber); | |
809e4170 | 4003 | } |
dc6755d0 MS |
4004 | } |
4005 | else { | |
4006 | window.location.reload(); | |
4007 | } | |
4008 | } | |
4009 | else { | |
4010 | // simply remove the table container | |
4011 | this._tableContainer.remove(); | |
4012 | } | |
4013 | }, | |
4014 | ||
4015 | /** | |
4016 | * Handles the removal of a DOM node. | |
4017 | */ | |
4018 | _remove: function(event) { | |
4019 | if ($.getLength(event)) { | |
4020 | var element = $(event.target); | |
4021 | ||
4022 | // check if DOM element is relevant | |
4023 | if (element.hasClass(this._rowClassName)) { | |
55b070f6 AE |
4024 | if (this._options.isTable) { |
4025 | var tbody = element.parents('tbody:eq(0)'); | |
4026 | ||
4027 | // check if table will be empty if DOM node is removed | |
4028 | if (tbody.children('tr').length == 1) { | |
4029 | this._handleEmptyTable(); | |
4030 | } | |
4031 | } | |
4032 | else if (this._getRowCount() === 1) { | |
dc6755d0 | 4033 | this._handleEmptyTable(); |
809e4170 MS |
4034 | } |
4035 | } | |
4036 | } | |
dc6755d0 MS |
4037 | else if (!this._getRowCount()) { |
4038 | this._handleEmptyTable(); | |
4039 | } | |
809e4170 MS |
4040 | } |
4041 | }); | |
4042 | ||
046e1292 AE |
4043 | /** |
4044 | * Namespace for search related classes. | |
4045 | */ | |
4046 | WCF.Search = {}; | |
4047 | ||
4048 | /** | |
4ad00d80 | 4049 | * Performs a quick search. |
aa2ddaf4 | 4050 | * |
58d7e8f8 | 4051 | * @deprecated 3.0 - please use `WoltLabSuite/Core/Ui/Search/Input` instead |
046e1292 | 4052 | */ |
4ad00d80 | 4053 | WCF.Search.Base = Class.extend({ |
046e1292 AE |
4054 | /** |
4055 | * notification callback | |
4056 | * @var object | |
4057 | */ | |
4058 | _callback: null, | |
4ad00d80 | 4059 | |
187ebf27 AE |
4060 | /** |
4061 | * represents index of search string (relative to original caret position) | |
4062 | * @var integer | |
4063 | */ | |
4064 | _caretAt: -1, | |
4065 | ||
4d10750a AE |
4066 | /** |
4067 | * class name | |
4068 | * @var string | |
4069 | */ | |
4ad00d80 | 4070 | _className: '', |
c000b08a | 4071 | |
cf55c379 | 4072 | /** |
1615fc2e | 4073 | * comma separated list |
cf55c379 AE |
4074 | * @var boolean |
4075 | */ | |
4076 | _commaSeperated: false, | |
4077 | ||
44f8f78b | 4078 | /** |
1615fc2e | 4079 | * delay in milliseconds before a request is send to the server |
44f8f78b AE |
4080 | * @var integer |
4081 | */ | |
4082 | _delay: 0, | |
4083 | ||
c000b08a | 4084 | /** |
1615fc2e | 4085 | * list with values that are excluded from searching |
c000b08a MS |
4086 | * @var array |
4087 | */ | |
4088 | _excludedSearchValues: [], | |
cf55c379 | 4089 | |
9a130ab4 AE |
4090 | /** |
4091 | * count of available results | |
4092 | * @var integer | |
4093 | */ | |
4094 | _itemCount: 0, | |
4095 | ||
4096 | /** | |
4097 | * item index, -1 if none is selected | |
4098 | * @var integer | |
4099 | */ | |
4100 | _itemIndex: -1, | |
4101 | ||
046e1292 AE |
4102 | /** |
4103 | * result list | |
4104 | * @var jQuery | |
4105 | */ | |
4106 | _list: null, | |
cf55c379 AE |
4107 | |
4108 | /** | |
4109 | * old search string, used for comparison | |
4110 | * @var array<string> | |
4111 | */ | |
4112 | _oldSearchString: [ ], | |
4113 | ||
046e1292 AE |
4114 | /** |
4115 | * action proxy | |
4116 | * @var WCF.Action.Proxy | |
4117 | */ | |
4118 | _proxy: null, | |
cf55c379 | 4119 | |
046e1292 AE |
4120 | /** |
4121 | * search input field | |
4122 | * @var jQuery | |
4123 | */ | |
4124 | _searchInput: null, | |
4d10750a AE |
4125 | |
4126 | /** | |
4127 | * minimum search input length, MUST be 1 or higher | |
4128 | * @var integer | |
4129 | */ | |
fbe99f89 | 4130 | _triggerLength: 3, |
cf55c379 | 4131 | |
44f8f78b AE |
4132 | /** |
4133 | * delay timer | |
4134 | * @var WCF.PeriodicalExecuter | |
4135 | */ | |
4136 | _timer: null, | |
4137 | ||
046e1292 AE |
4138 | /** |
4139 | * Initializes a new search. | |
4140 | * | |
4141 | * @param jQuery searchInput | |
4142 | * @param object callback | |
c000b08a | 4143 | * @param array excludedSearchValues |
cf55c379 | 4144 | * @param boolean commaSeperated |
80da9de3 | 4145 | * @param boolean showLoadingOverlay |
046e1292 | 4146 | */ |
80da9de3 | 4147 | init: function(searchInput, callback, excludedSearchValues, commaSeperated, showLoadingOverlay) { |
b0ce5298 | 4148 | if (callback !== null && callback !== undefined && !$.isFunction(callback)) { |
6f475a52 | 4149 | console.debug("[WCF.Search.Base] The given callback is invalid, aborting."); |
046e1292 AE |
4150 | return; |
4151 | } | |
b0ce5298 AE |
4152 | |
4153 | this._callback = (callback) ? callback : null; | |
187ebf27 | 4154 | this._caretAt = -1; |
44f8f78b | 4155 | this._delay = 0; |
fdd8763a | 4156 | this._excludedSearchValues = []; |
c000b08a MS |
4157 | if (excludedSearchValues) { |
4158 | this._excludedSearchValues = excludedSearchValues; | |
4159 | } | |
71662ae8 AE |
4160 | |
4161 | this._searchInput = $(searchInput); | |
4162 | if (!this._searchInput.length) { | |
4163 | console.debug("[WCF.Search.Base] Selector '" + searchInput + "' for search input is invalid, aborting."); | |
4164 | return; | |
4165 | } | |
4166 | ||
fbdfbaba | 4167 | this._searchInput.keydown($.proxy(this._keyDown, this)).keyup($.proxy(this._keyUp, this)).wrap('<span class="dropdown" />'); |
b986ad3d MS |
4168 | |
4169 | if ($.browser.mozilla && $.browser.touch) { | |
4170 | this._searchInput.on('input', $.proxy(this._keyUp, this)); | |
4171 | } | |
4172 | ||
7f45f320 | 4173 | this._list = $('<ul class="dropdownMenu" />').insertAfter(this._searchInput); |
cf55c379 AE |
4174 | this._commaSeperated = (commaSeperated) ? true : false; |
4175 | this._oldSearchString = [ ]; | |
046e1292 | 4176 | |
9a130ab4 AE |
4177 | this._itemCount = 0; |
4178 | this._itemIndex = -1; | |
4179 | ||
046e1292 | 4180 | this._proxy = new WCF.Action.Proxy({ |
387cd7da | 4181 | showLoadingOverlay: (showLoadingOverlay !== true ? false : true), |
40b173ec MK |
4182 | success: $.proxy(this._success, this), |
4183 | autoAbortPrevious: true | |
046e1292 | 4184 | }); |
b0ce5298 | 4185 | |
404c9abe | 4186 | if (this._searchInput.is('input')) { |
b0ce5298 AE |
4187 | this._searchInput.attr('autocomplete', 'off'); |
4188 | } | |
03e34cc3 AE |
4189 | |
4190 | this._searchInput.blur($.proxy(this._blur, this)); | |
38d131ce AE |
4191 | |
4192 | WCF.Dropdown.initDropdownFragment(this._searchInput.parent(), this._list); | |
03e34cc3 AE |
4193 | }, |
4194 | ||
4195 | /** | |
4196 | * Closes the dropdown after a short delay. | |
4197 | */ | |
4198 | _blur: function() { | |
4199 | var self = this; | |
4200 | new WCF.PeriodicalExecuter(function(pe) { | |
4201 | if (self._list.is(':visible')) { | |
4202 | self._clearList(false); | |
4203 | } | |
4204 | ||
4205 | pe.stop(); | |
369b47cd | 4206 | }, 250); |
046e1292 | 4207 | }, |
4ad00d80 | 4208 | |
fbdfbaba AE |
4209 | /** |
4210 | * Blocks execution of 'Enter' event. | |
4211 | * | |
4212 | * @param object event | |
4213 | */ | |
4214 | _keyDown: function(event) { | |
7c6f7523 AE |
4215 | if (event.which === $.ui.keyCode.ENTER) { |
4216 | var $dropdown = this._searchInput.parents('.dropdown'); | |
4217 | ||
4218 | if ($dropdown.data('disableAutoFocus')) { | |
4219 | if (this._itemIndex !== -1) { | |
4220 | event.preventDefault(); | |
4221 | } | |
c138555b | 4222 | } |
7c6f7523 | 4223 | else if ($dropdown.data('preventSubmit') || this._itemIndex !== -1) { |
c138555b AE |
4224 | event.preventDefault(); |
4225 | } | |
fbdfbaba AE |
4226 | } |
4227 | }, | |
4228 | ||
046e1292 AE |
4229 | /** |
4230 | * Performs a search upon key up. | |
cf55c379 AE |
4231 | * |
4232 | * @param object event | |
046e1292 | 4233 | */ |
cf55c379 | 4234 | _keyUp: function(event) { |
9a130ab4 AE |
4235 | // handle arrow keys and return key |
4236 | switch (event.which) { | |
4237 | case 37: // arrow-left | |
4238 | case 39: // arrow-right | |
4239 | return; | |
9a130ab4 AE |
4240 | |
4241 | case 38: // arrow up | |
4242 | this._selectPreviousItem(); | |
4243 | return; | |
9a130ab4 AE |
4244 | |
4245 | case 40: // arrow down | |
4246 | this._selectNextItem(); | |
4247 | return; | |
9a130ab4 AE |
4248 | |
4249 | case 13: // return key | |
4250 | return this._selectElement(event); | |
9a130ab4 AE |
4251 | } |
4252 | ||
cf55c379 | 4253 | var $content = this._getSearchString(event); |
046e1292 | 4254 | if ($content === '') { |
c9de3955 | 4255 | this._clearList(false); |
046e1292 | 4256 | } |
4d10750a | 4257 | else if ($content.length >= this._triggerLength) { |
4ad00d80 AE |
4258 | var $parameters = { |
4259 | data: { | |
c000b08a | 4260 | excludedSearchValues: this._excludedSearchValues, |
4ad00d80 | 4261 | searchString: $content |
596a5751 | 4262 | } |
4ad00d80 AE |
4263 | }; |
4264 | ||
44f8f78b AE |
4265 | if (this._delay) { |
4266 | if (this._timer !== null) { | |
4267 | this._timer.stop(); | |
4268 | } | |
4269 | ||
4270 | var self = this; | |
4271 | this._timer = new WCF.PeriodicalExecuter(function() { | |
4272 | self._queryServer($parameters); | |
4273 | ||
4274 | self._timer.stop(); | |
4275 | self._timer = null; | |
4276 | }, this._delay); | |
4277 | } | |
4278 | else { | |
4279 | this._queryServer($parameters); | |
4280 | } | |
046e1292 | 4281 | } |
4d10750a AE |
4282 | else { |
4283 | // input below trigger length | |
4284 | this._clearList(false); | |
4285 | } | |
046e1292 | 4286 | }, |
4ad00d80 | 4287 | |
44f8f78b AE |
4288 | /** |
4289 | * Queries the server. | |
4290 | * | |
4291 | * @param object parameters | |
4292 | */ | |
4293 | _queryServer: function(parameters) { | |
4294 | this._searchInput.parents('.searchBar').addClass('loading'); | |
4295 | this._proxy.setOption('data', { | |
4296 | actionName: 'getSearchResultList', | |
4297 | className: this._className, | |
4298 | interfaceName: 'wcf\\data\\ISearchAction', | |
4299 | parameters: this._getParameters(parameters) | |
4300 | }); | |
4301 | this._proxy.sendRequest(); | |
4302 | }, | |
4303 | ||
4304 | /** | |
4305 | * Sets query delay in miliseconds. | |
4306 | * | |
4307 | * @param integer delay | |
4308 | */ | |
4309 | setDelay: function(delay) { | |
4310 | this._delay = delay; | |
4311 | }, | |
4312 | ||
9a130ab4 AE |
4313 | /** |
4314 | * Selects the next item in list. | |
4315 | */ | |
4316 | _selectNextItem: function() { | |
4317 | if (this._itemCount === 0) { | |
4318 | return; | |
4319 | } | |
4320 | ||
4321 | // remove previous marking | |
4322 | this._itemIndex++; | |
4323 | if (this._itemIndex === this._itemCount) { | |
4324 | this._itemIndex = 0; | |
4325 | } | |
4326 | ||
4327 | this._highlightSelectedElement(); | |
4328 | }, | |
4329 | ||
4330 | /** | |
4331 | * Selects the previous item in list. | |
4332 | */ | |
4333 | _selectPreviousItem: function() { | |
4334 | if (this._itemCount === 0) { | |
4335 | return; | |
4336 | } | |
4337 | ||
4338 | this._itemIndex--; | |
4339 | if (this._itemIndex === -1) { | |
4340 | this._itemIndex = this._itemCount - 1; | |
4341 | } | |
4342 | ||
4343 | this._highlightSelectedElement(); | |
4344 | }, | |
4345 | ||
4346 | /** | |
4347 | * Highlights the active item. | |
4348 | */ | |
4349 | _highlightSelectedElement: function() { | |
4350 | this._list.find('li').removeClass('dropdownNavigationItem'); | |
4351 | this._list.find('li:eq(' + this._itemIndex + ')').addClass('dropdownNavigationItem'); | |
4352 | }, | |
4353 | ||
4354 | /** | |
4355 | * Selects the active item by pressing the return key. | |
4356 | * | |
4357 | * @param object event | |
4358 | * @return boolean | |
4359 | */ | |
4360 | _selectElement: function(event) { | |
4361 | if (this._itemCount === 0) { | |
4362 | return true; | |
4363 | } | |
4364 | ||
4365 | this._list.find('li.dropdownNavigationItem').trigger('click'); | |
4366 | ||
4367 | return false; | |
4368 | }, | |
4369 | ||
cf55c379 AE |
4370 | /** |
4371 | * Returns search string. | |
4372 | * | |
4373 | * @return string | |
4374 | */ | |
4375 | _getSearchString: function(event) { | |
4376 | var $searchString = $.trim(this._searchInput.val()); | |
4377 | if (this._commaSeperated) { | |
4378 | var $keyCode = event.keyCode || event.which; | |
3e597f7d AE |
4379 | if ($keyCode == $.ui.keyCode.COMMA) { |
4380 | // ignore event if char is ',' | |
cf55c379 AE |
4381 | return ''; |
4382 | } | |
4383 | ||
4384 | var $current = $searchString.split(','); | |
68097635 AE |
4385 | var $length = $current.length; |
4386 | for (var $i = 0; $i < $length; $i++) { | |
cf55c379 AE |
4387 | // remove whitespaces at the beginning or end |
4388 | $current[$i] = $.trim($current[$i]); | |
68097635 AE |
4389 | } |
4390 | ||
4391 | for (var $i = 0; $i < $length; $i++) { | |
cf55c379 AE |
4392 | var $part = $current[$i]; |
4393 | ||
4394 | if (this._oldSearchString[$i]) { | |
4395 | // compare part | |
4396 | if ($part != this._oldSearchString[$i]) { | |
4397 | // current part was changed | |
4398 | $searchString = $part; | |
187ebf27 AE |
4399 | this._caretAt = $i; |
4400 | ||
cf55c379 AE |
4401 | break; |
4402 | } | |
4403 | } | |
4404 | else { | |
4405 | // new part was added | |
4406 | $searchString = $part; | |
4407 | break; | |
4408 | } | |
4409 | } | |
4410 | ||
4411 | this._oldSearchString = $current; | |
4412 | } | |
4413 | ||
4414 | return $searchString; | |
4415 | }, | |
4416 | ||
4ad00d80 AE |
4417 | /** |
4418 | * Returns parameters for quick search. | |
4419 | * | |
4420 | * @param object parameters | |
4421 | * @return object | |
4422 | */ | |
4423 | _getParameters: function(parameters) { | |
4424 | return parameters; | |
4425 | }, | |
9f959ced | 4426 | |
046e1292 AE |
4427 | /** |
4428 | * Evalutes search results. | |
4429 | * | |
4430 | * @param object data | |
4431 | * @param string textStatus | |
4432 | * @param jQuery jqXHR | |
4433 | */ | |
4434 | _success: function(data, textStatus, jqXHR) { | |
7f45f320 | 4435 | this._clearList(false); |
b608ea34 | 4436 | this._searchInput.parents('.searchBar').removeClass('loading'); |
7f45f320 | 4437 | |
c138555b AE |
4438 | if ($.getLength(data.returnValues)) { |
4439 | for (var $i in data.returnValues) { | |
4440 | var $item = data.returnValues[$i]; | |
4441 | ||
4442 | this._createListItem($item); | |
4443 | } | |
046e1292 | 4444 | } |
c138555b AE |
4445 | else if (!this._handleEmptyResult()) { |
4446 | return; | |
046e1292 | 4447 | } |
4ad00d80 | 4448 | |
1af138b0 AE |
4449 | WCF.CloseOverlayHandler.addCallback('WCF.Search.Base', $.proxy(function() { this._clearList(); }, this)); |
4450 | ||
2d305fef AE |
4451 | var $containerID = this._searchInput.parents('.dropdown').wcfIdentify(); |
4452 | if (!WCF.Dropdown.getDropdownMenu($containerID).hasClass('dropdownOpen')) { | |
4453 | WCF.Dropdown.toggleDropdown($containerID); | |
71c70823 MS |
4454 | |
4455 | this._openDropdown(); | |
114bb112 AE |
4456 | } |
4457 | ||
1af138b0 | 4458 | // pre-select first item |
4765dbfd | 4459 | this._itemIndex = -1; |
2d305fef AE |
4460 | if (!WCF.Dropdown.getDropdown($containerID).data('disableAutoFocus')) { |
4461 | this._selectNextItem(); | |
4462 | } | |
046e1292 | 4463 | }, |
4ad00d80 | 4464 | |
71c70823 MS |
4465 | /** |
4466 | * Is called after the dropdown has been opened. | |
4467 | */ | |
4468 | _openDropdown: function() { | |
4469 | // does nothing | |
4470 | }, | |
4471 | ||
c138555b AE |
4472 | /** |
4473 | * Handles empty result lists, should return false if dropdown should be hidden. | |
4474 | * | |
4475 | * @return boolean | |
4476 | */ | |
4477 | _handleEmptyResult: function() { | |
4478 | return false; | |
4479 | }, | |
4480 | ||
4ad00d80 AE |
4481 | /** |
4482 | * Creates a new list item. | |
4483 | * | |
4484 | * @param object item | |
4485 | * @return jQuery | |
4486 | */ | |
4487 | _createListItem: function(item) { | |
83fccf43 | 4488 | var $listItem = $('<li><span>' + WCF.String.escapeHTML(item.label) + '</span></li>').appendTo(this._list); |
1c93e6e7 | 4489 | $listItem.data('objectID', item.objectID).data('label', item.label).click($.proxy(this._executeCallback, this)); |
4ad00d80 | 4490 | |
9a130ab4 AE |
4491 | this._itemCount++; |
4492 | ||
4ad00d80 AE |
4493 | return $listItem; |
4494 | }, | |
9f959ced | 4495 | |
046e1292 AE |
4496 | /** |
4497 | * Executes callback upon result click. | |
4498 | * | |
4499 | * @param object event | |
4500 | */ | |
4501 | _executeCallback: function(event) { | |
b0ce5298 | 4502 | var $clearSearchInput = false; |
046e1292 | 4503 | var $listItem = $(event.currentTarget); |
046e1292 | 4504 | // notify callback |
cf55c379 AE |
4505 | if (this._commaSeperated) { |
4506 | // auto-complete current part | |
4507 | var $result = $listItem.data('label'); | |
187ebf27 AE |
4508 | this._oldSearchString[this._caretAt] = $result; |
4509 | this._searchInput.val(this._oldSearchString.join(', ')); | |
4510 | ||
4511 | if ($.browser.webkit) { | |
4512 | // chrome won't display the new value until the textarea is rendered again | |
4513 | // this quick fix forces chrome to render it again, even though it changes nothing | |
4514 | this._searchInput.css({ display: 'block' }); | |
cf55c379 | 4515 | } |
187ebf27 AE |
4516 | |
4517 | // set focus on input field again | |
4518 | var $position = this._searchInput.val().toLowerCase().indexOf($result.toLowerCase()) + $result.length; | |
4519 | this._searchInput.focus().setCaret($position); | |
cf55c379 AE |
4520 | } |
4521 | else { | |
b0ce5298 AE |
4522 | if (this._callback === null) { |
4523 | this._searchInput.val($listItem.data('label')); | |
4524 | } | |
4525 | else { | |
4526 | $clearSearchInput = (this._callback($listItem.data()) === true) ? true : false; | |
4527 | } | |
cf55c379 | 4528 | } |
9f959ced | 4529 | |
046e1292 | 4530 | // close list and revert input |
f28efd50 | 4531 | this._clearList($clearSearchInput); |
046e1292 | 4532 | }, |
9f959ced | 4533 | |
046e1292 AE |
4534 | /** |
4535 | * Closes the suggestion list and clears search input on demand. | |
4536 | * | |
4537 | * @param boolean clearSearchInput | |
4538 | */ | |
4539 | _clearList: function(clearSearchInput) { | |
cf55c379 | 4540 | if (clearSearchInput && !this._commaSeperated) { |
046e1292 AE |
4541 | this._searchInput.val(''); |
4542 | } | |
9f959ced | 4543 | |
596a5751 MS |
4544 | // close dropdown |
4545 | WCF.Dropdown.getDropdown(this._searchInput.parents('.dropdown').wcfIdentify()).removeClass('dropdownOpen'); | |
4546 | WCF.Dropdown.getDropdownMenu(this._searchInput.parents('.dropdown').wcfIdentify()).removeClass('dropdownOpen'); | |
4547 | ||
4548 | this._list.end().empty(); | |
7f45f320 AE |
4549 | |
4550 | WCF.CloseOverlayHandler.removeCallback('WCF.Search.Base'); | |
9a130ab4 AE |
4551 | |
4552 | // reset item navigation | |
4553 | this._itemCount = 0; | |
4554 | this._itemIndex = -1; | |
c000b08a MS |
4555 | }, |
4556 | ||
4557 | /** | |
4558 | * Adds an excluded search value. | |
4559 | * | |
4560 | * @param string value | |
4561 | */ | |
4562 | addExcludedSearchValue: function(value) { | |
4563 | if (!WCF.inArray(value, this._excludedSearchValues)) { | |
4564 | this._excludedSearchValues.push(value); | |
4565 | } | |
4566 | }, | |
4567 | ||
4568 | /** | |
0b17ce2f | 4569 | * Removes an excluded search value. |
c000b08a MS |
4570 | * |
4571 | * @param string value | |
4572 | */ | |
4573 | removeExcludedSearchValue: function(value) { | |
4574 | var index = $.inArray(value, this._excludedSearchValues); | |
4575 | if (index != -1) { | |
4576 | this._excludedSearchValues.splice(index, 1); | |
4577 | } | |
046e1292 AE |
4578 | } |
4579 | }); | |
4580 | ||
4ad00d80 AE |
4581 | /** |
4582 | * Provides quick search for users and user groups. | |
4583 | * | |
4584 | * @see WCF.Search.Base | |
58d7e8f8 | 4585 | * @deprecated 3.0 - please use `WoltLabSuite/Core/Ui/User/Search/Input` instead |
4ad00d80 AE |
4586 | */ |
4587 | WCF.Search.User = WCF.Search.Base.extend({ | |
4588 | /** | |
4589 | * @see WCF.Search.Base._className | |
4590 | */ | |
4591 | _className: 'wcf\\data\\user\\UserAction', | |
4592 | ||
4593 | /** | |
4594 | * include user groups in search | |
4595 | * @var boolean | |
4596 | */ | |
4597 | _includeUserGroups: false, | |
4598 | ||
4599 | /** | |
cf55c379 | 4600 | * @see WCF.Search.Base.init() |
4ad00d80 | 4601 | */ |
cf55c379 | 4602 | init: function(searchInput, callback, includeUserGroups, excludedSearchValues, commaSeperated) { |
4ad00d80 AE |
4603 | this._includeUserGroups = includeUserGroups; |
4604 | ||
cf55c379 | 4605 | this._super(searchInput, callback, excludedSearchValues, commaSeperated); |
4ad00d80 AE |
4606 | }, |
4607 | ||
4608 | /** | |
4609 | * @see WCF.Search.Base._getParameters() | |
4610 | */ | |
4611 | _getParameters: function(parameters) { | |
4ccbb0bb | 4612 | parameters.data.includeUserGroups = this._includeUserGroups ? 1 : 0; |
1c93e6e7 AE |
4613 | |
4614 | return parameters; | |
4ad00d80 AE |
4615 | }, |
4616 | ||
4617 | /** | |
4618 | * @see WCF.Search.Base._createListItem() | |
4619 | */ | |
4620 | _createListItem: function(item) { | |
4621 | var $listItem = this._super(item); | |
4622 | ||
c2d0b2d6 MS |
4623 | var $icon = null; |
4624 | if (item.icon) { | |
4625 | $icon = $(item.icon); | |
4626 | } | |
4627 | else if (this._includeUserGroups && item.type === 'group') { | |
ca8bfa53 | 4628 | $icon = $('<span class="icon icon16 fa-users" />'); |
c2d0b2d6 MS |
4629 | } |
4630 | ||
4631 | if ($icon) { | |
4632 | var $label = $listItem.find('span').detach(); | |
4633 | ||
4634 | var $box16 = $('<div />').addClass('box16').appendTo($listItem); | |
4635 | ||
32811021 | 4636 | $box16.append($icon); |
c2d0b2d6 MS |
4637 | $box16.append($('<div />').append($label)); |
4638 | } | |
4639 | ||
4ad00d80 | 4640 | // insert item type |
4ad00d80 AE |
4641 | $listItem.data('type', item.type); |
4642 | ||
4643 | return $listItem; | |
4644 | } | |
4645 | }); | |
4646 | ||
5568e009 AE |
4647 | /** |
4648 | * Namespace for system-related classes. | |
4649 | */ | |
4650 | WCF.System = { }; | |
4651 | ||
eb1537e3 AE |
4652 | /** |
4653 | * Namespace for dependency-related classes. | |
4654 | */ | |
4655 | WCF.System.Dependency = { }; | |
4656 | ||
4657 | /** | |
4658 | * JavaScript Dependency Manager. | |
4659 | */ | |
4660 | WCF.System.Dependency.Manager = { | |
4661 | /** | |
4662 | * list of callbacks grouped by identifier | |
4663 | * @var object | |
4664 | */ | |
4665 | _callbacks: { }, | |
4666 | ||
4667 | /** | |
4668 | * list of loaded identifiers | |
4669 | * @var array<string> | |
4670 | */ | |
4671 | _loaded: [ ], | |
4672 | ||
4673 | /** | |
4674 | * list of setup callbacks grouped by identifier | |
4675 | * @var object | |
4676 | */ | |
4677 | _setupCallbacks: { }, | |
4678 | ||
4679 | /** | |
4680 | * Registers a callback for given identifier, will be executed after all setup | |
4681 | * callbacks have been invoked. | |
4682 | * | |
4683 | * @param string identifier | |
4684 | * @param object callback | |
4685 | */ | |
4686 | register: function(identifier, callback) { | |
4687 | if (!$.isFunction(callback)) { | |
4688 | console.debug("[WCF.System.Dependency.Manager] Callback for identifier '" + identifier + "' is invalid, aborting."); | |
4689 | return; | |
4690 | } | |
4691 | ||
4692 | // already loaded, invoke now | |
4693 | if (WCF.inArray(identifier, this._loaded)) { | |
9fb5eeac AE |
4694 | setTimeout(function() { |
4695 | callback(); | |
4696 | }, 1); | |
eb1537e3 AE |
4697 | } |
4698 | else { | |
4699 | if (!this._callbacks[identifier]) { | |
4700 | this._callbacks[identifier] = [ ]; | |
4701 | } | |
4702 | ||
4703 | this._callbacks[identifier].push(callback); | |
4704 | } | |
4705 | }, | |
4706 | ||
4707 | /** | |
4708 | * Registers a setup callback for given identifier, will be invoked | |
4709 | * prior to all other callbacks. | |
4710 | * | |
4711 | * @param string identifier | |
4712 | * @param object callback | |
4713 | */ | |
4714 | setup: function(identifier, callback) { | |
4715 | if (!$.isFunction(callback)) { | |
4716 | console.debug("[WCF.System.Dependency.Manager] Setup callback for identifier '" + identifier + "' is invalid, aborting."); | |
4717 | return; | |
4718 | } | |
4719 | ||
4720 | if (!this._setupCallbacks[identifier]) { | |
4721 | this._setupCallbacks[identifier] = [ ]; | |
4722 | } | |
4723 | ||
4724 | this._setupCallbacks[identifier].push(callback); | |
4725 | }, | |
4726 | ||
4727 | /** | |
4728 | * Invokes all callbacks for given identifier and marks it as loaded. | |
4729 | * | |
4730 | * @param string identifier | |
4731 | */ | |
4732 | invoke: function(identifier) { | |
4733 | if (this._setupCallbacks[identifier]) { | |
4734 | for (var $i = 0, $length = this._setupCallbacks[identifier].length; $i < $length; $i++) { | |
4735 | this._setupCallbacks[identifier][$i](); | |
4736 | } | |
2f51337e AE |
4737 | |
4738 | delete this._setupCallbacks[identifier]; | |
eb1537e3 AE |
4739 | } |
4740 | ||
4741 | this._loaded.push(identifier); | |
4742 | ||
4743 | if (this._callbacks[identifier]) { | |
4744 | for (var $i = 0, $length = this._callbacks[identifier].length; $i < $length; $i++) { | |
4745 | this._callbacks[identifier][$i](); | |
4746 | } | |
2f51337e AE |
4747 | |
4748 | delete this._callbacks[identifier]; | |
eb1537e3 | 4749 | } |
126b3f33 AE |
4750 | }, |
4751 | ||
4752 | reset: function(identifier) { | |
4753 | var index = this._loaded.indexOf(identifier); | |
4754 | if (index !== -1) { | |
4755 | this._loaded.splice(index, 1); | |
4756 | } | |
eb1537e3 AE |
4757 | } |
4758 | }; | |
4759 | ||
88c8e868 | 4760 | /** |
7b608580 | 4761 | * Provides flexible dropdowns for tab-based menus. |
88c8e868 | 4762 | */ |
7b608580 | 4763 | WCF.System.FlexibleMenu = { |
7b608580 AE |
4764 | /** |
4765 | * Initializes the WCF.System.FlexibleMenu class. | |
4766 | */ | |
c318ad3b | 4767 | init: function() { /* does nothing */ }, |
88c8e868 | 4768 | |
7b608580 AE |
4769 | /** |
4770 | * Registers a tab-based menu by id. | |
4771 | * | |
4772 | * Required DOM: | |
4773 | * <container> | |
4774 | * <ul style="white-space: nowrap"> | |
4775 | * <li>tab 1</li> | |
4776 | * <li>tab 2</li> | |
4777 | * ... | |
4778 | * <li>tab n</li> | |
4779 | * </ul> | |
4780 | * </container> | |
4781 | * | |
4782 | * @param string containerID | |
4783 | */ | |
4784 | registerMenu: function(containerID) { | |
58d7e8f8 | 4785 | require(['WoltLabSuite/Core/Ui/FlexibleMenu'], function(UiFlexibleMenu) { |
477b8397 | 4786 | UiFlexibleMenu.register(containerID); |
c318ad3b | 4787 | }); |
7b608580 AE |
4788 | }, |
4789 | ||
4790 | /** | |
4791 | * Rebuilds a container, will be automatically invoked on window resize and registering. | |
4792 | * | |
4793 | * @param string containerID | |
4794 | */ | |
4795 | rebuild: function(containerID) { | |
58d7e8f8 | 4796 | require(['WoltLabSuite/Core/Ui/FlexibleMenu'], function(UiFlexibleMenu) { |
477b8397 | 4797 | UiFlexibleMenu.rebuild(containerID); |
c318ad3b | 4798 | }); |
88c8e868 MW |
4799 | } |
4800 | }; | |
4801 | ||
23192d23 AE |
4802 | /** |
4803 | * Namespace for mobile device-related classes. | |
4804 | */ | |
4805 | WCF.System.Mobile = { }; | |
4806 | ||
240b34e0 AE |
4807 | /** |
4808 | * Stores object references for global access. | |
4809 | */ | |
4810 | WCF.System.ObjectStore = { | |
4811 | /** | |
4812 | * list of objects grouped by identifier | |
4813 | * @var object<array> | |
4814 | */ | |
4815 | _objects: { }, | |
4816 | ||
4817 | /** | |
4818 | * Adds a new object to the collection. | |
4819 | * | |
4820 | * @param string identifier | |
4821 | * @param object object | |
4822 | */ | |
4823 | add: function(identifier, obj) { | |
4824 | if (this._objects[identifier] === undefined) { | |
4825 | this._objects[identifier] = [ ]; | |
4826 | } | |
4827 | ||
4828 | this._objects[identifier].push(obj); | |
4829 | }, | |
4830 | ||
4831 | /** | |
4832 | * Invokes a callback passing the matching objects as a parameter. | |
4833 | * | |
4834 | * @param string identifier | |
4835 | * @param object callback | |
4836 | */ | |
4837 | invoke: function(identifier, callback) { | |
4838 | if (this._objects[identifier]) { | |
4839 | for (var $i = 0; $i < this._objects[identifier].length; $i++) { | |
4840 | callback(this._objects[identifier][$i]); | |
4841 | } | |
4842 | } | |
4843 | } | |
4844 | }; | |
4845 | ||
96714cab MS |
4846 | /** |
4847 | * Stores captcha callbacks used for captchas in AJAX contexts. | |
4848 | */ | |
4849 | WCF.System.Captcha = { | |
d386d76e MS |
4850 | /** |
4851 | * ids of registered captchas | |
4852 | * @var {string[]} | |
4853 | */ | |
4854 | _registeredCaptchas: [], | |
4855 | ||
96714cab MS |
4856 | /** |
4857 | * Adds a callback for a certain captcha. | |
4858 | * | |
4859 | * @param string captchaID | |
4860 | * @param function callback | |
4861 | */ | |
4862 | addCallback: function(captchaID, callback) { | |
58d7e8f8 | 4863 | require(['WoltLabSuite/Core/Controller/Captcha'], function(ControllerCaptcha) { |
1ea26041 MS |
4864 | try { |
4865 | ControllerCaptcha.add(captchaID, callback); | |
d386d76e MS |
4866 | |
4867 | this._registeredCaptchas.push(captchaID); | |
1ea26041 MS |
4868 | } |
4869 | catch (e) { | |
4870 | if (e instanceof TypeError) { | |
4871 | console.debug('[WCF.System.Captcha] Given callback is no function'); | |
4872 | return; | |
4873 | } | |
4874 | ||
4875 | // ignore other errors | |
4876 | } | |
d386d76e | 4877 | }.bind(this)); |
96714cab MS |
4878 | }, |
4879 | ||
4880 | /** | |
4881 | * Returns the captcha data for the captcha with the given id. | |
4882 | * | |
4883 | * @return object | |
4884 | */ | |
4885 | getData: function(captchaID) { | |
1ea26041 | 4886 | var returnValue; |
18a8c2b7 | 4887 | |
d386d76e MS |
4888 | if (this._registeredCaptchas.indexOf(captchaID) === -1) { |
4889 | return returnValue; | |
4890 | } | |
4891 | ||
18a8c2b7 MS |
4892 | var ControllerCaptcha = require('WoltLabSuite/Core/Controller/Captcha'); |
4893 | try { | |
4894 | returnValue = ControllerCaptcha.getData(captchaID); | |
4895 | } | |
4896 | catch (e) { | |
4897 | console.debug('[WCF.System.Captcha] Unknow captcha id "' + captchaID + '"'); | |
4898 | } | |
96714cab | 4899 | |
1ea26041 | 4900 | return returnValue; |
96714cab MS |
4901 | }, |
4902 | ||
4903 | /** | |
4904 | * Removes the callback with the given captcha id. | |
4905 | */ | |
4906 | removeCallback: function(captchaID) { | |
58d7e8f8 | 4907 | require(['WoltLabSuite/Core/Controller/Captcha'], function(ControllerCaptcha) { |
1ea26041 MS |
4908 | try { |
4909 | ControllerCaptcha.delete(captchaID); | |
d386d76e MS |
4910 | |
4911 | this._registeredCaptchas.splice(this._registeredCaptchas.indexOf(item), 1); | |
1ea26041 MS |
4912 | } |
4913 | catch (e) { | |
4914 | // ignore errors for unknown captchas | |
4915 | } | |
d386d76e | 4916 | }.bind(this)); |
96714cab MS |
4917 | } |
4918 | }; | |
4919 | ||
eb0f6246 AE |
4920 | WCF.System.Page = { }; |
4921 | ||
5568e009 AE |
4922 | /** |
4923 | * System notification overlays. | |
4924 | * | |
e71525e4 | 4925 | * @deprecated 3.0 - please use `Ui/Notification` instead |
45433290 | 4926 | * |
5568e009 AE |
4927 | * @param string message |
4928 | * @param string cssClassNames | |
4929 | */ | |
4930 | WCF.System.Notification = Class.extend({ | |
d2e126c6 | 4931 | _cssClassNames: '', |
d2e126c6 AE |
4932 | _message: '', |
4933 | ||
5568e009 AE |
4934 | /** |
4935 | * Creates a new system notification overlay. | |
4936 | * | |
4937 | * @param string message | |
4938 | * @param string cssClassNames | |
4939 | */ | |
4940 | init: function(message, cssClassNames) { | |
45433290 AE |
4941 | this._cssClassNames = cssClassNames || ''; |
4942 | this._message = message || ''; | |
5568e009 AE |
4943 | }, |
4944 | ||
4945 | /** | |
4946 | * Shows the notification overlay. | |
4947 | * | |
4948 | * @param object callback | |
4949 | * @param integer duration | |
7c1fa02a AE |
4950 | * @param string message |
4951 | * @param string cssClassName | |
5568e009 | 4952 | */ |
7c1fa02a | 4953 | show: function(callback, duration, message, cssClassNames) { |
45433290 AE |
4954 | require(['Ui/Notification'], (function(UiNotification) { |
4955 | UiNotification.show( | |
4956 | message || this._message, | |
4957 | callback, | |
4958 | cssClassNames || this._cssClassNames | |
4959 | ); | |
4960 | }).bind(this)); | |
5568e009 AE |
4961 | } |
4962 | }); | |
4963 | ||
34e158d9 AE |
4964 | /** |
4965 | * Provides dialog-based confirmations. | |
d81268de | 4966 | * |
e71525e4 | 4967 | * @deprecated 3.0 - please use `Ui/Confirmation` instead |
34e158d9 AE |
4968 | */ |
4969 | WCF.System.Confirmation = { | |
34e158d9 AE |
4970 | /** |
4971 | * Displays a confirmation dialog. | |
4972 | * | |
4973 | * @param string message | |
4974 | * @param object callback | |
f02b3f75 | 4975 | * @param object parameters |
73b84427 | 4976 | * @param jQuery template |
23e43ac5 | 4977 | * @param boolean messageIsHtml |
34e158d9 | 4978 | */ |
23e43ac5 MW |
4979 | show: function(message, callback, parameters, template, messageIsHtml) { |
4980 | if (typeof template === 'object') { | |
4981 | var $wrapper = $('<div />'); | |
4982 | $wrapper.append(template); | |
4983 | template = $wrapper.html(); | |
4984 | } | |
477b8397 MS |
4985 | require(['Ui/Confirmation'], function(UiConfirmation) { |
4986 | UiConfirmation.show({ | |
d81268de AE |
4987 | legacyCallback: callback, |
4988 | message: message, | |
4989 | parameters: parameters, | |
23e43ac5 MW |
4990 | template: (template || ''), |
4991 | messageIsHtml: (messageIsHtml === true) | |
d81268de | 4992 | }); |
34e158d9 | 4993 | }); |
34e158d9 AE |
4994 | } |
4995 | }; | |
4996 | ||
b2918c9d TD |
4997 | /** |
4998 | * Disables the ability to scroll the page. | |
4999 | */ | |
5000 | WCF.System.DisableScrolling = { | |
5001 | /** | |
5002 | * number of times scrolling was disabled (nested calls) | |
5003 | * @var integer | |
5004 | */ | |
5005 | _depth: 0, | |
5006 | ||
5007 | /** | |
5008 | * old overflow-value of the body element | |
5009 | * @var string | |
5010 | */ | |
5011 | _oldOverflow: null, | |
5012 | ||
5013 | /** | |
5014 | * Disables scrolling. | |
5015 | */ | |
5016 | disable: function () { | |
1dd0c00d AE |
5017 | // do not block scrolling on touch devices |
5018 | if ($.browser.touch) { | |
5019 | return; | |
5020 | } | |
5021 | ||
b2918c9d TD |
5022 | if (this._depth === 0) { |
5023 | this._oldOverflow = $(document.body).css('overflow'); | |
5024 | $(document.body).css('overflow', 'hidden'); | |
5025 | } | |
5026 | ||
5027 | this._depth++; | |
5028 | }, | |
5029 | ||
5030 | /** | |
5031 | * Enables scrolling again. | |
5032 | * Must be called the same number of times disable() was called to enable scrolling. | |
5033 | */ | |
5034 | enable: function () { | |
5035 | if (this._depth === 0) return; | |
5036 | ||
5037 | this._depth--; | |
5038 | ||
5039 | if (this._depth === 0) { | |
5040 | $(document.body).css('overflow', this._oldOverflow); | |
5041 | } | |
5042 | } | |
5043 | }; | |
5044 | ||
9c31391d AE |
5045 | /** |
5046 | * Disables the ability to zoom the page. | |
5047 | */ | |
5048 | WCF.System.DisableZoom = { | |
5049 | /** | |
5050 | * number of times zoom was disabled (nested calls) | |
5051 | * @var integer | |
5052 | */ | |
5053 | _depth: 0, | |
5054 | ||
5055 | /** | |
5056 | * old viewport settings in meta[name=viewport] | |
5057 | * @var string | |
5058 | */ | |
5059 | _oldViewportSettings: null, | |
5060 | ||
5061 | /** | |
5062 | * Disables zooming. | |
5063 | */ | |
5064 | disable: function () { | |
5065 | if (this._depth === 0) { | |
5066 | var $meta = $('meta[name=viewport]'); | |
5067 | this._oldViewportSettings = $meta.attr('content'); | |
5068 | $meta.attr('content', this._oldViewportSettings + ',maximum-scale=1'); | |
5069 | } | |
5070 | ||
5071 | this._depth++; | |
5072 | }, | |
5073 | ||
5074 | /** | |
5075 | * Enables scrolling again. | |
5076 | * Must be called the same number of times disable() was called to enable scrolling. | |
5077 | */ | |
5078 | enable: function () { | |
5079 | if (this._depth === 0) return; | |
5080 | ||
5081 | this._depth--; | |
5082 | ||
5083 | if (this._depth === 0) { | |
5084 | $('meta[name=viewport]').attr('content', this._oldViewportSettings); | |
5085 | } | |
5086 | } | |
5087 | }; | |
5088 | ||
244be538 TD |
5089 | /** |
5090 | * Puts an element into HTML 5 fullscreen mode. | |
5091 | */ | |
5092 | WCF.System.Fullscreen = { | |
5093 | /** | |
5094 | * Puts the given element into full screen mode. | |
5095 | * Note: This must be a raw HTMLElement, not a jQuery wrapped one. | |
5096 | * Note: This must be called from an user triggered event listener for | |
5097 | * security reasons. | |
5098 | * | |
5099 | * @param object Element to show full screen. | |
5100 | */ | |
5101 | enterFullscreen: function (element) { | |
5102 | if (element.requestFullscreen) { | |
5103 | element.requestFullscreen(); | |
5104 | } | |
5105 | else if (element.msRequestFullscreen) { | |
5106 | element.msRequestFullscreen(); | |
5107 | } | |
5108 | else if (element.mozRequestFullScreen) { | |
5109 | element.mozRequestFullScreen(); | |
5110 | } | |
5111 | else if (element.webkitRequestFullscreen) { | |
5112 | element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); | |
5113 | } | |
5114 | }, | |
5115 | /** | |
5116 | * Toggles full screen mode. Either calls `enterFullscreen` with the given | |
5117 | * element, if full screen mode is not active. Calls `exitFullscreen` | |
5118 | * otherwise. | |
5119 | */ | |
5120 | toggleFullscreen: function (element) { | |
5121 | if (this.getFullscreenElement() === null) { | |
5122 | this.enterFullscreen(element); | |
5123 | } | |
5124 | else { | |
5125 | this.exitFullscreen(); | |
5126 | } | |
5127 | }, | |
5128 | /** | |
5129 | * Retrieves the element that is shown in full screen mode. | |
5130 | * Returns null if either full screen mode is not supported or | |
5131 | * if full screen mode is not active. | |
5132 | * | |
5133 | * @return object | |
5134 | */ | |
5135 | getFullscreenElement: function () { | |
5136 | if (document.fullscreenElement) { | |
5137 | return document.fullscreenElement; | |
5138 | } | |
5139 | else if (document.mozFullScreenElement) { | |
5140 | return document.mozFullScreenElement; | |
5141 | } | |
5142 | else if (document.webkitFullscreenElement) { | |
5143 | return document.webkitFullscreenElement; | |
5144 | } | |
5145 | else if (document.msFullscreenElement) { | |
5146 | return document.msFullscreenElement; | |
5147 | } | |
5148 | ||
5149 | return null; | |
5150 | }, | |
5151 | /** | |
5152 | * Exits full screen mode. | |
5153 | */ | |
5154 | exitFullscreen: function () { | |
5155 | if (document.exitFullscreen) { | |
5156 | document.exitFullscreen(); | |
5157 | } | |
5158 | else if (document.msExitFullscreen) { | |
5159 | document.msExitFullscreen(); | |
5160 | } | |
5161 | else if (document.mozCancelFullScreen) { | |
5162 | document.mozCancelFullScreen(); | |
5163 | } | |
5164 | else if (document.webkitExitFullscreen) { | |
5165 | document.webkitExitFullscreen(); | |
5166 | } | |
5167 | }, | |
5168 | /** | |
5169 | * Returns whether the full screen API is supported in this browser. | |
5170 | * | |
5171 | * @return boolean | |
5172 | */ | |
5173 | isSupported: function () { | |
5174 | if (document.documentElement.requestFullscreen || document.documentElement.msRequestFullscreen || document.documentElement.mozRequestFullScreen || document.documentElement.webkitRequestFullscreen) { | |
5175 | return true; | |
5176 | } | |
5177 | ||
5178 | return false; | |
5179 | } | |
5180 | }; | |
5181 | ||
d83e246c AE |
5182 | /** |
5183 | * Provides the 'jump to page' overlay. | |
ec86f434 | 5184 | * |
58d7e8f8 | 5185 | * @deprecated 3.0 - use `WoltLabSuite/Core/Ui/Page/JumpTo` instead |
d83e246c AE |
5186 | */ |
5187 | WCF.System.PageNavigation = { | |
3bdc6920 | 5188 | init: function(selector, callback) { |
58d7e8f8 | 5189 | require(['WoltLabSuite/Core/Ui/Page/JumpTo'], function(UiPageJumpTo) { |
ec86f434 AE |
5190 | var elements = elBySelAll(selector); |
5191 | for (var i = 0, length = elements.length; i < length; i++) { | |
5192 | UiPageJumpTo.init(elements[i], callback); | |
d83e246c | 5193 | } |
d83e246c | 5194 | }); |
d83e246c AE |
5195 | } |
5196 | }; | |
5197 | ||
dd932bc6 AE |
5198 | /** |
5199 | * Sends periodical requests to protect the session from expiring. By default | |
5200 | * it will send a request 1 minute before it would expire. | |
5201 | * | |
5202 | * @param integer seconds | |
5203 | */ | |
5204 | WCF.System.KeepAlive = Class.extend({ | |
5205 | /** | |
5206 | * Initializes the WCF.System.KeepAlive class. | |
5207 | * | |
5208 | * @param integer seconds | |
5209 | */ | |
5210 | init: function(seconds) { | |
9b1326f4 | 5211 | new WCF.PeriodicalExecuter(function(pe) { |
dd932bc6 AE |
5212 | new WCF.Action.Proxy({ |
5213 | autoSend: true, | |
5214 | data: { | |
5215 | actionName: 'keepAlive', | |
5216 | className: 'wcf\\data\\session\\SessionAction' | |
90b713ac | 5217 | }, |
9b1326f4 | 5218 | failure: function() { pe.stop(); }, |
88432fe0 | 5219 | showLoadingOverlay: false, |
7f737b83 AE |
5220 | success: function(data) { |
5221 | WCF.System.PushNotification.executeCallbacks(data); | |
5222 | }, | |
88432fe0 | 5223 | suppressErrors: true |
dd932bc6 AE |
5224 | }); |
5225 | }, (seconds * 1000)); | |
5226 | } | |
5227 | }); | |
5228 | ||
7f737b83 AE |
5229 | /** |
5230 | * System-wide handler for push notifications. | |
5231 | */ | |
5232 | WCF.System.PushNotification = { | |
5233 | /** | |
1615fc2e | 5234 | * list of callbacks grouped by type |
7f737b83 AE |
5235 | * @var object<array> |
5236 | */ | |
5237 | _callbacks: { }, | |
5238 | ||
5239 | /** | |
5240 | * Adds a callback for a specific notification type. | |
5241 | * | |
5242 | * @param string type | |
5243 | * @param object callback | |
5244 | */ | |
5245 | addCallback: function(type, callback) { | |
5246 | if (this._callbacks[type] === undefined) { | |
5247 | this._callbacks[type] = [ ]; | |
5248 | } | |
5249 | ||
5250 | this._callbacks[type].push(callback); | |
5251 | }, | |
5252 | ||
5253 | /** | |
5254 | * Executes all registered callbacks by type. | |
5255 | * | |
5256 | * @param object data | |
5257 | */ | |
5258 | executeCallbacks: function(data) { | |
5259 | for (var $type in data.returnValues) { | |
5260 | if (this._callbacks[$type] !== undefined) { | |
5261 | for (var $i = 0; $i < this._callbacks[$type].length; $i++) { | |
5262 | this._callbacks[$type][$i](data.returnValues[$type]); | |
5263 | } | |
5264 | } | |
5265 | } | |
5266 | } | |
c62197d6 AE |
5267 | }; |
5268 | ||
5269 | /** | |
5270 | * System-wide event system. | |
126b3f33 | 5271 | * |
e71525e4 | 5272 | * @deprecated 3.0 - please use `EventHandler` instead |
c62197d6 AE |
5273 | */ |
5274 | WCF.System.Event = { | |
c62197d6 AE |
5275 | /** |
5276 | * Registers a new event listener. | |
5277 | * | |
5278 | * @param string identifier | |
5279 | * @param string action | |
5280 | * @param object listener | |
996dd9e0 | 5281 | * @return string |
c62197d6 AE |
5282 | */ |
5283 | addListener: function(identifier, action, listener) { | |
126b3f33 | 5284 | return window.__wcf_bc_eventHandler.add(identifier, action, listener); |
996dd9e0 AE |
5285 | }, |
5286 | ||
5287 | /** | |
5288 | * Removes a listener, requires the uuid returned by addListener(). | |
5289 | * | |
5290 | * @param string identifier | |
5291 | * @param string action | |
5292 | * @param string uuid | |
5293 | * @return boolean | |
5294 | */ | |
5295 | removeListener: function(identifier, action, uuid) { | |
126b3f33 | 5296 | return window.__wcf_bc_eventHandler.remove(identifier, action, uuid); |
996dd9e0 AE |
5297 | }, |
5298 | ||
5299 | /** | |
5300 | * Removes all registered event listeners for given identifier and action. | |
5301 | * | |
5302 | * @param string identifier | |
5303 | * @param string action | |
5304 | * @return boolean | |
5305 | */ | |
5306 | removeAllListeners: function(identifier, action) { | |
126b3f33 | 5307 | return window.__wcf_bc_eventHandler.removeAll(identifier, action); |
c62197d6 AE |
5308 | }, |
5309 | ||
5310 | /** | |
5311 | * Fires a new event and notifies all registered event listeners. | |
5312 | * | |
5313 | * @param string identifier | |
5314 | * @param string action | |
5315 | * @param object data | |
5316 | */ | |
5317 | fireEvent: function(identifier, action, data) { | |
126b3f33 | 5318 | window.__wcf_bc_eventHandler.fire(identifier, action, data); |
c62197d6 AE |
5319 | } |
5320 | }; | |
7f737b83 | 5321 | |
f5e3a61b | 5322 | if (COMPILER_TARGET_DEFAULT) { |
e53269d6 | 5323 | /** |
f5e3a61b AE |
5324 | * Worker support for frontend based upon DatabaseObjectActions. |
5325 | * | |
5326 | * @param string className | |
5327 | * @param string title | |
5328 | * @param object parameters | |
5329 | * @param object callback | |
e53269d6 | 5330 | */ |
f5e3a61b AE |
5331 | WCF.System.Worker = Class.extend({ |
5332 | /** | |
5333 | * worker aborted | |
5334 | * @var boolean | |
5335 | */ | |
5336 | _aborted: false, | |
e53269d6 | 5337 | |
f5e3a61b AE |
5338 | /** |
5339 | * DBOAction method name | |
5340 | * @var string | |
5341 | */ | |
5342 | _actionName: '', | |
e53269d6 | 5343 | |
f5e3a61b AE |
5344 | /** |
5345 | * callback invoked after worker completed | |
5346 | * @var object | |
5347 | */ | |
5348 | _callback: null, | |
e53269d6 | 5349 | |
f5e3a61b AE |
5350 | /** |
5351 | * DBOAction class name | |
5352 | * @var string | |
5353 | */ | |
5354 | _className: '', | |
e53269d6 | 5355 | |
f5e3a61b AE |
5356 | /** |
5357 | * dialog object | |
5358 | * @var jQuery | |
5359 | */ | |
5360 | _dialog: null, | |
5361 | ||
5362 | /** | |
5363 | * action proxy | |
5364 | * @var WCF.Action.Proxy | |
5365 | */ | |
5366 | _proxy: null, | |
5367 | ||
5368 | /** | |
5369 | * dialog title | |
5370 | * @var string | |
5371 | */ | |
5372 | _title: '', | |
5373 | ||
5374 | /** | |
5375 | * Initializes a new worker instance. | |
5376 | * | |
5377 | * @param string actionName | |
5378 | * @param string className | |
5379 | * @param string title | |
5380 | * @param object parameters | |
5381 | * @param object callback | |
5382 | * @param object confirmMessage | |
5383 | */ | |
5384 | init: function (actionName, className, title, parameters, callback) { | |
5385 | this._aborted = false; | |
5386 | this._actionName = actionName; | |
5387 | this._callback = callback || null; | |
5388 | this._className = className; | |
5389 | this._dialog = null; | |
5390 | this._proxy = new WCF.Action.Proxy({ | |
5391 | autoSend: true, | |
5392 | data: { | |
5393 | actionName: this._actionName, | |
5394 | className: this._className, | |
5395 | parameters: parameters || {} | |
5396 | }, | |
5397 | showLoadingOverlay: false, | |
5398 | success: $.proxy(this._success, this) | |
e53269d6 | 5399 | }); |
f5e3a61b AE |
5400 | this._title = title; |
5401 | }, | |
e7f87db1 | 5402 | |
f5e3a61b AE |
5403 | /** |
5404 | * Handles response from server. | |
5405 | * | |
5406 | * @param object data | |
5407 | */ | |
5408 | _success: function (data) { | |
5409 | // init binding | |
5410 | if (this._dialog === null) { | |
5411 | this._dialog = $('<div />').hide().appendTo(document.body); | |
5412 | this._dialog.wcfDialog({ | |
5413 | closeConfirmMessage: WCF.Language.get('wcf.worker.abort.confirmMessage'), | |
5414 | closeViaModal: false, | |
5415 | onClose: $.proxy(function () { | |
5416 | this._aborted = true; | |
5417 | this._proxy.abortPrevious(); | |
5418 | ||
5419 | window.location.reload(); | |
5420 | }, this), | |
5421 | title: this._title | |
5422 | }); | |
150fb135 | 5423 | } |
e7f87db1 | 5424 | |
f5e3a61b | 5425 | if (this._aborted) { |
e7f87db1 AE |
5426 | return; |
5427 | } | |
5428 | ||
f5e3a61b AE |
5429 | if (data.returnValues.template) { |
5430 | this._dialog.html(data.returnValues.template); | |
1212ae1f AE |
5431 | } |
5432 | ||
f5e3a61b AE |
5433 | // update progress |
5434 | this._dialog.find('progress').attr('value', data.returnValues.progress).text(data.returnValues.progress + '%').next('span').text(data.returnValues.progress + '%'); | |
e7f87db1 | 5435 | |
f5e3a61b AE |
5436 | // worker is still busy with its business, carry on |
5437 | if (data.returnValues.progress < 100) { | |
5438 | // send request for next loop | |
5439 | var $parameters = data.returnValues.parameters || {}; | |
5440 | $parameters.loopCount = data.returnValues.loopCount; | |
e7f87db1 | 5441 | |
f5e3a61b AE |
5442 | this._proxy.setOption('data', { |
5443 | actionName: this._actionName, | |
5444 | className: this._className, | |
5445 | parameters: $parameters | |
5446 | }); | |
5447 | this._proxy.sendRequest(); | |
e7f87db1 | 5448 | } |
f5e3a61b AE |
5449 | else if (this._callback !== null) { |
5450 | this._callback(this, data); | |
4c5cfd1a | 5451 | } |
f5e3a61b AE |
5452 | else { |
5453 | // exchange icon | |
5454 | this._dialog.find('.fa-spinner').removeClass('fa-spinner').addClass('fa-check green'); | |
5455 | this._dialog.find('.contentHeader h1').text(WCF.Language.get('wcf.global.worker.completed')); | |
5456 | ||
5457 | // display continue button | |
5458 | var $formSubmit = $('<div class="formSubmit" />').appendTo(this._dialog); | |
5459 | $('<button class="buttonPrimary">' + WCF.Language.get('wcf.global.button.next') + '</button>').appendTo($formSubmit).focus().click(function () { | |
5460 | if (data.returnValues.redirectURL) { | |
5461 | window.location = data.returnValues.redirectURL; | |
0dcde1e2 AE |
5462 | } |
5463 | else { | |
f5e3a61b | 5464 | window.location.reload(); |
0dcde1e2 | 5465 | } |
f5e3a61b | 5466 | }); |
0dcde1e2 | 5467 | |
f5e3a61b | 5468 | this._dialog.wcfDialog('render'); |
0dcde1e2 | 5469 | } |
e7f87db1 | 5470 | } |
f5e3a61b | 5471 | }); |
e7f87db1 AE |
5472 | |
5473 | /** | |
f5e3a61b AE |
5474 | * Default implementation for inline editors. |
5475 | * | |
5476 | * @param string elementSelector | |
e7f87db1 | 5477 | */ |
f5e3a61b AE |
5478 | WCF.InlineEditor = Class.extend({ |
5479 | /** | |
5480 | * list of registered callbacks | |
5481 | * @var array<object> | |
5482 | */ | |
5483 | _callbacks: [], | |
5484 | ||
5485 | /** | |
5486 | * list of dropdown selections | |
5487 | * @var object | |
5488 | */ | |
5489 | _dropdowns: {}, | |
5490 | ||
5491 | /** | |
5492 | * list of container elements | |
5493 | * @var object | |
5494 | */ | |
5495 | _elements: {}, | |
5496 | ||
5497 | /** | |
5498 | * notification object | |
5499 | * @var WCF.System.Notification | |
5500 | */ | |
5501 | _notification: null, | |
5502 | ||
5503 | /** | |
5504 | * list of known options | |
5505 | * @var array<object> | |
5506 | */ | |
5507 | _options: [], | |
5508 | ||
5509 | /** | |
5510 | * action proxy | |
5511 | * @var WCF.Action.Proxy | |
5512 | */ | |
5513 | _proxy: null, | |
5514 | ||
5515 | /** | |
5516 | * list of trigger elements by element id | |
5517 | * @var object<object> | |
5518 | */ | |
5519 | _triggerElements: {}, | |
5520 | ||
5521 | /** | |
5522 | * list of data to update upon success | |
5523 | * @var array<object> | |
5524 | */ | |
5525 | _updateData: [], | |
5526 | ||
5527 | /** | |
5528 | * Initializes a new inline editor. | |
5529 | */ | |
5530 | init: function (elementSelector) { | |
5531 | var $elements = $(elementSelector); | |
5532 | if (!$elements.length) { | |
5533 | return; | |
5534 | } | |
5535 | ||
5536 | this._setOptions(); | |
5537 | var $quickOption = ''; | |
5538 | for (var $i = 0, $length = this._options.length; $i < $length; $i++) { | |
5539 | if (this._options[$i].isQuickOption) { | |
5540 | $quickOption = this._options[$i].optionName; | |
5541 | break; | |
e7f87db1 AE |
5542 | } |
5543 | } | |
f5e3a61b AE |
5544 | |
5545 | var self = this; | |
5546 | $elements.each(function (index, element) { | |
5547 | var $element = $(element); | |
5548 | var $elementID = $element.wcfIdentify(); | |
5549 | ||
5550 | // find trigger element | |
5551 | var $trigger = self._getTriggerElement($element); | |
5552 | if ($trigger === null || $trigger.length !== 1) { | |
5553 | return; | |
5554 | } | |
5555 | ||
5556 | $trigger.on(WCF_CLICK_EVENT, $.proxy(self._show, self)).data('elementID', $elementID); | |
5557 | if ($quickOption) { | |
5558 | // simulate click on target action | |
5559 | $trigger.disableSelection().data('optionName', $quickOption).dblclick($.proxy(self._click, self)); | |
5560 | } | |
5561 | ||
5562 | // store reference | |
5563 | self._elements[$elementID] = $element; | |
5564 | }); | |
5565 | ||
5566 | this._proxy = new WCF.Action.Proxy({ | |
5567 | success: $.proxy(this._success, this) | |
5568 | }); | |
5569 | ||
5570 | WCF.CloseOverlayHandler.addCallback('WCF.InlineEditor', $.proxy(this._closeAll, this)); | |
5571 | ||
5572 | this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'), 'success'); | |
5573 | }, | |
e7f87db1 | 5574 | |
f5e3a61b AE |
5575 | /** |
5576 | * Closes all inline editors. | |
5577 | */ | |
5578 | _closeAll: function () { | |
5579 | for (var $elementID in this._elements) { | |
5580 | this._hide($elementID); | |
5581 | } | |
5582 | }, | |
f3ca0ddf | 5583 | |
f5e3a61b AE |
5584 | /** |
5585 | * Sets options for this inline editor. | |
5586 | */ | |
5587 | _setOptions: function () { | |
5588 | this._options = []; | |
5589 | }, | |
f3ca0ddf | 5590 | |
f5e3a61b AE |
5591 | /** |
5592 | * Register an option callback for validation and execution. | |
5593 | * | |
5594 | * @param object callback | |
5595 | */ | |
5596 | registerCallback: function (callback) { | |
5597 | if ($.isFunction(callback)) { | |
5598 | this._callbacks.push(callback); | |
5599 | } | |
5600 | }, | |
e7f87db1 | 5601 | |
f5e3a61b AE |
5602 | /** |
5603 | * Returns the triggering element. | |
5604 | * | |
5605 | * @param jQuery element | |
5606 | * @return jQuery | |
5607 | */ | |
5608 | _getTriggerElement: function (element) { | |
5609 | return null; | |
5610 | }, | |
e7f87db1 | 5611 | |
f5e3a61b AE |
5612 | /** |
5613 | * Shows a dropdown menu if options are available. | |
5614 | * | |
5615 | * @param object event | |
5616 | */ | |
5617 | _show: function (event) { | |
5618 | event.preventDefault(); | |
5619 | var $elementID = $(event.currentTarget).data('elementID'); | |
5620 | ||
5621 | // build dropdown | |
5622 | var $trigger = null; | |
5623 | if (!this._dropdowns[$elementID]) { | |
5624 | this._triggerElements[$elementID] = $trigger = this._getTriggerElement(this._elements[$elementID]).addClass('dropdownToggle'); | |
5625 | var parent = $trigger[0].parentNode; | |
5626 | if (parent && parent.nodeName === 'LI' && parent.childElementCount === 1) { | |
5627 | // do not add a wrapper element if the trigger is the only child | |
5628 | parent.classList.add('dropdown'); | |
5629 | } | |
5630 | else { | |
5631 | $trigger.wrap('<span class="dropdown" />'); | |
e7f87db1 | 5632 | } |
f5e3a61b AE |
5633 | |
5634 | this._dropdowns[$elementID] = $('<ul class="dropdownMenu" />').insertAfter($trigger); | |
e7f87db1 | 5635 | } |
f5e3a61b AE |
5636 | this._dropdowns[$elementID].empty(); |
5637 | ||
5638 | // validate options | |
5639 | var $hasOptions = false; | |
5640 | var $lastElementType = ''; | |
5641 | for (var $i = 0, $length = this._options.length; $i < $length; $i++) { | |
5642 | var $option = this._options[$i]; | |
5643 | ||
5644 | if ($option.optionName === 'divider') { | |
5645 | if ($lastElementType !== '' && $lastElementType !== 'divider') { | |
5646 | $('<li class="dropdownDivider" />').appendTo(this._dropdowns[$elementID]); | |
5647 | $lastElementType = $option.optionName; | |
5648 | } | |
5649 | } | |
5650 | else if (this._validate($elementID, $option.optionName) || this._validateCallbacks($elementID, $option.optionName)) { | |
5651 | var $listItem = $('<li><span>' + $option.label + '</span></li>').appendTo(this._dropdowns[$elementID]); | |
5652 | $listItem.data('elementID', $elementID).data('optionName', $option.optionName).data('isQuickOption', ($option.isQuickOption ? true : false)).click($.proxy(this._click, this)); | |
5653 | ||
5654 | $hasOptions = true; | |
5655 | $lastElementType = $option.optionName; | |
5656 | } | |
5657 | } | |
5658 | ||
5659 | if ($hasOptions) { | |
5660 | // if last child is divider, remove it | |
5661 | var $lastChild = this._dropdowns[$elementID].children().last(); | |
5662 | if ($lastChild.hasClass('dropdownDivider')) { | |
5663 | $lastChild.remove(); | |
5664 | } | |
5665 | ||
5666 | // check if only element is a quick option | |
5667 | var $quickOption = null; | |
5668 | var $count = 0; | |
5669 | this._dropdowns[$elementID].children().each(function (index, child) { | |
5670 | var $child = $(child); | |
5671 | if (!$child.hasClass('dropdownDivider')) { | |
5672 | if ($child.data('isQuickOption')) { | |
5673 | $quickOption = $child; | |
5674 | } | |
5675 | else { | |
5676 | $count++; | |
5677 | } | |
5678 | } | |
5679 | }); | |
5680 | ||
5681 | if (!$count) { | |
5682 | $quickOption.trigger('click'); | |
5683 | ||
5684 | if (this._triggerElements[$elementID]) { | |
5685 | WCF.Dropdown.close(this._triggerElements[$elementID].parents('.dropdown').wcfIdentify()); | |
5686 | } | |
5687 | ||
5688 | return false; | |
5689 | } | |
5690 | } | |
5691 | ||
5692 | if ($trigger !== null) { | |
5693 | WCF.Dropdown.initDropdown($trigger, true); | |
5694 | } | |
5695 | ||
5696 | return false; | |
5697 | }, | |
e7f87db1 | 5698 | |
f5e3a61b AE |
5699 | /** |
5700 | * Validates an option. | |
5701 | * | |
5702 | * @param string elementID | |
5703 | * @param string optionName | |
5704 | * @returns boolean | |
5705 | */ | |
5706 | _validate: function (elementID, optionName) { | |
5707 | return false; | |
5708 | }, | |
5709 | ||
5710 | /** | |
5711 | * Validates an option provided by callbacks. | |
5712 | * | |
5713 | * @param string elementID | |
5714 | * @param string optionName | |
5715 | * @return boolean | |
5716 | */ | |
5717 | _validateCallbacks: function (elementID, optionName) { | |
5718 | var $length = this._callbacks.length; | |
5719 | if ($length) { | |
5720 | for (var $i = 0; $i < $length; $i++) { | |
5721 | if (this._callbacks[$i].validate(this._elements[elementID], optionName)) { | |
5722 | return true; | |
5723 | } | |
5724 | } | |
5725 | } | |
5726 | ||
5727 | return false; | |
5728 | }, | |
5729 | ||
5730 | /** | |
5731 | * Handles AJAX responses. | |
5732 | * | |
5733 | * @param object data | |
5734 | * @param string textStatus | |
5735 | * @param jQuery jqXHR | |
5736 | */ | |
5737 | _success: function (data, textStatus, jqXHR) { | |
5738 | var $length = this._updateData.length; | |
5739 | if (!$length) { | |
5740 | return; | |
5741 | } | |
5742 | ||
5743 | this._updateState(data); | |
5744 | ||
5745 | this._updateData = []; | |
5746 | }, | |
5747 | ||
5748 | /** | |
5749 | * Update element states based upon update data. | |
5750 | * | |
5751 | * @param object data | |
5752 | */ | |
5753 | _updateState: function (data) { | |
5754 | }, | |
5755 | ||
5756 | /** | |
5757 | * Handles clicks within dropdown. | |
5758 | * | |
5759 | * @param object event | |
5760 | */ | |
5761 | _click: function (event) { | |
5762 | var $listItem = $(event.currentTarget); | |
5763 | var $elementID = $listItem.data('elementID'); | |
5764 | var $optionName = $listItem.data('optionName'); | |
5765 | ||
5766 | if (!this._execute($elementID, $optionName)) { | |
5767 | this._executeCallback($elementID, $optionName); | |
5768 | } | |
5769 | ||
5770 | this._hide($elementID); | |
5771 | }, | |
5772 | ||
5773 | /** | |
5774 | * Executes actions associated with an option. | |
5775 | * | |
5776 | * @param string elementID | |
5777 | * @param string optionName | |
5778 | * @return boolean | |
5779 | */ | |
5780 | _execute: function (elementID, optionName) { | |
5781 | return false; | |
5782 | }, | |
5783 | ||
5784 | /** | |
5785 | * Executes actions associated with an option provided by callbacks. | |
5786 | * | |
5787 | * @param string elementID | |
5788 | * @param string optionName | |
5789 | * @return boolean | |
5790 | */ | |
5791 | _executeCallback: function (elementID, optionName) { | |
5792 | var $length = this._callbacks.length; | |
5793 | if ($length) { | |
5794 | for (var $i = 0; $i < $length; $i++) { | |
5795 | if (this._callbacks[$i].execute(this._elements[elementID], optionName)) { | |
5796 | return true; | |
5797 | } | |
5798 | } | |
5799 | } | |
5800 | ||
5801 | return false; | |
5802 | }, | |
5803 | ||
5804 | /** | |
5805 | * Hides a dropdown menu. | |
5806 | * | |
5807 | * @param string elementID | |
5808 | */ | |
5809 | _hide: function (elementID) { | |
5810 | if (this._dropdowns[elementID]) { | |
5811 | this._dropdowns[elementID].empty().removeClass('dropdownOpen'); | |
5812 | } | |
83428b7f | 5813 | } |
f5e3a61b | 5814 | }); |
c1aaf7a7 MW |
5815 | |
5816 | /** | |
f5e3a61b AE |
5817 | * Default implementation for ajax file uploads. |
5818 | * | |
5819 | * @deprecated Use WoltLabSuite/Core/Upload | |
5820 | * | |
5821 | * @param jquery buttonSelector | |
5822 | * @param jquery fileListSelector | |
5823 | * @param string className | |
5824 | * @param jquery options | |
c1aaf7a7 | 5825 | */ |
f5e3a61b AE |
5826 | WCF.Upload = Class.extend({ |
5827 | /** | |
5828 | * name of the upload field | |
5829 | * @var string | |
5830 | */ | |
5831 | _name: '__files[]', | |
c1aaf7a7 | 5832 | |
f5e3a61b AE |
5833 | /** |
5834 | * button selector | |
5835 | * @var jQuery | |
5836 | */ | |
5837 | _buttonSelector: null, | |
89912888 | 5838 | |
f5e3a61b AE |
5839 | /** |
5840 | * file list selector | |
5841 | * @var jQuery | |
5842 | */ | |
5843 | _fileListSelector: null, | |
c1aaf7a7 | 5844 | |
f5e3a61b AE |
5845 | /** |
5846 | * upload file | |
5847 | * @var jQuery | |
5848 | */ | |
5849 | _fileUpload: null, | |
c1aaf7a7 | 5850 | |
f5e3a61b AE |
5851 | /** |
5852 | * class name | |
5853 | * @var string | |
5854 | */ | |
5855 | _className: '', | |
0d2b7a3b | 5856 | |
f5e3a61b AE |
5857 | /** |
5858 | * iframe for IE<10 fallback | |
5859 | * @var jQuery | |
5860 | */ | |
5861 | _iframe: null, | |
5862 | ||
5863 | /** | |
5864 | * internal file id | |
5865 | * @var integer | |
5866 | */ | |
5867 | _internalFileID: 0, | |
5868 | ||
5869 | /** | |
5870 | * additional options | |
5871 | * @var jQuery | |
5872 | */ | |
5873 | _options: {}, | |
5874 | ||
5875 | /** | |
5876 | * upload matrix | |
5877 | * @var array | |
5878 | */ | |
5879 | _uploadMatrix: [], | |
5880 | ||
5881 | /** | |
5882 | * true, if the active user's browser supports ajax file uploads | |
5883 | * @var boolean | |
5884 | */ | |
5885 | _supportsAJAXUpload: true, | |
5886 | ||
5887 | /** | |
5888 | * fallback overlay for stupid browsers | |
5889 | * @var jquery | |
5890 | */ | |
5891 | _overlay: null, | |
5892 | ||
5893 | /** | |
5894 | * Initializes a new upload handler. | |
5895 | * | |
5896 | * @param string buttonSelector | |
5897 | * @param string fileListSelector | |
5898 | * @param string className | |
5899 | * @param object options | |
5900 | */ | |
5901 | init: function (buttonSelector, fileListSelector, className, options) { | |
5902 | this._buttonSelector = buttonSelector; | |
5903 | this._fileListSelector = fileListSelector; | |
5904 | this._className = className; | |
5905 | this._internalFileID = 0; | |
5906 | this._options = $.extend(true, { | |
5907 | action: 'upload', | |
5908 | multiple: false, | |
5909 | url: 'index.php?ajax-upload/&t=' + SECURITY_TOKEN | |
5910 | }, options || {}); | |
5911 | ||
5912 | this._options.url = WCF.convertLegacyURL(this._options.url); | |
5913 | if (this._options.url.indexOf('index.php') === 0) { | |
5914 | this._options.url = WSC_API_URL + this._options.url; | |
5915 | } | |
5916 | ||
5917 | // check for ajax upload support | |
5918 | var $xhr = new XMLHttpRequest(); | |
5919 | this._supportsAJAXUpload = ($xhr && ('upload' in $xhr) && ('onprogress' in $xhr.upload)); | |
5920 | ||
5921 | // create upload button | |
5922 | this._createButton(); | |
5923 | }, | |
5924 | ||
5925 | /** | |
5926 | * Creates the upload button. | |
5927 | */ | |
5928 | _createButton: function () { | |
5929 | if (this._supportsAJAXUpload) { | |
5930 | this._fileUpload = $('<input type="file" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/>'); | |
5931 | this._fileUpload.change($.proxy(this._upload, this)); | |
5932 | var $button = $('<p class="button uploadButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>'); | |
5933 | $button.prepend(this._fileUpload); | |
5934 | } | |
5935 | else { | |
5936 | var $button = $('<p class="button uploadFallbackButton"><span>' + WCF.Language.get('wcf.global.button.upload') + '</span></p>'); | |
5937 | $button.click($.proxy(this._showOverlay, this)); | |
5938 | } | |
5939 | ||
5940 | this._insertButton($button); | |
5941 | }, | |
5942 | ||
5943 | /** | |
5944 | * Inserts the upload button. | |
5945 | * | |
5946 | * @param jQuery button | |
5947 | */ | |
5948 | _insertButton: function (button) { | |
5949 | this._buttonSelector.prepend(button); | |
5950 | }, | |
5951 | ||
5952 | /** | |
5953 | * Removes the upload button. | |
5954 | */ | |
5955 | _removeButton: function () { | |
5956 | var $selector = '.uploadButton'; | |
5957 | if (!this._supportsAJAXUpload) { | |
5958 | $selector = '.uploadFallbackButton'; | |
5959 | } | |
5960 | ||
5961 | this._buttonSelector.find($selector).remove(); | |
5962 | }, | |
5963 | ||
5964 | /** | |
5965 | * Callback for file uploads. | |
5966 | * | |
5967 | * @param object event | |
5968 | * @param File file | |
5969 | * @param Blob blob | |
5970 | * @return integer | |
5971 | */ | |
5972 | _upload: function (event, file, blob) { | |
5973 | var $uploadID = null; | |
5974 | var $files = []; | |
5975 | if (file) { | |
5976 | $files.push(file); | |
5977 | } | |
5978 | else if (blob) { | |
5979 | var $ext = ''; | |
5980 | switch (blob.type) { | |
5981 | case 'image/png': | |
5982 | $ext = '.png'; | |
5983 | break; | |
5984 | ||
5985 | case 'image/jpeg': | |
5986 | $ext = '.jpg'; | |
5987 | break; | |
5988 | ||
5989 | case 'image/gif': | |
5990 | $ext = '.gif'; | |
5991 | break; | |
5992 | } | |
5993 | ||
5994 | $files.push({ | |
5995 | name: 'pasted-from-clipboard' + $ext | |
5996 | }); | |
5997 | } | |
5998 | else { | |
5999 | $files = this._fileUpload.prop('files'); | |
6000 | } | |
6001 | ||
6002 | if ($files.length) { | |
6003 | var $fd = new FormData(); | |
6004 | $uploadID = this._createUploadMatrix($files); | |
6005 | ||
6006 | // no more files left, abort | |
6007 | if (!this._uploadMatrix[$uploadID].length) { | |
6008 | return null; | |
6009 | } | |
6010 | ||
6011 | for (var $i = 0, $length = $files.length; $i < $length; $i++) { | |
6012 | if (this._uploadMatrix[$uploadID][$i]) { | |
6013 | var $internalFileID = this._uploadMatrix[$uploadID][$i].data('internalFileID'); | |
6014 | ||
6015 | if (blob) { | |
6016 | $fd.append('__files[' + $internalFileID + ']', blob, $files[$i].name); | |
6017 | } | |
6018 | else { | |
6019 | $fd.append('__files[' + $internalFileID + ']', $files[$i]); | |
6020 | } | |
6021 | } | |
6022 | } | |
6023 | ||
6024 | $fd.append('actionName', this._options.action); | |
6025 | $fd.append('className', this._className); | |
6026 | var $additionalParameters = this._getParameters(); | |
6027 | for (var $name in $additionalParameters) { | |
6028 | $fd.append('parameters[' + $name + ']', $additionalParameters[$name]); | |
6029 | } | |
6030 | ||
6031 | var self = this; | |
6032 | $.ajax({ | |
6033 | type: 'POST', | |
6034 | url: this._options.url, | |
6035 | enctype: 'multipart/form-data', | |
6036 | data: $fd, | |
6037 | contentType: false, | |
6038 | processData: false, | |
6039 | success: function (data, textStatus, jqXHR) { | |
6040 | self._success($uploadID, data); | |
6041 | }, | |
6042 | error: $.proxy(this._error, this), | |
6043 | xhr: function () { | |
6044 | var $xhr = $.ajaxSettings.xhr(); | |
6045 | if ($xhr) { | |
6046 | $xhr.upload.addEventListener('progress', function (event) { | |
6047 | self._progress($uploadID, event); | |
6048 | }, false); | |
6049 | } | |
6050 | return $xhr; | |
6051 | }, | |
6052 | xhrFields: { | |
6053 | withCredentials: true | |
6054 | } | |
6055 | }); | |
6056 | } | |
6057 | ||
6058 | return $uploadID; | |
6059 | }, | |
6060 | ||
6061 | /** | |
6062 | * Creates upload matrix for provided files. | |
6063 | * | |
6064 | * @param array<object> files | |
6065 | * @return integer | |
6066 | */ | |
6067 | _createUploadMatrix: function (files) { | |
6068 | if (files.length) { | |
6069 | var $uploadID = this._uploadMatrix.length; | |
6070 | this._uploadMatrix[$uploadID] = []; | |
6071 | ||
6072 | for (var $i = 0, $length = files.length; $i < $length; $i++) { | |
6073 | var $file = files[$i]; | |
6074 | var $li = this._initFile($file); | |
6075 | ||
6076 | if (!$li.hasClass('uploadFailed')) { | |
6077 | $li.data('filename', $file.name).data('internalFileID', this._internalFileID++); | |
6078 | this._uploadMatrix[$uploadID][$i] = $li; | |
6079 | } | |
6080 | } | |
6081 | ||
6082 | return $uploadID; | |
6083 | } | |
6084 | ||
6085 | return null; | |
6086 | }, | |
6087 | ||
6088 | /** | |
6089 | * Callback for success event. | |
6090 | * | |
6091 | * @param integer uploadID | |
6092 | * @param object data | |
6093 | */ | |
6094 | _success: function (uploadID, data) { | |
6095 | }, | |
6096 | ||
6097 | /** | |
6098 | * Callback for error event. | |
6099 | * | |
6100 | * @param jQuery jqXHR | |
6101 | * @param string textStatus | |
6102 | * @param string errorThrown | |
6103 | */ | |
6104 | _error: function (jqXHR, textStatus, errorThrown) { | |
6105 | }, | |
6106 | ||
6107 | /** | |
6108 | * Callback for progress event. | |
6109 | * | |
6110 | * @param integer uploadID | |
6111 | * @param object event | |
6112 | */ | |
6113 | _progress: function (uploadID, event) { | |
6114 | var $percentComplete = Math.round(event.loaded * 100 / event.total); | |
6115 | ||
6116 | for (var $i in this._uploadMatrix[uploadID]) { | |
6117 | this._uploadMatrix[uploadID][$i].find('progress').attr('value', $percentComplete); | |
6118 | } | |
6119 | }, | |
6120 | ||
6121 | /** | |
6122 | * Returns additional parameters. | |
6123 | * | |
6124 | * @return object | |
6125 | */ | |
6126 | _getParameters: function () { | |
6127 | return {}; | |
6128 | }, | |
6129 | ||
6130 | /** | |
6131 | * Initializes list item for uploaded file. | |
6132 | * | |
6133 | * @return jQuery | |
6134 | */ | |
6135 | _initFile: function (file) { | |
6136 | return $('<li>' + file.name + ' (' + file.size + ')<progress max="100" /></li>').appendTo(this._fileListSelector); | |
6137 | }, | |
6138 | ||
6139 | /** | |
6140 | * Shows the fallback overlay (work in progress) | |
6141 | */ | |
6142 | _showOverlay: function () { | |
6143 | // create iframe | |
6144 | if (this._iframe === null) { | |
6145 | this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body); | |
6146 | } | |
6147 | ||
6148 | // create overlay | |
6149 | if (!this._overlay) { | |
6150 | this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body); | |
6151 | ||
6152 | var $form = this._overlay.find('form'); | |
6153 | $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form); | |
6154 | $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form); | |
6155 | ||
6156 | $('<input type="hidden" name="isFallback" value="1" />').appendTo($form); | |
6157 | $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form); | |
6158 | $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form); | |
6159 | var $additionalParameters = this._getParameters(); | |
6160 | for (var $name in $additionalParameters) { | |
6161 | $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form); | |
6162 | } | |
6163 | ||
6164 | $form.submit($.proxy(function () { | |
6165 | var $file = { | |
6166 | name: this._getFilename(), | |
6167 | size: '' | |
6168 | }; | |
6169 | ||
6170 | var $uploadID = this._createUploadMatrix([$file]); | |
6171 | var self = this; | |
6172 | this._iframe.data('loading', true).off('load').load(function () { | |
6173 | self._evaluateResponse($uploadID); | |
6174 | }); | |
6175 | this._overlay.wcfDialog('close'); | |
6176 | }, this)); | |
6177 | } | |
6178 | ||
6179 | this._overlay.wcfDialog({ | |
6180 | title: WCF.Language.get('wcf.global.button.upload') | |
6181 | }); | |
6182 | }, | |
6183 | ||
6184 | /** | |
6185 | * Evaluates iframe response. | |
6186 | * | |
6187 | * @param integer uploadID | |
6188 | */ | |
6189 | _evaluateResponse: function (uploadID) { | |
6190 | var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html()); | |
6191 | this._success(uploadID, $returnValues); | |
6192 | }, | |
6193 | ||
6194 | /** | |
6195 | * Returns name of selected file. | |
6196 | * | |
6197 | * @return string | |
6198 | */ | |
6199 | _getFilename: function () { | |
6200 | return $('#__fileUpload').val().split('\\').pop(); | |
6201 | } | |
6202 | }); | |
0d2b7a3b | 6203 | |
c1aaf7a7 | 6204 | /** |
f5e3a61b AE |
6205 | * Default implementation for parallel AJAX file uploads. |
6206 | * | |
6207 | * @deprecated Use WoltLabSuite/Core/Upload | |
c1aaf7a7 | 6208 | */ |
f5e3a61b AE |
6209 | WCF.Upload.Parallel = WCF.Upload.extend({ |
6210 | /** | |
6211 | * @see WCF.Upload.init() | |
6212 | */ | |
6213 | init: function (buttonSelector, fileListSelector, className, options) { | |
6214 | // force multiple uploads | |
6215 | options = $.extend(true, options || {}, { | |
6216 | multiple: true | |
b2cc4656 | 6217 | }); |
404c9f0d | 6218 | |
f5e3a61b AE |
6219 | this._super(buttonSelector, fileListSelector, className, options); |
6220 | }, | |
6221 | ||
6222 | /** | |
6223 | * @see WCF.Upload._upload() | |
6224 | */ | |
6225 | _upload: function () { | |
6226 | var $files = this._fileUpload.prop('files'); | |
e919d1d3 | 6227 | for (var $i = 0, $length = $files.length; $i < $length; $i++) { |
f5e3a61b AE |
6228 | var $file = $files[$i]; |
6229 | var $formData = new FormData(); | |
6230 | var $internalFileID = this._createUploadMatrix($file); | |
6231 | ||
6232 | if (!this._uploadMatrix[$internalFileID].length) { | |
6233 | continue; | |
404c9f0d | 6234 | } |
f5e3a61b AE |
6235 | |
6236 | $formData.append('__files[' + $internalFileID + ']', $file); | |
6237 | $formData.append('actionName', this._options.action); | |
6238 | $formData.append('className', this._className); | |
6239 | var $additionalParameters = this._getParameters(); | |
6240 | for (var $name in $additionalParameters) { | |
6241 | $formData.append('parameters[' + $name + ']', $additionalParameters[$name]); | |
6242 | } | |
6243 | ||
6244 | this._sendRequest($internalFileID, $formData); | |
c1aaf7a7 | 6245 | } |
f5e3a61b AE |
6246 | }, |
6247 | ||
6248 | /** | |
6249 | * Sends an AJAX request to upload a file. | |
6250 | * | |
6251 | * @param integer internalFileID | |
6252 | * @param FormData formData | |
c9b1260d | 6253 | * @return jqXHR |
f5e3a61b AE |
6254 | */ |
6255 | _sendRequest: function (internalFileID, formData) { | |
e919d1d3 | 6256 | var self = this; |
c9b1260d MS |
6257 | |
6258 | return $.ajax({ | |
c1aaf7a7 MW |
6259 | type: 'POST', |
6260 | url: this._options.url, | |
6261 | enctype: 'multipart/form-data', | |
f5e3a61b | 6262 | data: formData, |
c1aaf7a7 MW |
6263 | contentType: false, |
6264 | processData: false, | |
f5e3a61b AE |
6265 | success: function (data, textStatus, jqXHR) { |
6266 | self._success(internalFileID, data); | |
32f6cd95 | 6267 | }, |
c1aaf7a7 | 6268 | error: $.proxy(this._error, this), |
f5e3a61b | 6269 | xhr: function () { |
c1aaf7a7 MW |
6270 | var $xhr = $.ajaxSettings.xhr(); |
6271 | if ($xhr) { | |
f5e3a61b AE |
6272 | $xhr.upload.addEventListener('progress', function (event) { |
6273 | self._progress(internalFileID, event); | |
c1aaf7a7 MW |
6274 | }, false); |
6275 | } | |
6276 | return $xhr; | |
6277 | } | |
6278 | }); | |
f5e3a61b | 6279 | }, |
c1aaf7a7 | 6280 | |
f5e3a61b AE |
6281 | /** |
6282 | * Creates upload matrix for provided file and returns its internal file id. | |
6283 | * | |
6284 | * @param object file | |
6285 | * @return integer | |
6286 | */ | |
6287 | _createUploadMatrix: function (file) { | |
6288 | var $li = this._initFile(file); | |
6289 | if (!$li.hasClass('uploadFailed')) { | |
6290 | $li.data('filename', file.name).data('internalFileID', this._internalFileID); | |
6291 | this._uploadMatrix[this._internalFileID++] = $li; | |
e919d1d3 | 6292 | |
f5e3a61b | 6293 | return this._internalFileID - 1; |
dedc6a49 MS |
6294 | } |
6295 | ||
f5e3a61b AE |
6296 | return null; |
6297 | }, | |
ee1a6ccb | 6298 | |
f5e3a61b AE |
6299 | /** |
6300 | * Callback for success event. | |
6301 | * | |
6302 | * @param integer internalFileID | |
6303 | * @param object data | |
6304 | */ | |
6305 | _success: function (internalFileID, data) { | |
6306 | }, | |
afa0b934 | 6307 | |
f5e3a61b AE |
6308 | /** |
6309 | * Callback for progress event. | |
6310 | * | |
6311 | * @param integer internalFileID | |
6312 | * @param object event | |
6313 | */ | |
6314 | _progress: function (internalFileID, event) { | |
6315 | var $percentComplete = Math.round(event.loaded * 100 / event.total); | |
f64c8912 | 6316 | |
f5e3a61b AE |
6317 | this._uploadMatrix[internalFileID].find('progress').attr('value', $percentComplete); |
6318 | }, | |
d1a6b448 | 6319 | |
f5e3a61b AE |
6320 | /** |
6321 | * @see WCF.Upload._showOverlay() | |
6322 | */ | |
6323 | _showOverlay: function () { | |
6324 | // create iframe | |
6325 | if (this._iframe === null) { | |
6326 | this._iframe = $('<iframe name="__fileUploadIFrame" />').hide().appendTo(document.body); | |
6327 | } | |
6328 | ||
6329 | // create overlay | |
6330 | if (!this._overlay) { | |
6331 | this._overlay = $('<div><form enctype="multipart/form-data" method="post" action="' + this._options.url + '" target="__fileUploadIFrame" /></div>').hide().appendTo(document.body); | |
6332 | ||
6333 | var $form = this._overlay.find('form'); | |
6334 | $('<dl class="wide"><dd><input type="file" id="__fileUpload" name="' + this._name + '" ' + (this._options.multiple ? 'multiple="true" ' : '') + '/></dd></dl>').appendTo($form); | |
6335 | $('<div class="formSubmit"><input type="submit" value="Upload" accesskey="s" /></div></form>').appendTo($form); | |
6336 | ||
6337 | $('<input type="hidden" name="isFallback" value="1" />').appendTo($form); | |
6338 | $('<input type="hidden" name="actionName" value="' + this._options.action + '" />').appendTo($form); | |
6339 | $('<input type="hidden" name="className" value="' + this._className + '" />').appendTo($form); | |
6340 | var $additionalParameters = this._getParameters(); | |
6341 | for (var $name in $additionalParameters) { | |
6342 | $('<input type="hidden" name="' + $name + '" value="' + $additionalParameters[$name] + '" />').appendTo($form); | |
a6e646a3 | 6343 | } |
f5e3a61b AE |
6344 | |
6345 | $form.submit($.proxy(function () { | |
6346 | var $file = { | |
6347 | name: this._getFilename(), | |
6348 | size: '' | |
6349 | }; | |
6350 | ||
6351 | var $internalFileID = this._createUploadMatrix($file); | |
6352 | var self = this; | |
6353 | this._iframe.data('loading', true).off('load').load(function () { | |
6354 | self._evaluateResponse($internalFileID); | |
6355 | }); | |
6356 | this._overlay.wcfDialog('close'); | |
6357 | }, this)); | |
a6e646a3 AE |
6358 | } |
6359 | ||
f5e3a61b AE |
6360 | this._overlay.wcfDialog({ |
6361 | title: WCF.Language.get('wcf.global.button.upload') | |
6362 | }); | |
6363 | }, | |
6364 | ||
6365 | /** | |
6366 | * Evaluates iframe response. | |
6367 | * | |
6368 | * @param integer internalFileID | |
6369 | */ | |
6370 | _evaluateResponse: function (internalFileID) { | |
6371 | var $returnValues = $.parseJSON(this._iframe.contents().find('pre').html()); | |
6372 | this._success(internalFileID, $returnValues); | |
6d8f21ac | 6373 | } |
f5e3a61b AE |
6374 | }); |
6375 | } | |
6376 | else { | |
6377 | WCF.System.Worker = Class.extend({ | |
6378 | _aborted: false, | |
6379 | _actionName: "", | |
6380 | _callback: {}, | |
6381 | _className: "", | |
6382 | _dialog: {}, | |
6383 | _proxy: {}, | |
6384 | _title: "", | |
6385 | init: function() {}, | |
6386 | _success: function() {} | |
6387 | }); | |
6388 | ||
6389 | WCF.InlineEditor = Class.extend({ | |
6390 | _callbacks: {}, | |
6391 | _dropdowns: {}, | |
6392 | _elements: {}, | |
6393 | _notification: {}, | |
6394 | _options: {}, | |
6395 | _proxy: {}, | |
6396 | _triggerElements: {}, | |
6397 | _updateData: {}, | |
6398 | init: function() {}, | |
6399 | _closeAll: function() {}, | |
6400 | _setOptions: function() {}, | |
6401 | registerCallback: function() {}, | |
6402 | _getTriggerElement: function() {}, | |
6403 | _show: function() {}, | |
6404 | _validate: function() {}, | |
6405 | _validateCallbacks: function() {}, | |
6406 | _success: function() {}, | |
6407 | _updateState: function() {}, | |
6408 | _click: function() {}, | |
6409 | _execute: function() {}, | |
6410 | _executeCallback: function() {}, | |
6411 | _hide: function() {} | |
6412 | }); | |
6413 | ||
6414 | WCF.Upload = Class.extend({ | |
6415 | _name: "", | |
6416 | _buttonSelector: {}, | |
6417 | _fileListSelector: {}, | |
6418 | _fileUpload: {}, | |
6419 | _className: "", | |
6420 | _iframe: {}, | |
6421 | _internalFileID: 0, | |
6422 | _options: {}, | |
6423 | _uploadMatrix: {}, | |
6424 | _supportsAJAXUpload: true, | |
6425 | _overlay: {}, | |
6426 | init: function() {}, | |
6427 | _createButton: function() {}, | |
6428 | _insertButton: function() {}, | |
6429 | _removeButton: function() {}, | |
6430 | _upload: function() {}, | |
6431 | _createUploadMatrix: function() {}, | |
6432 | _success: function() {}, | |
6433 | _error: function() {}, | |
6434 | _progress: function() {}, | |
6435 | _getParameters: function() {}, | |
6436 | _initFile: function() {}, | |
6437 | _showOverlay: function() {}, | |
6438 | _evaluateResponse: function() {}, | |
6439 | _getFilename: function() {} | |
6440 | }); | |
6441 | ||
6442 | WCF.Upload.Parallel = WCF.Upload.extend({ | |
6443 | init: function() {}, | |
6444 | _upload: function() {}, | |
6445 | _sendRequest: function() {}, | |
6446 | _createUploadMatrix: function() {}, | |
6447 | _success: function() {}, | |
6448 | _progress: function() {}, | |
6449 | _showOverlay: function() {}, | |
6450 | _evaluateResponse: function() {}, | |
6451 | _name: "", | |
6452 | _buttonSelector: {}, | |
6453 | _fileListSelector: {}, | |
6454 | _fileUpload: {}, | |
6455 | _className: "", | |
6456 | _iframe: {}, | |
6457 | _internalFileID: 0, | |
6458 | _options: {}, | |
6459 | _uploadMatrix: {}, | |
6460 | _supportsAJAXUpload: true, | |
6461 | _overlay: {}, | |
6462 | _createButton: function() {}, | |
6463 | _insertButton: function() {}, | |
6464 | _removeButton: function() {}, | |
6465 | _error: function() {}, | |
6466 | _getParameters: function() {}, | |
6467 | _initFile: function() {}, | |
6468 | _getFilename: function() {} | |
6469 | }); | |
6470 | } | |
6471 | ||
6472 | /** | |
6473 | * Namespace for sortables. | |
6474 | */ | |
6475 | WCF.Sortable = { }; | |
6476 | ||
6477 | if (COMPILER_TARGET_DEFAULT) { | |
d8475f48 | 6478 | /** |
f5e3a61b AE |
6479 | * Sortable implementation for lists. |
6480 | * | |
6481 | * @param string containerID | |
6482 | * @param string className | |
6483 | * @param integer offset | |
6484 | * @param object options | |
d8475f48 | 6485 | */ |
f5e3a61b AE |
6486 | WCF.Sortable.List = Class.extend({ |
6487 | /** | |
6488 | * additional parameters for AJAX request | |
6489 | * @var object | |
6490 | */ | |
6491 | _additionalParameters: {}, | |
d8475f48 | 6492 | |
f5e3a61b AE |
6493 | /** |
6494 | * action class name | |
6495 | * @var string | |
6496 | */ | |
6497 | _className: '', | |
6498 | ||
6499 | /** | |
6500 | * container id | |
6501 | * @var string | |
6502 | */ | |
6503 | _containerID: '', | |
6504 | ||
6505 | /** | |
6506 | * container object | |
6507 | * @var jQuery | |
6508 | */ | |
6509 | _container: null, | |
6510 | ||
6511 | /** | |
6512 | * notification object | |
6513 | * @var WCF.System.Notification | |
6514 | */ | |
6515 | _notification: null, | |
6516 | ||
6517 | /** | |
6518 | * show order offset | |
6519 | * @var integer | |
6520 | */ | |
6521 | _offset: 0, | |
6522 | ||
6523 | /** | |
6524 | * list of options | |
6525 | * @var object | |
6526 | */ | |
6527 | _options: {}, | |
6528 | ||
6529 | /** | |
6530 | * proxy object | |
6531 | * @var WCF.Action.Proxy | |
6532 | */ | |
6533 | _proxy: null, | |
6534 | ||
6535 | /** | |
6536 | * object structure | |
6537 | * @var object | |
6538 | */ | |
6539 | _structure: {}, | |
2a16194a | 6540 | |
f5e3a61b AE |
6541 | /** |
6542 | * Creates a new sortable list. | |
6543 | * | |
6544 | * @param string containerID | |
6545 | * @param string className | |
6546 | * @param integer offset | |
6547 | * @param object options | |
6548 | * @param boolean isSimpleSorting | |
6549 | * @param object additionalParameters | |
6550 | */ | |
6551 | init: function (containerID, className, offset, options, isSimpleSorting, additionalParameters) { | |
6552 | this._additionalParameters = additionalParameters || {}; | |
6553 | this._containerID = $.wcfEscapeID(containerID); | |
6554 | this._container = $('#' + this._containerID); | |
6555 | this._className = className; | |
6556 | this._offset = (offset) ? offset : 0; | |
6557 | this._proxy = new WCF.Action.Proxy({ | |
6558 | success: $.proxy(this._success, this) | |
6559 | }); | |
6560 | this._structure = {}; | |
ee1a6ccb | 6561 | |
f5e3a61b AE |
6562 | // init sortable |
6563 | this._options = $.extend(true, { | |
6564 | axis: 'y', | |
6565 | connectWith: '#' + this._containerID + ' .sortableList', | |
6566 | disableNesting: 'sortableNoNesting', | |
6567 | doNotClear: true, | |
6568 | errorClass: 'sortableInvalidTarget', | |
6569 | forcePlaceholderSize: true, | |
6570 | handle: '', | |
6571 | helper: 'clone', | |
6572 | items: 'li:not(.sortableNoSorting)', | |
6573 | opacity: .6, | |
6574 | placeholder: 'sortablePlaceholder', | |
6575 | tolerance: 'pointer', | |
6576 | toleranceElement: '> span' | |
6577 | }, options || {}); | |
6578 | ||
6579 | var sortableList = $('#' + this._containerID + ' .sortableList'); | |
6580 | if (sortableList.is('tbody') && this._options.helper === 'clone') { | |
6581 | this._options.helper = this._tableRowHelper.bind(this); | |
6582 | ||
6583 | // explicitly set column widths to avoid column resizing during dragging | |
6584 | var thead = sortableList.prev('thead'); | |
6585 | if (thead) { | |
6586 | thead.find('th').each(function (index, element) { | |
6587 | element = $(element); | |
6588 | ||
6589 | element.width(element.width()); | |
6590 | }); | |
6591 | } | |
6592 | } | |
6593 | ||
6594 | if (isSimpleSorting) { | |
6595 | sortableList.sortable(this._options); | |
6596 | } | |
6597 | else { | |
6598 | sortableList.nestedSortable(this._options); | |
6599 | } | |
6600 | ||
6601 | if (this._className) { | |
6602 | var $formSubmit = this._container.find('.formSubmit'); | |
6603 | if (!$formSubmit.length) { | |
6604 | $formSubmit = this._container.next('.formSubmit'); | |
6605 | if (!$formSubmit.length) { | |
6606 | console.debug("[WCF.Sortable.Simple] Unable to find form submit for saving, aborting."); | |
6607 | return; | |
83a29d77 | 6608 | } |
f5e3a61b AE |
6609 | } |
6610 | ||
6611 | $formSubmit.children('button[data-type="submit"]').click($.proxy(this._submit, this)); | |
83a29d77 | 6612 | } |
f5e3a61b | 6613 | }, |
ee1a6ccb | 6614 | |
f5e3a61b AE |
6615 | /** |
6616 | * Fixes the width of the cells of the dragged table row. | |
6617 | * | |
6618 | * @param {Event} event | |
6619 | * @param {jQuery} ui | |
6620 | * @return {jQuery} | |
6621 | */ | |
6622 | _tableRowHelper: function (event, ui) { | |
6623 | ui.children('td').each(function (index, element) { | |
6624 | element = $(element); | |
6625 | ||
6626 | element.width(element.width()); | |
6627 | }); | |
6628 | ||
6629 | return ui; | |
6630 | }, | |
2a16194a | 6631 | |
f5e3a61b AE |
6632 | /** |
6633 | * Saves object structure. | |
6634 | */ | |
6635 | _submit: function () { | |
6636 | // reset structure | |
6637 | this._structure = {}; | |
6638 | ||
6639 | // build structure | |
6640 | this._container.find('.sortableList').each($.proxy(function (index, list) { | |
6641 | var $list = $(list); | |
6642 | var $parentID = $list.data('objectID'); | |
6643 | ||
6644 | if ($parentID !== undefined) { | |
6645 | $list.children(this._options.items).each($.proxy(function (index, listItem) { | |
6646 | var $objectID = $(listItem).data('objectID'); | |
6647 | ||
6648 | if (!this._structure[$parentID]) { | |
6649 | this._structure[$parentID] = []; | |
6650 | } | |
6651 | ||
6652 | this._structure[$parentID].push($objectID); | |
6653 | }, this)); | |
6654 | } | |
6655 | }, this)); | |
6656 | ||
6657 | // send request | |
6658 | var $parameters = $.extend(true, { | |
6659 | data: { | |
6660 | offset: this._offset, | |
6661 | structure: this._structure | |
6662 | } | |
6663 | }, this._additionalParameters); | |
6664 | ||
6665 | this._proxy.setOption('data', { | |
6666 | actionName: 'updatePosition', | |
6667 | className: this._className, | |
6668 | interfaceName: 'wcf\\data\\ISortableAction', | |
6669 | parameters: $parameters | |
6670 | }); | |
6671 | this._proxy.sendRequest(); | |
6672 | }, | |
ee1a6ccb | 6673 | |
f5e3a61b AE |
6674 | /** |
6675 | * Shows notification upon success. | |
6676 | * | |
6677 | * @param object data | |
6678 | * @param string textStatus | |
6679 | * @param jQuery jqXHR | |
6680 | */ | |
6681 | _success: function (data, textStatus, jqXHR) { | |
6682 | if (this._notification === null) { | |
6683 | this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success.edit')); | |
6684 | } | |
6685 | ||
6686 | this._notification.show(); | |
6687 | } | |
6688 | }); | |
6689 | } | |
6690 | else { | |
6691 | WCF.Sortable.List = Class.extend({ | |
6692 | _additionalParameters: {}, | |
6693 | _className: "", | |
6694 | _containerID: "", | |
6695 | _container: {}, | |
6696 | _notification: {}, | |
6697 | _offset: 0, | |
6698 | _options: {}, | |
6699 | _proxy: {}, | |
6700 | _structure: {}, | |
6701 | init: function() {}, | |
6702 | _tableRowHelper: function() {}, | |
6703 | _submit: function() {}, | |
6704 | _success: function() {} | |
6705 | }); | |
6706 | } | |
ee1a6ccb | 6707 | |
453aced6 AE |
6708 | WCF.Popover = Class.extend({ |
6709 | /** | |
6710 | * currently active element id | |
6711 | * @var string | |
6712 | */ | |
6713 | _activeElementID: '', | |
6714 | ||
b8d16c73 AE |
6715 | _identifier: '', |
6716 | _popoverObj: null, | |
453aced6 AE |
6717 | |
6718 | /** | |
6719 | * Initializes a new WCF.Popover object. | |
6720 | * | |
6721 | * @param string selector | |
6722 | */ | |
6723 | init: function(selector) { | |
818d71a4 MS |
6724 | var mobile = false; |
6725 | require(['Environment'], function(Environment) { | |
6726 | if (Environment.platform() !== 'desktop') { | |
6727 | mobile = true; | |
6728 | } | |
6729 | }.bind(this)); | |
6730 | if (mobile) return; | |
3f42eecb | 6731 | |
453aced6 AE |
6732 | // assign default values |
6733 | this._activeElementID = ''; | |
b8d16c73 AE |
6734 | this._identifier = selector; |
6735 | ||
58d7e8f8 | 6736 | require(['WoltLabSuite/Core/Controller/Popover'], (function(popover) { |
b8d16c73 AE |
6737 | popover.init({ |
6738 | attributeName: 'legacy', | |
6739 | className: selector, | |
6740 | identifier: this._identifier, | |
6741 | legacy: true, | |
6742 | loadCallback: this._legacyLoad.bind(this) | |
5b755307 | 6743 | }); |
b8d16c73 | 6744 | }).bind(this)); |
453aced6 AE |
6745 | }, |
6746 | ||
b8d16c73 | 6747 | _initContainers: function() {}, |
453aced6 | 6748 | |
b8d16c73 AE |
6749 | _legacyLoad: function(objectId, popover) { |
6750 | this._activeElementID = objectId; | |
6751 | this._popoverObj = popover; | |
453aced6 | 6752 | |
b8d16c73 | 6753 | this._loadContent(); |
523908ab AE |
6754 | }, |
6755 | ||
b8d16c73 AE |
6756 | _insertContent: function(elementId, template) { |
6757 | this._popoverObj.setContent(this._identifier, elementId, template); | |
453aced6 AE |
6758 | } |
6759 | }); | |
6760 | ||
cede0aec AE |
6761 | /** |
6762 | * Provides an extensible item list with built-in search. | |
6763 | * | |
6764 | * @param string itemListSelector | |
6765 | * @param string searchInputSelector | |
6766 | */ | |
d27e2667 | 6767 | WCF.EditableItemList = Class.extend({ |
cede0aec AE |
6768 | /** |
6769 | * allows custom input not recognized by search to be added | |
6770 | * @var boolean | |
6771 | */ | |
6772 | _allowCustomInput: false, | |
6773 | ||
6774 | /** | |
6775 | * action class name | |
6776 | * @var string | |
6777 | */ | |
6778 | _className: '', | |
6779 | ||
6780 | /** | |
6781 | * internal data storage | |
6782 | * @var mixed | |
6783 | */ | |
d27e2667 | 6784 | _data: { }, |
cede0aec AE |
6785 | |
6786 | /** | |
6787 | * form container | |
6788 | * @var jQuery | |
6789 | */ | |
6790 | _form: null, | |
6791 | ||
6792 | /** | |
6793 | * item list container | |
6794 | * @var jQuery | |
6795 | */ | |
d27e2667 | 6796 | _itemList: null, |
cede0aec AE |
6797 | |
6798 | /** | |
6799 | * current object id | |
6800 | * @var integer | |
6801 | */ | |
6802 | _objectID: 0, | |
6803 | ||
6804 | /** | |
6805 | * object type id | |
6806 | * @var integer | |
6807 | */ | |
6808 | _objectTypeID: 0, | |
6809 | ||
6810 | /** | |
6811 | * search controller | |
6812 | * @var WCF.Search.Base | |
6813 | */ | |
d27e2667 | 6814 | _search: null, |
cede0aec AE |
6815 | |
6816 | /** | |
6817 | * search input element | |
6818 | * @var jQuery | |
6819 | */ | |
d27e2667 AE |
6820 | _searchInput: null, |
6821 | ||
cede0aec AE |
6822 | /** |
6823 | * Creates a new WCF.EditableItemList object. | |
6824 | * | |
6825 | * @param string itemListSelector | |
6826 | * @param string searchInputSelector | |
6827 | */ | |
d27e2667 AE |
6828 | init: function(itemListSelector, searchInputSelector) { |
6829 | this._itemList = $(itemListSelector); | |
6830 | this._searchInput = $(searchInputSelector); | |
0579855e | 6831 | this._data = { }; |
d27e2667 AE |
6832 | |
6833 | if (!this._itemList.length || !this._searchInput.length) { | |
6834 | console.debug("[WCF.EditableItemList] Item list and/or search input do not exist, aborting."); | |
6835 | return; | |
6836 | } | |
6837 | ||
cede0aec AE |
6838 | this._objectID = this._getObjectID(); |
6839 | this._objectTypeID = this._getObjectTypeID(); | |
6840 | ||
d27e2667 AE |
6841 | // bind item listener |
6842 | this._itemList.find('.jsEditableItem').click($.proxy(this._click, this)); | |
cede0aec AE |
6843 | |
6844 | // create item list | |
6845 | if (!this._itemList.children('ul').length) { | |
6846 | $('<ul />').appendTo(this._itemList); | |
6847 | } | |
6848 | this._itemList = this._itemList.children('ul'); | |
6849 | ||
6850 | // bind form submit | |
6851 | this._form = this._itemList.parents('form').submit($.proxy(this._submit, this)); | |
6852 | ||
6853 | if (this._allowCustomInput) { | |
3289653d | 6854 | var self = this; |
0328aea9 | 6855 | this._searchInput.keydown($.proxy(this._keyDown, this)).keypress($.proxy(this._keyPress, this)).on('paste', function() { |
3289653d AE |
6856 | setTimeout(function() { self._onPaste(); }, 100); |
6857 | }); | |
80da9de3 | 6858 | } |
7c6f7523 AE |
6859 | |
6860 | // block form submit through [ENTER] | |
6861 | this._searchInput.parents('.dropdown').data('preventSubmit', true); | |
80da9de3 AE |
6862 | }, |
6863 | ||
6864 | /** | |
6865 | * Handles the key down event. | |
6866 | * | |
6867 | * @param object event | |
6868 | */ | |
6869 | _keyDown: function(event) { | |
0328aea9 AE |
6870 | if (event === null) { |
6871 | return this._keyPress(null); | |
6872 | } | |
6873 | ||
6874 | return true; | |
6875 | }, | |
6876 | ||
6877 | /** | |
6878 | * Handles the key press event. | |
6879 | * | |
6880 | * @param object event | |
6881 | */ | |
6882 | _keyPress: function(event) { | |
6883 | // 44 = [,] (charCode != keyCode) | |
1e06deca | 6884 | if (event === null || event.charCode === 44 || event.charCode === $.ui.keyCode.ENTER || ($.browser.mozilla && event.keyCode === $.ui.keyCode.ENTER)) { |
0328aea9 | 6885 | if (event !== null && event.charCode === $.ui.keyCode.ENTER && this._search) { |
7c6f7523 AE |
6886 | if (this._search._itemIndex !== -1) { |
6887 | return false; | |
6888 | } | |
6889 | } | |
6890 | ||
80da9de3 | 6891 | var $value = $.trim(this._searchInput.val()); |
e3423595 AE |
6892 | |
6893 | // read everything left from caret position | |
0328aea9 | 6894 | if (event && event.charCode === 44) { |
e3423595 AE |
6895 | $value = $value.substring(0, this._searchInput.getCaret()); |
6896 | } | |
9bc7b242 | 6897 | |
80da9de3 | 6898 | if ($value === '') { |
cede0aec | 6899 | return true; |
80da9de3 AE |
6900 | } |
6901 | ||
6902 | this.addItem({ | |
6903 | objectID: 0, | |
6904 | label: $value | |
cede0aec | 6905 | }); |
80da9de3 AE |
6906 | |
6907 | // reset input | |
0328aea9 | 6908 | if (event && event.charCode === 44) { |
e3423595 AE |
6909 | this._searchInput.val($.trim(this._searchInput.val().substr(this._searchInput.getCaret()))); |
6910 | } | |
6911 | else { | |
6912 | this._searchInput.val(''); | |
6913 | } | |
80da9de3 AE |
6914 | |
6915 | if (event !== null) { | |
6916 | event.stopPropagation(); | |
6917 | } | |
6918 | ||
6919 | return false; | |
cede0aec | 6920 | } |
80da9de3 AE |
6921 | |
6922 | return true; | |
d27e2667 AE |
6923 | }, |
6924 | ||
3289653d AE |
6925 | /** |
6926 | * Handle paste event. | |
6927 | */ | |
6928 | _onPaste: function() { | |
6929 | // split content by comma | |
6930 | var $value = $.trim(this._searchInput.val()); | |
6931 | $value = $value.split(','); | |
6932 | ||
6933 | for (var $i = 0, $length = $value.length; $i < $length; $i++) { | |
6934 | var $label = $.trim($value[$i]); | |
6935 | if ($label === '') { | |
6936 | continue; | |
6937 | } | |
6938 | ||
6939 | this.addItem({ | |
6940 | objectID: 0, | |
6941 | label: $label | |
6942 | }); | |
6943 | } | |
6944 | ||
6945 | this._searchInput.val(''); | |
6946 | }, | |
6947 | ||
d27e2667 AE |
6948 | /** |
6949 | * Loads raw data and converts it into internal structure. Override this methods | |
6950 | * in your derived classes. | |
6951 | * | |
6952 | * @param object data | |
6953 | */ | |
6954 | load: function(data) { }, | |
6955 | ||
cede0aec AE |
6956 | /** |
6957 | * Removes an item on click. | |
6958 | * | |
6959 | * @param object event | |
6960 | * @return boolean | |
6961 | */ | |
d27e2667 AE |
6962 | _click: function(event) { |
6963 | var $element = $(event.currentTarget); | |
6964 | var $objectID = $element.data('objectID'); | |
cede0aec | 6965 | var $label = $element.data('label'); |
d27e2667 | 6966 | |
0b9520f4 MS |
6967 | if (this._search) { |
6968 | this._search.removeExcludedSearchValue($label); | |
6969 | } | |
cede0aec | 6970 | this._removeItem($objectID, $label); |
d27e2667 AE |
6971 | |
6972 | $element.remove(); | |
6973 | ||
6974 | event.stopPropagation(); | |
6975 | return false; | |
6976 | }, | |
6977 | ||
cede0aec AE |
6978 | /** |
6979 | * Returns current object id. | |
6980 | * | |
6981 | * @return integer | |
6982 | */ | |
6983 | _getObjectID: function() { | |
6984 | return 0; | |
6985 | }, | |
6986 | ||
6987 | /** | |
6988 | * Returns current object type id. | |
6989 | * | |
6990 | * @return integer | |
6991 | */ | |
6992 | _getObjectTypeID: function() { | |
6993 | return 0; | |
6994 | }, | |
6995 | ||
6996 | /** | |
6997 | * Adds a new item to the list. | |
6998 | * | |
6999 | * @param object data | |
7000 | * @return boolean | |
7001 | */ | |
7002 | addItem: function(data) { | |
7003 | if (this._data[data.objectID]) { | |
8717050f AE |
7004 | if (!(data.objectID === 0 && this._allowCustomInput)) { |
7005 | return true; | |
7006 | } | |
cede0aec AE |
7007 | } |
7008 | ||
e5f1745c | 7009 | var $listItem = $('<li class="badge">' + WCF.String.escapeHTML(data.label) + '</li>').data('objectID', data.objectID).data('label', data.label).appendTo(this._itemList); |
cede0aec | 7010 | $listItem.click($.proxy(this._click, this)); |
d27e2667 | 7011 | |
0b9520f4 MS |
7012 | if (this._search) { |
7013 | this._search.addExcludedSearchValue(data.label); | |
7014 | } | |
cede0aec AE |
7015 | this._addItem(data.objectID, data.label); |
7016 | ||
7017 | return true; | |
7018 | }, | |
7019 | ||
4d28f1de MS |
7020 | /** |
7021 | * Clears the list of items. | |
7022 | */ | |
7023 | clearList: function() { | |
7024 | this._itemList.children('li').each($.proxy(function(index, element) { | |
7025 | var $element = $(element); | |
7026 | ||
7027 | if (this._search) { | |
7028 | this._search.removeExcludedSearchValue($element.data('label')); | |
7029 | } | |
7030 | ||
7031 | $element.remove(); | |
7032 | this._removeItem($element.data('objectID'), $element.data('label')); | |
7033 | }, this)); | |
7034 | }, | |
7035 | ||
cede0aec AE |
7036 | /** |
7037 | * Handles form submit, override in your class. | |
7038 | */ | |
80da9de3 AE |
7039 | _submit: function() { |
7040 | this._keyDown(null); | |
7041 | }, | |
cede0aec AE |
7042 | |
7043 | /** | |
7044 | * Adds an item to internal storage. | |
7045 | * | |
7046 | * @param integer objectID | |
7047 | * @param string label | |
7048 | */ | |
7049 | _addItem: function(objectID, label) { | |
7050 | this._data[objectID] = label; | |
7051 | }, | |
7052 | ||
7053 | /** | |
7054 | * Removes an item from internal storage. | |
7055 | * | |
7056 | * @param integer objectID | |
7057 | * @param string label | |
7058 | */ | |
7059 | _removeItem: function(objectID, label) { | |
7060 | delete this._data[objectID]; | |
4d28f1de MS |
7061 | }, |
7062 | ||
7063 | /** | |
7064 | * Returns the search input field. | |
7065 | * | |
7066 | * @return jQuery | |
7067 | */ | |
7068 | getSearchInput: function() { | |
7069 | return this._searchInput; | |
d27e2667 AE |
7070 | } |
7071 | }); | |
7072 | ||
a5423278 AE |
7073 | /** |
7074 | * Provides a language chooser. | |
51ab1086 AE |
7075 | * |
7076 | * @param {string} containerId input element conainer id | |
7077 | * @param {string} chooserId input element id | |
7078 | * @param {int} languageId selected language id | |
7079 | * @param {object<int, object<string, string>>} languages data of available languages | |
7080 | * @param {function} callback function called after a language is selected | |
7081 | * @param {boolean} allowEmptyValue true if no language may be selected | |
a5423278 | 7082 | * |
58d7e8f8 | 7083 | * @deprecated 3.0 - please use `WoltLabSuite/Core/Language/Chooser` instead |
a5423278 AE |
7084 | */ |
7085 | WCF.Language.Chooser = Class.extend({ | |
a5423278 AE |
7086 | /** |
7087 | * Initializes the language chooser. | |
51ab1086 | 7088 | * |
1615fc2e | 7089 | * @param {string} containerId input element container id |
51ab1086 AE |
7090 | * @param {string} chooserId input element id |
7091 | * @param {int} languageId selected language id | |
7092 | * @param {object<int, object<string, string>>} languages data of available languages | |
7093 | * @param {function} callback function called after a language is selected | |
7094 | * @param {boolean} allowEmptyValue true if no language may be selected | |
7095 | */ | |
7096 | init: function(containerId, chooserId, languageId, languages, callback, allowEmptyValue) { | |
58d7e8f8 | 7097 | require(['WoltLabSuite/Core/Language/Chooser'], function(LanguageChooser) { |
51ab1086 AE |
7098 | LanguageChooser.init(containerId, chooserId, languageId, languages, callback, allowEmptyValue); |
7099 | }); | |
a5423278 AE |
7100 | } |
7101 | }); | |
7102 | ||
83736ee3 AE |
7103 | /** |
7104 | * Namespace for style related classes. | |
7105 | */ | |
7106 | WCF.Style = { }; | |
7107 | ||
271dbf28 AE |
7108 | /** |
7109 | * Converts static user panel items into interactive dropdowns. | |
7110 | * | |
611bfec8 AE |
7111 | * @deprecated 2.1 - Please use WCF.User.Panel.Interactive instead |
7112 | * | |
271dbf28 AE |
7113 | * @param string containerID |
7114 | */ | |
7115 | WCF.UserPanel = Class.extend({ | |
7116 | /** | |
7117 | * target container | |
7118 | * @var jQuery | |
7119 | */ | |
7120 | _container: null, | |
7121 | ||
7122 | /** | |
7123 | * initialization state | |
7124 | * @var boolean | |
7125 | */ | |
7126 | _didLoad: false, | |
7127 | ||
7128 | /** | |
7129 | * original link element | |
7130 | * @var jQuery | |
7131 | */ | |
7132 | _link: null, | |
7133 | ||
531e7fb1 AE |
7134 | /** |
7135 | * language variable name for 'no items' | |
7136 | * @var string | |
7137 | */ | |
7138 | _noItems: '', | |
7139 | ||
271dbf28 AE |
7140 | /** |
7141 | * reverts to original link if return values are empty | |
7142 | * @var boolean | |
7143 | */ | |
7144 | _revertOnEmpty: true, | |
7145 | ||
7146 | /** | |
1615fc2e | 7147 | * Initializes the WCF.UserPanel class. |
271dbf28 AE |
7148 | * |
7149 | * @param string containerID | |
7150 | */ | |
7151 | init: function(containerID) { | |
7152 | this._container = $('#' + containerID); | |
7153 | this._didLoad = false; | |
7154 | this._revertOnEmpty = true; | |
7155 | ||
7156 | if (this._container.length != 1) { | |
1615fc2e | 7157 | console.debug("[WCF.UserPanel] Unable to find container identified by '" + containerID + "', aborting."); |
271dbf28 AE |
7158 | return; |
7159 | } | |
7160 | ||
531e7fb1 | 7161 | this._convert(); |
271dbf28 AE |
7162 | }, |
7163 | ||
7164 | /** | |
7165 | * Converts link into an interactive dropdown menu. | |
7166 | */ | |
7167 | _convert: function() { | |
271dbf28 AE |
7168 | this._container.addClass('dropdown'); |
7169 | this._link = this._container.children('a').remove(); | |
7170 | ||
7d7dea52 | 7171 | var $button = $('<a href="' + this._link.attr('href') + '" class="dropdownToggle">' + this._link.html() + '</a>').appendTo(this._container).click($.proxy(this._click, this)); |
271dbf28 AE |
7172 | var $dropdownMenu = $('<ul class="dropdownMenu" />').appendTo(this._container); |
7173 | $('<li class="jsDropdownPlaceholder"><span>' + WCF.Language.get('wcf.global.loading') + '</span></li>').appendTo($dropdownMenu); | |
7174 | ||
7175 | this._addDefaultItems($dropdownMenu); | |
531e7fb1 AE |
7176 | |
7177 | this._container.dblclick($.proxy(function() { | |
7178 | window.location = this._link.attr('href'); | |
7179 | return false; | |
7180 | }, this)); | |
3ef8dee9 AE |
7181 | |
7182 | WCF.Dropdown.initDropdown($button, false); | |
271dbf28 AE |
7183 | }, |
7184 | ||
7185 | /** | |
7186 | * Adds default items to dropdown menu. | |
7187 | * | |
7188 | * @param jQuery dropdownMenu | |
7189 | */ | |
7190 | _addDefaultItems: function(dropdownMenu) { }, | |
7191 | ||
7192 | /** | |
7193 | * Adds a dropdown divider. | |
7194 | * | |
7195 | * @param jQuery dropdownMenu | |
7196 | */ | |
7197 | _addDivider: function(dropdownMenu) { | |
7198 | $('<li class="dropdownDivider" />').appendTo(dropdownMenu); | |
7199 | }, | |
7200 | ||
7201 | /** | |
7202 | * Handles clicks on the dropdown item. | |
7d7dea52 AE |
7203 | * |
7204 | * @param object event | |
271dbf28 | 7205 | */ |
7d7dea52 AE |
7206 | _click: function(event) { |
7207 | event.preventDefault(); | |
7208 | ||
271dbf28 AE |
7209 | if (this._didLoad) { |
7210 | return; | |
7211 | } | |
7212 | ||
7213 | new WCF.Action.Proxy({ | |
7214 | autoSend: true, | |
7215 | data: this._getParameters(), | |
7216 | success: $.proxy(this._success, this) | |
7217 | }); | |
7218 | ||
7219 | this._didLoad = true; | |
7220 | }, | |
7221 | ||
7222 | /** | |
7223 | * Returns a list of parameters for AJAX request. | |
7224 | * | |
7225 | * @return object | |
7226 | */ | |
7227 | _getParameters: function() { | |
7228 | return { }; | |
7229 | }, | |
7230 | ||
7231 | /** | |
7232 | * Handles successful AJAX requests. | |
7233 | * | |
7234 | * @param object data | |
7235 | * @param string textStatus | |
7236 | * @param jQuery jqXHR | |
7237 | */ | |
7238 | _success: function(data, textStatus, jqXHR) { | |
3ef8dee9 | 7239 | var $dropdownMenu = WCF.Dropdown.getDropdownMenu(this._container.wcfIdentify()); |
531e7fb1 AE |
7240 | $dropdownMenu.children('.jsDropdownPlaceholder').remove(); |
7241 | ||
271dbf28 | 7242 | if (data.returnValues && data.returnValues.template) { |
271dbf28 | 7243 | $('' + data.returnValues.template).prependTo($dropdownMenu); |
5d10caf8 AE |
7244 | |
7245 | // update badge | |
7f737b83 | 7246 | this._updateBadge(data.returnValues.totalCount); |
5d10caf8 | 7247 | |
1682f3cb | 7248 | this._after($dropdownMenu); |
271dbf28 AE |
7249 | } |
7250 | else { | |
531e7fb1 | 7251 | $('<li><span>' + WCF.Language.get(this._noItems) + '</span></li>').prependTo($dropdownMenu); |
271dbf28 AE |
7252 | |
7253 | // remove badge | |
7f737b83 AE |
7254 | this._updateBadge(0); |
7255 | } | |
7256 | }, | |
7257 | ||
7258 | /** | |
7259 | * Updates badge count. | |
7260 | * | |
7261 | * @param integer count | |
7262 | */ | |
7263 | _updateBadge: function(count) { | |
bd3d1965 AE |
7264 | count = parseInt(count) || 0; |
7265 | ||
7f737b83 AE |
7266 | if (count) { |
7267 | var $badge = this._container.find('.badge'); | |
7268 | if (!$badge.length) { | |
a0216dd3 | 7269 | $badge = $('<span class="badge badgeUpdate" />').appendTo(this._container.children('.dropdownToggle')); |
7f737b83 AE |
7270 | $badge.before(' '); |
7271 | } | |
7272 | $badge.html(count); | |
7273 | } | |
7274 | else { | |
271dbf28 AE |
7275 | this._container.find('.badge').remove(); |
7276 | } | |
1682f3cb AE |
7277 | }, |
7278 | ||
7279 | /** | |
7280 | * Execute actions after the dropdown menu has been populated. | |
7281 | * | |
7282 | * @param object dropdownMenu | |
7283 | */ | |
7284 | _after: function(dropdownMenu) { } | |
271dbf28 AE |
7285 | }); |
7286 | ||
09f7100b | 7287 | jQuery.fn.extend({ |
d8f608b0 | 7288 | // shim for 'ui.wcfDialog' |
09f7100b AE |
7289 | wcfDialog: function(method) { |
7290 | var args = arguments; | |
9f959ced | 7291 | |
477b8397 MS |
7292 | require(['Dom/Util', 'Ui/Dialog'], (function(DomUtil, UiDialog) { |
7293 | var id = DomUtil.identify(this[0]); | |
5a1b4042 | 7294 | |
09f7100b | 7295 | if (method === 'close') { |
477b8397 | 7296 | UiDialog.close(id); |
f74219d0 | 7297 | } |
09f7100b | 7298 | else if (method === 'render') { |
477b8397 | 7299 | UiDialog.rebuild(id); |
5a1b4042 | 7300 | } |
09f7100b | 7301 | else if (method === 'option') { |
23cffb2d AE |
7302 | if (args.length === 3) { |
7303 | if (args[1] === 'title' && typeof args[2] === 'string') { | |
7304 | UiDialog.setTitle(id, args[2]); | |
7305 | } | |
7306 | else if (args[1].indexOf('on') === 0) { | |
7307 | UiDialog.setCallback(id, args[1], args[2]); | |
7308 | } | |
7309 | else if (args[1] === 'closeConfirmMessage' && args[2] === null) { | |
7310 | UiDialog.setCallback(id, 'onBeforeClose', null); | |
7311 | } | |
af7da802 | 7312 | } |
e3a0f1eb AE |
7313 | } |
7314 | else { | |
a4a25c10 | 7315 | if (this[0].parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
477b8397 | 7316 | // if element is not already part of the DOM, UiDialog.open() will fail |
c18a1eb5 AE |
7317 | document.body.appendChild(this[0]); |
7318 | } | |
7319 | ||
7e862639 AE |
7320 | var options = (args.length === 1 && typeof args[0] === 'object') ? args[0] : {}; |
7321 | UiDialog.openStatic(id, null, options); | |
7322 | ||
7323 | if (options.hasOwnProperty('title')) { | |
7324 | UiDialog.setTitle(id, options.title); | |
7325 | } | |
e3a0f1eb | 7326 | } |
09f7100b | 7327 | }).bind(this)); |
10833b9a MS |
7328 | |
7329 | return this; | |
25763f41 AE |
7330 | } |
7331 | }); | |
7332 | ||
848d0782 AE |
7333 | /** |
7334 | * Provides a slideshow for lists. | |
7335 | */ | |
7336 | $.widget('ui.wcfSlideshow', { | |
7337 | /** | |
7338 | * button list object | |
7339 | * @var jQuery | |
7340 | */ | |
7341 | _buttonList: null, | |
7342 | ||
7343 | /** | |
7344 | * number of items | |
7345 | * @var integer | |
7346 | */ | |
7347 | _count: 0, | |
7348 | ||
7349 | /** | |
7350 | * item index | |
7351 | * @var integer | |
7352 | */ | |
7353 | _index: 0, | |
7354 | ||
7355 | /** | |
7356 | * item list object | |
7357 | * @var jQuery | |
7358 | */ | |
7359 | _itemList: null, | |
7360 | ||
7361 | /** | |
7362 | * list of items | |
7363 | * @var jQuery | |
7364 | */ | |
7365 | _items: null, | |
7366 | ||
7367 | /** | |
7368 | * timer object | |
7369 | * @var WCF.PeriodicalExecuter | |
7370 | */ | |
7371 | _timer: null, | |
7372 | ||
7373 | /** | |
7374 | * list item width | |
7375 | * @var integer | |
7376 | */ | |
7377 | _width: 0, | |
7378 | ||
7379 | /** | |
7380 | * list of options | |
7381 | * @var object | |
7382 | */ | |
7383 | options: { | |
7384 | /* enables automatic cycling of items */ | |
7385 | cycle: true, | |
7386 | /* cycle interval in seconds */ | |
7387 | cycleInterval: 5, | |
7388 | /* gap between items in pixels */ | |
9b5478e6 | 7389 | itemGap: 50 |
848d0782 AE |
7390 | }, |
7391 | ||
7392 | /** | |
7393 | * Creates a new instance of ui.wcfSlideshow. | |
7394 | */ | |
7395 | _create: function() { | |
7396 | this._itemList = this.element.children('ul'); | |
7397 | this._items = this._itemList.children('li'); | |
7398 | this._count = this._items.length; | |
7399 | this._index = 0; | |
7400 | ||
b567c0c6 MW |
7401 | if (this._count > 1) { |
7402 | this._initSlideshow(); | |
7403 | } | |
848d0782 AE |
7404 | }, |
7405 | ||
7406 | /** | |
7407 | * Initializes the slideshow. | |
7408 | */ | |
7409 | _initSlideshow: function() { | |
7410 | // calculate item dimensions | |
7411 | var $itemHeight = $(this._items.get(0)).outerHeight(); | |
7412 | this._items.addClass('slideshowItem'); | |
7413 | this._width = this.element.css('height', $itemHeight).innerWidth(); | |
7414 | this._itemList.addClass('slideshowItemList').css('left', 0); | |
7415 | ||
7416 | this._items.each($.proxy(function(index, item) { | |
7417 | $(item).show().css({ | |
7418 | height: $itemHeight, | |
7419 | left: ((this._width + this.options.itemGap) * index), | |
7420 | width: this._width | |
7421 | }); | |
7422 | }, this)); | |
7423 | ||
7424 | this.element.css({ | |
7425 | height: $itemHeight, | |
7426 | width: this._width | |
7427 | }).hover($.proxy(this._hoverIn, this), $.proxy(this._hoverOut, this)); | |
7428 | ||
7429 | // create toggle buttons | |
b567c0c6 MW |
7430 | this._buttonList = $('<ul class="slideshowButtonList" />').appendTo(this.element); |
7431 | for (var $i = 0; $i < this._count; $i++) { | |
ca8bfa53 | 7432 | var $link = $('<li><a><span class="icon icon16 fa-circle" /></a></li>').data('index', $i).click($.proxy(this._click, this)).appendTo(this._buttonList); |
b567c0c6 MW |
7433 | if ($i == 0) { |
7434 | $link.find('.icon').addClass('active'); | |
848d0782 AE |
7435 | } |
7436 | } | |
7437 | ||
b567c0c6 MW |
7438 | this._resetTimer(); |
7439 | ||
848d0782 AE |
7440 | $(window).resize($.proxy(this._resize, this)); |
7441 | }, | |
7442 | ||
4caeed3d AE |
7443 | /** |
7444 | * Rebuilds slideshow height in case the initial height contained resources affecting the | |
7445 | * element height, but loading has not completed on slideshow init. | |
7446 | */ | |
7447 | rebuildHeight: function() { | |
7448 | var $firstItem = $(this._items.get(0)).css('height', 'auto'); | |
7449 | var $itemHeight = $firstItem.outerHeight(); | |
7450 | ||
7451 | this._items.css('height', $itemHeight + 'px'); | |
7452 | this.element.css('height', $itemHeight + 'px'); | |
7453 | }, | |
7454 | ||
848d0782 AE |
7455 | /** |
7456 | * Handles browser resizing | |
7457 | */ | |
7458 | _resize: function() { | |
7459 | this._width = this.element.css('width', 'auto').innerWidth(); | |
7460 | this._items.each($.proxy(function(index, item) { | |
7461 | $(item).css({ | |
7462 | left: ((this._width + this.options.itemGap) * index), | |
7463 | width: this._width | |
7464 | }); | |
7465 | }, this)); | |
7466 | ||
7467 | this._index--; | |
7468 | this.moveTo(null); | |
7469 | }, | |
7470 | ||
7471 | /** | |
7472 | * Disables cycling while hovering. | |
7473 | */ | |
7474 | _hoverIn: function() { | |
7475 | if (this._timer !== null) { | |
7476 | this._timer.stop(); | |
7477 | } | |
7478 | }, | |
7479 | ||
7480 | /** | |
7481 | * Enables cycling after mouse out. | |
7482 | */ | |
7483 | _hoverOut: function() { | |
7484 | this._resetTimer(); | |
7485 | }, | |
7486 | ||
7487 | /** | |
7488 | * Resets cycle timer. | |
7489 | */ | |
7490 | _resetTimer: function() { | |
7491 | if (!this.options.cycle) { | |
7492 | return; | |
7493 | } | |
7494 | ||
7495 | if (this._timer !== null) { | |
7496 | this._timer.stop(); | |
7497 | } | |
7498 | ||
7499 | var self = this; | |
7500 | this._timer = new WCF.PeriodicalExecuter(function() { | |
7501 | self.moveTo(null); | |
7502 | }, this.options.cycleInterval * 1000); | |
7503 | }, | |
7504 | ||
7505 | /** | |
7506 | * Handles clicks on the select buttons. | |
7507 | * | |
7508 | * @param object event | |
7509 | */ | |
7510 | _click: function(event) { | |
7511 | this.moveTo($(event.currentTarget).data('index')); | |
7512 | ||
7513 | this._resetTimer(); | |
7514 | }, | |
7515 | ||
7516 | /** | |
7517 | * Moves to a specified item index, NULL will move to the next item in list. | |
7518 | * | |
7519 | * @param integer index | |
7520 | */ | |
7521 | moveTo: function(index) { | |
7522 | this._index = (index === null) ? this._index + 1 : index; | |
7523 | if (this._index == this._count) { | |
7524 | this._index = 0; | |
7525 | } | |
7526 | ||
7527 | $(this._buttonList.find('.icon').removeClass('active').get(this._index)).addClass('active'); | |
7528 | this._itemList.css('left', this._index * (this._width + this.options.itemGap) * -1); | |
c96906ac AE |
7529 | |
7530 | this._trigger('moveTo', null, { index: this._index }); | |
7531 | }, | |
7532 | ||
7533 | /** | |
7534 | * Returns item by index or null if index is invalid. | |
7535 | * | |
7536 | * @return jQuery | |
7537 | */ | |
7538 | getItem: function(index) { | |
7539 | if (this._items[index]) { | |
7540 | return this._items[index]; | |
7541 | } | |
7542 | ||
7543 | return null; | |
848d0782 AE |
7544 | } |
7545 | }); | |
7546 | ||
5a961a44 AE |
7547 | jQuery.fn.extend({ |
7548 | datepicker: function(method) { | |
7549 | var element = this[0], parameters = Array.prototype.slice.call(arguments, 1); | |
7550 | ||
7551 | switch (method) { | |
7552 | case 'destroy': | |
7553 | window.__wcf_bc_datePicker.destroy(element); | |
7554 | break; | |
7555 | ||
7556 | case 'getDate': | |
7557 | return window.__wcf_bc_datePicker.getDate(element); | |
b2955586 | 7558 | |
5a961a44 AE |
7559 | case 'option': |
7560 | if (parameters[0] === 'onClose') { | |
b2955586 MW |
7561 | if (parameters.length > 1) { |
7562 | return this.datepicker('setOption', 'onClose', parameters[1]); | |
7563 | } | |
7564 | ||
5a961a44 AE |
7565 | return function() {}; |
7566 | } | |
7567 | ||
7568 | console.warn("datepicker('option') supports only 'onClose'."); | |
7569 | break; | |
b2955586 | 7570 | |
5a961a44 AE |
7571 | case 'setDate': |
7572 | window.__wcf_bc_datePicker.setDate(element, parameters[0]); | |
7573 | break; | |
7574 | ||
7575 | case 'setOption': | |
7576 | if (parameters[0] === 'onClose') { | |
7577 | window.__wcf_bc_datePicker.setCloseCallback(element, parameters[1]); | |
7578 | } | |
7579 | else { | |
7580 | console.warn("datepicker('setOption') supports only 'onClose'."); | |
7581 | } | |
7582 | break; | |
7583 | ||
7584 | default: | |
7585 | console.debug("Unsupported method '" + method + "' for datepicker()"); | |
7586 | break; | |
7587 | } | |
8b394aba AE |
7588 | |
7589 | return this; | |
5a961a44 AE |
7590 | } |
7591 | }); | |
7592 | ||
4bbf6ff1 AE |
7593 | jQuery.fn.extend({ |
7594 | wcfTabs: function(method) { | |
5a961a44 | 7595 | var element = this[0], parameters = Array.prototype.slice.call(arguments, 1); |
dbd319de | 7596 | |
58d7e8f8 | 7597 | require(['Dom/Util', 'WoltLabSuite/Core/Ui/TabMenu'], function(DomUtil, TabMenu) { |
477b8397 | 7598 | var container = TabMenu.getTabMenu(DomUtil.identify(element)); |
4bbf6ff1 | 7599 | if (container !== null) { |
5a961a44 | 7600 | container[method].apply(container, parameters); |
22b63e33 | 7601 | } |
5a961a44 | 7602 | }); |
158bd3ca TD |
7603 | } |
7604 | }); | |
7605 | ||
7606 | /** | |
7607 | * jQuery widget implementation of the wcf pagination. | |
ec86f434 | 7608 | * |
58d7e8f8 | 7609 | * @deprecated 3.0 - use `WoltLabSuite/Core/Ui/Pagination` instead |
158bd3ca TD |
7610 | */ |
7611 | $.widget('ui.wcfPages', { | |
ec86f434 AE |
7612 | _api: null, |
7613 | ||
158bd3ca TD |
7614 | SHOW_LINKS: 11, |
7615 | SHOW_SUB_LINKS: 20, | |
7616 | ||
7617 | options: { | |
7618 | // vars | |
7619 | activePage: 1, | |
ec86f434 | 7620 | maxPage: 1 |
158bd3ca TD |
7621 | }, |
7622 | ||
7623 | /** | |
7624 | * Creates the pages widget. | |
7625 | */ | |
7626 | _create: function() { | |
58d7e8f8 | 7627 | require(['WoltLabSuite/Core/Ui/Pagination'], (function(UiPagination) { |
57903201 | 7628 | this._api = new UiPagination(this.element[0], { |
ec86f434 AE |
7629 | activePage: this.options.activePage, |
7630 | maxPage: this.options.maxPage, | |
7631 | ||
7632 | callbackShouldSwitch: (function(pageNo) { | |
7633 | var result = this._trigger('shouldSwitch', undefined, { | |
7634 | nextPage: pageNo | |
7635 | }); | |
7636 | ||
7637 | return (result !== false); | |
7638 | }).bind(this), | |
7639 | callbackSwitch: (function(pageNo) { | |
7640 | this._trigger('switched', undefined, { | |
7641 | activePage: pageNo | |
7642 | }); | |
7643 | }).bind(this) | |
7644 | }); | |
7645 | }).bind(this)); | |
158bd3ca TD |
7646 | }, |
7647 | ||
7648 | /** | |
7649 | * Destroys the pages widget. | |
7650 | */ | |
7651 | destroy: function() { | |
7652 | $.Widget.prototype.destroy.apply(this, arguments); | |
7653 | ||
ec86f434 AE |
7654 | this._api = null; |
7655 | this.element[0].innerHTML = ''; | |
158bd3ca TD |
7656 | }, |
7657 | ||
7658 | /** | |
7659 | * Sets the given option to the given value. | |
7660 | * See the jQuery UI widget documentation for more. | |
7661 | */ | |
7662 | _setOption: function(key, value) { | |
7663 | if (key == 'activePage') { | |
7664 | if (value != this.options[key] && value > 0 && value <= this.options.maxPage) { | |
7665 | // you can prevent the page switching by returning false or by event.preventDefault() | |
7666 | // in a shouldSwitch-callback. e.g. if an AJAX request is already running. | |
7667 | var $result = this._trigger('shouldSwitch', undefined, { | |
81d1b3b5 | 7668 | nextPage: value |
158bd3ca TD |
7669 | }); |
7670 | ||
a19475d3 | 7671 | if ($result || $result !== undefined) { |
ec86f434 AE |
7672 | this._api.switchPage(value); |
7673 | ||
158bd3ca TD |
7674 | } |
7675 | else { | |
7676 | this._trigger('notSwitched', undefined, { | |
81d1b3b5 | 7677 | activePage: value |
158bd3ca TD |
7678 | }); |
7679 | } | |
7680 | } | |
7681 | } | |
158bd3ca TD |
7682 | |
7683 | return this; | |
158bd3ca TD |
7684 | } |
7685 | }); | |
7686 | ||
02e410dc MW |
7687 | /** |
7688 | * Namespace for category related classes. | |
7689 | */ | |
7690 | WCF.Category = { }; | |
7691 | ||
f5e3a61b | 7692 | if (COMPILER_TARGET_DEFAULT) { |
02e410dc | 7693 | /** |
f5e3a61b | 7694 | * Handles selection of categories. |
02e410dc | 7695 | */ |
f5e3a61b AE |
7696 | WCF.Category.NestedList = Class.extend({ |
7697 | /** | |
7698 | * list of categories | |
7699 | * @var object | |
7700 | */ | |
7701 | _categories: {}, | |
7702 | ||
7703 | /** | |
7704 | * Initializes the WCF.Category.NestedList object. | |
7705 | */ | |
7706 | init: function () { | |
7707 | var self = this; | |
7708 | $('.jsCategory').each(function (index, category) { | |
7709 | var $category = $(category).data('parentCategoryID', null).change($.proxy(self._updateSelection, self)); | |
7710 | self._categories[$category.val()] = $category; | |
02e410dc | 7711 | |
f5e3a61b AE |
7712 | // find child categories |
7713 | var $childCategoryIDs = []; | |
7714 | $category.parents('li').find('.jsChildCategory').each(function (innerIndex, childCategory) { | |
7715 | var $childCategory = $(childCategory).data('parentCategoryID', $category.val()).change($.proxy(self._updateSelection, self)); | |
7716 | self._categories[$childCategory.val()] = $childCategory; | |
7717 | $childCategoryIDs.push($childCategory.val()); | |
7718 | ||
7719 | if ($childCategory.is(':checked')) { | |
7720 | $category.prop('checked', 'checked'); | |
7721 | } | |
7722 | }); | |
7723 | ||
7724 | $category.data('childCategoryIDs', $childCategoryIDs); | |
02e410dc | 7725 | }); |
f5e3a61b | 7726 | }, |
02e410dc | 7727 | |
f5e3a61b AE |
7728 | /** |
7729 | * Updates selection of categories. | |
7730 | * | |
7731 | * @param object event | |
7732 | */ | |
7733 | _updateSelection: function (event) { | |
7734 | var $category = $(event.currentTarget); | |
7735 | var $parentCategoryID = $category.data('parentCategoryID'); | |
7736 | ||
7737 | if ($category.is(':checked')) { | |
7738 | // child category | |
7739 | if ($parentCategoryID !== null) { | |
7740 | // mark parent category as checked | |
7741 | this._categories[$parentCategoryID].prop('checked', 'checked'); | |
7742 | } | |
02e410dc | 7743 | } |
f5e3a61b AE |
7744 | else { |
7745 | // top-level category | |
7746 | if ($parentCategoryID === null) { | |
7747 | // unmark all child categories | |
7748 | var $childCategoryIDs = $category.data('childCategoryIDs'); | |
7749 | for (var $i = 0, $length = $childCategoryIDs.length; $i < $length; $i++) { | |
7750 | this._categories[$childCategoryIDs[$i]].prop('checked', false); | |
7751 | } | |
02e410dc MW |
7752 | } |
7753 | } | |
7754 | } | |
f5e3a61b | 7755 | }); |
f07b12a5 AE |
7756 | |
7757 | /** | |
f5e3a61b | 7758 | * Handles selection of categories. |
f07b12a5 | 7759 | */ |
f5e3a61b AE |
7760 | WCF.Category.FlexibleCategoryList = Class.extend({ |
7761 | /** | |
7762 | * category list container | |
7763 | * @var jQuery | |
7764 | */ | |
7765 | _list: null, | |
f07b12a5 | 7766 | |
f5e3a61b AE |
7767 | /** |
7768 | * list of children per category id | |
7769 | * @var object<integer> | |
7770 | */ | |
7771 | _categories: {}, | |
54e2ad49 | 7772 | |
f5e3a61b AE |
7773 | init: function (elementID) { |
7774 | this._list = $('#' + elementID); | |
f07b12a5 | 7775 | |
f5e3a61b AE |
7776 | this._buildStructure(); |
7777 | ||
7778 | this._list.find('input:checked').each(function () { | |
7779 | $(this).trigger('change'); | |
7780 | }); | |
7781 | ||
7782 | if (this._list.children('li').length < 2) { | |
7783 | this._list.addClass('flexibleCategoryListDisabled'); | |
7784 | return; | |
7785 | } | |
7786 | }, | |
7787 | ||
7788 | _buildStructure: function () { | |
7789 | var self = this; | |
7790 | this._list.find('.jsCategory').each(function (i, category) { | |
7791 | var $category = $(category).change(self._updateSelection.bind(self)); | |
7792 | var $categoryID = parseInt($category.val()); | |
7793 | var $childCategories = []; | |
f07b12a5 | 7794 | |
f5e3a61b AE |
7795 | $category.parents('li:eq(0)').find('.jsChildCategory').each(function (j, childCategory) { |
7796 | var $childCategory = $(childCategory); | |
7797 | $childCategory.data('parentCategory', $category).change(self._updateSelection.bind(self)); | |
7798 | ||
7799 | var $childCategoryID = parseInt($childCategory.val()); | |
7800 | $childCategories.push($childCategory); | |
7801 | ||
7802 | var $subChildCategories = []; | |
7803 | ||
7804 | $childCategory.parents('li:eq(0)').find('.jsSubChildCategory').each(function (k, subChildCategory) { | |
7805 | var $subChildCategory = $(subChildCategory); | |
7806 | $subChildCategory.data('parentCategory', $childCategory).change(self._updateSelection.bind(self)); | |
7807 | $subChildCategories.push($subChildCategory); | |
7808 | }); | |
7809 | ||
7810 | self._categories[$childCategoryID] = $subChildCategories; | |
f07b12a5 AE |
7811 | }); |
7812 | ||
f5e3a61b | 7813 | self._categories[$categoryID] = $childCategories; |
f07b12a5 | 7814 | }); |
f5e3a61b | 7815 | }, |
f07b12a5 | 7816 | |
f5e3a61b AE |
7817 | _updateSelection: function (event) { |
7818 | var $category = $(event.currentTarget); | |
7819 | var $categoryID = parseInt($category.val()); | |
7820 | var $parentCategory = $category.data('parentCategory'); | |
7821 | ||
7822 | if ($category.is(':checked')) { | |
f07b12a5 AE |
7823 | if ($parentCategory) { |
7824 | $parentCategory.prop('checked', 'checked'); | |
f07b12a5 | 7825 | |
f5e3a61b AE |
7826 | $parentCategory = $parentCategory.data('parentCategory'); |
7827 | if ($parentCategory) { | |
7828 | $parentCategory.prop('checked', 'checked'); | |
f07b12a5 AE |
7829 | } |
7830 | } | |
7831 | } | |
f5e3a61b AE |
7832 | else { |
7833 | // uncheck child categories | |
7834 | if (this._categories[$categoryID]) { | |
7835 | for (var $i = 0, $length = this._categories[$categoryID].length; $i < $length; $i++) { | |
7836 | var $childCategory = this._categories[$categoryID][$i]; | |
7837 | $childCategory.prop('checked', false); | |
7838 | ||
7839 | var $childCategoryID = parseInt($childCategory.val()); | |
7840 | if (this._categories[$childCategoryID]) { | |
7841 | for (var $j = 0, $innerLength = this._categories[$childCategoryID].length; $j < $innerLength; $j++) { | |
7842 | this._categories[$childCategoryID][$j].prop('checked', false); | |
7843 | } | |
7844 | } | |
f07b12a5 AE |
7845 | } |
7846 | } | |
7847 | ||
f5e3a61b | 7848 | // uncheck direct parent if it has no more checked children |
f07b12a5 | 7849 | if ($parentCategory) { |
f5e3a61b | 7850 | var $parentCategoryID = parseInt($parentCategory.val()); |
f07b12a5 AE |
7851 | for (var $i = 0, $length = this._categories[$parentCategoryID].length; $i < $length; $i++) { |
7852 | if (this._categories[$parentCategoryID][$i].prop('checked')) { | |
7853 | // at least one child is checked, break | |
7854 | return; | |
7855 | } | |
7856 | } | |
f5e3a61b AE |
7857 | |
7858 | $parentCategory = $parentCategory.data('parentCategory'); | |
7859 | if ($parentCategory) { | |
7860 | $parentCategoryID = parseInt($parentCategory.val()); | |
7861 | for (var $i = 0, $length = this._categories[$parentCategoryID].length; $i < $length; $i++) { | |
7862 | if (this._categories[$parentCategoryID][$i].prop('checked')) { | |
7863 | // at least one child is checked, break | |
7864 | return; | |
7865 | } | |
7866 | } | |
7867 | } | |
f07b12a5 AE |
7868 | } |
7869 | } | |
7870 | } | |
f5e3a61b AE |
7871 | }); |
7872 | } | |
7873 | else { | |
7874 | WCF.Category.NestedList = Class.extend({ | |
7875 | _categories: {}, | |
7876 | init: function() {}, | |
7877 | _updateSelection: function() {} | |
7878 | }); | |
7879 | ||
7880 | WCF.Category.FlexibleCategoryList = Class.extend({ | |
7881 | _list: {}, | |
7882 | _categories: {}, | |
7883 | init: function() {}, | |
7884 | _buildStructure: function() {}, | |
7885 | _updateSelection: function() {} | |
7886 | }); | |
7887 | } | |
f07b12a5 | 7888 | |
20933e61 MS |
7889 | /** |
7890 | * Initializes WCF.Condition namespace. | |
7891 | */ | |
7892 | WCF.Condition = { }; | |
7893 | ||
20933e61 MS |
7894 | /** |
7895 | * Initialize WCF.Notice namespace. | |
7896 | */ | |
7897 | WCF.Notice = { }; | |
7898 | ||
158bd3ca TD |
7899 | /** |
7900 | * Encapsulate eval() within an own function to prevent problems | |
7901 | * with optimizing and minifiny JS. | |
7902 | * | |
7903 | * @param mixed expression | |
7904 | * @returns mixed | |
7905 | */ | |
7906 | function wcfEval(expression) { | |
7907 | return eval(expression); | |
7908 | } |