<section class="accordion" id="udol-course-taster-exercise">
    <div class="accordion-tablist" role="tablist">
        <div class="accordion-tab">
            <a id="change-blindness-label" href="#change-blindness" class="accordion-tab-title-container collapse-toggle" data-collapse role="tab" aria-expanded="false" aria-controls="change-blindness">
                <h4 class="accordion-tab-title">
                    Change blindness
                    <span class="accordion-tab-title-indicator">
                        <i class="uod-icons uod-icons-chevron-down"></i>
                    </span>
                </h4>
            </a>
            <div id="change-blindness" class="accordion-tab-content collapse" role="tabpanel" aria-labelledby="change-blindness-label">
                <div class="accordion-tab-content-inner">
                    A change in a visual stimulus is introduced and the observer does not notice it.
                </div>
            </div>
        </div>
        <div class="accordion-tab">
            <a id="inattentional-blindness-label" href="#inattentional-blindness" class="accordion-tab-title-container collapse-toggle" data-collapse role="tab" aria-expanded="false" aria-controls="inattentional-blindness">
                <h4 class="accordion-tab-title">
                    Inattentional blindness
                    <span class="accordion-tab-title-indicator">
                        <i class="uod-icons uod-icons-chevron-down"></i>
                    </span>
                </h4>
            </a>
            <div id="inattentional-blindness" class="accordion-tab-content collapse" role="tabpanel" aria-labelledby="inattentional-blindness-label">
                <div class="accordion-tab-content-inner">
                    Attention is diverted to another object or task so observers often fail to perceive an unexpected object, even if it appears at fixation.
                </div>
            </div>
        </div>
        <div class="accordion-tab">
            <a id="perceptual-sets-label" href="#perceptual-sets" class="accordion-tab-title-container collapse-toggle" data-collapse role="tab" aria-expanded="false" aria-controls="perceptual-sets">
                <h4 class="accordion-tab-title">
                    Perceptual sets
                    <span class="accordion-tab-title-indicator">
                        <i class="uod-icons uod-icons-chevron-down"></i>
                    </span>
                </h4>
            </a>
            <div id="perceptual-sets" class="accordion-tab-content collapse" role="tabpanel" aria-labelledby="perceptual-sets-label">
                <div class="accordion-tab-content-inner">
                    Going beyond the information given and using prior knowledge to perceive stimuli in a certain way.
                </div>
            </div>
        </div>
        <div class="accordion-tab">
            <a id="perception-label" href="#perception" class="accordion-tab-title-container collapse-toggle" data-collapse role="tab" aria-expanded="false" aria-controls="perception">
                <h4 class="accordion-tab-title">
                    Perception
                    <span class="accordion-tab-title-indicator">
                        <i class="uod-icons uod-icons-chevron-down"></i>
                    </span>
                </h4>
            </a>
            <div id="perception" class="accordion-tab-content collapse" role="tabpanel" aria-labelledby="perception-label">
                <div class="accordion-tab-content-inner">
                    The way in which we interpret the information gathered (and processed) by the senses. We sense the presence of a stimulus, but we perceive what it is.
                </div>
            </div>
        </div>
    </div>
</section>

<style type="text/css">
    #udol-course-taster-exercise .accordion-tab:after {
        display: inline-block;
        transform: translate(0, 0);
        text-rendering: auto;
        font: normal normal 400 14px/1 uod-icons;
        font-size: inherit;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        content: "\ea0d";
        font-size: 2em;
        color: #101d49;
        width: 100%;
        text-align: center;
        padding-top: 20px;
    }

    #udol-course-taster-exercise .accordion-tab:last-child:after {
        content: '';
        padding-top: 0;
    }
