Last active
August 26, 2016 21:22
-
-
Save jessejjohnson/849245aa444b3b3391dba344e8919ccd to your computer and use it in GitHub Desktop.
Bootstrap modal as typescript class with custom event names
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
import { Modal} from "Modal"; | |
let modal = new Modal("#mymodal"); | |
modal.show(); |
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
import * as $ from "jquery"; | |
const EVENTS = { | |
SHOW: "show.omacro.modal", | |
SHOWN: "shown.omacro.modal", | |
HIDE: "hide.omacro.modal", | |
HIDDEN: "hidden.omacro.modal", | |
LOADED: "loaded.omacro.modal", | |
MOUSEDOWN: "mousedown.omacro.modal", | |
MOUSEUP: "mouseup.omacro.modal", | |
KEYDOWN: "keydown.omacro.modal", | |
CLICK: "click.omacro.modal", | |
FOCUSIN: "focusin.omacro.modal", | |
RESIZE: "resize.omacro.modal" | |
}; | |
export interface IModalOptions { | |
backdrop?: boolean | "static"; | |
keyboard?: boolean; | |
remote?: string; | |
show?: boolean; | |
} | |
export class Modal { | |
static TRANSITION_DURATION = 300; | |
static BACKDROP_TRANSITION_DURATION = 150; | |
static DEFAULTS: IModalOptions = { | |
backdrop: true, | |
keyboard: false, | |
show: false, | |
remote: null | |
}; | |
private $body: JQuery; | |
private $element: JQuery; | |
private $dialog: JQuery; | |
private $backdrop: JQuery; | |
private isShown: boolean; | |
private originalBodyPad: number; | |
private scrollbarWidth: number; | |
private ignoreBackdropClick: boolean; | |
private bodyIsOverflowing: boolean; | |
private options: IModalOptions = Modal.DEFAULTS; | |
constructor(element: HTMLElement | string, options?: IModalOptions) { | |
$.extend(this.options, options); | |
this.$body = $(document.body); | |
this.$element = $(element); | |
this.$dialog = this.$element.find('.modal-dialog'); | |
this.$backdrop = null; | |
this.isShown = null; | |
this.originalBodyPad = null; | |
this.scrollbarWidth = 0; | |
this.ignoreBackdropClick = false; | |
if (this.options.remote) { | |
this.$element | |
.find('.modal-content') | |
.load(this.options.remote, $.proxy(function () { | |
this.$element.trigger(EVENTS.LOADED); | |
}, this)); | |
} | |
} | |
public toggle() { | |
return this.isShown ? this.hide() : this.show(); | |
} | |
public show() { | |
let _relatedTarget = this.$element[0]; | |
var e = $.Event(EVENTS.SHOW, { relatedTarget: _relatedTarget }); | |
this.$element.trigger(e); | |
if (this.isShown || e.isDefaultPrevented()) return; | |
this.isShown = true; | |
this.checkScrollbar(); | |
this.setScrollbar(); | |
this.$body.addClass('modal-open'); | |
this.escape(); | |
this.resize(); | |
this.$element.on(EVENTS.CLICK, '[data-dismiss="modal"]', $.proxy(this.hide, this)); | |
this.$dialog.on(EVENTS.MOUSEDOWN, () => { | |
this.$element.one(EVENTS.MOUSEUP, (e) => { | |
if ($(e.target).is(this.$element)) this.ignoreBackdropClick = true; | |
}); | |
}); | |
this.backdrop(() => { | |
var transition = $.support.transition && this.$element.hasClass('fade'); | |
if (!this.$element.parent().length) { | |
this.$element.appendTo(this.$body); // don't move modals dom position | |
} | |
this.$element | |
.show() | |
.scrollTop(0); | |
this.adjustDialog(); | |
if (transition) { | |
this.$element[0].offsetWidth; // force reflow | |
} | |
this.$element.addClass('in'); | |
this.enforceFocus(); | |
var e = $.Event(EVENTS.SHOWN, { relatedTarget: _relatedTarget }); | |
transition ? | |
this.$dialog // wait for modal to slide in | |
.one('bsTransitionEnd', () => { | |
this.$element.trigger('focus').trigger(e); | |
}) | |
.emulateTransitionEnd(Modal.TRANSITION_DURATION) : | |
this.$element.trigger('focus').trigger(e); | |
}); | |
} | |
public hide(e?: JQueryEventObject) { | |
if (e) e.preventDefault(); | |
e = $.Event(EVENTS.HIDE); | |
this.$element.trigger(e); | |
if (!this.isShown || e.isDefaultPrevented()) return; | |
this.isShown = false; | |
this.escape(); | |
this.resize(); | |
$(document).off(EVENTS.FOCUSIN); | |
this.$element | |
.removeClass('in') | |
.off(EVENTS.CLICK) | |
.off(EVENTS.MOUSEUP); | |
this.$dialog.off(EVENTS.MOUSEDOWN); | |
$.support.transition && this.$element.hasClass('fade') ? | |
this.$element | |
.one('bsTransitionEnd', $.proxy(this.hideModal, this)) | |
.emulateTransitionEnd(Modal.TRANSITION_DURATION) : | |
this.hideModal(); | |
} | |
public enforceFocus() { | |
$(document) | |
.off(EVENTS.FOCUSIN) // guard against infinite focus loop | |
.on(EVENTS.FOCUSIN, $.proxy(function (e) { | |
if (document !== e.target && | |
this.$element[0] !== e.target && | |
!this.$element.has(e.target).length) { | |
this.$element.trigger('focus'); | |
} | |
}, this)); | |
} | |
public handleUpdate() { | |
this.adjustDialog(); | |
} | |
private escape() { | |
if (this.isShown && this.options.keyboard) { | |
this.$element.on(EVENTS.KEYDOWN, $.proxy(function (e) { | |
e.which == 27 && this.hide(); | |
}, this)); | |
} else if (!this.isShown) { | |
this.$element.off(EVENTS.KEYDOWN); | |
} | |
} | |
private resize() { | |
if (this.isShown) { | |
$(window).on(EVENTS.RESIZE, $.proxy(this.handleUpdate, this)); | |
} else { | |
$(window).off(EVENTS.RESIZE); | |
} | |
} | |
private hideModal() { | |
var that = this; | |
this.$element.hide(); | |
this.backdrop(function () { | |
that.$body.removeClass('modal-open'); | |
that.resetAdjustments(); | |
that.resetScrollbar(); | |
that.$element.trigger(EVENTS.HIDDEN); | |
}); | |
} | |
private removeBackdrop() { | |
this.$backdrop && this.$backdrop.remove(); | |
this.$backdrop = null; | |
} | |
private backdrop(callback?: () => void) { | |
var that = this; | |
var animate = this.$element.hasClass('fade') ? 'fade' : ''; | |
if (this.isShown && this.options.backdrop) { | |
var doAnimate = $.support.transition && animate; | |
this.$backdrop = $(document.createElement('div')) | |
.addClass('modal-backdrop ' + animate) | |
.appendTo(this.$body); | |
this.$element.on(EVENTS.CLICK, $.proxy(function (e) { | |
if (this.ignoreBackdropClick) { | |
this.ignoreBackdropClick = false; | |
return; | |
} | |
if (e.target !== e.currentTarget) return; | |
this.options.backdrop == 'static' | |
? this.$element[0].focus() | |
: this.hide(); | |
}, this)); | |
if (doAnimate) this.$backdrop[0].offsetWidth; // force reflow | |
this.$backdrop.addClass('in'); | |
if (!callback) return; | |
doAnimate ? | |
this.$backdrop | |
.one('bsTransitionEnd', callback) | |
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : | |
callback(); | |
} else if (!this.isShown && this.$backdrop) { | |
this.$backdrop.removeClass('in'); | |
var callbackRemove = function () { | |
that.removeBackdrop(); | |
callback && callback(); | |
}; | |
$.support.transition && this.$element.hasClass('fade') ? | |
this.$backdrop | |
.one('bsTransitionEnd', callbackRemove) | |
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : | |
callbackRemove(); | |
} else if (callback) { | |
callback(); | |
} | |
} | |
private adjustDialog() { | |
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight; | |
this.$element.css({ | |
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', | |
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' | |
}); | |
} | |
private resetAdjustments() { | |
this.$element.css({ | |
paddingLeft: '', | |
paddingRight: '' | |
}); | |
} | |
private checkScrollbar() { | |
var fullWindowWidth = window.innerWidth; | |
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 | |
var documentElementRect = document.documentElement.getBoundingClientRect(); | |
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left); | |
} | |
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth; | |
this.scrollbarWidth = this.measureScrollbar(); | |
} | |
private setScrollbar() { | |
var bodyPad = parseInt((this.$body.css('padding-right') || "0"), 10); | |
this.originalBodyPad = parseInt(document.body.style.paddingRight || '0'); | |
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth); | |
} | |
private resetScrollbar() { | |
this.$body.css('padding-right', this.originalBodyPad); | |
} | |
private measureScrollbar() { | |
var scrollDiv = document.createElement('div'); | |
scrollDiv.className = 'modal-scrollbar-measure'; | |
this.$body.append(scrollDiv); | |
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; | |
this.$body[0].removeChild(scrollDiv); | |
return scrollbarWidth; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment