368d6fafea
Code backup
425 lines
13 KiB
JavaScript
425 lines
13 KiB
JavaScript
/**
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
(function (root, factory) {
|
|
"use strict";
|
|
|
|
if (typeof define === 'function' && define.amd) {
|
|
define([], factory);
|
|
}
|
|
else if (typeof module === 'object' && module.exports) {
|
|
module.exports = factory();
|
|
}
|
|
else {
|
|
root.webphone_api.picoModal = factory();
|
|
}
|
|
}(this, function () {
|
|
|
|
/**
|
|
* A self-contained modal library
|
|
*/
|
|
"use strict";
|
|
|
|
/** Returns whether a value is a dom node */
|
|
function isNode(value) {
|
|
if ( typeof Node === "object" ) {
|
|
return value instanceof Node;
|
|
}
|
|
else {
|
|
return value &&
|
|
typeof value === "object" &&
|
|
typeof value.nodeType === "number";
|
|
}
|
|
}
|
|
|
|
/** Returns whether a value is a string */
|
|
function isString(value) {
|
|
return typeof value === "string";
|
|
}
|
|
|
|
/**
|
|
* Generates observable objects that can be watched and triggered
|
|
*/
|
|
function observable() {
|
|
var callbacks = [];
|
|
return {
|
|
watch: callbacks.push.bind(callbacks),
|
|
trigger: function( modal ) {
|
|
|
|
var unprevented = true;
|
|
var event = {
|
|
preventDefault: function preventDefault () {
|
|
unprevented = false;
|
|
}
|
|
};
|
|
|
|
for (var i = 0; i < callbacks.length; i++) {
|
|
callbacks[i](modal, event);
|
|
}
|
|
|
|
return unprevented;
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* A small interface for creating and managing a dom element
|
|
*/
|
|
function Elem( elem ) {
|
|
this.elem = elem;
|
|
}
|
|
|
|
/**
|
|
* Creates a new div
|
|
*/
|
|
Elem.div = function ( parent ) {
|
|
var elem = document.createElement('div');
|
|
(parent || document.body).appendChild(elem);
|
|
return new Elem(elem);
|
|
};
|
|
|
|
Elem.prototype = {
|
|
|
|
/** Creates a child of this node */
|
|
child: function () {
|
|
return Elem.div(this.elem);
|
|
},
|
|
|
|
/** Applies a set of styles to an element */
|
|
stylize: function(styles) {
|
|
styles = styles || {};
|
|
|
|
if ( typeof styles.opacity !== "undefined" ) {
|
|
styles.filter =
|
|
"alpha(opacity=" + (styles.opacity * 100) + ")";
|
|
}
|
|
|
|
for (var prop in styles) {
|
|
if (styles.hasOwnProperty(prop)) {
|
|
this.elem.style[prop] = styles[prop];
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/** Adds a class name */
|
|
clazz: function (clazz) {
|
|
this.elem.className += " " + clazz;
|
|
return this;
|
|
},
|
|
|
|
/** Sets the HTML */
|
|
html: function (content) {
|
|
if ( isNode(content) ) {
|
|
this.elem.appendChild( content );
|
|
}
|
|
else {
|
|
this.elem.innerHTML = content;
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/** Adds a click handler to this element */
|
|
onClick: function(callback) {
|
|
this.elem.addEventListener('click', callback);
|
|
return this;
|
|
},
|
|
|
|
/** Removes this element from the DOM */
|
|
destroy: function() {
|
|
document.body.removeChild(this.elem);
|
|
},
|
|
|
|
/** Hides this element */
|
|
hide: function() {
|
|
this.elem.style.display = "none";
|
|
},
|
|
|
|
/** Shows this element */
|
|
show: function() {
|
|
this.elem.style.display = "block";
|
|
},
|
|
|
|
/** Sets an attribute on this element */
|
|
attr: function ( name, value ) {
|
|
this.elem.setAttribute(name, value);
|
|
return this;
|
|
},
|
|
|
|
/** Executes a callback on all the ancestors of an element */
|
|
anyAncestor: function ( predicate ) {
|
|
var elem = this.elem;
|
|
while ( elem ) {
|
|
if ( predicate( new Elem(elem) ) ) {
|
|
return true;
|
|
}
|
|
else {
|
|
elem = elem.parentNode;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
/** Generates the grey-out effect */
|
|
function buildOverlay( getOption, close ) {
|
|
return Elem.div()
|
|
.clazz("pico-overlay")
|
|
.clazz( getOption("overlayClass", "") )
|
|
.stylize({
|
|
display: "none",
|
|
position: "fixed",
|
|
top: "0px",
|
|
left: "0px",
|
|
height: "100%",
|
|
width: "100%",
|
|
zIndex: 10000
|
|
})
|
|
.stylize(getOption('overlayStyles', {
|
|
opacity: 0.5,
|
|
background: "#000"
|
|
}))
|
|
.onClick(function () {
|
|
if ( getOption('overlayClose', true) ) {
|
|
close();
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Builds the content of a modal */
|
|
function buildModal( getOption, close ) {
|
|
var width = getOption('width', 'auto');
|
|
if ( typeof width === "number" ) {
|
|
width = "" + width + "px";
|
|
}
|
|
|
|
var elem = Elem.div()
|
|
.clazz("pico-content")
|
|
.clazz( getOption("modalClass", "") )
|
|
.stylize({
|
|
display: 'none',
|
|
position: 'fixed',
|
|
zIndex: 10001,
|
|
left: "50%",
|
|
top: "50px",
|
|
width: width,
|
|
'-ms-transform': 'translateX(-50%)',
|
|
'-moz-transform': 'translateX(-50%)',
|
|
'-webkit-transform': 'translateX(-50%)',
|
|
'-o-transform': 'translateX(-50%)',
|
|
'transform': 'translateX(-50%)'
|
|
})
|
|
.stylize(getOption('modalStyles', {
|
|
backgroundColor: "white",
|
|
padding: "20px",
|
|
borderRadius: "5px"
|
|
}))
|
|
.html( getOption('content') )
|
|
.attr("role", "dialog")
|
|
.onClick(function (event) {
|
|
var isCloseClick = new Elem(event.target)
|
|
.anyAncestor(function (elem) {
|
|
return /\bpico-close\b/.test(elem.elem.className);
|
|
});
|
|
if ( isCloseClick ) {
|
|
close();
|
|
}
|
|
});
|
|
|
|
return elem;
|
|
}
|
|
|
|
/** Builds the close button */
|
|
function buildClose ( elem, getOption ) {
|
|
if ( getOption('closeButton', true) ) {
|
|
return elem.child()
|
|
.html( getOption('closeHtml', "×") )
|
|
.clazz("pico-close")
|
|
.clazz( getOption("closeClass") )
|
|
.stylize( getOption('closeStyles', {
|
|
borderRadius: "2px",
|
|
cursor: "pointer",
|
|
height: "15px",
|
|
width: "15px",
|
|
position: "absolute",
|
|
top: "5px",
|
|
right: "5px",
|
|
fontSize: "16px",
|
|
textAlign: "center",
|
|
lineHeight: "15px",
|
|
background: "#CCC"
|
|
}) );
|
|
}
|
|
}
|
|
|
|
/** Builds a method that calls a method and returns an element */
|
|
function buildElemAccessor( builder ) {
|
|
return function () {
|
|
return builder().elem;
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* Displays a modal
|
|
*/
|
|
return function picoModal(options) {
|
|
|
|
if ( isString(options) || isNode(options) ) {
|
|
options = { content: options };
|
|
}
|
|
|
|
var afterCreateEvent = observable();
|
|
var beforeShowEvent = observable();
|
|
var afterShowEvent = observable();
|
|
var beforeCloseEvent = observable();
|
|
var afterCloseEvent = observable();
|
|
|
|
/**
|
|
* Returns a named option if it has been explicitly defined. Otherwise,
|
|
* it returns the given default value
|
|
*/
|
|
function getOption ( opt, defaultValue ) {
|
|
var value = options[opt];
|
|
if ( typeof value === "function" ) {
|
|
value = value( defaultValue );
|
|
}
|
|
return value === undefined ? defaultValue : value;
|
|
}
|
|
|
|
/** Hides this modal */
|
|
function forceClose () {
|
|
shadowElem().hide();
|
|
modalElem().hide();
|
|
afterCloseEvent.trigger(iface);
|
|
}
|
|
|
|
/** Gracefully hides this modal */
|
|
function close () {
|
|
if ( beforeCloseEvent.trigger(iface) ) {
|
|
forceClose();
|
|
}
|
|
}
|
|
|
|
/** Wraps a method so it returns the modal interface */
|
|
function returnIface ( callback ) {
|
|
return function () {
|
|
callback.apply(this, arguments);
|
|
return iface;
|
|
};
|
|
}
|
|
|
|
|
|
// The constructed dom nodes
|
|
var built;
|
|
|
|
/** Builds a method that calls a method and returns an element */
|
|
function build ( name ) {
|
|
if ( !built ) {
|
|
var modal = buildModal(getOption, close);
|
|
built = {
|
|
modal: modal,
|
|
overlay: buildOverlay(getOption, close),
|
|
close: buildClose(modal, getOption)
|
|
};
|
|
afterCreateEvent.trigger(iface);
|
|
}
|
|
return built[name];
|
|
}
|
|
|
|
var modalElem = build.bind(window, 'modal');
|
|
var shadowElem = build.bind(window, 'overlay');
|
|
var closeElem = build.bind(window, 'close');
|
|
|
|
|
|
var iface = {
|
|
|
|
/** Returns the wrapping modal element */
|
|
modalElem: buildElemAccessor(modalElem),
|
|
|
|
/** Returns the close button element */
|
|
closeElem: buildElemAccessor(closeElem),
|
|
|
|
/** Returns the overlay element */
|
|
overlayElem: buildElemAccessor(shadowElem),
|
|
|
|
/** Builds the dom without showing the modal */
|
|
buildDom: returnIface(build),
|
|
|
|
/** Shows this modal */
|
|
show: function () {
|
|
if ( beforeShowEvent.trigger(iface) ) {
|
|
shadowElem().show();
|
|
closeElem();
|
|
modalElem().show();
|
|
afterShowEvent.trigger(iface);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/** Hides this modal */
|
|
close: returnIface(close),
|
|
|
|
/**
|
|
* Force closes this modal. This will not call beforeClose
|
|
* events and will just immediately hide the modal
|
|
*/
|
|
forceClose: returnIface(forceClose),
|
|
|
|
/** Destroys this modal */
|
|
destroy: function () {
|
|
modalElem = modalElem().destroy();
|
|
shadowElem = shadowElem().destroy();
|
|
closeElem = undefined;
|
|
},
|
|
|
|
/**
|
|
* Updates the options for this modal. This will only let you
|
|
* change options that are re-evaluted regularly, such as
|
|
* `overlayClose`.
|
|
*/
|
|
options: function ( opts ) {
|
|
options = opts;
|
|
},
|
|
|
|
/** Executes after the DOM nodes are created */
|
|
afterCreate: returnIface(afterCreateEvent.watch),
|
|
|
|
/** Executes a callback before this modal is closed */
|
|
beforeShow: returnIface(beforeShowEvent.watch),
|
|
|
|
/** Executes a callback after this modal is shown */
|
|
afterShow: returnIface(afterShowEvent.watch),
|
|
|
|
/** Executes a callback before this modal is closed */
|
|
beforeClose: returnIface(beforeCloseEvent.watch),
|
|
|
|
/** Executes a callback after this modal is closed */
|
|
afterClose: returnIface(afterCloseEvent.watch)
|
|
};
|
|
|
|
return iface;
|
|
};
|
|
|
|
}));
|