<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"
}
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);
}
}
});
}
.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;
}
}
}
/**
* 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;
});