</style>
<section class="accordion{% if fullWidth == true %} full-width{% endif %}{% if scheme %} accordion-scheme-{{ scheme }}{% endif %}"{% if id %} id="{{ id|raw }}"{% endif %}>
    {% if title %}<h2 id="{% if id %}{{ id }}{% else %}{{ title | lower | replace({ ' ' : '-' }) }}{% endif %}" class="accordion-title">{{ title|raw }}</h2>{% endif %}
    {% if intro %}<p class="accordion-intro">{{ intro|raw }}</p>{% endif %}
    <div class="accordion-tablist" role="tablist">
        {% for item in contents %}
        <div class="accordion-tab">
            <a id="{{ item.tag|raw }}-label" href="#{{ item.tag|raw }}" class="accordion-tab-title-container collapse-toggle{{ item.expanded == true ? ' active'}}{% if usehash == true %} usehash{% endif %}"
                data-collapse {% if modal == true %} data-group="{{ tag|raw }}" {% endif %}
                role="tab" aria-expanded="{{ item.expanded == true ? 'true' : 'false' }}" aria-controls="{{ item.tag|raw }}">
                {% if useDivTitles %}
                <div class="accordion-tab-title h4">
                    {{ item.label|raw }}
                    <span class="accordion-tab-title-indicator">
                        <i class="uod-icons uod-icons-chevron-down"></i>
                    </span>
                </div>
                {% else %}
                <h4 class="accordion-tab-title">
                    {{ item.label|raw }}
                    <span class="accordion-tab-title-indicator">
                        <i class="uod-icons uod-icons-chevron-down"></i>
                    </span>
                </h4>
                {% endif %}
            </a>
            <div id="{{ item.tag|raw }}" class="accordion-tab-content collapse{{ item.expanded == true ? ' active'}}" role="tabpanel" aria-labelledby="{{ item.tag|raw }}-label">
                <div class="accordion-tab-content-inner">
                    {{ item.content|raw }}
                </div>
            </div>
        </div>
        {% endfor %}
    </div>
</section>


<style type="text/css">
    #udol-course-taster-exercise .accordion-tab:after {
        display: inline-block;
        transform: translate(0, 0);
        text-rendering: auto;
        font: normal normal 400 14px/1 uod-icons;
        font-size: inherit;
        -moz-osx-font-smoothing: grayscale;
        -webkit-font-smoothing: antialiased;
        content: "\ea0d";
        font-size: 2em;
        color: #101d49;
        width: 100%;
        text-align: center;
        padding-top: 20px;
    }

    #udol-course-taster-exercise .accordion-tab:last-child:after {
        content: '';
        padding-top: 0;
    }
