Skip to content

Instantly share code, notes, and snippets.

@foleyatwork
Created January 22, 2016 16:01
Show Gist options
  • Save foleyatwork/83a54322e3ff863c5519 to your computer and use it in GitHub Desktop.
Save foleyatwork/83a54322e3ff863c5519 to your computer and use it in GitHub Desktop.
/* globals Ok, _, ReactDOM, ENV, Mousetrap */
const OkModalManager = require("~/okmodal/util/OkModalManager");
const App = require("~/okmodal/components/App");
const C = require("~/okmodal/util/Constants");
/**
* Log an error string to the console with a styling OkModal prefix.
* @method logSimpleError
* @param {String} message
*/
function logSimpleError(message) {
if (!message || ENV === "prod") {
return;
}
console.error("%cOkModal:", "font-weight: bold; font-style: italic;", message);
}
/**
* Creates a new instance of a modal and renders it inside the target wrapper.
* Most of the time you won't need to get all fancy, just create a new OkModal
* (as shown in the example below) and don't do anything else, you don't even
* really need to store the instance.
*
* To close the OkModal manually, run OkModalManager.closeAll()
*
* @class OkModal
* @param {Object} config
* @example
* // Default values are filled in below.
* new OkModal({
* body: "",
* ctaLink: "#", // Required, if onAction isn't defined.
* ctaText: "Learn more",
* headline: "", // Required
* image: "", // Required
* onAction: function(){}, // Required, if ctaLink isn't defined.
* onClose: function(){},
* subheadline: "",
* windowshade: true,
* });
*/
class OkModal {
constructor(config) {
if (!config.headline || (!config.ctaLink && !config.onAction)) {
logSimpleError("Not enough information to initiate modal.");
return;
}
this._active = true;
this.config = _.extend(C.DEFAULT_CONFIG, config);
// Set the default target node.
// This has to be done here instead of stored in constants becase the
// node isn't on the page when Constants.coffee is pulled in.
if (!this.config.target) {
this.config.target = document.getElementById(C.CSS_PREFIX);
}
if (!this.config.target) {
logSimpleError("Couldn't find the target element. Pass an HTMLElement in the \"target\" prop.");
return;
}
this._modalID = `modal-${Date.now()}`;
OkModalManager.add(this._modalID, this);
this.open();
}
destructor() {
// Make sure the destructor runs once and only once. This is here so devs
// can indiscriminately call OkModalManager.closeAll().
if (this._active === false) {
return;
}
this._active = false;
this.fadeOut(() => {
this.config.onClose();
// Remove the node from OkModalManager.
OkModalManager.remove(this._modalID);
// Remove event listeners.
this._modalNode.removeEventListener("click", (e) => {
this._handleMouseClick(e);
});
Mousetrap.unbind("escape");
// Remove markup.
this.config.target.removeChild(this._modalNode);
// Null out variables to hint to the browser they're ready for GC.
this.config = null;
this._modalID = null;
this._modalNode = null;
this._active = null;
});
}
/**
* It's usually better to run OkModalManager.closeAll() but if you do need to
* close a specific modal, use this method.
*
* @method close
*/
close() {
this.destructor();
}
/**
* Fade the modal in then run a callback function.
*
* @method fadeIn
* @param {Function} callback
*/
fadeIn(callback) {
this._modalNode.className += " is-visible";
setTimeout(callback, C.FADE_OUT_TIME);
}
/**
* Fade the modal out then run a callback function.
*
* @method fadeOut
* @param {Function} callback
*/
fadeOut(callback) {
this._modalNode.className = this._modalNode.className.replace(/\ ?is\-visible/, "");
setTimeout(callback, C.FADE_OUT_TIME);
}
/**
* Opens the modal.
*
* @method open
*/
open() {
this._modalNode = document.getElementById(this._modalID);
if (!this._modalNode) {
this._modalNode = document.createElement("div");
this._modalNode.id = this._modalID;
this._modalNode.className = C.CSS_PREFIX + "-inner";
this.config.target.appendChild(this._modalNode);
}
ReactDOM.render(<App {...this.config} />, this._modalNode);
setTimeout(() => {
this._modalNode.className += " is-visible";
}, 50);
this._modalNode.addEventListener("click", (e) => {
this._handleMouseClick(e);
});
Mousetrap.bind("escape", this.close.bind(this));
}
/**
* Callback for delegated click events on the modal screen.
*
* @method _handleMouseClick
* @param {Object} e
*/
_handleMouseClick(e) {
if (
e.target.className.indexOf("FullscreenOverlay") > -1 ||
e.target.parentNode.className.indexOf(`${C.CSS_PREFIX}Content-dismiss`) > -1
) {
this.close();
}
}
}
// For legacy JS, Jan. 4 2016. Once we can use require() everywhere this is no
// longer necessary.
Ok.OkModal = OkModal;
/** @exports OkModal */
module.exports = OkModal;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment