Modal Base: Speedinvest Stock Table Empty

See "img upload" in sidebar snet/molecules for variants, or go to info panel and look for it in "references" (@snet-img-upload)

Refer to modal frame found in view tab for complete structure without content.

A modal's default size is 640px

Modifier classes for modals

  • modal-sm: 480px
  • modal-md: 720px (mainly used for modals containing images)
  • modal-lg: 896px
  • modal-xl: 1120px

Content loading

A modal with a content loading component behaves as follows:

  • It has a second body element with the -isLoading class that is hidden by default, and shown only when ...
  • ... the -contentLoading modifier is applied to the modal element ...
  • ... which in turn hides the regular modal body element which into which the content is injected
<div class="sds-modal modal fade" id="speedinvestStockModalEmpty" aria-labelledby="speedinvestStockModalEmptyTitle" aria-modal="true" role="dialog" tabindex="-1">
    <div class="sds-modal__dialog modal-dialog modal-dialog-centered modal-dialog-scrollable">
        <div class="sds-modal__content modal-content">

            <div class="sds-modal__header modal-header">

                <button type="button" class="sds-btn -iconBtn -btnSecondary -ghost sds-modal__close" data-dismiss="modal" aria-label="Close">

                    <span aria-hidden="true" class="sds-icon sds-icon-close"></span>

                </button>

            </div>

            <div class="sds-modal__body modal-body">

                <div class="sds-stackXl">
                    <div class="sds-box -boxInsetLg -boxSunken">
                        <div class="sds-emptyState text-center">
                            <div class="sds-stackXs">
                                <span class="sds-emptyState__icon sds-icon sds-icon-graphline-l"></span>
                                <div class="sds-stackXxs">
                                    <p class="sds-textBodyLg sds-textSemiBold">Tableau récapitulatif non disponible</p>

                                    <p class="sds-textSemiBold">Le tableau indiquant la performance TWR &amp; MWR de vos titres sera disponible lorsque vous aurez acquis des titres.</p>

                                </div>
                            </div>

                        </div>
                    </div>
                    <div class="sds-stackXs">
                        <div class="h3">Calculs Gains journaliers</div>
                        <p>La classification des produits financiers est celle opérée par la norme comptable IFRS 7.
                            Elle distingue trois types d’instruments financiers en fonction de la disponibilité et de la liquidité
                            des éléments de valorisation. Le premier niveau se caractérise par un prix disponible directement (prix
                            de marché) et une liquidité suffisante. La deuxième regroupe les produits qui peuvent être valorisés par
                            un modèle standard et des données cotées.</p>
                    </div>
                    <div class="sds-stackXs">
                        <div class="h3">Calculs Gains totaux</div>
                        <p>La classification des produits financiers est celle opérée par la norme comptable IFRS 7.
                            Elle distingue trois types d’instruments financiers en fonction de la disponibilité et de la liquidité
                            des
                            éléments de valorisation. Le premier niveau se caractérise par un prix disponible directement (prix de
                            marché) et une liquidité suffisante. La deuxième regroupe les produits qui peuvent être valorisés par un
                            modèle standard et des données cotées.</p>
                    </div>
                </div>

            </div>

        </div>
    </div>
