Last active
August 29, 2015 14:20
-
-
Save Romiko/84bfd02cf4a087738d54 to your computer and use it in GitHub Desktop.
Modal Dialog Box
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*global Modernizr, console */ | |
if (!window.CS) { | |
window.CS = {}; | |
} | |
if (!window.CS.Components) { | |
window.CS.Components = {}; | |
} | |
(function ($) { | |
var name = 'modalDialog', | |
hook = '.js-modal-dialog', | |
nextId = 0; | |
/** | |
* Modal dialog | |
* http://campmon.com/wiki/display/FG/Modal+dialog+component | |
* | |
* @param {string=|object=} component - Component selector, element or JQuery object. Defaults to '.js-modal-dialog'. | |
* @param {object=|boolean=} options - Options for the component, or true to obtain options from the 'data-cs-<option>' attributes. | |
* @param {boolean=} options.allowclose - If true, 'Esc' key can be used to close the dialog. | |
* @param {string=|object=} options.callback - Callback function on dialog hide, executed in global scope. Will be passed the component object. | |
* @param {string=|object=} options.siblingWrapper - HTML structure to wrap the content adjacent to the dialog. If undefined, wrapping will not be performed. | |
*/ | |
$[name] = function (component, options) { | |
this.$component = $(component || hook); | |
this.id = this.$component.attr('id') || nextId++; | |
this.debug('Binding', this.$component); | |
this.$prevTrigger = $('.js-modal-dialog-pagination-prev', this.$component); | |
this.$nextTrigger = $('.js-modal-dialog-pagination-next', this.$component); | |
this.$closePageTrigger = $('.js-modal-dialog-pagination-close', this.$component); | |
this.$closeTrigger = $('.js-modal-dialog-close', this.$component); | |
this.$featureTarget = $('.js-modal-dialog-feature-target', this.$component); | |
this.$featureItems = $('.js-modal-dialog-feature-item', this.$component); | |
this.$callToAction = $('.js-modal-dialog-call-to-action', this.$component); | |
this.$overlay = this.$component.siblings('.js-modal-dialog-overlay'); | |
this.$siblingContent = this.$component.nextAll().not(this.$overlay); | |
this.existingFocus = undefined; | |
this.currentFeature = undefined; | |
this.init(options); | |
}; | |
$[name].defaults = { | |
allowclose: false, | |
callback: undefined, | |
siblingWrapper: '<div aria-hidden="false" />' | |
}; | |
$[name].prototype = { | |
init: function (options) { | |
var that = this, | |
isWebkit = 'WebkitAppearance' in document.documentElement.style; | |
if (options === true) { | |
options = this.getOptions(this.$component); | |
} | |
this.setOptions(options); | |
// Cross-browser/markup tweaks... | |
if (isWebkit) { | |
document.documentElement.className += " vendor-webkit"; | |
} | |
if (!Modernizr.csstransforms || isWebkit) { | |
this.$component[0].style.marginTop = '-' + (this.$component[0].offsetHeight / 2) + 'px'; | |
this.$component[0].style.marginLeft = '-' + (this.$component[0].offsetWidth / 2) + 'px'; | |
} | |
if (this.options.siblingWrapper) { | |
this.$siblingContent.wrapAll(this.options.siblingWrapper); | |
} | |
// Event handlers... | |
if (this.$prevTrigger != undefined) | |
this.$prevTrigger.click(function (e) { | |
that.setFeature(that.currentFeature - 1); | |
return false; | |
}); | |
if (this.$nextTrigger != undefined) | |
this.$nextTrigger.click(function (e) { | |
that.setFeature(that.currentFeature + 1); | |
return false; | |
}); | |
if (this.$closeTrigger != undefined) | |
this.$closeTrigger.click(function (e) { | |
that.hide(); | |
return false; | |
}); | |
if (this.$closePageTrigger != undefined) | |
this.$closePageTrigger.click(function (e) { | |
that.hide(); | |
return false; | |
}); | |
if (this.$closeNoCallback != undefined) | |
this.$closeNoCallback.click(function (e) { | |
that.hide(true); | |
return false; | |
}); | |
if (this.$callToAction != undefined) | |
this.$callToAction.click(function (e) { | |
var noCallback = false; | |
var element = $(e.currentTarget); | |
if (element.hasClass('js-no-callback')) { | |
noCallback = true; | |
} | |
that.hide(noCallback); | |
if (element.attr('href') != undefined && element.attr('href').length > 0) { | |
window.location = e.currentTarget.href; | |
} | |
return false; | |
}); | |
this.$component.keydown(function (e) { | |
if (e.which === 9) { | |
// Tab - trapped to prevent navigation outside the modal. | |
var $tabbable = that.$component.find(':tabbable'); | |
var focused = $tabbable.index(document.activeElement); | |
focused = (focused + (e.shiftKey ? -1 : 1)) % $tabbable.length; | |
if (focused < 0) { | |
focused += $tabbable.length; | |
} | |
$tabbable[focused].focus(); | |
return false; | |
} else if (e.which === 37) { | |
// Left arrow - previous feature. | |
if (that.$prevTrigger.hasClass('is-visible')) { | |
that.$prevTrigger.click(); | |
return false; | |
} | |
} else if (e.which === 39) { | |
// Right arrow - next feature. | |
if (that.$nextTrigger.hasClass('is-visible')) { | |
that.$nextTrigger.click(); | |
return false; | |
} | |
} else if (e.which === 27) { | |
// Esc - close dialog. | |
if (that.options.allowclose) { | |
that.hide(); | |
} | |
return false; | |
} | |
return true; | |
}); | |
this.$component.click(function (e) { | |
// Keep focus in dialog. | |
that.update(); | |
return false; | |
}); | |
this.$overlay.click(function (e) { | |
// Keep focus in dialog. | |
var element = $(e.currentTarget); | |
if (element.hasClass('js-no-modal-focus')) { | |
that.hide(); | |
} else { | |
that.update(); | |
} | |
return false; | |
}); | |
// Start with the first feature. | |
this.setFeature(0); | |
}, | |
// Get options from the 'data-cs-<option>' attributes. | |
getOptions: function ($component) { | |
var options = {}; | |
$.each($component.get(0).attributes, function (index, attr) { | |
if (attr.name.match(/^data-cs-/)) { | |
options[attr.name.replace(/^data-cs-/, '')] = attr.value; | |
} | |
}); | |
return options; | |
}, | |
setOptions: function (options) { | |
this.options = $.extend(true, $[name].defaults, options); | |
this.debug('Options', this.options); | |
}, | |
setFeature: function (i) { | |
this.currentFeature = i; | |
this.update(); | |
}, | |
// Update dialog presentation. | |
update: function () { | |
this.debug('Current feature', this.currentFeature); | |
if (this.$featureTarget[0]) { | |
this.$featureTarget[0].setAttribute('data-visible-feature-item', this.currentFeature + 1); | |
} | |
if (this.currentFeature < this.$featureItems.length - 1) { | |
// More features to get to... | |
this.$closePageTrigger.removeClass('is-visible'); | |
this.$nextTrigger.addClass('is-visible').focus(); | |
} else { | |
// At the last feature. | |
this.$nextTrigger.removeClass('is-visible'); | |
if (this.$closePageTrigger.length > 0) { | |
this.$closePageTrigger.addClass('is-visible').focus(); | |
} else if (this.$closeTrigger.length > 0) { | |
this.$closeTrigger.addClass('is-visible').focus(); | |
} | |
} | |
if (this.currentFeature > 0 && this.$featureItems.length > 1) { | |
this.$prevTrigger.addClass('is-visible'); | |
} else { | |
this.$prevTrigger.removeClass('is-visible'); | |
} | |
}, | |
show: function () { | |
var that = this; | |
this.debug('Show dialog'); | |
this.existingFocus = document.activeElement; | |
if (this.options.siblingWrapper) { | |
this.$siblingContent.parent().attr('aria-hidden', 'true'); | |
} | |
if (Modernizr.csstransitions) { | |
// Set focus after animation finishes. | |
this.$component.one("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function () { | |
that.update(); | |
}); | |
} | |
this.$component.addClass('is-visible'); | |
this.$component.attr('aria-hidden', 'false'); | |
if (!Modernizr.csstransitions) { | |
// No animation, set the focus right away. | |
this.update(); | |
} | |
}, | |
hide: function (nocallback) { | |
this.debug('Hide dialog'); | |
this.$component.removeClass('is-visible'); | |
this.$component.attr('aria-hidden', 'true'); | |
if (this.options.siblingWrapper) { | |
this.$siblingContent.parent().attr('aria-hidden', 'false'); | |
} | |
this.existingFocus.focus(); | |
if (nocallback === true) | |
return; | |
switch (typeof this.options.callback) { | |
case 'function': | |
this.debug('Callback', this.options.callback, this); | |
this.options.callback.call(window, this); | |
break; | |
case 'string': | |
this.debug('Callback', this.options.callback, this); | |
CS.executeFunctionByName(this.options.callback, window, this); | |
break; | |
} | |
}, | |
debug: function (/* arguments */) { | |
if (CS.Components.debug) { | |
console.log(name, this.id, arguments); | |
} | |
} | |
}; | |
/** | |
* Install JQuery plugin. | |
*/ | |
$.fn[name] = function (options /*, args */) { | |
var args = Array.prototype.slice.call(arguments, 1); | |
return this.each(function () { | |
var instance = $(this).data(name); | |
if (instance) { | |
if (typeof options === 'string') { | |
// Method call. | |
instance[options].apply(instance, args); | |
} else { | |
instance.debug('Already bound'); | |
} | |
} else { | |
instance = new $[name](this, options || true); | |
$(this).data(name, instance); | |
} | |
}); | |
}; | |
/** | |
* Auto-bind components with attribute 'data-cs-bind'. | |
*/ | |
$[name].autoBind = function () { | |
$(hook).each(function () { | |
if ($(this).attr('data-cs-bind') !== undefined) { | |
$(this)[name](true); | |
} | |
}); | |
}; | |
$($[name].autoBind); | |
})(jQuery); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="modal-dialog modal-dialog--transition-zoom-fade js-modal-dialog s-templatestabmodal is-visible" aria-hidden="true" aria-labelledby="modal-dialog-title" aria-describedby="modal-dialog-description" role="dialog" data-cs-callback="finishOnboarding" data-cs-allowclose="true"> | |
<div class="modal-dialog__anim-target"> | |
<div class="modal-dialog__content"> | |
<h1 id="modal-dialog-title" class="modal-dialog__heading">Want to create a campaign?</h1> | |
<p id="modal-dialog-description" class="modal-dialog__text">This is where you manage templates. If you want to create and send a campaign, the easiest way is to go to the campaigns tab.</p> | |
<div class="modal-dialog__tour"> | |
<ul class="modal-dialog__tour__features js-modal-dialog-feature-target"> | |
<li class="modal-dialog__tour__feature-item js-modal-dialog-feature-item"> | |
<div class="demo-header"> | |
<div class="demo-header__main"> | |
<div class="demo-header__logo"></div> | |
<div class="demo-header__links"> | |
<div class="demo-header__links__blank"></div> | |
<div class="demo-header__links__dummy">Campaigns</div> | |
<div class="demo-header__links__blank"></div> | |
<div class="demo-header__links__blank"></div> | |
</div> | |
</div> | |
</div> | |
</li> | |
</ul> | |
</div> | |
<div class="modal-dialog__pagination"> | |
<a href="/campaigns/create/new/" class="button primary huge js-modal-dialog-call-to-action js-no-callback">Create a new campaign</a> | |
<span class="cancel">or, <a href="/templates/" class="js-modal-dialog-close">Manage templates</a></span> | |
</div> | |
</div> | |
<button class="modal-dialog_closeicon js-modal-dialog-close"><em class="u-hide-visually">Close</em></button> | |
</div> | |
</div> | |
<div class="modal-dialog-overlay js-modal-dialog-overlay js-no-modal-focus"></div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment