Modal
Sage modal
Animated
Asyncronous Modals
There are a number of situations where it might be most practical to load content for a modal asyncronously. For example, in a list of products, each product may allow an option to edit that product's details in a modal. In this case you may have a route that delivers only the contents of the modal that can be called asyncronously when the modal trigger is clicked.
Set this up as follows:
- Ensure that you have at least one modal stub in your view using
SageModal
and be sure to provide anid
for it along with any other configurations. As best practices consider:- Use the
remove_content_on_close: true
to ensure that any asynchronous content is removed from the modal when it is closed. - You can provide default content (typically inside of a
SageModalContent
nested within theSageModal
stub) such as aSageLoader
inside this modal stub. This will be replaced by the asynchronous content once it loads and be put back when the content unloads.
- Use the
- Ensure you have an endpoint that delivers only the content of the modal, typically wrapped by the
SageModalContent
component. - In the primary view, add hyperlinks, buttons, or other clickable triggers. On such triggers use the following:
data-js-modaltrigger="[modal-id]"
wheremodal-id
is theid
you placed on the correspondingSageModal
stub.data-js-remote-url="[url]"
whereurl
is the edpoint that delivers the content.
Note as well that you can set up remote_url
's on both the main SageModal
stub and on triggers.
In such cases, the remote_url
from the triggers overrides the one on the modal stub; but if no remote_url
is provided from the trigger, the one on the modal itself is used.
As shown elsewhere on this page default content (such as SageLoader
) can also be manually populated inside the SageModal
that is then replaced by asynchronous remote_url
content.
Events (monitor in browser console)
Use these events for hooking into modal functionality using JS event listeners.
Event name | Event type | Description |
---|---|---|
|
global |
Fires when any modal has been opened |
|
global |
Fires when any modal has been closed |
|
instance | Fires immediately when a modal has been opened |
|
instance |
Fires after a modal has loaded. If |
Sage Component
SageModal
<%
sample_body_text = "<p class='#{SageClassnames::TYPE::BODY}'>Add a description of the content you're showing in the modal.</p>".html_safe
long_sample_text = "<p class=' #{SageClassnames::TYPE::BODY}'>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse urna leo, condimentum nec pellentesque finibus, ultricies pulvinar ante. Donec eu interdum ligula. Pellentesque aliquam ullamcorper orci, nec tempor libero tristique in. Aliquam vitae felis at leo condimentum placerat eget id libero. In dictum tortor ac accumsan aliquam. Donec sit amet tortor porttitor, tincidunt nisl at, egestas lacus. Integer metus augue, aliquet accumsan vulputate eget, tristique id erat. Donec a venenatis nibh, ac molestie risus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Vivamus in orci vitae ex tempor ultrices in a leo. Sed purus magna, vulputate aliquet ligula eget, consectetur sagittis nunc.</p>".html_safe
%>
<%= sage_component SagePanelBlock, {} do %>
<%= sage_component SageButtonGroup, { wrap: true, gap: :sm, spacer: { bottom: :sm }} do %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "Basic",
attributes: {
"data-js-modaltrigger": "cool-modal",
}
} %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "Scrollable Content",
attributes: {
"data-js-modaltrigger": "cool-modal-scrolling",
}
} %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "Fullscreen",
attributes: {
"data-js-modaltrigger": "cool-modal-fullscreen",
}
} %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "Destructive",
attributes: {
"data-js-modaltrigger": "cool-modal-destructive",
}
} %>
<% end %>
<%# Standard Modal %>
<%= sage_component SageModal, { id: "cool-modal", size: "lg" } do %>
<%= sage_component SageModalContent, { title: "Modal header" } do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sample_body_text %>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Link",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "secondary",
value: "Cancel",
} %>
<%= sage_component SageButton, {
style: "primary",
value: "Confirm",
} %>
<% end %>
<% end %>
<% end %>
<%# Standard Modal with Scrolling %>
<%= sage_component SageModal, { allow_scroll: true, id: "cool-modal-scrolling" } do %>
<%= sage_component SageModalContent, { title: "Modal header" } do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= long_sample_text %>
<%= long_sample_text %>
<%= long_sample_text %>
<%= long_sample_text %>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Link",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "secondary",
value: "Cancel",
} %>
<%= sage_component SageButton, {
style: "primary",
value: "Confirm",
} %>
<% end %>
<% end %>
<% end %>
<%# Fullscreen Modal %>
<%= sage_component SageModal, {
id: "cool-modal-fullscreen",
fullscreen: true
} do %>
<%= sage_component SageModalContent, {
title: "Modal header"
} do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sample_body_text %>
<% end %>
<% end %>
<%# Destructive Modal %>
<%= sage_component SageModal, { id: "cool-modal-destructive" } do %>
<%= sage_component SageModalContent, {
title: "Delete item",
help_content: "<p>Are you sure you want to delete this item? This action cannot be undone.</p>",
help_link: {
href: "#",
name: "Link"
}
} do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Link",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "secondary",
value: "Cancel",
} %>
<%= sage_component SageButton, {
style: "danger",
icon: { name: "trash", style: "left" },
value: "Delete",
} %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= sage_component SagePanelBlock, {} do %>
<h3 class="<%= SageClassnames::TYPE::HEADING_6 %>">Animated</h3>
<%= sage_component SageButtonGroup, { gap: :sm, spacer: { bottom: :sm }} do %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "Default",
attributes: {
"data-js-modaltrigger": "cool-modal-animate-default",
}
} %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "To top",
attributes: {
"data-js-modaltrigger": "cool-modal-animate-top",
}
} %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "To left (blur off)",
attributes: {
"data-js-modaltrigger": "cool-modal-animate-left",
}
} %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "To right (blur off)",
attributes: {
"data-js-modaltrigger": "cool-modal-animate-right",
}
} %>
<% end %>
<%# Animated Modals %>
<%= sage_component SageModal, { id: "cool-modal-animate-default", animate: true, css_classes: "custom-class-name-here" } do %>
<%= sage_component SageModalContent, { title: "Modal header" } do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sample_body_text %>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "primary",
value: "Confirm",
} %>
<% end %>
<% end %>
<% end %>
<%= sage_component SageModal, { id: "cool-modal-animate-top", animate: { direction: "top" } } do %>
<%= sage_component SageModalContent, { title: "Modal header" } do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sample_body_text %>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Link",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "primary",
value: "Confirm",
} %>
<% end %>
<% end %>
<% end %>
<%= sage_component SageModal, { id: "cool-modal-animate-left", disable_background_blur: true, animate: { direction: "left" } } do %>
<%= sage_component SageModalContent, { title: "Modal header" } do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sample_body_text %>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Link",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "primary",
value: "Confirm",
} %>
<% end %>
<% end %>
<% end %>
<%= sage_component SageModal, { id: "cool-modal-animate-right", disable_background_blur: true, animate: { direction: "right" } } do %>
<%= sage_component SageModalContent, { title: "Modal header" } do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sample_body_text %>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Link",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "check", style: "left" },
value: "Confirm"
} %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= md("
<h3 class=\"SageClassnames::TYPE::HEADING_6\">Asyncronous Modals</h3>
There are a number of situations where it might be most practical to load content for a modal asyncronously.
For example, in a list of products, each product may allow an option to edit that product's details in a modal.
In this case you may have a route that delivers only the contents of the modal that can be called asyncronously when the modal trigger is clicked.
Set this up as follows:
1. Ensure that you have at least one modal stub in your view using `SageModal` and be sure to provide an `id` for it along with any other configurations.
As best practices consider:
- Use the `remove_content_on_close: true` to ensure that any asynchronous content is removed from the modal when it is closed.
- You can provide default content (typically inside of a `SageModalContent` nested within the `SageModal` stub) such as a `SageLoader` inside this modal stub.
This will be replaced by the asynchronous content once it loads and be put back when the content unloads.
2. Ensure you have an endpoint that delivers _only_ the content of the modal, typically wrapped by the `SageModalContent` component.
3. In the primary view, add hyperlinks, buttons, or other clickable triggers. On such triggers use the following:
- `data-js-modaltrigger=\"[modal-id]\"` where `modal-id` is the `id` you placed on the corresponding `SageModal` stub.
- `data-js-remote-url=\"[url]\"` where `url` is the edpoint that delivers the content.
Note as well that you can set up `remote_url`'s on both the main `SageModal` stub and on triggers.
In such cases, the `remote_url` from the triggers overrides the one on the modal stub; but if no `remote_url` is provided from the trigger, the one on the modal itself is used.
As shown elsewhere on this page default content (such as `SageLoader`) can also be manually populated inside the `SageModal` that is then replaced by asynchronous `remote_url` content.
", use_sage_type: true) %>
<%= sage_component SageButtonGroup, { gap: :sm } do %>
<%= sage_component SageButton, {
html_attributes: {
"data-js-modaltrigger": "test-modal",
},
style: "primary",
value: "Open modal A",
} %>
<%= sage_component SageButton, {
html_attributes: {
"data-js-modaltrigger": "test-modal",
"data-js-remote-url": async_path("modal-demo-2"),
},
style: "primary",
value: "Open modal B",
} %>
<%= sage_component SageButton, {
html_attributes: {
"data-js-modaltrigger": "test-modal",
"data-js-remote-url": async_path("modal-demo-3"),
},
style: "primary",
value: "Open modal C",
} %>
<% end %>
<%= sage_component SageModal, { id: "test-modal", remote_url: async_path("modal-demo"), remove_content_on_close: true, } do %>
<%= sage_component SageModalContent, {} do %>
<%= sage_component SageLoader, { type: "spinner" } %>
<% end %>
<% end %>
<%= sage_component SagePanelStack, {} do %>
<h3 class="<%= SageClassnames::TYPE::HEADING_6 %>">Events (monitor in browser console)</h3>
<p>Use these events for hooking into modal functionality using JS event listeners.</p>
<%= sage_component SageTable, {
striped: true,
responsive: true,
headers: [
"Event name",
"Event type",
"Description"
],
rows: [
{
col_1: md("`sage.modal.active`"),
col_2: "global",
col_3: md("Fires when **any** modal has been opened")
},
{
col_1: md("`sage.modal.closeAll`"),
col_2: "global",
col_3: md("Fires when **any** modal has been closed")
},
{
col_1: md("`sage.modal.opening`"),
col_2: "instance",
col_3: "Fires immediately when a modal has been opened"
},
{
col_1: md("`sage.modal.open`"),
col_2: "instance",
col_3: md("Fires after a modal has loaded. If `animate` is enabled, this event will run upon completion of the modal\'s animation. Otherwise, this event will run immediately along with `sage.modal.opening`.")
},
]
} %>
<%= sage_component SageButtonGroup, { gap: :sm, spacer: { bottom: :sm }} do %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "'sage.modal.active'",
attributes: {
"data-js-modaltrigger": "cool-modal-event-active",
}
} %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "'sage.modal.opening'",
attributes: {
"data-js-modaltrigger": "cool-modal-event-opening",
}
} %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "launch", style: "right" },
value: "'sage.modal.open'",
attributes: {
"data-js-modaltrigger": "cool-modal-event-open",
}
} %>
<% end %>
<%# Event-enabled Modals %>
<%= sage_component SageModal, { id: "cool-modal-event-active", disable_background_blur: true, animate: true } do %>
<%= sage_component SageModalContent, { title: "Example 'Active' Event Modal" } do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sample_body_text %>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "check", style: "left" },
value: "Take An Action",
} %>
<% end %>
<% end %>
<% end %>
<%= sage_component SageModal, { id: "cool-modal-event-open", disable_background_blur: true, animate: true, css_classes: "custom-class-name-here" } do %>
<%= sage_component SageModalContent, { title: "Example 'Open' Event Modal" } do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sample_body_text %>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "check", style: "left" },
value: "Take An Action",
} %>
<% end %>
<% end %>
<% end %>
<%= sage_component SageModal, { id: "cool-modal-event-opening", disable_background_blur: true, animate: true } do %>
<%= sage_component SageModalContent, { title: "Example 'Opening' Event Modal" } do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sample_body_text %>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "check", style: "left" },
value: "Take An Action",
} %>
<% end %>
<% end %>
<% end %>
<%= sage_component SageModal, { id: "cool-modal-event-close", disable_background_blur: true, animate: true } do %>
<%= sage_component SageModalContent, { title: "Example 'CloseAll' Event Modal" } do %>
<% content_for :sage_header_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
icon: { name: "remove", style: "only" },
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sample_body_text %>
<% content_for :sage_footer do %>
<% content_for :sage_footer_aside do %>
<%= sage_component SageButton, {
style: "secondary",
subtle: true,
value: "Close Modal",
attributes: { "data-js-modal": true }
} %>
<% end %>
<%= sage_component SageButton, {
style: "primary",
icon: { name: "check", style: "left" },
value: "Take An Action",
} %>
<% end %>
<% end %>
<% end %>
<% end %>
<script>
(function() {
const modalOpeningExample = document.querySelector("[data-js-modal=cool-modal-event-opening]");
const modalOpenExample = document.querySelector("[data-js-modal=cool-modal-event-open]");
document.addEventListener("sage.modal.active", function(e) {
console.info("sage.modal.active global event fired", e);
});
modalOpeningExample.addEventListener("sage.modal.opening", function(e) {
console.info("sage.modal.opening event fired", e);
});
modalOpenExample.addEventListener("sage.modal.open", function(e) {
console.info("sage.modal.open event fired", e);
});
document.addEventListener("sage.modal.closeAll", function(e) {
console.info("sage.modal.closeAll global event fired", e);
});
})();
</script>
Property | Description | Type | Default |
---|---|---|---|
|
Enabling this property will return the JS early to not initialize any handlers. |
Boolean |
|
|
When |
Boolean |
|
|
Outputs additional CSS classes as specified. |
String |
|
|
Sets the direction of the animation when |
String |
|
|
Disables the background blur filter, with a darkened background color. Recommended for increased animation performance when |
Boolean |
|
|
Enabling this property will return the JS early to not initialize any handlers. |
Boolean |
|
|
Enabling this property will return the JS early to not initialize any handlers. |
Boolean |
|
|
Toggles whether to use the fullscreen variant of the modal by attaching |
Boolean |
|
|
Unique identifier for component. Should match the |
String |
`nil |
|
Toggles whether to use the large variant of the modal by attaching |
Boolean |
|
|
Toggles whether to delete the |
Boolean |
|
|
Specify a url which from which to load the modal content on open. This will be requested & content replaced every time the modal is opened. |
String |
|
|
Presets a size for the modal. |
|
|
Modal Content | |||
|
When present, sets the icon with optional color in the header of the comoponent |
|
|
|
Sets the content for the Popover component |
String |
|
|
Array containing help link |
String |
|
|
Sets the title for the subheader Popover |
String |
|
|
Populates the |
String |
|
|
Optionally enforces either a |
|
|
|
Adds an optional subheader under the modal title. |
String |
|
Section: |
Populates the |
String |
|
Section: |
Populates the the page indicator in the header of standard modals. |
String |
|
Section: |
Populates the header of a fullscreen modal with a progress bar. |
String |
|
Section: |
Populates the |
String |
|
Section: |
Populates the |
String |
|
Events | |||
Global/window events | |||
|
This event is fired immediately when any modal has been opened. |
||
|
Fires on when a modal has been closed, either from actuating a button, |
||
Individual modal events | |||
|
This modal event is fired immediately when a modal has been opened. |
||
|
Fired after a modal has completed its loading animation. Note that if |
||
Sections |
|
||
|
When present, this will show containing |
|
|
|
This area holds the button that closes the modal. This resides in the header of the component. |
|
|
|
When present, sets the image in the header of the component |
|
|
|
This area holds the page indicator for the modal. This is to be used on multi-page modals. |
|
|
|
This can be used to provide custom header content, typically in lieue of the |
|
|
|
This area holds the cta buttons for the component. |
|
|
|
This area holds the cancel or close modal button in the footer of the component. |
|
- Be sure that the
<button>
'sdata-js-modaltrigger
and the modalid
match to establish the necessary link.
- DO NOT use the
remove_content_on_close
property if your modal's content is not populated using JavaScript. The modal will not have content the next time it's triggered