[FIX] daterange selector dialog
[cds-indico.git] / indico / htdocs / js / indico / Core / Dialogs / Popup.js
blobec5071043fdb392febe2d5e3700e51b51fca526e
1 type("PopupDialog", ["PopupWidget"], {
2     draw: function(content, x, y) {
3         return this.PopupWidget.prototype.draw.call(this, content, x, y, null, true);
4     },
6     clickTriggersClosing: function(target) {
7         // Check if one of the elements that should not trigger
8         // close is clicked
9         var nonCloseTriggeringClick = false;
10         each(this.nonCloseTriggeringElements, function(e) {
11             if (e.ancestorOf(target)) {
12                 nonCloseTriggeringClick = true;
13             }
14         });
15         return (!this.canvas.ancestorOf(target) &&
16                 !this.triggerElement.ancestorOf(target) &&
17                 !nonCloseTriggeringClick);
18     },
20     open: function(x, y) {
21         var self = this;
22         this.PopupWidget.prototype.open.call(this, x, y);
24         self.clickHandler = function(event) {
25             if (self.clickTriggersClosing($E(eventTarget(event)))) {
26                 self.close();
27             }
28         };
29         IndicoUtil.onclickHandlerAdd(self.clickHandler);
30     },
32     close: function() {
33         if (this.closeHandler() && this.isopen) {
34             IndicoUtil.onclickHandlerRemove(this.clickHandler);
35             this.PopupWidget.prototype.close.call(this);
36         }
37     },
38     /**
39      * Adds an element to the list of elements that when clicked
40      * on do not trigger close of the popup dialog.
41      */
42     addNonCloseTriggeringElement: function(element) {
43         this.nonCloseTriggeringElements.push(element);
44     }
45     },
46      function(content, triggerElement, closeHandler, nonCloseTriggeringElements) {
47          this.content = content;
48          this.PopupWidget();
49          this.triggerElement = triggerElement;
50          this.closeHandler = any(closeHandler, function() {return true; });
51          this.nonCloseTriggeringElements = any(nonCloseTriggeringElements, []);
52      }
53     );
56 type("ExclusivePopup", ["Printable"], {
57     open: function() {
58         this.draw();
59         this.canvas.dialog('open');
60     },
62     draw: function(content, customStyle, popupStyle) {
63         customStyle = customStyle || {};
64         if(!content) {
65             content = '';
66         }
67         else if(content.dom) {
68             content = content.dom;
69         }
71         if(popupStyle === undefined) {
72             popupStyle = customStyle;
73         }
74         var container = $('<div class="exclusivePopup"/>').css(popupStyle).append(content);
76         this.showCloseButton = !!this.title;
77         this._makeCanvas();
78         this.canvas.empty().css(customStyle).append(container);
79         this.dialogElement.css(customStyle);
80     },
82     close: function() {
83         if(this.isopen) {
84             this.canvas.dialog('close');
85         }
86     },
88     _getDialogOptions: function() {
89         return {};
90     },
92     _makeCanvas: function() {
93         if(!this.canvas) {
94             var opts = $.extend(true, {
95                 autoOpen: false,
96                 draggable: true,
97                 modal: true,
98                 resizable: false,
99                 closeOnEscape: true,
100                 title: this.title,
101                 minWidth: '250px',
102                 minHeight: 0,
103                 open: $.proxy(this._onOpen, this),
104                 close: $.proxy(this._onClose, this),
105                 beforeClose: $.proxy(this._onBeforeClose, this)
106             }, this._getDialogOptions());
107             this.canvas = $('<div/>').dialog(opts);
108         }
109         if(!this.dialogElement) {
110             this.dialogElement = this.canvas.dialog('widget');
111         }
112         this.buttons = this.dialogElement.find('.ui-dialog-buttonset button');
113     },
115     _onBeforeClose: function(e) {
116         // Close button clicked
117         if(e.originalEvent && $(e.originalEvent.currentTarget).hasClass('ui-dialog-titlebar-close')) {
118             if(isFunction(this.closeHandler) && !this.closeHandler()) {
119                 return false;
120             }
121         }
122         // Escape key
123         else if(e.keyCode && e.keyCode === $.ui.keyCode.ESCAPE) {
124             e.stopPropagation(); // otherwise this triggers twice for some reason
125             if(this.closeHandler === null || !this.showCloseButton) {
126                 // Ignore escape if we don't have a close button
127                 return false;
128             }
129             if(isFunction(this.closeHandler) && !this.closeHandler()) {
130                 return false;
131             }
132         }
133     },
135     _onOpen: function(e) {
136         this.isopen = true;
137         if(this.closeHandler === null || !this.showCloseButton) {
138             this.dialogElement.find('.ui-dialog-titlebar-close').hide();
139             if(!this.title) {
140                 this.dialogElement.find('.ui-dialog-titlebar').hide();
141             }
142         }
144         if(this.postDraw() === true) {
145             // refresh position
146             var pos = this.canvas.dialog('option', 'position');
147             this.canvas.dialog('option', 'position', pos);
148         }
149     },
151     postDraw: function() {
153     },
155     _onClose: function(e, ui) {
156         this.isopen = false;
157         this.canvas.dialog('destroy');
158         this.canvas.remove();
159         this.canvas = this.dialogElement = null;
160         this.buttons = [];
161     }
163 }, function(title, closeButtonHandler, printable, showPrintButton, noCanvas) {
164     this.title = any(title, null);
166     // Called when user clicks the close button, if the function
167     // returns true the dialog will be closed.
168     this.closeHandler = any(closeButtonHandler, positive);
169     // the close button will be enabled in draw() so if that method is overridden it will not be drawn
170     this.showCloseButton = false;
172     // The maximum allowed height, used since it doesn't look
173     // very nice it the dialog gets too big.
174     this.maxHeight = 600;
176     // Decides whether the popup should be printable. That is, when the user
177     // clicks print only the content of the dialog will be printed not the
178     // whole page. Should be true in general unless the dialog is containing
179     // something users normally don't want to print, i.e. the loading dialog.
180     this.printable = any(printable, true);
182     // Whether to show the print button or not in the title
183     // Note: the button will only be shown if the popup dialog has a title.
184     // and is printable.
185     this.showPrintButton = any(showPrintButton && title && printable, false);
187     this.buttons = $();
188     if(!noCanvas) {
189         this._makeCanvas();
190     }
197  * Builds an exclusive popup with a button bar
198  * Constructor arguments: the same ones as ExclusivePopup
199  */
200 type("ExclusivePopupWithButtons", ["ExclusivePopup"], {
201     _getButtons: function() {
202         return null;
203     },
204     _getDialogOptions: function() {
205         var self = this;
206         var buttons = this._getButtons();
207         var dlgButtons = [];
208         this.defaultButton = null;
209         if(buttons) {
210             $.each(buttons, function(i, button) {
211                 dlgButtons.push({
212                     text: button[0],
213                     click: button[1]
214                 });
215                 if(button.length > 2 && button[2]) {
216                     self.defaultButton = i;
217                 }
218             });
219         }
220         return {
221             buttons: dlgButtons
222         };
223     },
224     _onOpen: function(e) {
225         this.ExclusivePopup.prototype._onOpen.call(this, e);
226         if(this.defaultButton !== null) {
227             this.buttons[this.defaultButton].focus();
228         }
229     },
230     draw: function(mainContent, buttonContent, popupCustomStyle, mainContentStyle, buttonBarStyle) {
231         this.ExclusivePopup.prototype.draw.call(this, mainContent, popupCustomStyle);
232     }
233 }, function(title, closeButtonHandler, printable, showPrintButton, noCanvas){
234     this.ExclusivePopup(title, closeButtonHandler, printable, showPrintButton, noCanvas);
237 type("BalloonPopup", ["PopupDialog"], {
238     draw: function(x, y) {
239         var self = this;
241         this.closeButton = Html.div({className: 'balloonPopupCloseButton'});
242         this.balloonContent = Html.div({className: this.balloonClass}, this.hasCloseButton ? this.closeButton : '', this.content);
243         this.arrowDiv = Html.div({className: this.arrowClass, style: {width: this.arrowWidth, height: this.arrowHeight}});
244         this.mainDiv = Html.div({}, this.balloonContent, this.arrowDiv);
246         // Hide it until everything is prepared
247         this.mainDiv.dom.style.visibility = 'hidden';
249         // Sets the orientation to up
250         this.switchOrientation();
252         var toReturn = this.PopupDialog.prototype.draw.call(this, this.mainDiv, x, y);
254         this.arrowDiv.dom.style.left = pixels(0);
256         if (this.hasCloseButton) {
257             this.closeButton.observeClick(function() {self.close();});
258         }
260         return toReturn;
261     },
262     open: function(x, y) {
263         var self = this;
265         this.x = x;
266         this.y = y;
268         this.PopupDialog.prototype.open.call(this, x, y);
269         this.verifyXPos();
270         this.verifyYPos();
272         // Everything is done, can now be shown to user
273         this.mainDiv.dom.style.visibility = 'visible';
274     },
275     switchOrientation: function() {
276         if (this.balloonContent.dom.style.bottom === '') {
277             // current orientation is down, set it to up
278             this.balloonContent.dom.style.bottom = pixels(this.arrowHeight - 1);
279             this.balloonContent.dom.style.top = '';
280             this.arrowDiv.dom.style.backgroundPosition = '0 -6px';
281             this.arrowDiv.dom.style.top = '';
282             this.arrowDiv.dom.style.bottom = pixels(0);
283         } else {
284             // current orientation is up, set it to down
285             this.balloonContent.dom.style.top = pixels(this.arrowHeight - 1);
286             this.balloonContent.dom.style.bottom = '';
287             this.arrowDiv.dom.style.backgroundPosition = '0px -25px';
288             this.arrowDiv.dom.style.bottom = '';
289             this.arrowDiv.dom.style.top = pixels(0);
290         }
291     },
292     verifyYPos: function() {
293         var height = this.getBalloonHeight();
295         if ((this.y - height) < 5) {
296             this.switchOrientation();
297             return;
298         }
300         if ((this.y - height) < $(window).scrollTop())  {
301             if (($(window).height() + $(window).scrollTop()) > (this.y + height)) {
302                 this.switchOrientation();
303                 return;
304             }
305         }
306     },
307     verifyXPos: function() {
308         var balloonWidth = this.balloonContent.dom.offsetWidth;
310         // Try place the middle of the balloon on mouse pointer position
311         var leftPos = this.x - Math.floor(balloonWidth/2);
313         // Check if the balloon is outside left side of browser window
314         if (leftPos - $(window).scrollLeft() < 0) {
315             leftPos = $(window).scrollLeft() + 5; // 5 pixel margin
317             // Check if the arrow is outside the balloon, then move the balloon to
318             // a correct position based on the arrow
319             var arrowLeftMargin = this.x - Math.floor(this.arrowWidth/2) - this.cornerRadius;
320             if (arrowLeftMargin < leftPos) {
321                 leftPos = arrowLeftMargin;
322             }
323         }
324         // Check if the balloon is outside the right side of browser windows
325         // Counts width 25px margin because of the scrollbar.
326         else if (leftPos + balloonWidth > $(window).scrollLeft() + $(window).width() - 25) {
328             leftPos = $(window).scrollLeft() + $(window).width() - balloonWidth - 25;
330             // Check if the arrow is outside the balloon, then move the balloon to
331             // a correct position based on the arrow
332             var arrowRightMargin = this.x + Math.floor(this.arrowWidth/2) + this.cornerRadius;
333             if (arrowRightMargin > leftPos + balloonWidth) {
334                 leftPos = arrowRightMargin - balloonWidth;
335             }
336         }
338         this.canvas.dom.style.left   = pixels(leftPos);
339         this.arrowDiv.dom.style.left = pixels(this.x - leftPos - Math.floor(this.arrowWidth/2));
340     },
341     getBalloonHeight: function() {
342         return this.balloonContent.dom.offsetHeight +
343             this.arrowDiv.dom.offsetHeight;
344     }
345     },
346      function(content, triggerElement, closeHandler, nonCloseElements, balloonClass, arrowClass) {
347          this.PopupDialog(content, triggerElement, closeHandler, nonCloseElements);
349          this.hasCloseButton = exists(closeHandler);
351          this.balloonClass = balloonClass || 'balloonPopup';
352          this.arrowClass = arrowClass || 'balloonPopupArrow';
354          // Constants
355          this.arrowHeight = 19;
356          this.arrowWidth = 35;
357          this.cornerRadius = 6;
358      }
362  * Utility function to display a simple notification popup.
363  * @param {XElement} pointElement The element that triggers the event (onClick on it will be ignored)
364  * @param {XElement} content Anything you want to put inside.
365  */
366 type("NotificationBalloonPopup", ["BalloonPopup"],
367      {
368      },
369      function(pointElement, content) {
370          this.pointElement = pointElement;
372          var canvas = Html.div({style:
373                                 {padding: '5px'}}, content);
375          this.BalloonPopup(canvas,
376                            pointElement,
377                            null,
378                            null,
379                            'balloonPopup yellowBalloon',
380                            'balloonPopupArrow yellowArrow');
381      });
385  * Utility function to display a simple alert popup.
386  * You can think of it as an "alert" replacement.
387  * It will have a title, a close button, and an OK button.
388  * @param {Html or String} title The title of the error popup.
389  * @param {Element} content Anything you want to put inside.
390  */
391 type("AlertPopup", ["ExclusivePopupWithButtons"],
392     {
393         draw: function() {
394             var content = $('<div/>').css({
395                 maxWidth: '400px',
396                 padding: '10px',
397                 textAlign: 'center'
398             }).append($('<div/>').css('textAlign', 'left').html(this.content));
399             return this.ExclusivePopup.prototype.draw.call(this, content);
400         },
402         _getButtons: function() {
403             var self = this;
404             return [
405                 [$T('OK'), function() {
406                     self.close();
407                     self.callback();
408                 }]
409             ];
410         }
411     },
413     function(title, content, callback) {
414         this.content = content;
415         this.callback = callback || positive;
416         this.ExclusivePopup(title, this.callback);
417     }
421  * Utility function to display a simple alert popup.
422  * You can think of it as an "confirm" replacement.
423  * It will have a title, a close button, an OK button and a Cancel button.
424  * @param {Html or String} title The title of the error popup.
425  * @param {Element} content Anything you want to put inside.
426  * @param {function} handler A function that will be called with a boolean as argument:
427  *                   true if the user pressers "ok", or false if the user presses "cancel"
428  */
429 type("ConfirmPopup", ["ExclusivePopupWithButtons"],
430     {
431         draw: function() {
432             return this.ExclusivePopup.prototype.draw.call(this, this.content);
433         },
434         _getButtons: function() {
435             var self = this;
436             return [
437                 [$T(this.buttonTitle), function() {
438                     self.close();
439                     self.handler(true);
440                 }],
441                 [$T(this.cancelButtonTitle), function() {
442                     self.close();
443                     self.handler(false);
444                 }]
445             ];
446         }
447     },
449     function(title, content, handler, buttonTitle, cancelButtonTitle) {
450         var self = this;
452         this.buttonTitle = buttonTitle || 'OK';
453         this.cancelButtonTitle = cancelButtonTitle || 'Cancel';
454         this.content = content;
455         this.handler = handler;
456         this.ExclusivePopupWithButtons(title, function() {
457             self.handler(false);
458             return true;
459         });
460     }
465  * Works exactly the same as the ConfirmPopup, but includes a parametermanager to perform checks when pressing OK
466  */
467 type("ConfirmPopupWithPM", ["ExclusivePopupWithButtons"],
468     {
469         draw: function() {
470             return this.ExclusivePopup.prototype.draw.call(this, this.content);
471         },
472         _getButtons: function() {
473             var self = this;
474             return [
475                 [$T(this.buttonTitle), function() {
476                      if(self.parameterManager.check()) {
477                          self.handler(true);
478                      }
479                 }],
480                 [$T(this.cancelButtonTitle), function() {
481                     self.close();
482                     self.handler(false);
483                 }]
484             ];
485         }
486     },
488     function(title, content, handler) {
489         var self = this;
491         this.content = content;
492         this.handler = handler;
493         this.parameterManager = new IndicoUtil.parameterManager();
494         this.ExclusivePopupWithButtons(title, function(){
495             self.handler(false);
496             return true;
497         });
498     }
502  * Utility function to display a three buttons popup.
503  * The difference with ConfirmButton is the existence of a third button.
504  * Apart from the title and close button, the three buttons display, two of them configurables and Cancel
505  * @param {Html or String} title The title of the error popup.
506  * @param {Element} content Anything you want to put inside.
507  * @param {function} handler A function that will be called with an Integer as argument:
508  *                   1 if the user press button1, 2 for button2, 0 for "Cancel"
509  */
510 type("SpecialRemovePopup", ["ExclusivePopupWithButtons"],
511     {
512         draw: function() {
513             return this.ExclusivePopupWithButtons.prototype.draw.call(this, this.content);
514         },
516         _getButtons: function() {
517             var self = this;
518             return [
519                 [$T(this.buttonTitle1), function() {
520                     self.close();
521                     self.handler(1);
522                 }],
523                 [$T(this.buttonTitle2), function() {
524                     self.close();
525                     self.handler(2);
526                 }],
527                 [$T('Cancel'), function() {
528                     self.close();
529                     self.handler(0);
530                 }]
531             ];
532         }
533     },
535     function(title, content, handler, buttonTitle1, buttonTitle2) {
536         var self = this;
538         this.buttonTitle1 = buttonTitle1;
539         this.buttonTitle2 = buttonTitle2;
540         this.content = content;
541         this.handler = handler;
542         this.ExclusivePopupWithButtons(title, function(){
543             self.handler(0);
544             return true;
545         });
546     }
551  * Utility function to display a three buttons popup.
552  * The difference with ConfirmButton is the existence of a third button.
553  * Apart from the title and close button, the three buttons display Save, Don't Save and Cancel
554  * @param {Html or String} title The title of the error popup.
555  * @param {Element} content Anything you want to put inside.
556  * @param {function} handler A function that will be called with an Integer as argument:
557  *                   1 if the user press "Save", 2 for "Don't Save", 0 for "Cancel"
558  */
559 type("SaveConfirmPopup", ["ExclusivePopupWithButtons"],
560     {
561         draw: function() {
562             return this.ExclusivePopupWithButtons.prototype.draw.call(this, this.content);
563         },
565         _getButtons: function() {
566             var self = this;
567             return [
568                 [$T('Save'), function() {
569                     self.close();
570                     self.handler(1);
571                 }, true],
572                 [$T('Don\'t Save'), function() {
573                     self.close();
574                     self.handler(2);
575                 }],
576                 [$T('Cancel'), function() {
577                     self.close();
578                     self.handler(0);
579                 }]
580             ];
581         }
582     },
584     function(title, content, handler) {
585         var self = this;
587         this.content = content;
588         this.handler = handler;
589         this.ExclusivePopupWithButtons(title, function(){
590             self.handler(0);
591             return true;
592         });
593     }
597 type("WarningPopup", ["AlertPopup"],
598     {
599         _formatLine: function(line) {
600             var result = Html.div({paddingBottom: pixels(2)});
603             var linkStart = 0;
604             var linkMiddle;
605             var linkEnd = 0;
607             while (linkStart >= 0) {
608                 linkStart = line.indexOf('[[', linkEnd);
610                 if (linkStart >= 0) {
611                     result.append(Html.span('',line.substring(linkEnd, linkStart)));
613                     linkMiddle = line.indexOf(' ', linkStart);
614                     linkEnd = line.indexOf(']]', linkStart);
616                     result.append(Html.a({href: line.substring(linkStart+2, linkMiddle)}, line.substring(linkMiddle + 1, linkEnd) ));
617                     linkEnd+= 2;
618                 } else {
619                     result.append(Html.span('',line.substring(linkEnd, line.length)));
620                 }
621             }
623             return result;
624         },
626         _formatContent: function(content, level) {
627             var self = this;
629             if (isString(content)) {
630                 return Html.span('', self._formatLine(content));
632             } else if (isArray(content)) {
633                 var result = Html.ul(level === 0 ? 'warningLevel0' : 'warningLevel1');
634                 each (content, function(line){
635                     if (isString(line)) {
636                         result.append(Html.li({}, self._formatLine(line)));
637                     } else if (isArray(line)) {
638                         result.append(self._formatContent(line, level + 1));
639                     } else {
640                         result.append(Html.li({}, line));
641                     }
642                 });
643                 return result;
644             }
645         }
646     },
647     function(title, lines) {
648         this.AlertPopup(title, this._formatContent(lines, 0).dom);
649     }
653  * Utility function to display a popup with errors.
654  * Useful for notifying the user of input mistakes or other errors.
655  * @param {Html or String} title The title of the error popup.
656  * @param {Array of String} errors An Array of strings with the errors to display.
657  * @param {Html or String} afterMessage A message to display after the list of errors.
658  */
659 type("ErrorPopup", ["ExclusivePopup"],
660      {
661          draw: function() {
662              var errorList = null;
663              if (this.errors.length == 1) {
664                  errorList = Html.div({className:"errorList"}, this.errors[0]);
665              }else{
666                  errorList = Html.ul("errorList");
667                  each(this.errors, function(e) {
668                      errorList.append(Html.li('', e));
669                  });
670              }
672              return this.ExclusivePopup.prototype.draw.call(this, Widget.block([errorList, this.afterMessage]).dom);
673          }
674      },
676      function(title, errors, afterMessage) {
677          this.afterMessage = afterMessage;
678          this.errors = errors;
679          this.ExclusivePopup(title, positive);
680      }
681     );