###* Modal dialogs ============= Instead of linking to another page fragment, you can also choose to open any target CSS selector in a modal dialog. For small popup overlays ("dropdowns") see [up.popup](/up.popup) instead. @class up.modal ### up.modal = (-> u = up.util config = width: 'auto' height: 'auto' openAnimation: 'fade-in' closeAnimation: 'fade-out' closeLabel: 'X' template: (config) -> """
#{config.closeLabel}
""" ###* Sets default options for future modals. @method up.modal.defaults @param {Number} [options.width='auto'] The width of the dialog in pixels. Defaults to `'auto'`, meaning that the dialog will grow to fit its contents. @param {Number} [options.height='auto'] The height of the dialog in pixels. Defaults to `'auto'`, meaning that the dialog will grow to fit its contents. @param {String|Function(config)} [options.template] A string containing the HTML structure of the modal. You can supply an alternative template string, but make sure that it contains tags with the classes `up-modal`, `up-modal-dialog` and `up-modal-content`. You can also supply a function that returns a HTML string. The function will be called with the modal options (merged from these defaults and any per-open overrides) whenever a modal opens. @param {String} [options.closeLabel='X'] The label of the button that closes the dialog. @param {String} [options.openAnimation='fade-in'] The animation used to open the modal. The animation will be applied to both the dialog box and the overlay dimming the page. @param {String} [options.closeAnimation='fade-out'] The animation used to close the modal. The animation will be applied to both the dialog box and the overlay dimming the page. ### defaults = (options) -> u.extend(config, options) templateHtml = -> template = config.template if u.isFunction(template) template(config) else template rememberHistory = -> $popup = $('.up-modal') $popup.attr('up-previous-url', up.browser.url()) $popup.attr('up-previous-title', document.title) discardHistory = -> $popup = $('.up-modal') $popup.removeAttr('up-previous-url') $popup.removeAttr('up-previous-title') createHiddenModal = (selector, width, height, sticky) -> $modal = $(templateHtml()) $modal.attr('up-sticky', '') if sticky $modal.attr('up-previous-url', up.browser.url()) $modal.attr('up-previous-title', document.title) $dialog = $modal.find('.up-modal-dialog') $dialog.css('width', width) if u.isPresent(width) $dialog.css('height', height) if u.isPresent(height) $content = $dialog.find('.up-modal-content') $placeholder = u.$createElementFromSelector(selector) $placeholder.appendTo($content) $modal.appendTo(document.body) rememberHistory() $modal.hide() $modal updated = ($modal, animation, animateOptions) -> $modal.show() up.animate($modal, animation, animateOptions) ###* Opens the given link's destination in a modal overlay: var $link = $('...'); up.modal.open($link); Any option attributes for [`a[up-modal]`](#a.up-modal) will be honored. You can also open a URL directly like this: up.modal.open({ url: '/foo', target: '.list' }) This will request `/foo`, extract the `.list` selector from the response and open the selected container in a modal dialog. @method up.modal.open @param {Element|jQuery|String} [elementOrSelector] The link to follow. Can be omitted if you give `options.url` instead. @param {String} [options.url] The URL to open. Can be omitted if you give `elementOrSelector` instead. @param {String} [options.target] The selector to extract from the response and open in a modal dialog. @param {Number} [options.width] The width of the dialog in pixels. By [default](#up.modal.defaults) the dialog will grow to fit its contents. @param {Number} [options.height] The width of the dialog in pixels. By [default](#up.modal.defaults) the dialog will grow to fit its contents. @param {Boolean} [options.sticky=false] If set to `true`, the modal remains open even if the page changes in the background. @param {Object} [options.history=true] Whether to add a browser history entry for the modal's source URL. @param {String} [options.animation] The animation to use when opening the modal. @param {Number} [options.duration] The duration of the animation. See [`up.animate`](/up.motion#up.animate). @param {Number} [options.delay] The delay before the animation starts. See [`up.animate`](/up.motion#up.animate). @param {String} [options.easing] The timing function that controls the animation's acceleration. [`up.animate`](/up.motion#up.animate). @return {Promise} A promise that will be resolved when the modal has finished loading. ### open = (args...) -> if u.isObject(args[0]) && !u.isElement(args[0]) && !u.isJQuery(args[0]) $link = u.nullJquery() options = args[0] else $link = $(args[0]) options = args[1] options = u.options(options) url = u.option(options.url, $link.attr('up-href'), $link.attr('href')) selector = u.option(options.target, $link.attr('up-modal'), 'body') width = u.option(options.width, $link.attr('up-width'), config.width) height = u.option(options.height, $link.attr('up-height'), config.height) animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation) sticky = u.option(options.sticky, $link.is('[up-sticky]')) history = if up.browser.canPushState() then u.option(options.history, $link.attr('up-history'), true) else false animateOptions = up.motion.animateOptions(options, $link) close() $modal = createHiddenModal(selector, width, height, sticky) up.replace(selector, url, history: history insert: -> updated($modal, animation, animateOptions) ) ###* Returns the source URL for the fragment displayed in the current modal overlay, or `undefined` if no modal is currently open. @method up.modal.source @return {String} the source URL ### source = -> $modal = $('.up-modal') unless $modal.is('.up-destroying') $modal.find('[up-source]').attr('up-source') ###* Closes a currently opened modal overlay. Does nothing if no modal is currently open. @method up.modal.close @param {Object} options See options for [`up.animate`](/up.motion#up.animate) ### close = (options) -> $modal = $('.up-modal') if $modal.length options = u.options(options, animation: config.closeAnimation, url: $modal.attr('up-previous-url') title: $modal.attr('up-previous-title') ) up.destroy($modal, options) autoclose = -> unless $('.up-modal').is('[up-sticky]') discardHistory() close() ###* Clicking this link will load the destination via AJAX and open the given selector in a modal dialog. Example: Switch blog Clicking would request the path `/blog` and select `.blog-list` from the HTML response. Up.js will dim the page with an overlay and place the matching `.blog-list` tag will be placed in a modal dialog. \#\#\#\# Customizing the dialog design Loading the Up.js stylesheet will give you a minimal dialog design: - Dialog contents are displayed in a white box that is centered vertically and horizontally. - There is a a subtle box shadow around the dialog - The box will grow to fit the dialog contents, but never grow larger than the screen - The box is placed over a semi-transparent background to dim the rest of the page - There is a button to close the dialog in the top-right corner The easiest way to change how the dialog looks is by overriding the [default CSS styles](https://github.com/makandra/upjs/blob/master/lib/assets/stylesheets/up/modal.css.sass). By default the dialog uses the following DOM structure (continuing the blog-switcher example from above):
X
    ...
If you want to change the design beyond CSS, you can configure Up.js to [use a different HTML structure](#up.modal.defaults). \#\#\#\# Closing behavior By default the dialog automatically closes *whenever a page fragment below the dialog is updated*. This is useful to have the dialog interact with the page that opened it, e.g. by updating parts of a larger form or by signing in a user and revealing additional information. To disable this behavior, give the opening link an `up-sticky` attribute: Settings @method a[up-modal] @ujs @param [up-sticky] @param [up-animation] @param [up-height] @param [up-width] @param [up-history] ### up.on('click', 'a[up-modal]', (event, $link) -> event.preventDefault() if $link.is('.up-current') close() else open($link) ) # Close the modal when someone clicks outside the dialog # (but not on a modal opener). up.on('click', 'body', (event, $body) -> $target = $(event.target) unless $target.closest('.up-modal-dialog').length || $target.closest('[up-modal]').length close() ) up.bus.on('fragment:ready', ($fragment) -> unless $fragment.closest('.up-modal').length autoclose() ) # Close the pop-up overlay when the user presses ESC. up.magic.onEscape(-> close()) ###* When this element is clicked, closes a currently open dialog. Does nothing if no modal is currently open. @method [up-close] @ujs ### up.on('click', '[up-close]', (event, $element) -> if $element.closest('.up-modal') close() ) # The framework is reset between tests, so also close # a currently open modal dialog. up.bus.on 'framework:reset', close open: open close: close source: source defaults: defaults )()