From ca9c850ebbfc880dc5021b451ffc9fa12184ff87 Mon Sep 17 00:00:00 2001 From: fat Date: Sun, 10 May 2015 19:45:38 -0700 Subject: add getters for Version and Default where applicable add modal my gawd --- js/src/modal.js | 492 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 js/src/modal.js (limited to 'js/src/modal.js') diff --git a/js/src/modal.js b/js/src/modal.js new file mode 100644 index 000000000..b2e8fd77f --- /dev/null +++ b/js/src/modal.js @@ -0,0 +1,492 @@ +import Util from './util' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): modal.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const Modal = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'modal' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.modal' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const TRANSITION_DURATION = 300 + const BACKDROP_TRANSITION_DURATION = 150 + + const Default = { + backdrop : true, + keyboard : true, + show : true + } + + const Event = { + HIDE   : 'hide.bs.modal', + HIDDEN   : 'hidden.bs.modal', + SHOW   : 'show.bs.modal', + SHOWN   : 'shown.bs.modal', + DISMISS : 'click.dismiss.bs.modal', + KEYDOWN : 'keydown.dismiss.bs.modal', + FOCUSIN : 'focusin.bs.modal', + RESIZE : 'resize.bs.modal', + CLICK : 'click.bs.modal.data-api', + MOUSEDOWN : 'mousedown.dismiss.bs.modal', + MOUSEUP : 'mouseup.dismiss.bs.modal' + } + + const ClassName = { + BACKDROP : 'modal-backdrop', + OPEN : 'modal-open', + FADE : 'fade', + IN : 'in' + } + + const Selector = { + DIALOG : '.modal-dialog', + DATA_TOGGLE : '[data-toggle="modal"]', + DATA_DISMISS : '[data-dismiss="modal"]', + SCROLLBAR_MEASURER : 'modal-scrollbar-measure' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class Modal { + + constructor(element, config) { + this._config = config + this._element = element + this._dialog = $(element).find(Selector.DIALOG)[0] + this._backdrop = null + this._isShown = false + this._isBodyOverflowing = false + this._ignoreBackdropClick = false + this._originalBodyPadding = 0 + this._scrollbarWidth = 0 + } + + + // getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + + // public + + toggle(relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget) + } + + show(relatedTarget) { + let showEvent = $.Event(Event.SHOW, { + relatedTarget: relatedTarget + }) + + $(this._element).trigger(showEvent) + + if (this._isShown || showEvent.isDefaultPrevented()) { + return + } + + this._isShown = true + + this._checkScrollbar() + this._setScrollbar() + + $(document.body).addClass(ClassName.OPEN) + + this._setEscapeEvent() + this._setResizeEvent() + + $(this._element).on( + Event.DISMISS, + Selector.DATA_DISMISS, + this.hide.bind(this) + ) + + $(this._dialog).on(Event.MOUSEDOWN, () => { + $(this._element).one(Event.MOUSEUP, (event) => { + if ($(event.target).is(this._element)) { + that._ignoreBackdropClick = true + } + }) + }) + + this._showBackdrop( + this._showElement.bind(this, relatedTarget) + ) + } + + hide(event) { + if (event) { + event.preventDefault() + } + + let hideEvent = $.Event(Event.HIDE) + + $(this._element).trigger(hideEvent) + + if (!this._isShown || hideEvent.isDefaultPrevented()) { + return + } + + this._isShown = false + + this._setEscapeEvent() + this._setResizeEvent() + + $(document).off(Event.FOCUSIN) + + $(this._element).removeClass(ClassName.IN) + + $(this._element).off(Event.DISMISS) + $(this._dialog).off(Event.MOUSEDOWN) + + if (Util.supportsTransitionEnd() && + ($(this._element).hasClass(ClassName.FADE))) { + + $(this._element) + .one(Util.TRANSITION_END, this._hideModal.bind(this)) + .emulateTransitionEnd(TRANSITION_DURATION) + } else { + this._hideModal() + } + } + + + // private + + _showElement(relatedTarget) { + let transition = Util.supportsTransitionEnd() && + $(this._element).hasClass(ClassName.FADE) + + if (!this._element.parentNode || + (this._element.parentNode.nodeType !== Node.ELEMENT_NODE)) { + // don't move modals dom position + document.body.appendChild(this._element) + } + + this._element.style.display = 'block' + this._element.scrollTop = 0 + + if (transition) { + Util.reflow(this._element) + } + + $(this._element).addClass(ClassName.IN) + + this._enforceFocus() + + let shownEvent = $.Event(Event.SHOWN, { + relatedTarget: relatedTarget + }) + + let transitionComplete = () => { + this._element.focus() + $(this._element).trigger(shownEvent) + } + + if (transition) { + $(this._dialog) + .one(Util.TRANSITION_END, transitionComplete) + .emulateTransitionEnd(TRANSITION_DURATION) + } else { + transitionComplete() + } + } + + _enforceFocus() { + $(document) + .off(Event.FOCUSIN) // guard against infinite focus loop + .on(Event.FOCUSIN, (event) => { + if (this._element !== event.target && + (!$(this._element).has(event.target).length)) { + this._element.focus() + } + }) + } + + _setEscapeEvent() { + if (this._isShown && this._config.keyboard) { + $(this._element).on(Event.KEYDOWN, (event) => { + if (event.which === 27) { + this.hide() + } + }) + + } else if (!this._isShown) { + $(this._element).off(Event.KEYDOWN) + } + } + + _setResizeEvent() { + if (this._isShown) { + $(window).on(Event.RESIZE, this._handleUpdate.bind(this)) + } else { + $(window).off(Event.RESIZE) + } + } + + _hideModal() { + this._element.style.display = 'none' + this._showBackdrop(() => { + $(document.body).removeClass(ClassName.OPEN) + this._resetAdjustments() + this._resetScrollbar() + $(this._element).trigger(Event.HIDDEN) + }) + } + + _removeBackdrop() { + if (this._backdrop) { + $(this._backdrop).remove() + this._backdrop = null + } + } + + _showBackdrop(callback) { + let animate = $(this._element).hasClass(ClassName.FADE) ? + ClassName.FADE : '' + + if (this._isShown && this._config.backdrop) { + let doAnimate = Util.supportsTransitionEnd() && animate + + this._backdrop = document.createElement('div') + this._backdrop.className = ClassName.BACKDROP + + if (animate) { + $(this._backdrop).addClass(animate) + } + + $(this._backdrop).appendTo(this.$body) + + $(this._element).on(Event.DISMISS, (event) => { + if (this._ignoreBackdropClick) { + this._ignoreBackdropClick = false + return + } + if (event.target !== event.currentTarget) { + return + } + if (this._config.backdrop === 'static') { + this._element.focus() + } else { + this.hide() + } + }) + + if (doAnimate) { + Util.reflow(this._backdrop) + } + + $(this._backdrop).addClass(ClassName.IN) + + if (!callback) { + return + } + + if (!doAnimate) { + callback() + return + } + + $(this._backdrop) + .one(Util.TRANSITION_END, callback) + .emulateTransitionEnd(BACKDROP_TRANSITION_DURATION) + + } else if (!this._isShown && this._backdrop) { + $(this._backdrop).removeClass(ClassName.IN) + + let callbackRemove = () => { + this._removeBackdrop() + if (callback) { + callback() + } + } + + if (Util.supportsTransitionEnd() && + ($(this._element).hasClass(ClassName.FADE))) { + $(this._backdrop) + .one(Util.TRANSITION_END, callbackRemove) + .emulateTransitionEnd(BACKDROP_TRANSITION_DURATION) + } else { + callbackRemove() + } + + } else if (callback) { + callback() + } + } + + + // ---------------------------------------------------------------------- + // the following methods are used to handle overflowing modals + // todo (fat): these should probably be refactored out of modal.js + // ---------------------------------------------------------------------- + + _handleUpdate() { + this._adjustDialog() + } + + _adjustDialog() { + let isModalOverflowing = + this._element.scrollHeight > document.documentElement.clientHeight + + if (!this._isBodyOverflowing && isModalOverflowing) { + this._element.style.paddingLeft = this._scrollbarWidth + 'px' + } + + if (this._isBodyOverflowing && !isModalOverflowing) { + this._element.style.paddingRight = this._scrollbarWidth + 'px' + } + } + + _resetAdjustments() { + this._element.style.paddingLeft = '' + this._element.style.paddingRight = '' + } + + _checkScrollbar() { + let fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + let documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = + documentElementRect.right - Math.abs(documentElementRect.left) + } + this._isBodyOverflowing = document.body.clientWidth < fullWindowWidth + this._scrollbarWidth = this._getScrollbarWidth() + } + + _setScrollbar() { + let bodyPadding = parseInt( + $(document.body).css('padding-right') || 0, + 10 + ) + + this._originalBodyPadding = document.body.style.paddingRight || '' + + if (this._isBodyOverflowing) { + document.body.style.paddingRight = + bodyPadding + this._scrollbarWidth + 'px' + } + } + + _resetScrollbar() { + document.body.style.paddingRight = this._originalBodyPadding + } + + _getScrollbarWidth() { // thx d.walsh + let scrollDiv = document.createElement('div') + scrollDiv.className = Selector.SCROLLBAR_MEASURER + document.body.appendChild(scrollDiv) + let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + document.body.removeChild(scrollDiv) + return scrollbarWidth + } + + + // static + + static _jQueryInterface(config, relatedTarget) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + let _config = $.extend( + {}, + Modal.Default, + $(this).data(), + typeof config === 'object' && config + ) + + if (!data) { + data = new Modal(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config === 'string') { + data[config](relatedTarget) + + } else if (_config.show) { + data.show(relatedTarget) + } + }) + } + + } + + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(document).on(Event.CLICK, Selector.DATA_TOGGLE, function (event) { + let target + let selector = Util.getSelectorFromElement(this) + + if (selector) { + target = $(selector)[0] + } + + let config = $(target).data(DATA_KEY) ? + 'toggle' : $.extend({}, $(target).data(), $(this).data()) + + if (this.tagName === 'A') { + event.preventDefault() + } + + let $target = $(target).one(Event.SHOW, (showEvent) => { + if (showEvent.isDefaultPrevented()) { + // only register focus restorer if modal will actually get shown + return + } + + $target.one(Event.HIDDEN, () => { + if ($(this).is(':visible')) { + this.focus() + } + }) + }) + + Modal._jQueryInterface.call($(target), config, this) + }) + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = Modal._jQueryInterface + $.fn[NAME].Constructor = Modal + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Modal._jQueryInterface + } + + return Modal + +})(jQuery) + +export default Modal -- cgit v1.2.3 From ab1578465aee4a776412b48f16bfefca79381919 Mon Sep 17 00:00:00 2001 From: fat Date: Tue, 12 May 2015 16:52:54 -0700 Subject: grunt test-js, grunt dist-js now working --- js/src/modal.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'js/src/modal.js') diff --git a/js/src/modal.js b/js/src/modal.js index b2e8fd77f..300ea2a7f 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -121,7 +121,7 @@ const Modal = (($) => { $(this._element).on( Event.DISMISS, Selector.DATA_DISMISS, - this.hide.bind(this) + $.proxy(this.hide, this) ) $(this._dialog).on(Event.MOUSEDOWN, () => { @@ -133,7 +133,7 @@ const Modal = (($) => { }) this._showBackdrop( - this._showElement.bind(this, relatedTarget) + $.proxy(this._showElement, this, relatedTarget) ) } @@ -166,7 +166,7 @@ const Modal = (($) => { ($(this._element).hasClass(ClassName.FADE))) { $(this._element) - .one(Util.TRANSITION_END, this._hideModal.bind(this)) + .one(Util.TRANSITION_END, $.proxy(this._hideModal, this)) .emulateTransitionEnd(TRANSITION_DURATION) } else { this._hideModal() @@ -241,7 +241,7 @@ const Modal = (($) => { _setResizeEvent() { if (this._isShown) { - $(window).on(Event.RESIZE, this._handleUpdate.bind(this)) + $(window).on(Event.RESIZE, $.proxy(this._handleUpdate, this)) } else { $(window).off(Event.RESIZE) } -- cgit v1.2.3 From f8b2569ec8956a1f4d09fe6fc9865bd200ecde43 Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 12:48:34 -0700 Subject: implement global dispose method --- js/src/modal.js | 61 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 20 deletions(-) (limited to 'js/src/modal.js') diff --git a/js/src/modal.js b/js/src/modal.js index 300ea2a7f..d0e1ec21b 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -20,6 +20,8 @@ const Modal = (($) => { const NAME = 'modal' const VERSION = '4.0.0' const DATA_KEY = 'bs.modal' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] const TRANSITION_DURATION = 300 const BACKDROP_TRANSITION_DURATION = 150 @@ -31,17 +33,17 @@ const Modal = (($) => { } const Event = { - HIDE   : 'hide.bs.modal', - HIDDEN   : 'hidden.bs.modal', - SHOW   : 'show.bs.modal', - SHOWN   : 'shown.bs.modal', - DISMISS : 'click.dismiss.bs.modal', - KEYDOWN : 'keydown.dismiss.bs.modal', - FOCUSIN : 'focusin.bs.modal', - RESIZE : 'resize.bs.modal', - CLICK : 'click.bs.modal.data-api', - MOUSEDOWN : 'mousedown.dismiss.bs.modal', - MOUSEUP : 'mouseup.dismiss.bs.modal' + HIDE   : `hide${EVENT_KEY}`, + HIDDEN   : `hidden${EVENT_KEY}`, + SHOW   : `show${EVENT_KEY}`, + SHOWN   : `shown${EVENT_KEY}`, + FOCUSIN : `focusin${EVENT_KEY}`, + RESIZE : `resize${EVENT_KEY}`, + CLICK_DISMISS : `click.dismiss${EVENT_KEY}`, + KEYDOWN_DISMISS : `keydown.dismiss${EVENT_KEY}`, + MOUSEUP_DISMISS : `mouseup.dismiss${EVENT_KEY}`, + MOUSEDOWN_DISMISS : `mousedown.dismiss${EVENT_KEY}`, + CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -119,13 +121,13 @@ const Modal = (($) => { this._setResizeEvent() $(this._element).on( - Event.DISMISS, + Event.CLICK_DISMISS, Selector.DATA_DISMISS, $.proxy(this.hide, this) ) - $(this._dialog).on(Event.MOUSEDOWN, () => { - $(this._element).one(Event.MOUSEUP, (event) => { + $(this._dialog).on(Event.MOUSEDOWN_DISMISS, () => { + $(this._element).one(Event.MOUSEUP_DISMISS, (event) => { if ($(event.target).is(this._element)) { that._ignoreBackdropClick = true } @@ -159,8 +161,8 @@ const Modal = (($) => { $(this._element).removeClass(ClassName.IN) - $(this._element).off(Event.DISMISS) - $(this._dialog).off(Event.MOUSEDOWN) + $(this._element).off(Event.CLICK_DISMISS) + $(this._dialog).off(Event.MOUSEDOWN_DISMISS) if (Util.supportsTransitionEnd() && ($(this._element).hasClass(ClassName.FADE))) { @@ -173,6 +175,25 @@ const Modal = (($) => { } } + dispose() { + $.removeData(this._element, DATA_KEY) + + $(window).off(EVENT_KEY) + $(document).off(EVENT_KEY) + $(this._element).off(EVENT_KEY) + $(this._backdrop).off(EVENT_KEY) + + this._config = null + this._element = null + this._dialog = null + this._backdrop = null + this._isShown = null + this._isBodyOverflowing = null + this._ignoreBackdropClick = null + this._originalBodyPadding = null + this._scrollbarWidth = null + } + // private @@ -228,14 +249,14 @@ const Modal = (($) => { _setEscapeEvent() { if (this._isShown && this._config.keyboard) { - $(this._element).on(Event.KEYDOWN, (event) => { + $(this._element).on(Event.KEYDOWN_DISMISS, (event) => { if (event.which === 27) { this.hide() } }) } else if (!this._isShown) { - $(this._element).off(Event.KEYDOWN) + $(this._element).off(Event.KEYDOWN_DISMISS) } } @@ -280,7 +301,7 @@ const Modal = (($) => { $(this._backdrop).appendTo(this.$body) - $(this._element).on(Event.DISMISS, (event) => { + $(this._element).on(Event.CLICK_DISMISS, (event) => { if (this._ignoreBackdropClick) { this._ignoreBackdropClick = false return @@ -440,7 +461,7 @@ const Modal = (($) => { * ------------------------------------------------------------------------ */ - $(document).on(Event.CLICK, Selector.DATA_TOGGLE, function (event) { + $(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { let target let selector = Util.getSelectorFromElement(this) -- cgit v1.2.3 From 7ef0e52fd042da2fdf107a3347abab3486a67790 Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 13:22:26 -0700 Subject: add "focus" option for turning off modal focusing #16050 --- js/src/modal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'js/src/modal.js') diff --git a/js/src/modal.js b/js/src/modal.js index d0e1ec21b..88a3c8950 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -29,6 +29,7 @@ const Modal = (($) => { const Default = { backdrop : true, keyboard : true, + focus : true, show : true } @@ -223,7 +224,7 @@ const Modal = (($) => { }) let transitionComplete = () => { - this._element.focus() + if (this._config.focus) this._element.focus() $(this._element).trigger(shownEvent) } -- cgit v1.2.3 From b0d142334f0d15e63577b28e2d7045c2199dcd6a Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 13:43:56 -0700 Subject: fix #15301 --- js/src/modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src/modal.js') diff --git a/js/src/modal.js b/js/src/modal.js index 88a3c8950..084c4ec3a 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -217,7 +217,7 @@ const Modal = (($) => { $(this._element).addClass(ClassName.IN) - this._enforceFocus() + if (this._config.focus) this._enforceFocus() let shownEvent = $.Event(Event.SHOWN, { relatedTarget: relatedTarget -- cgit v1.2.3 From eaab1def7af7d7e1ab32ff69d043b46e2815ca22 Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 14:46:50 -0700 Subject: add simple type checker implementation --- js/src/modal.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'js/src/modal.js') diff --git a/js/src/modal.js b/js/src/modal.js index 084c4ec3a..2ca603b23 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -33,6 +33,13 @@ const Modal = (($) => { show : true } + const DefaultType = { + backdrop : '(boolean|string)', + keyboard : 'boolean', + focus : 'boolean', + show : 'boolean' + } + const Event = { HIDE   : `hide${EVENT_KEY}`, HIDDEN   : `hidden${EVENT_KEY}`, @@ -71,7 +78,7 @@ const Modal = (($) => { class Modal { constructor(element, config) { - this._config = config + this._config = this._getConfig(config) this._element = element this._dialog = $(element).find(Selector.DIALOG)[0] this._backdrop = null @@ -198,6 +205,12 @@ const Modal = (($) => { // private + _getConfig(config) { + config = $.extend({}, Default, config) + Util.typeCheckConfig(NAME, config, DefaultType) + return config + } + _showElement(relatedTarget) { let transition = Util.supportsTransitionEnd() && $(this._element).hasClass(ClassName.FADE) -- cgit v1.2.3