</div>
{% extends "@snet-modal-base-frame" %}
{% block header %}
{% endblock %}
{% block body %}
	<div class="{{ namespace }}stackXl">
		<div class="{{ namespace }}box -boxInsetLg -boxSunken">
			{% render "@empty-state--title-text",{
				button: false,
				icon: "icon-graphline-l",
				title: "Tableau récapitulatif non disponible",
				text: "Le tableau indiquant la performance TWR & MWR de vos titres sera disponible lorsque vous aurez acquis des titres."
			},true %}
		</div>
		<div class="{{ namespace }}stackXs">
			<div class="h3">Calculs Gains journaliers</div>
			<p>La classification des produits financiers est celle opérée par la norme comptable IFRS 7.
				Elle distingue trois types d’instruments financiers en fonction de la disponibilité et de la liquidité
				des éléments de valorisation. Le premier niveau se caractérise par un prix disponible directement (prix
				de marché) et une liquidité suffisante. La deuxième regroupe les produits qui peuvent être valorisés par
				un modèle standard et des données cotées.</p>
		</div>
		<div class="{{ namespace }}stackXs">
			<div class="h3">Calculs Gains totaux</div>
			<p>La classification des produits financiers est celle opérée par la norme comptable IFRS 7.
				Elle distingue trois types d’instruments financiers en fonction de la disponibilité et de la liquidité
				des
				éléments de valorisation. Le premier niveau se caractérise par un prix disponible directement (prix de
				marché) et une liquidité suffisante. La deuxième regroupe les produits qui peuvent être valorisés par un
				modèle standard et des données cotées.</p>
		</div>
	</div>
{% endblock %}
  • Content:
    export default class ModalTrap {
    	constructor() {
    
    		// select all modals with the dialog role and modal classe.
    		// this ensures that only modals with the right attributes are selected
    		this.modals = document.querySelectorAll("[role='dialog'].modal");
    
    		// select all modal trigger buttons that don't have a data-dismiss attribute
    		// buttons with data-dismiss and data-toggle are typically only used within modals to trigger another modal
    		// we want to exclude these from the selection
    		this.modalToggles = document.querySelectorAll("[data-toggle='modal']:not([data-dismiss='modal'])");
    
    		// list of focusable elements inside a modal
    		this.focusableElements = [
    			"a[href]",
    			"button:not([disabled])",
    			"textarea:not([disabled])",
    			"input:not([disabled])",
    			"select:not([disabled])",
    			"[tabindex]:not([tabindex='-1'])"
    		];
    
    		// this will ensure that any modal with the dialog-scrollable modifier gets the intended behaviour
    		// all other modals will get ignored
    		this.modalScrollBody = ".modal-dialog-scrollable .modal-body"
    
    		// modal trigger element with same data-target value as id of triggered modal
    		this.modalTriggerElement = null;
    
    		// Used to store keydown handlers per modal
    		this.focusHandlers = new WeakMap();
    		this.init();
    
    	}
    
    	init() {
    
    		// loop over all modal trigger buttons
    		this.modalToggles.forEach(btn => {
    
    			// if data target value is erroneous or doesn't match any modal ID, return false
    			const targetSelector = btn.getAttribute('data-target');
    			if ((targetSelector === "#") || (!targetSelector)) return;
    
    			// on modal trigger interaction, set the modal trigger element's value to that of its DOM representation
    			// and store it
    			btn.addEventListener('click', (e) => {
    				e.preventDefault();
    				this.modalTriggerElement = e.target;
    			});
    
    		});
    
    		// loop over all modals
    		this.modals.forEach(modal => {
    
    			const closeBtn = modal.querySelector('.sds-modal__close');
    
    			// when a modal shown event is fired:
    			$(modal).on('shown.bs.modal', (e) => {
    
    				// check if a close button exists and focus it if so
    				if (closeBtn) {
    					closeBtn.focus();
    				}
    				else {
    
    					// if no close is present focus first focusable element instead
    					let focusableElements = this.getFocusableElements(modal);
    					let first = focusableElements[0];
    
    					first.focus()
    
    				}
    
    				// check for a scrollable body element inside the modal
    				// set focus trap inside modal while open
    				this.checkScrollabilityAndSetFocusTrap(modal);
    
    				/*
    				 * if the modal gets shown through the bootstrap method instead of
    				 * through a trigger element interaction, store the value of the trigger element
    				 * so it can be focused when closing the modal
    				 *
    				 * this will ensure that no matter how the modal is shown,
    				 * there's always an element to put the focus back on when closed
    				 *
    				 * closing a modal after navigating from modal to modal will also put the focus back on
    				 * the initial modal trigger element or corresponding modal's trigger element, if applicable
    				 * for this purpose, modal trigger elements inside modals that have the data-dismiss="modal" attribute are ignored
    				 */
    
    				let lastFocusedElement = document.querySelector("[data-toggle='modal']:not([data-dismiss='modal'])[data-target='#"+ e.target.getAttribute("id") +"']");
    
    				if (lastFocusedElement) {
    					this.modalTriggerElement = lastFocusedElement;
    				}
    
    			});
    
    			$(modal).on('hidden.bs.modal', (e) => {
    
    				// if it exists focus the modal's trigger element upon closing the modal
    				if (this.modalTriggerElement) {
    					this.modalTriggerElement.focus();
    				}
    			});
    
    		});
    	}
    
    	checkScrollabilityAndSetFocusTrap(modal) {
    
    		const modalBodySelector = this.modalScrollBody;
    
    		// check if the modal scrollable modifier is applied to the current modal
    		// if not, the last element to receive focus will be the modal itself
    		const modalBody = modal.querySelector(modalBodySelector) ? modal.querySelector(modalBodySelector) : modal;
    
    		// add or remove tabindex depending on whether the modal body area is scrollable
    		function setScrollability () {
    
    			const isScrollable = modalBody.scrollHeight > modalBody.clientHeight;
    
    			if (isScrollable) {
    				// Ensure it's focusable
    				if (!modalBody.hasAttribute('tabindex')) {
    					modalBody.setAttribute('tabindex', '0');
    				}
    			} else {
    				if (modalBody.hasAttribute('tabindex')) {
    					modalBody.removeAttribute('tabindex');
    				}
    			}
    
    		}
    
    		// check if modal body is scriollable or not on page load
    		setScrollability();
    		// Initial trap
    		this.trapFocus(modal);
    
    		// Observe scrollability changes
    		const observer = new ResizeObserver(() => {
    			// check if modal body becomes scrollable on resize
    			setScrollability();
    			this.trapFocus(modal);
    		});
    
    		// Required to detect resizing/scrollability
    		observer.observe(modalBody);
    	}
    
    	// trap the kesyboard focus navigfation inside the modal while it is shown
    	trapFocus(modal) {
    
    		const focusable = this.getFocusableElements(modal);
    
    		// abort if no focusable elements are found inside modal
    		if (!focusable.length) return;
    
    		// define first and last focusable elements inside modal
    		const first = focusable[0];
    		const last = focusable[focusable.length - 1];
    
    		// Remove old handler if exists
    		if (this.focusHandlers.has(modal)) {
    			modal.removeEventListener('keydown', this.focusHandlers.get(modal));
    		}
    
    		// move focus from first to last and vice-versa when using tab or shift+tab
    		const handleKeydown = (e) => {
    			if (e.key !== 'Tab') return;
    
    			if (e.shiftKey && document.activeElement === first) {
    				e.preventDefault();
    				last.focus();
    			} else if (!e.shiftKey && document.activeElement === last) {
    				e.preventDefault();
    				first.focus();
    			}
    		};
    
    		modal.addEventListener('keydown', handleKeydown);
    		this.focusHandlers.set(modal, handleKeydown);
    	}
    
    	// function to get list of focusable elements in modal
    	getFocusableElements(container) {
    
    		// stringify list of focusable element to pass it to querySelectorAll
    		const selector = this.focusableElements.join();
    		return container.querySelectorAll(selector);
    
    	}
    }
  • URL: /components/raw/snet-modal-base/ModalTrap.js
  • Filesystem Path: components/snet/organisms/modal/modal-base/ModalTrap.js
  • Size: 6.1 KB