</style>
{
  "contents": [
    {
      "tag": "change-blindness",
      "label": "Change blindness",
      "expanded": null,
      "content": "A change in a visual stimulus is introduced and the observer does not notice it."
    },
    {
      "tag": "inattentional-blindness",
      "label": "Inattentional blindness",
      "expanded": null,
      "content": "Attention is diverted to another object or task so observers often fail to perceive an unexpected object, even if it appears at fixation."
    },
    {
      "tag": "perceptual-sets",
      "label": "Perceptual sets",
      "expanded": null,
      "content": "Going beyond the information given and using prior knowledge to perceive stimuli in a certain way."
    },
    {
      "tag": "perception",
      "label": "Perception",
      "expanded": null,
      "content": "The way in which we interpret the information gathered (and processed) by the senses. We sense the presence of a stimulus, but we perceive what it is."
    }
  ],
  "title": null,
  "intro": null,
  "tag": "accordion-demo",
  "modal": null,
  "id": "udol-course-taster-exercise"
}
  • Content:
    import houdini from './houdini.custom'
    
    /**
     * Polyfil this function (swapping out for jquery long term would be nice)
     */
    const forEach = function (array, callback, scope) {
        for (let i = 0; i < array.length; i++) {
            callback.call(scope, i, array[i]);
        }
    };
    
    /**
     * Custom Events
     */
    const ACCORDION_OPEN = 'ACCORDION_OPEN'
    const ACCORDION_CLOSE = 'ACCORDION_CLOSE'
    
    /**
     * When the page hash changes, scroll to that position.
     */
    function scrollIntoView() {
        const hash = window.location.hash;
        if (!hash) {
            return;
        }
        var toggle = document.querySelector(hash + '-label');
        if (toggle) {
            window.scrollTo(0, toggle.offsetTop);
        }
    }
    
    const accordionElementExistsOnPage = document.querySelector('.accordion') != undefined;
    if (accordionElementExistsOnPage) {
        const hash = window.location.hash;
        if (hash) {
            // auto-close any open panels which do not match the current hash
            const toggle = document.querySelectorAll('[data-collapse]' + ':not([href*="' + hash + '"])');
            forEach(toggle, function (index, value) {
                value.classList.remove('active');
                value.setAttribute('aria-expanded', 'false');
                const elementId = value.getAttribute('aria-controls');
                const content = document.querySelector('[id="' + elementId + '"]');
                if (content) {
                    content.classList.remove('active');
                }
            });
        }
    
        window.addEventListener('hashchange', scrollIntoView, false);
    
        houdini.init({
            callbackOpen: function (content, toggle) {
                // remove the focus rect created by houdinijs forcing focus to the new panel
                content.blur();
    
                // set a new max-height to the content height (max height can be animaed, height alone cannot)
                content.style['max-height'] = content.scrollHeight + 'px';
    
                if (toggle) {
                    // update the aria metadata for this component to reflect the new state
                    toggle.setAttribute('aria-expanded', 'true');
                    // dispatch a custom event to allow other components to react to the panel being opened
                    $('.main-navigation-mobile').trigger(ACCORDION_OPEN, toggle.getAttribute('aria-controls'));
                }
            },
            callbackClose: function (content, toggle) {
                // set a new max-height to allow the panel to be animated closed
                content.style['max-height'] = 0;
                if (toggle) {
                    // if the hash is currently in the url and we've just closed the panel, remove the hash
                    if (toggle.getAttribute('aria-expanded') == 'true' && '#' + toggle.getAttribute('aria-controls') === window.location.hash) {
                        history.pushState("", document.title, window.location.pathname + window.location.search);
                    }
                    // update the aria metadata for this component to reflect the new state
                    toggle.setAttribute('aria-expanded', 'false');
                    // remove the focus rect created by houdinijs forcing focus to the new panel
                    toggle.blur();
                    // dispatch a custom event to allow other components to react to the panel being closed
                    $('.main-navigation-mobile').trigger(ACCORDION_CLOSE, toggle.getAttribute('aria-controls'));
                } else {
                    // dispatch a custom event to allow other components to react to the panel being closed
                    $('.main-navigation-mobile').trigger(ACCORDION_CLOSE);
                }
            }
        });
    }
    
  • URL: /components/raw/accordion/accordion.js
  • Filesystem Path: components/components/accordion/accordion.js
  • Size: 3.6 KB
  • Content:
    .accordion-tab {
        margin-bottom: $margin-extra-small;
    
        &-title {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 0;
    
            .full-width & {
                @include full-bleed-inset;
            }
    
            &-container {
                @include disable-underlines;
    
                display: block;
                transition: background-color .3s $default-animation-curve;
                border-radius: 2px;
                background: $light-grey;
                padding: 13px 60px 13px 10px;
    
                @include for-tablet-portrait-up {
                    padding: 15.5px 65px 15.5px 20px;
                }
    
                .full-width & {
                    padding-right: 0;
                    padding-left: 0;
                }
    
                .accordion-scheme-dark & {
                    background-color: #191919;
                }
    
                &:hover,
                &:focus,
                &:active {
                    background-color: $light-grey-hover;
    
                    .accordion-scheme-dark & {
                        background-color: $text-black;
                    }
                }
            }
    
            &-indicator {
                display: inline-block;
                position: absolute;
                top: 50%;
                right: 10px;
                margin-top: -20px;
                margin-left: 10px;
                border: solid 2px $mid-blue;
                border-radius: 2px;
                width: 40px;
                height: 40px;
                text-align: center;
                line-height: 40px;
                color: $mid-blue;
                font-size: 9px; // the width of the arrow indicator
    
                @include for-tablet-portrait-up {
                    right: 20px;
                    margin-top: -22.5px;
                    width: 45px;
                    height: 45px;
                    line-height: 45px;
                    font-size: 11px;
                }
    
    
                .accordion-scheme-dark & {
                    border-color: $white;
                    color: $white;
                }
    
                .uod-icons {
                    transform: rotate(0deg);
                    transition: transform .3s $default-animation-curve;
                    transform-origin: 50%;
    
                    .active & {
                        transform: rotate(-180deg);
                    }
                }
            }
    
            a {
                &,
                &:link {
                    color: $primary-blue;
                }
    
                .accordion-scheme-dark & {
                    color: $white;
                }
            }
    
            .accordion-scheme-dark & {
                color: $white;
            }
        }
    
        &-content {
            position: relative;
            transition: max-height .3s $default-animation-curve;
            overflow: hidden;
    
            &:focus {
                outline: none;
            }
    
            &.active {
                margin-bottom: $margin-extra-small;
                border-bottom: 1px dashed $dark-grey;
            }
    
            &-inner {
                padding: 10px;
    
                @include for-tablet-portrait-up {
                    padding: $margin-extra-small;
                }
    
                .full-width & {
                    @include full-bleed-inset;
    
                    padding-top: 10px;
                    padding-bottom: 10px;
    
                    @include for-tablet-portrait-up {
                        padding-top: $margin-extra-small;
                        padding-bottom: $margin-extra-small;
                    }
                }
            }
        }
    
        .collapse {
            transition: max-height .3s $default-animation-curve;
            max-height: 0;
    
            &.active {
                max-height: 100vh;
            }
        }
    }
    
  • URL: /components/raw/accordion/accordion.scss
  • Filesystem Path: components/components/accordion/accordion.scss
  • Size: 3.5 KB
  • Content:
    /**
     * This a modified version of houdinijs v9.4.2 - https://github.com/cferdinandi/houdini
     * Modified to add the ability to toggle panels without updating the page hash, via the useHashClass setting.
     */
    (function (root, factory) {
    	if ( typeof define === 'function' && define.amd ) {
    		define([], factory(root));
    	} else if ( typeof exports === 'object' ) {
    		module.exports = factory(root);
    	} else {
    		root.houdini = factory(root);
    	}
    })(typeof global !== 'undefined' ? global : this.window || this.global, function (root) {
    
    	'use strict';
    
    	//
    	// Variables
    	//
    
    	var houdini = {}; // Object for public APIs
    	var supports = 'querySelector' in document && 'addEventListener' in root && 'classList' in document.createElement('_'); // Feature test
    	var settings, collapse;
    
    	// Default settings
    	var defaults = {
    		selectorToggle: '[data-collapse]',
    		selectorContent: '.collapse',
    		toggleActiveClass: 'active',
    		contentActiveClass: 'active',
    		useHashClass: 'usehash',
    		initClass: 'js-houdini',
    		stopVideo: true,
    		callbackOpen: function () {},
    		callbackClose: function () {}
    	};
    
    
    	//
    	// Methods
    	//
    
    	/**
    	 * A simple forEach() implementation for Arrays, Objects and NodeLists
    	 * @private
    	 * @param {Array|Object|NodeList} collection Collection of items to iterate
    	 * @param {Function} callback Callback function for each iteration
    	 * @param {Array|Object|NodeList} scope Object/NodeList/Array that forEach is iterating over (aka `this`)
    	 */
    	var forEach = function (collection, callback, scope) {
    		if (Object.prototype.toString.call(collection) === '[object Object]') {
    			for (var prop in collection) {
    				if (Object.prototype.hasOwnProperty.call(collection, prop)) {
    					callback.call(scope, collection[prop], prop, collection);
    				}
    			}
    		} else {
    			for (var i = 0, len = collection.length; i < len; i++) {
    				callback.call(scope, collection[i], i, collection);
    			}
    		}
    	};
    
    	/**
    	 * Merge defaults with user options
    	 * @private
    	 * @param {Object} defaults Default settings
    	 * @param {Object} options User options
    	 * @returns {Object} Merged values of defaults and options
    	 */
    	var extend = function () {
    
    		// Variables
    		var extended = {};
    		var deep = false;
    		var i = 0;
    		var length = arguments.length;
    
    		// Check if a deep merge
    		if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) {
    			deep = arguments[0];
    			i++;
    		}
    
    		// Merge the object into the extended object
    		var merge = function (obj) {
    			for ( var prop in obj ) {
    				if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) {
    					// If deep merge and property is an object, merge properties
    					if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) {
    						extended[prop] = extend( true, extended[prop], obj[prop] );
    					} else {
    						extended[prop] = obj[prop];
    					}
    				}
    			}
    		};
    
    		// Loop through each object and conduct a merge
    		for ( ; i < length; i++ ) {
    			var obj = arguments[i];
    			merge(obj);
    		}
    
    		return extended;
    
    	};
    
    	/**
    	 * Get the closest matching element up the DOM tree
    	 * @param {Element} elem Starting element
    	 * @param {String} selector Selector to match against (class, ID, or data attribute)
    	 * @return {Boolean|Element} Returns false if not match found
    	 */
    	var getClosest = function ( elem, selector ) {
    
    		// Element.matches() polyfill
    		if (!Element.prototype.matches) {
    			Element.prototype.matches =
    				Element.prototype.matchesSelector ||
    				Element.prototype.mozMatchesSelector ||
    				Element.prototype.msMatchesSelector ||
    				Element.prototype.oMatchesSelector ||
    				Element.prototype.webkitMatchesSelector ||
    				function(s) {
    					var matches = (this.document || this.ownerDocument).querySelectorAll(s),
    						i = matches.length;
    					while (--i >= 0 && matches.item(i) !== this) {}
    					return i > -1;
    				};
    		}
    
    		// Get closest match
    		for ( ; elem && elem !== document; elem = elem.parentNode ) {
    			if ( elem.matches( selector ) ) return elem;
    		}
    
    		return null;
    
    	};
    
    	/**
    	 * Escape special characters for use with querySelector
    	 * @public
    	 * @param {String} id The anchor ID to escape
    	 * @author Mathias Bynens
    	 * @link https://github.com/mathiasbynens/CSS.escape
    	 */
    	var escapeCharacters = function ( id ) {
    
    		// Remove leading hash
    		if ( id.charAt(0) === '#' ) {
    			id = id.substr(1);
    		}
    
    		var string = String(id);
    		var length = string.length;
    		var index = -1;
    		var codeUnit;
    		var result = '';
    		var firstCodeUnit = string.charCodeAt(0);
    		while (++index < length) {
    			codeUnit = string.charCodeAt(index);
    			// Note: there’s no need to special-case astral symbols, surrogate
    			// pairs, or lone surrogates.
    
    			// If the character is NULL (U+0000), then throw an
    			// `InvalidCharacterError` exception and terminate these steps.
    			if (codeUnit === 0x0000) {
    				throw new InvalidCharacterError(
    					'Invalid character: the input contains U+0000.'
    				);
    			}
    
    			if (
    				// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
    				// U+007F, […]
    				(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
    				// If the character is the first character and is in the range [0-9]
    				// (U+0030 to U+0039), […]
    				(index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
    				// If the character is the second character and is in the range [0-9]
    				// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
    				(
    					index === 1 &&
    					codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
    					firstCodeUnit === 0x002D
    				)
    			) {
    				// http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point
    				result += '\\' + codeUnit.toString(16) + ' ';
    				continue;
    			}
    
    			// If the character is not handled by one of the above rules and is
    			// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
    			// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
    			// U+005A), or [a-z] (U+0061 to U+007A), […]
    			if (
    				codeUnit >= 0x0080 ||
    				codeUnit === 0x002D ||
    				codeUnit === 0x005F ||
    				codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
    				codeUnit >= 0x0041 && codeUnit <= 0x005A ||
    				codeUnit >= 0x0061 && codeUnit <= 0x007A
    			) {
    				// the character itself
    				result += string.charAt(index);
    				continue;
    			}
    
    			// Otherwise, the escaped character.
    			// http://dev.w3.org/csswg/cssom/#escape-a-character
    			result += '\\' + string.charAt(index);
    
    		}
    
    		return '#' + result;
    
    	};
    
    	/**
    	 * Stop YouTube, Vimeo, and HTML5 videos from playing when leaving the slide
    	 * @private
    	 * @param  {Element} content The content container the video is in
    	 * @param  {String} activeClass The class asigned to expanded content areas
    	 */
    	var stopVideos = function ( content, settings ) {
    
    		// Check if stop video enabled
    		if ( !settings.stopVideo ) return;
    
    		// Only run if content container is open
    		if ( !content.classList.contains( settings.contentActiveClass ) ) return;
    
    		// Check if the video is an iframe or HTML5 video
    		var iframe = content.querySelector( 'iframe');
    		var video = content.querySelector( 'video' );
    
    		// Stop the video
    		if ( iframe ) {
    			var iframeSrc = iframe.src;
    			iframe.src = iframeSrc;
    		}
    		if ( video ) {
    			video.pause();
    		}
    
    	};
    
    	/**
    	 * Add focus to content
    	 * @private
    	 * @param  {node}   content  The content to bring into focus
    	 * @param  {object} settings Options
    	 */
    	var adjustFocus = function ( content, settings ) {
    
    		if ( content.hasAttribute( 'data-houdini-no-focus' ) ) return;
    
    		// If content is closed, remove tabindex
    		if ( !content.classList.contains( settings.contentActiveClass ) ) {
    			if ( content.hasAttribute( 'data-houdini-focused' ) ) {
    				content.removeAttribute( 'tabindex' );
    			}
    			return;
    		}
    
    		// Get current position on the page
    		var position = {
    			x: root.pageXOffset,
    			y: root.pageYOffset
    		};
    
    		// Set focus and reset position to account for page jump on focus
    		content.focus();
    		if ( document.activeElement.id !== content.id ) {
    			content.setAttribute( 'tabindex', '-1' );
    			content.setAttribute( 'data-houdini-focused', true );
    			content.focus();
    		}
    		root.scrollTo( position.x, position.y );
    
    	};
    
    	/**
    	 * Open collapsed content
    	 * @public
    	 * @param  {String} contentID The ID of the content area to close
    	 * @param  {Element} toggle The element that toggled the close action
    	 * @param  {Object} options
    	 */
    	houdini.closeContent = function ( contentID, toggle, options ) {
    
    		// Variables
    		var localSettings = extend( settings || defaults, options || {} );  // Merge user options with defaults
    		var content = document.querySelector( escapeCharacters( contentID ) ); // Get content area
    
    		// Sanity check
    		if ( !content ) return;
    
    		// Toggle the content
    		stopVideos( content, localSettings ); // If content area is closed, stop playing any videos
    		if ( toggle ) {
    			toggle.classList.remove( localSettings.toggleActiveClass );// Change text on collapse toggle
    		}
    		content.classList.remove( localSettings.contentActiveClass ); // Collapse or expand content area
    		adjustFocus( content, localSettings );
    
    		// Run callbacks after toggling content
    		localSettings.callbackClose( content, toggle );
    
    	};
    
    	/**
    	 * Open collapsed content
    	 * @public
    	 * @param  {String} contentID The ID of the content area to open
    	 * @param  {Element} toggle The element that toggled the open action
    	 * @param  {Object} options
    	 */
    	houdini.openContent = function ( contentID, toggle, options ) {
    
    		// Variables
    		var localSettings = extend( settings || defaults, options || {} );  // Merge user options with defaults
    		var content = document.querySelector( escapeCharacters( contentID ) ); // Get content area
    		var group = toggle && toggle.hasAttribute( 'data-group') ? document.querySelectorAll('[data-group="' + toggle.getAttribute( 'data-group') + '"]') : [];
    
    		// Sanity check
    		if ( !content ) return;
    
    		// If a group, close all other content areas
    		forEach(group, function (item) {
    			houdini.closeContent( item.hash, item );
    		});
    
    		// Open the content
    		if ( toggle ) {
    			toggle.classList.add( localSettings.toggleActiveClass ); // Change text on collapse toggle
    		}
    		content.classList.add( localSettings.contentActiveClass ); // Collapse or expand content area
    		adjustFocus( content, localSettings );
    		content.removeAttribute( 'data-houdini-no-focus' );
    
    		// Run callbacks after toggling content
    		localSettings.callbackOpen( content, toggle );
    
    	};
    
    	/**
    	 * Handle has change event
    	 * @private
    	 */
    	var hashChangeHandler = function (event) {
    
    		// Get hash from URL
    		var hash = root.location.hash;
    
    		// If clicked collapse is cached, reset it's ID
    		if ( collapse ) {
    			collapse.id = collapse.getAttribute( 'data-collapse-id' );
    			collapse = null;
    		}
    
    		// If there's a URL hash, open the content with matching ID
    		if ( !hash ) return;
    		var toggle = document.querySelector( settings.selectorToggle + '[href*="' + hash + '"]' );
    		houdini.openContent( hash, toggle );
    
    	};
    
    	/**
    	 * Handle toggle click events
    	 * @private
    	 */
    	var clickHandler = function (event) {
    		
    		// Don't run if right-click or command/control + click
    		if ( event.button !== 0 || event.metaKey || event.ctrlKey ) return;
    		
    		// Check if a toggle was clicked
    		var toggle = getClosest( event.target, settings.selectorToggle );
    		if ( !toggle || !toggle.hash ) return;
    
    		// Custom: Check if we're toggling without updating hashes
    		if ( !toggle.classList.contains( settings.useHashClass ) ) {
    			event.preventDefault();
    			if ( !toggle.classList.contains( settings.toggleActiveClass ) ) {
    				houdini.openContent( toggle.hash, toggle );
    				return;
    			}
    		}
    
    		// If the tab is already open, close it
    		if ( toggle.classList.contains( settings.toggleActiveClass ) ) {
    			event.preventDefault();
    			houdini.closeContent( toggle.hash, toggle );
    			return;
    		}
    
    		// Get the collapse content
    		collapse = document.querySelector( toggle.hash );
    
    		// If tab content exists, save the ID as a data attribute and remove it (prevents scroll jump)
    		if ( !collapse ) return;
    		collapse.setAttribute( 'data-collapse-id', collapse.id );
    		collapse.id = '';
    
    		// If no hash change event will happen, fire manually
    		if ( toggle.hash === root.location.hash ) {
    			event.preventDefault();
    			hashChangeHandler();
    		}
    
    	};
    
    	/**
    	 * Handle content focus events
    	 * @private
    	 */
    	var focusHandler = function (event) {
    
    		// Variables
    		collapse = getClosest( event.target, settings.selectorContent );
    
    		// Only run if content exists and isn't open already
    		if ( !collapse || collapse.classList.contains( settings.contentActiveClass ) ) return;
    
    		// Save the ID as a data attribute and remove it (prevents scroll jump)
    		var hash = collapse.id;
    		collapse.setAttribute( 'data-collapse-id', hash );
    		collapse.setAttribute( 'data-houdini-no-focus', true );
    		collapse.id = '';
    
    		// If no hash change event will happen, fire manually
    		if ( hash === root.location.hash.substring(1) ) {
    			hashChangeHandler();
    			return;
    		}
    
    		// Otherwise, update the hash
    		root.location.hash = hash;
    
    	};
    
    	/**
    	 * Destroy the current initialization.
    	 * @public
    	 */
    	houdini.destroy = function () {
    		if ( !settings ) return;
    		document.documentElement.classList.remove( settings.initClass );
    		document.removeEventListener('click', clickHandler, false);
    		document.removeEventListener('focus', focusHandler, true);
    		root.removeEventListener('hashchange', hashChangeHandler, false);
    		settings = null;
    		collapse = null;
    	};
    
    	/**
    	 * Initialize Houdini
    	 * @public
    	 * @param {Object} options User settings
    	 */
    	houdini.init = function ( options ) {
    
    		// feature test
    		if ( !supports ) return;
    
    		// Destroy any existing initializations
    		houdini.destroy();
    
    		// Merge user options with defaults
    		settings = extend( defaults, options || {} );
    
    		// Add class to HTML element to activate conditional CSS
    		document.documentElement.classList.add( settings.initClass );
    
    		// Listen for all click events
    		document.addEventListener('click', clickHandler, false);
    		document.addEventListener('focus', focusHandler, true);
    		root.addEventListener('hashchange', hashChangeHandler, false);
    
    		// If URL has a hash, activate hashed content by default
    		hashChangeHandler();
    
    	};
    
    
    	//
    	// Public APIs
    	//
    
    	return houdini;
    
    });
    
  • URL: /components/raw/accordion/houdini.custom.js
  • Filesystem Path: components/components/accordion/houdini.custom.js
  • Size: 14.2 KB

Accordion Component

Key Features

  • #link integration - activating an accordion will update the page url, and visiting a page url with a #link will deep-link to that content.
  • Keyboard accessible
  • ARIA Metadata
  • Optional use of anchor links and url updating

Options

  • Can be set to either Modal or Plural behaviour:
    • Modal allows only a single panel to be expanded at once, and automatically collapses other panels on activation of a new one.
    • Plural allows panels to be independently expanded or collapsed, without affecting the others.
    • Default mode is Plural.
  • Individual panels can have a default value for being expanded or collapsed.
  • Supports a dark or a light colour scheme, based on the scheme attribute being set.

Properties

Accordion

  • title [optional, html]
  • intro [optional, html]
  • tag [required, string]
  • modal [optional, default false]
  • usehash [optional, default true]
  • fullWidth [optional, default false]
  • contents [Array of Accordion Panel]
  • scheme [optional, string “dark” or undefined]

Accordion Panel

  • tag [required, string]
  • label [required, string]
  • expanded [optional, default false]
  • content [required, html]