Skip to content

Instantly share code, notes, and snippets.

@jessejjohnson
Last active August 26, 2016 21:22
Show Gist options
  • Save jessejjohnson/849245aa444b3b3391dba344e8919ccd to your computer and use it in GitHub Desktop.
Save jessejjohnson/849245aa444b3b3391dba344e8919ccd to your computer and use it in GitHub Desktop.
Bootstrap modal as typescript class with custom event names
import { Modal} from "Modal";
let modal = new Modal("#mymodal");
modal.show();
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