/*
---
name: Modal.BootStrap
description: provides a dom boostrap for modal content
authors: Daniel Buchner, Dimitar Christoff, Simon Smith
license: MIT-style license.
version: 1.09
requires:
- Core/String
- Core/Event
- Core/Element
- Core/Array
- Core/Class
provides: Modal.Overlay, Modal.Base, Modal.BootStrap, Modernizr
...
*/
(function() {
// setup a namespace
var Modal = this.Modal = {};
//detect css3 transition support via modernizr if needed
var Modernizr = this.Modernizr || function(a,b,c){function w(a){i.cssText=a}function x(a,b){return w(prefixes.join(a+";")+(b||""))}function y(a,b){return typeof a===b}function z(a,b){return!!~(""+a).indexOf(b)}function A(a,b){for(var d in a){var e=a[d];if(!z(e,"-")&&i[e]!==c)return b=="pfx"?e:!0}return!1}function B(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:y(f,"function")?f.bind(d||b):f}return!1}function C(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+m.join(d+" ")+d).split(" ");return y(b,"string")||y(b,"undefined")?A(e,b):(e=(a+" "+n.join(d+" ")+d).split(" "),B(e,b,c))}var d="2.6.1",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l="Webkit Moz O ms",m=l.split(" "),n=l.toLowerCase().split(" "),o={},p={},q={},r=[],s=r.slice,t,u={}.hasOwnProperty,v;!y(u,"undefined")&&!y(u.call,"undefined")?v=function(a,b){return u.call(a,b)}:v=function(a,b){return b in a&&y(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=s.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(s.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(s.call(arguments)))};return e}),o.csstransitions=function(){return C("transition")};for(var D in o)v(o,D)&&(t=D.toLowerCase(),e[t]=o[D](),r.push((e[t]?"":"no-")+t));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)v(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,enableClasses&&(f.className+=" "+(b?"":"no-")+a),e[a]=b}return e},w(""),h=j=null,e._version=d,e._domPrefixes=n,e._cssomPrefixes=m,e.testProp=function(a){return A([a])},e.testAllProps=C,e}(this,this.document);
// provides:
// Modernizr.csstransitions = false;
Element.NativeEvents.transitionend = 2;
Element.implement({
diffuse: function(position){
return this.setStyles({
position: position || 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
height: '100%',
width: '100%'
});
}
});
Modal.Overlay = new Class({
Implements: [Events, Chain, Options],
options: {
zIndex: 900000,
opacity: .3,
backgroundColor: '#555',
fx: {
duration: 300
}
},
initialize: function(container, options){
this.setOptions(options);
this.container = document.id(container);
var self = this;
this.element = new Element('div', {
'class': 'overlay',
styles: {
display: 'none',
opacity: 0,
zIndex: this.options.zIndex,
backgroundColor: this.options.backgroundColor
},
events: {
click: function() {
self.fireEvent("overlayClick");
}
},
tween: this.options.fx
}).diffuse('fixed').inject(this.container);
},
show: function(){
this.element.setStyle("display", "block").fade(this.options.opacity);
},
hide: function(){
this.element.fade(this.options.opacity).get("tween").chain(function() {
this.element.setStyle("display", "none");
});
}
});
Modal.Base = new Class({
Implements: [Events, Chain, Options],
options: {
id: "modal",
margin: 20,
overlay: true,
anyClose: true,
escClose: false,
limitHeight: true,
header: "",
body: "",
footer: "",
useTransitions: Modernizr.csstransitions,
transitionClass: "transition",
transitionClassBox: "modal-box-initial",
transitionShowClass: "modal-visible",
fx: {
duration: 400,
properties: {
show: {
opacity: [0,1],
marginTop: [30,0]
},
hide: {
opacity: [1,0],
marginTop: [0,30]
}
}
},
overlayFx: {
duration: 0
}
},
// the magic template by csuwldcat. tables ftw...
template: ['
',
'',
'',
'',
'',
' | ',
'
',
'',
'
'].join(""),
initialize: function(container, options){
this.setOptions(options);
this.container = document.id(container);
if (!this.container)
return;
var proxy = new Element("div", {
"html": this.template
});
// save references
this.element = proxy.getFirst().store('modal', this).inject(this.container);
this.box = this.element.getElement('div.modal-box').set({
'morph': this.options.fx,
'aria-hidden': true,
'id': 'modal',
'role': 'dialog',
'aria-labelledby': 'modal-header'
}).setStyles({
'opacity': 0
});
if (this.options.useTransitions) {
this.box.addClass(this.options.transitionClass);
this.box.addClass(this.options.transitionClassBox);
}
this.content = this.box.getElement('div.modal-content');
this.body = this.content.getFirst();
this.footer = this.content.getNext();
this.header = this.content.getPrevious().set('aria-label', 'modal-header');
this.closeButton = this.header.getPrevious().set({
'role': 'button',
'aria-controls': 'modal'
});
this.wrapper = this.box.getParent();
// modal instance id for toggling...
this.setId(this.options.id);
// use overlay?
this.overlay = (this.options.overlay) ? new Modal.Overlay(this.container, {
fx: this.options.overlayFx,
onOverlayClick: this.overlayClick.bind(this)
}) : false;
this.isShown = false;
// set defaults
this.fill(this.options);
// close methods...
this.attachEvents();
this.fireEvent("ready");
},
overlayClick: function(e) {
// handle click on overlay to close if allowed
e && e.preventDefault();
if (this.box.retrieve("options").anyClose) {
this.hide();
}
},
escapePress: function(e) {
// handle press of ESC to close if allowed
if (e && e.key) {
var opts = this.box.retrieve("options") || this.options;
if (e.key === 'esc' && opts.escClose) {
this.hide();
}
}
},
attachEvents: function() {
// ways to close...
this.box.addEvent('click:relay(.modal-close)', this.hide.bind(this));
window.addEvents({
'keydown': this.escapePress.bind(this),
'resize': this.limitHeight.bind(this)
});
return this;
},
setId: function(id) {
// needed so each modal can have a unique id.
this.box.store("uid", id);
return this;
}.protect(),
getId: function() {
return this.box.retrieve("uid") || this.options.id;
},
toggle: function() {
// simple toggler for the instance
var method = this.isShown ? "hide" : "show";
this[method].apply(this, arguments);
},
show: function(options){
options = options || this.options;
if (options.overlay)
this.overlay.show();
this.box.setStyle("visibility", "visible");
if (this.options.useTransitions) {
this.box.addClass(this.options.transitionShowClass);
}
else {
this.box.morph(options.fx.properties.show);
}
if (this.options.openClass) {
this.box.addClass(this.options.openClass);
}
this.isShown = true;
this.box.store("options", options);
this.box.set('aria-hidden', false);
this.fireEvent('show');
return this;
},
hide: function(){
var self = this;
var completeCallback = function() {
clearTimeout(self.hideTimer);
self.box.setStyle("visibility", "hidden").removeEvents("transitionend");
};
if (this.options.useTransitions) {
this.box.addEvent("transitionend", completeCallback);
this.box.removeClass(this.options.transitionShowClass);
}
else {
this.box.morph(this.options.fx.properties.hide);
}
this.hideTimer = completeCallback.delay(this.options.fx.duration);
this.isShown = false;
if (this.options.overlay)
this.overlay.hide();
this.box.set('aria-hidden', true);
this.fireEvent('hide');
return this;
},
fill: function(options){
var self = this, mapper = function(e){
if (options[e] !== undefined)
self.element.getElement('div.modal-' + e).set("html", options[e]);
};
['header', 'body', 'footer'].each(mapper);
this.offsetSize = this.element.getElements('div.modal-header, div.modal-footer').getSize();
this.limitHeight();
return this;
},
setTitle: function(title) {
var obj = {
header: title || ""
};
this.fill(obj);
this.fireEvent("content", obj);
return this;
},
setBody: function(body) {
var obj = {
body: body || ""
};
this.fill(obj);
this.fireEvent("content", obj);
return this;
},
setFooter: function(footer) {
var obj = {
footer: footer || ""
};
this.fill(obj);
this.fireEvent("content", obj);
return this;
},
limitHeight: function(){
if(this.options.limitHeight) this.content.setStyle('max-height', window.getSize().y - this.offsetSize[0].y - this.offsetSize[1].y - this.options.margin * 2);
}
});
Modal.BootStrap = new Class({
/*
this class will add dom bootstrap func for modal use w/o specific javascript func / calls by virtue of data-elements
*/
Extends: Modal.Base,
options: {
modalLinks: ".modal-overlay",
buttonsZen: "div.clearfix.modal-buttons",
loadingContent: "loading...",
autoOpenByHash: true,
props: {
href: "href",
modalType: "data-type",
modalTitle: "data-title",
modalBody: "data-body",
modalButtons: "data-buttons",
modalFooter: "data-footer",
modalOverlay: "data-overlay",
modalEasyClose: "data-any-close",
modalEscClose: "data-esc-close",
modalOpenEvent: "data-event-open",
modalCloseEvent: "data-event-close",
modalCustomClass: "data-class"
}
},
handledOptions: {},
boundEvents: {
open: {},
close: {}
},
initialize: function(container, options) {
this.setOptions(options);
this.parent(container, this.options);
this.attachBootstrap();
this.options.autoOpenByHash && this.applyHash();
this.fireEvent("ready");
},
attachBootstrap: function(mask) {
// what elements to listen on.
mask = mask || this.options.modalLinks;
var self = this;
this.container.addEvent(["click:relay(",mask,")"].join(""), function(e) {
self.handleClick(e, this);
});
return this;
},
handleClick: function(e, el) {
// when container click has found a match
e && e.preventDefault && e.preventDefault();
if (!el)
return;
el = e.target;
if (!el)
return;
// grab all properties we want
var props = {},
uid = Slick.uidOf(el),
self = this;
Object.each(this.options.props, function(value, key) {
props[key] = el.get(value) || "";
});
// store local options as they override the class options, more than 1 instance can exist
// and can behave differently. see .show which stores the passed options.
var options = Object.clone(this.options);
// overlay?
if (props.modalOverlay) {
options.overlay = !!JSON.decode(props.modalOverlay);
}
// any click closes on modal
if (props.modalEasyClose) {
options.anyClose = !!JSON.decode(props.modalEasyClose);
}
// allow close by pressing ESC key
if (props.modalEscClose) {
options.escClose = !!JSON.decode(props.modalEscClose);
}
// is it already shown? only works w/o overlay as it's click-driven. acts as toggle.
if (this.getId() == Slick.uidOf(el) && this.isShown) {
this.hide();
return;
}
// custom events
if (props.modalOpenEvent) {
// open
this.boundEvents.open[uid] = this.boundEvents.open[uid] || this.fireEvent.bind(this, props.modalOpenEvent);
this.addEvent("show", function() {
self.boundEvents.open[uid]();
self.removeEvent("show", arguments.callee);
});
}
if (props.modalCloseEvent) {
// close
this.boundEvents.close[uid] = this.boundEvents.close[uid] || this.fireEvent.bind(this, props.modalCloseEvent);
this.addEvent("hide", function() {
self.boundEvents.close[uid]();
self.removeEvent("hide", arguments.callee);
});
}
if (props.modalCustomClass) {
// add and remove class on show/hide, eg, .autoWidth as per demo.
options.openClass = props.modalCustomClass;
}
// set an ID so that it knows the trigger element (for toggle)
this.setId(Slick.uidOf(el));
// set the title of the modal
this.setTitle(this.getData(props.modalTitle));
// have we got modal buttons behaviour attached by data
if (props.modalButtons) {
var buttons = JSON.decode(props.modalButtons);
if (buttons && buttons.length) {
this.setFooter("");
// wrap it in this
var holder = new Element(this.options.buttonsZen);
Array.each(buttons, function(obj) {
var button = new Element("button", {
"class": obj.className,
html: obj.text
}).inject(holder).addClass(self.options.transitionClass);
if (obj.event) {
button.addEvent("click", function(event) {
self.fireEvent(obj.event);
})
}
});
holder.inject(this.footer);
}
}
else {
// else, look for a normal footer prop
this.setFooter(this.getData(props.modalFooter));
}
// save instance options
this.handledOptions[this.getId()] = options;
// what content to get, element or ajax are suported for now.
var val = props.modalBody || props.href;
switch (props.modalType) {
case "ajax":
new Request({
method: "get",
url: val,
onRequest: function() {
// spinner or text, whatever.
// self.setBody(self.options.loadingContent);
// self.show(options);
},
onSuccess: function() {
// set the body
self.setBody(this.response.text).show(options, el);
},
onFailure: function() {
self.setBody("Contents did not load. Close and try again").show(options, el);
}
}).send();
break;
default:
// get data from the href property as element directly
this.setBody(this.getData(val, !!props.href || !!props.modalBody));
this.show(options, el);
break;
}
},
show: function(options) {
if (this.handledOptions[this.getId()].openClass)
this.box.addClass(this.handledOptions[this.getId()].openClass);
this.parent(options);
},
hide: function() {
this.parent();
if (this.handledOptions[this.getId()].openClass) {
this.box.removeClass.delay(500, this.box, this.handledOptions[this.getId()].openClass);
}
if (this.savedHash && this.savedHash === location.hash) {
this.savedHash = false;
history.back();
}
},
getData: function(prop, hashChange) {
// internal that converts a property into data (html) or returns property itself.
var words = prop.split(/\s/);
if (words.length === 1) {
var target = document.id(words[0].replace('#', ''));
if (target) {
if (hashChange && location.hash != words[0]) {
this.savedHash = location.hash = words[0];
}
return target.get("html");
}
}
// is it an image?
var parts = prop.split(".");
if (parts.length) {
var extenstion = parts.getLast().toLowerCase();
if (['jpg','png','jpeg','gif'].contains(extenstion))
return [""].join("")
}
return prop;
}.protect(),
applyHash: function() {
// try to open a link to an id managed by an event handler automatically
var hash = window.location.hash;
if (!hash)
return;
var trigger = document.getElement([this.options.modalLinks, "[href=", hash, "]"].join("")) || document.getElement([this.options.modalLinks, "[", this.options.props.modalBody, "=", hash, "]"].join(""));
if (trigger) {
this.container.fireEvent("click", {
target: trigger
});
}
}
}); // end class
}());