/*! * jQuery UI Dialog @VERSION * http://jqueryui.com * * Copyright 2013 jQuery Foundation and other contributors * Released under the MIT license. * http://jquery.org/license * * http://api.jqueryui.com/dialog/ * * Depends: * jquery.ui.core.js * jquery.ui.widget.js * jquery.ui.button.js * jquery.ui.draggable.js * jquery.ui.mouse.js * jquery.ui.position.js * jquery.ui.resizable.js */ (function( $, undefined ) { var sizeRelatedOptions = { buttons: true, height: true, maxHeight: true, maxWidth: true, minHeight: true, minWidth: true, width: true }, resizableRelatedOptions = { maxHeight: true, maxWidth: true, minHeight: true, minWidth: true }; $.widget( "ui.dialog", { version: "@VERSION", options: { appendTo: "body", autoOpen: true, buttons: [], closeOnEscape: true, closeText: "Close", dialogClass: "", draggable: true, hide: null, height: "auto", maxHeight: null, maxWidth: null, minHeight: 150, minWidth: 150, modal: false, position: { my: "center", at: "center", of: window, collision: "fit", // Ensure the titlebar is always visible using: function( pos ) { var topOffset = $( this ).css( pos ).offset().top; if ( topOffset < 0 ) { $( this ).css( "top", pos.top - topOffset ); } } }, resizable: true, show: null, title: null, width: 300, // callbacks beforeClose: null, close: null, drag: null, dragStart: null, dragStop: null, focus: null, open: null, resize: null, resizeStart: null, resizeStop: null }, _create: function() { this.originalCss = { display: this.element[0].style.display, width: this.element[0].style.width, minHeight: this.element[0].style.minHeight, maxHeight: this.element[0].style.maxHeight, height: this.element[0].style.height }; this.originalPosition = { parent: this.element.parent(), index: this.element.parent().children().index( this.element ) }; this.originalTitle = this.element.attr("title"); this.options.title = this.options.title || this.originalTitle; this._createWrapper(); this.element .show() .removeAttr("title") .addClass("ui-dialog-content ui-widget-content") .appendTo( this.uiDialog ); this._createTitlebar(); this._createButtonPane(); if ( this.options.draggable && $.fn.draggable ) { this._makeDraggable(); } if ( this.options.resizable && $.fn.resizable ) { this._makeResizable(); } this._isOpen = false; }, _init: function() { if ( this.options.autoOpen ) { this.open(); } }, _appendTo: function() { var element = this.options.appendTo; if ( element && (element.jquery || element.nodeType) ) { return $( element ); } return this.document.find( element || "body" ).eq( 0 ); }, _destroy: function() { var next, originalPosition = this.originalPosition; this._destroyOverlay(); this.element .removeUniqueId() .removeClass("ui-dialog-content ui-widget-content") .css( this.originalCss ) // Without detaching first, the following becomes really slow .detach(); this.uiDialog.stop( true, true ).remove(); if ( this.originalTitle ) { this.element.attr( "title", this.originalTitle ); } next = originalPosition.parent.children().eq( originalPosition.index ); // Don't try to place the dialog next to itself (#8613) if ( next.length && next[0] !== this.element[0] ) { next.before( this.element ); } else { originalPosition.parent.append( this.element ); } }, widget: function() { return this.uiDialog; }, disable: $.noop, enable: $.noop, close: function( event ) { var that = this; if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) { return; } this._isOpen = false; this._destroyOverlay(); if ( !this.opener.filter(":focusable").focus().length ) { // Hiding a focused element doesn't trigger blur in WebKit // so in case we have nothing to focus on, explicitly blur the active element // https://bugs.webkit.org/show_bug.cgi?id=47182 $( this.document[0].activeElement ).blur(); } this._hide( this.uiDialog, this.options.hide, function() { that._trigger( "close", event ); }); }, isOpen: function() { return this._isOpen; }, moveToTop: function() { this._moveToTop(); }, _moveToTop: function( event, silent ) { var moved = !!this.uiDialog.nextAll(":visible").insertBefore( this.uiDialog ).length; if ( moved && !silent ) { this._trigger( "focus", event ); } return moved; }, open: function() { var that = this; if ( this._isOpen ) { if ( this._moveToTop() ) { this._focusTabbable(); } return; } this._isOpen = true; this.opener = $( this.document[0].activeElement ); this._size(); this._position(); this._createOverlay(); this._moveToTop( null, true ); this._show( this.uiDialog, this.options.show, function() { that._focusTabbable(); that._trigger("focus"); }); this._trigger("open"); }, _focusTabbable: function() { // Set focus to the first match: // 1. First element inside the dialog matching [autofocus] // 2. Tabbable element inside the content element // 3. Tabbable element inside the buttonpane // 4. The close button // 5. The dialog itself var hasFocus = this.element.find("[autofocus]"); if ( !hasFocus.length ) { hasFocus = this.element.find(":tabbable"); } if ( !hasFocus.length ) { hasFocus = this.uiDialogButtonPane.find(":tabbable"); } if ( !hasFocus.length ) { hasFocus = this.uiDialogTitlebarClose.filter(":tabbable"); } if ( !hasFocus.length ) { hasFocus = this.uiDialog; } hasFocus.eq( 0 ).focus(); }, _keepFocus: function( event ) { function checkFocus() { var activeElement = this.document[0].activeElement, isActive = this.uiDialog[0] === activeElement || $.contains( this.uiDialog[0], activeElement ); if ( !isActive ) { this._focusTabbable(); } } event.preventDefault(); checkFocus.call( this ); // support: IE // IE <= 8 doesn't prevent moving focus even with event.preventDefault() // so we check again later this._delay( checkFocus ); }, _createWrapper: function() { this.uiDialog = $("
") .addClass( "ui-dialog ui-widget ui-widget-content ui-corner-all ui-front " + this.options.dialogClass ) .hide() .attr({ // Setting tabIndex makes the div focusable tabIndex: -1, role: "dialog" }) .appendTo( this._appendTo() ); this._on( this.uiDialog, { keydown: function( event ) { if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode && event.keyCode === $.ui.keyCode.ESCAPE ) { event.preventDefault(); this.close( event ); return; } // prevent tabbing out of dialogs if ( event.keyCode !== $.ui.keyCode.TAB ) { return; } var tabbables = this.uiDialog.find(":tabbable"), first = tabbables.filter(":first"), last = tabbables.filter(":last"); if ( ( event.target === last[0] || event.target === this.uiDialog[0] ) && !event.shiftKey ) { first.focus( 1 ); event.preventDefault(); } else if ( ( event.target === first[0] || event.target === this.uiDialog[0] ) && event.shiftKey ) { last.focus( 1 ); event.preventDefault(); } }, mousedown: function( event ) { if ( this._moveToTop( event ) ) { this._focusTabbable(); } } }); // We assume that any existing aria-describedby attribute means // that the dialog content is marked up properly // otherwise we brute force the content as the description if ( !this.element.find("[aria-describedby]").length ) { this.uiDialog.attr({ "aria-describedby": this.element.uniqueId().attr("id") }); } }, _createTitlebar: function() { var uiDialogTitle; this.uiDialogTitlebar = $("
") .addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix") .prependTo( this.uiDialog ); this._on( this.uiDialogTitlebar, { mousedown: function( event ) { // Don't prevent click on close button (#8838) // Focusing a dialog that is partially scrolled out of view // causes the browser to scroll it into view, preventing the click event if ( !$( event.target ).closest(".ui-dialog-titlebar-close") ) { // Dialog isn't getting focus when dragging (#8063) this.uiDialog.focus(); } } }); // support: IE // Use type="button" to prevent enter keypresses in textboxes from closing the // dialog in IE (#9312) this.uiDialogTitlebarClose = $( "" ) .button({ label: this.options.closeText, icons: { primary: "ui-icon-closethick" }, text: false }) .addClass("ui-dialog-titlebar-close") .appendTo( this.uiDialogTitlebar ); this._on( this.uiDialogTitlebarClose, { click: function( event ) { event.preventDefault(); this.close( event ); } }); uiDialogTitle = $("") .uniqueId() .addClass("ui-dialog-title") .prependTo( this.uiDialogTitlebar ); this._title( uiDialogTitle ); this.uiDialog.attr({ "aria-labelledby": uiDialogTitle.attr("id") }); }, _title: function( title ) { if ( !this.options.title ) { title.html(" "); } title.text( this.options.title ); }, _createButtonPane: function() { this.uiDialogButtonPane = $("
") .addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"); this.uiButtonSet = $("
") .addClass("ui-dialog-buttonset") .appendTo( this.uiDialogButtonPane ); this._createButtons(); }, _createButtons: function() { var that = this, buttons = this.options.buttons; // if we already have a button pane, remove it this.uiDialogButtonPane.remove(); this.uiButtonSet.empty(); if ( $.isEmptyObject( buttons ) || ($.isArray( buttons ) && !buttons.length) ) { this.uiDialog.removeClass("ui-dialog-buttons"); return; } $.each( buttons, function( name, props ) { var click, buttonOptions; props = $.isFunction( props ) ? { click: props, text: name } : props; // Default to a non-submitting button props = $.extend( { type: "button" }, props ); // Change the context for the click callback to be the main element click = props.click; props.click = function() { click.apply( that.element[0], arguments ); }; buttonOptions = { icons: props.icons, text: props.showText }; delete props.icons; delete props.showText; $( "", props ) .button( buttonOptions ) .appendTo( that.uiButtonSet ); }); this.uiDialog.addClass("ui-dialog-buttons"); this.uiDialogButtonPane.appendTo( this.uiDialog ); }, _makeDraggable: function() { var that = this, options = this.options; function filteredUi( ui ) { return { position: ui.position, offset: ui.offset }; } this.uiDialog.draggable({ cancel: ".ui-dialog-content, .ui-dialog-titlebar-close", handle: ".ui-dialog-titlebar", containment: "document", start: function( event, ui ) { $( this ).addClass("ui-dialog-dragging"); that._blockFrames(); that._trigger( "dragStart", event, filteredUi( ui ) ); }, drag: function( event, ui ) { that._trigger( "drag", event, filteredUi( ui ) ); }, stop: function( event, ui ) { var left = ui.offset.left - that.document.scrollLeft(), top = ui.offset.top - that.document.scrollTop(); options.position = { my: "left top", at: "left" + (left >= 0 ? "+" : "") + left + " " + "top" + (top >= 0 ? "+" : "") + top, of: that.window }; $( this ).removeClass("ui-dialog-dragging"); that._unblockFrames(); that._trigger( "dragStop", event, filteredUi( ui ) ); } }); }, _makeResizable: function() { var that = this, options = this.options, handles = options.resizable, // .ui-resizable has position: relative defined in the stylesheet // but dialogs have to use absolute or fixed positioning position = this.uiDialog.css("position"), resizeHandles = typeof handles === "string" ? handles : "n,e,s,w,se,sw,ne,nw"; function filteredUi( ui ) { return { originalPosition: ui.originalPosition, originalSize: ui.originalSize, position: ui.position, size: ui.size }; } this.uiDialog.resizable({ cancel: ".ui-dialog-content", containment: "document", alsoResize: this.element, maxWidth: options.maxWidth, maxHeight: options.maxHeight, minWidth: options.minWidth, minHeight: this._minHeight(), handles: resizeHandles, start: function( event, ui ) { $( this ).addClass("ui-dialog-resizing"); that._blockFrames(); that._trigger( "resizeStart", event, filteredUi( ui ) ); }, resize: function( event, ui ) { that._trigger( "resize", event, filteredUi( ui ) ); }, stop: function( event, ui ) { var offset = that.uiDialog.offset(), left = offset.left - that.document.scrollLeft(), top = offset.top - that.document.scrollTop(); options.height = that.uiDialog.height(); options.width = that.uiDialog.width(); options.position = { my: "left top", at: "left" + (left >= 0 ? "+" : "") + left + " " + "top" + (top >= 0 ? "+" : "") + top, of: that.window }; $( this ).removeClass("ui-dialog-resizing"); that._unblockFrames(); that._trigger( "resizeStop", event, filteredUi( ui ) ); } }) .css( "position", position ); }, _minHeight: function() { var options = this.options; return options.height === "auto" ? options.minHeight : Math.min( options.minHeight, options.height ); }, _position: function() { // Need to show the dialog to get the actual offset in the position plugin var isVisible = this.uiDialog.is(":visible"); if ( !isVisible ) { this.uiDialog.show(); } this.uiDialog.position( this.options.position ); if ( !isVisible ) { this.uiDialog.hide(); } }, _setOptions: function( options ) { var that = this, resize = false, resizableOptions = {}; $.each( options, function( key, value ) { that._setOption( key, value ); if ( key in sizeRelatedOptions ) { resize = true; } if ( key in resizableRelatedOptions ) { resizableOptions[ key ] = value; } }); if ( resize ) { this._size(); this._position(); } if ( this.uiDialog.is(":data(ui-resizable)") ) { this.uiDialog.resizable( "option", resizableOptions ); } }, _setOption: function( key, value ) { var isDraggable, isResizable, uiDialog = this.uiDialog; if ( key === "dialogClass" ) { uiDialog .removeClass( this.options.dialogClass ) .addClass( value ); } if ( key === "disabled" ) { return; } this._super( key, value ); if ( key === "appendTo" ) { this.uiDialog.appendTo( this._appendTo() ); } if ( key === "buttons" ) { this._createButtons(); } if ( key === "closeText" ) { this.uiDialogTitlebarClose.button({ // Ensure that we always pass a string label: "" + value }); } if ( key === "draggable" ) { isDraggable = uiDialog.is(":data(ui-draggable)"); if ( isDraggable && !value ) { uiDialog.draggable("destroy"); } if ( !isDraggable && value ) { this._makeDraggable(); } } if ( key === "position" ) { this._position(); } if ( key === "resizable" ) { // currently resizable, becoming non-resizable isResizable = uiDialog.is(":data(ui-resizable)"); if ( isResizable && !value ) { uiDialog.resizable("destroy"); } // currently resizable, changing handles if ( isResizable && typeof value === "string" ) { uiDialog.resizable( "option", "handles", value ); } // currently non-resizable, becoming resizable if ( !isResizable && value !== false ) { this._makeResizable(); } } if ( key === "title" ) { this._title( this.uiDialogTitlebar.find(".ui-dialog-title") ); } }, _size: function() { // If the user has resized the dialog, the .ui-dialog and .ui-dialog-content // divs will both have width and height set, so we need to reset them var nonContentHeight, minContentHeight, maxContentHeight, options = this.options; // Reset content sizing this.element.show().css({ width: "auto", minHeight: 0, maxHeight: "none", height: 0 }); if ( options.minWidth > options.width ) { options.width = options.minWidth; } // reset wrapper sizing // determine the height of all the non-content elements nonContentHeight = this.uiDialog.css({ height: "auto", width: options.width }) .outerHeight(); minContentHeight = Math.max( 0, options.minHeight - nonContentHeight ); maxContentHeight = typeof options.maxHeight === "number" ? Math.max( 0, options.maxHeight - nonContentHeight ) : "none"; if ( options.height === "auto" ) { this.element.css({ minHeight: minContentHeight, maxHeight: maxContentHeight, height: "auto" }); } else { this.element.height( Math.max( 0, options.height - nonContentHeight ) ); } if (this.uiDialog.is(":data(ui-resizable)") ) { this.uiDialog.resizable( "option", "minHeight", this._minHeight() ); } }, _blockFrames: function() { this.iframeBlocks = this.document.find( "iframe" ).map(function() { var iframe = $( this ); return $( "
" ) .css({ position: "absolute", width: iframe.outerWidth(), height: iframe.outerHeight() }) .appendTo( iframe.parent() ) .offset( iframe.offset() )[0]; }); }, _unblockFrames: function() { if ( this.iframeBlocks ) { this.iframeBlocks.remove(); delete this.iframeBlocks; } }, _allowInteraction: function( event ) { if ( $( event.target ).closest(".ui-dialog").length ) { return true; } // TODO: Remove hack when datepicker implements // the .ui-front logic (#8989) return !!$( event.target ).closest(".ui-datepicker").length; }, _createOverlay: function() { if ( !this.options.modal ) { return; } // We use a delay in case the overlay is created from an // event that we're going to be cancelling (#2804) var isOpening = true; this._delay(function() { isOpening = false; }); if ( !this.document.data( "ui-dialog-overlays" ) ) { // Prevent use of anchors and inputs this._on( this.document, { focusin: function( event ) { if ( isOpening ) { return; } if ( !this._allowInteraction( event ) ) { event.preventDefault(); this.document.find( ".ui-dialog:visible:last .ui-dialog-content" ) .data( this.widgetFullName )._focusTabbable(); } } }); } this.overlay = $("
") .addClass("ui-widget-overlay ui-front") .appendTo( this._appendTo() ); this._on( this.overlay, { mousedown: "_keepFocus" }); this.document.data( "ui-dialog-overlays", (this.document.data( "ui-dialog-overlays" ) || 0) + 1 ); }, _destroyOverlay: function() { if ( !this.options.modal ) { return; } if ( this.overlay ) { var overlays = this.document.data( "ui-dialog-overlays" ) - 1; if ( !overlays ) { this.document .off( "focusin" ) .removeData( "ui-dialog-overlays" ); } else { this.document.data( "ui-dialog-overlays", overlays ); } this.overlay.remove(); this.overlay = null; } } }); }( jQuery ) );