bootstrap-modal icon indicating copy to clipboard operation
bootstrap-modal copied to clipboard

event.relatedTarget undefined

Open JDqv opened this issue 10 years ago • 22 comments

Seems like event property relatedTarget gets lost. It would be nice to keep bs3 documented functionality intact. Maybe I overlooked something... (?)

 $('#modal').on('show.bs.modal', function (e) {
      var invoker = $(e.relatedTarget);
      console.log(invoker); //undefined when bootstrap-modal included
});

JDqv avatar Mar 13 '14 11:03 JDqv

can anyone show me a way to get modal caller element in another way? Now its the 4th time the need for it comes up and e.relatedTarget would be very useful.

...sorry for double post

JDqv avatar Mar 21 '14 16:03 JDqv

Hi @JDqv , did you put the right attributes on the caller? Example: <button class="btn btn-default" data-toggle="modal" data-target="#modal">Open</button>

JBassx avatar Mar 31 '14 15:03 JBassx

@JBassx Yea, showing modal is not the problem, problem is determining which button was clicked when modal opens, which in pure BS is done via e.relatedTarget. Consider this: we have 5 buttons, one of them gets clicked, now... how to tell which button was pressed in show.bs.modal event. Its doable in many ugly hackedy ways, but not with BS native e.realtedTarget. I cant figure out neat way to do this.

JDqv avatar Apr 02 '14 12:04 JDqv

@JDqv I seem to have a similar problem. Closing the modal but the event shouldn't matter. When using:

 $('#myModal').on('hidden.bs.modal', function(e) {
     console.log($(e.relatedTarget));
 });

I get an Object in the console, but it doesn't learn me which button was clicked (Clear of Save)... That is what I would be expecting maybe i didn't get the behaviour right.

jandante avatar Jul 28 '14 10:07 jandante

I have the same problem, although I only have to get the relatedTarget value in the shown event, so I tried to hack the plugin. I'm posting the code for both bootstrap-modal.js and the appendModal function of the bootstrap-modalmanager.js file just to give you guys an idea on how to make a work-around for it:

bootstrap-modal.js

!function ($) {

    "use strict"; // jshint ;_;

    /* MODAL CLASS DEFINITION
    * ====================== */

       /* I just included the relatedTarget here*/
    var Modal = function (element, options, _relatedTarget) {
        this.init(element, options, _relatedTarget);
    };

    Modal.prototype = {

        constructor: Modal,

        init: function (element, options, _relatedTarget) {
            var that = this;

            this.options = options;

            this.$element = $(element)
                .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this));

            this.options.remote && this.$element.find('.modal-body').load(this.options.remote, function () {
                var e = $.Event('loaded');
                that.$element.trigger(e);
            });

            var manager = typeof this.options.manager === 'function' ?
                this.options.manager.call(this) : this.options.manager;

            manager = manager.appendModal ?
                manager : $(manager).modalmanager().data('modalmanager');

                        /* As you can see here, I'm passing the related target to the appendModal function to be handled in the modal manager */
            manager.appendModal(this, _relatedTarget);
        },

        toggle: function () {
            return this[!this.isShown ? 'show' : 'hide']();
        },

        show: function (_relatedTarget) {
            var e = $.Event('show');

            if (this.isShown) return;

            this.$element.trigger(e);

            if (e.isDefaultPrevented()) return;

            this.escape();

            this.tab();

            this.options.loading && this.loading();
        },

        hide: function (e) {
            e && e.preventDefault();

            e = $.Event('hide');

            this.$element.trigger(e);

            if (!this.isShown || e.isDefaultPrevented()) return;

            this.isShown = false;

            this.escape();

            this.tab();

            this.isLoading && this.loading();

            $(document).off('focusin.modal');

            this.$element
                .removeClass('in')
                .removeClass('animated')
                .removeClass(this.options.attentionAnimation)
                .removeClass('modal-overflow')
                .attr('aria-hidden', true);

            $.support.transition && this.$element.hasClass('fade') ?
                this.hideWithTransition() :
                this.hideModal();
        },

        layout: function () {
            var prop = this.options.height ? 'height' : 'max-height',
                value = this.options.height || this.options.maxHeight;

            if (this.options.width){
                this.$element.css('width', this.options.width);

                var that = this;
                this.$element.css('margin-left', function () {
                    if (/%/ig.test(that.options.width)){
                        return -(parseInt(that.options.width) / 2) + '%';
                    } else {
                        return -($(this).width() / 2) + 'px';
                    }
                });
            } else {
                this.$element.css('width', '');
                this.$element.css('margin-left', '');
            }

            this.$element.find('.modal-body')
                .css('overflow', '')
                .css(prop, '');

            if (value){
                this.$element.find('.modal-body')
                    .css('overflow', 'auto')
                    .css(prop, value);
            }

            var modalOverflow = $(window).height() - 10 < this.$element.height();

            if (modalOverflow || this.options.modalOverflow) {
                this.$element
                    .css('margin-top', 0)
                    .addClass('modal-overflow');
            } else {
                this.$element
                    .css('margin-top', 0 - this.$element.height() / 2)
                    .removeClass('modal-overflow');
            }
        },

        tab: function () {
            var that = this;

            if (this.isShown && this.options.consumeTab) {
                this.$element.on('keydown.tabindex.modal', '[data-tabindex]', function (e) {
                    if (e.keyCode && e.keyCode == 9){
                        var $next = $(this),
                            $rollover = $(this);

                        that.$element.find('[data-tabindex]:enabled:not([readonly])').each(function (e) {
                            if (!e.shiftKey){
                                $next = $next.data('tabindex') < $(this).data('tabindex') ?
                                    $next = $(this) :
                                    $rollover = $(this);
                            } else {
                                $next = $next.data('tabindex') > $(this).data('tabindex') ?
                                    $next = $(this) :
                                    $rollover = $(this);
                            }
                        });

                        $next[0] !== $(this)[0] ?
                            $next.focus() : $rollover.focus();

                        e.preventDefault();
                    }
                });
            } else if (!this.isShown) {
                this.$element.off('keydown.tabindex.modal');
            }
        },

        escape: function () {
            var that = this;
            if (this.isShown && this.options.keyboard) {
                if (!this.$element.attr('tabindex')) this.$element.attr('tabindex', -1);

                this.$element.on('keyup.dismiss.modal', function (e) {
                    e.which == 27 && that.hide();
                });
            } else if (!this.isShown) {
                this.$element.off('keyup.dismiss.modal')
            }
        },

        hideWithTransition: function () {
            var that = this
                , timeout = setTimeout(function () {
                    that.$element.off($.support.transition.end);
                    that.hideModal();
                }, 500);

            this.$element.one($.support.transition.end, function () {
                clearTimeout(timeout);
                that.hideModal();
            });
        },

        hideModal: function () {
            var prop = this.options.height ? 'height' : 'max-height';
            var value = this.options.height || this.options.maxHeight;

            if (value){
                this.$element.find('.modal-body')
                    .css('overflow', '')
                    .css(prop, '');
            }

            this.$element
                .hide()
                .trigger('hidden');
        },

        removeLoading: function () {
            this.$loading.remove();
            this.$loading = null;
            this.isLoading = false;
        },

        loading: function (callback) {
            callback = callback || function () {};

            var animate = this.$element.hasClass('fade') ? 'fade' : '';

            if (!this.isLoading) {
                var doAnimate = $.support.transition && animate;

                this.$loading = $('<div class="loading-mask ' + animate + '">')
                    .append(this.options.spinner)
                    .appendTo(this.$element);

                if (doAnimate) this.$loading[0].offsetWidth; // force reflow

                this.$loading.addClass('in');

                this.isLoading = true;

                doAnimate ?
                    this.$loading.one($.support.transition.end, callback) :
                    callback();

            } else if (this.isLoading && this.$loading) {
                this.$loading.removeClass('in');

                var that = this;
                $.support.transition && this.$element.hasClass('fade')?
                    this.$loading.one($.support.transition.end, function () { that.removeLoading() }) :
                    that.removeLoading();

            } else if (callback) {
                callback(this.isLoading);
            }
        },

        focus: function () {
            var $focusElem = this.$element.find(this.options.focusOn);

            $focusElem = $focusElem.length ? $focusElem : this.$element;

            $focusElem.focus();
        },

        attention: function (){
            // NOTE: transitionEnd with keyframes causes odd behaviour

            if (this.options.attentionAnimation){
                this.$element
                    .removeClass('animated')
                    .removeClass(this.options.attentionAnimation);

                var that = this;

                setTimeout(function () {
                    that.$element
                        .addClass('animated')
                        .addClass(that.options.attentionAnimation);
                }, 0);
            }


            this.focus();
        },


        destroy: function () {
            var e = $.Event('destroy');

            this.$element.trigger(e);

            if (e.isDefaultPrevented()) return;

            this.$element
                .off('.modal')
                .removeData('modal')
                .removeClass('in')
                .attr('aria-hidden', true);

            if (this.$parent !== this.$element.parent()) {
                this.$element.appendTo(this.$parent);
            } else if (!this.$parent.length) {
                // modal is not part of the DOM so remove it.
                this.$element.remove();
                this.$element = null;
            }

            this.$element.trigger('destroyed');
        }
    };


    /* MODAL PLUGIN DEFINITION
    * ======================= */

        /* args here is the relatedTarget DOM element */
    $.fn.modal = function (option, args) {
        return this.each(function () {
            var $this = $(this),
                data = $this.data('modal'),
                options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option);
                        /* I then passed the args to the Modal constructor */
            if (!data) $this.data('modal', (data = new Modal(this, options, args)));
            if (typeof option == 'string') data[option].apply(data, [].concat(args));
            else if (options.show) data.show(args)
        })
    };

    $.fn.modal.defaults = {
        keyboard: true,
        backdrop: true,
        loading: false,
        show: true,
        width: null,
        height: null,
        maxHeight: null,
        modalOverflow: false,
        consumeTab: true,
        focusOn: null,
        replace: false,
        resize: false,
        attentionAnimation: 'shake',
        manager: 'body',
        spinner: '<div class="loading-spinner" style="width: 200px; margin-left: -100px;"><div class="progress progress-striped active"><div class="bar" style="width: 100%;"></div></div></div>',
        backdropTemplate: '<div class="modal-backdrop" />'
    };

    $.fn.modal.Constructor = Modal;


    /* MODAL DATA-API
    * ============== */

    $(function () {
        $(document).off('click.modal').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
            var $this = $(this),
                href = $this.attr('href'),
                $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))), //strip for ie7
                option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data());

            e.preventDefault();
                        /* notice here that I included the DOM element as the 2nd parameter in the modal creation */
            $target
                .modal(option, this)
                .one('hide', function () {
                    $this.focus();
                })
        });
    });

}(window.jQuery);

appendModal - bootstrap-modalmanager.js

appendModal: function (modal, _relatedTarget) {
            this.stack.push(modal);

            var that = this;

            modal.$element.on('show.modalmanager', targetIsSelf(function (e) {

                var showModal = function(relatedTarget) {
                    modal.isShown = true;

                    var transition = $.support.transition && modal.$element.hasClass('fade');

                    that.$element
                        .toggleClass('modal-open', that.hasOpenModal())
                        .toggleClass('page-overflow', $(window).height() < that.$element.height());

                    modal.$parent = modal.$element.parent();

                    modal.$container = that.createContainer(modal);

                    modal.$element.appendTo(modal.$container);

                    that.backdrop(modal, function () {
                        modal.$element.show();

                        if (transition) {       
                            //modal.$element[0].style.display = 'run-in';       
                            modal.$element[0].offsetWidth;
                            //modal.$element.one($.support.transition.end, function () { modal.$element[0].style.display = 'block' });  
                        }

                        modal.layout();

                        modal.$element
                            .addClass('in')
                            .attr('aria-hidden', false);

                        var complete = function () {
                            that.setFocus();
                            var e = $.Event('shown', { relatedTarget: relatedTarget });

                            modal.$element.trigger(e);
                        };

                        transition ?
                            modal.$element.one($.support.transition.end, complete) :
                            complete();
                    });
                };

                modal.options.replace ?
                    that.replace(_relatedTarget, showModal) :
                    showModal(_relatedTarget);
            }));

            modal.$element.on('hidden.modalmanager', targetIsSelf(function (e) {
                that.backdrop(modal);
                // handle the case when a modal may have been removed from the dom before this callback executes
                if (!modal.$element.parent().length) {
                    that.destroyModal(modal);
                } else if (modal.$backdrop){
                    var transition = $.support.transition && modal.$element.hasClass('fade');

                    // trigger a relayout due to firebox's buggy transition end event 
                    if (transition) { modal.$element[0].offsetWidth; }
                    $.support.transition && modal.$element.hasClass('fade') ?
                        modal.$backdrop.one($.support.transition.end, function () { modal.destroy(); }) :
                        modal.destroy();
                } else {
                    modal.destroy();
                }

            }));

            modal.$element.on('destroyed.modalmanager', targetIsSelf(function (e) {
                that.destroyModal(modal);
            }));
        },

I'm not sure if I did it the right/best way, but it does work for me. Again, the code above only works for the shown event.

Also, take note that the plugin is not using the Bootstrap v3 event namespaces, so instead of using shown.bs.modal to hook up the events, try using just shown. When I used the namespaces, the events would fire once, but won't fire the next time you invoke the modal.

micahbule avatar Sep 10 '14 09:09 micahbule

Does this still not work properly (without hacks)?

rickydeez avatar Oct 15 '14 19:10 rickydeez

I have the same problem with

 <a class="btn" data-toggle="modal" data-target="#testmodal" >test</a>

mrxx avatar Dec 02 '14 01:12 mrxx

this is pretty much a show stopper for me. Any ideas?

achaffman avatar Jan 03 '15 13:01 achaffman

Show Stopper for me also. Trying to use this example from getboostrap.com: data-toggle="modal" data-target="#exampleModal" data-whatever="@mdo"

$('#exampleModal').on('show.bs.modal', function (event) { var button = $(event.relatedTarget) // Button that triggered the modal var recipient = button.data('whatever') // Extract info from data-* attributes And button.data does not exist. Please help.

VG-cbt avatar Feb 05 '15 15:02 VG-cbt

This doesn't just appear to be isolated to modals. i'm seeing this issue in 3.3.4 for tabs as well. The relatedTarget isn't appearing on the event object at all.

gunthercox avatar Mar 22 '15 14:03 gunthercox

I can confirm what @gunthercox says, is missing e.relatedTarget in the shown.bs.tab event. Also hidden.bs.tab and hide.bs.tab aren't called.

roccogalluzzo avatar Mar 23 '15 10:03 roccogalluzzo

A quick note: e.relatedTarget is available when the modal is opened with the data-toggle and data-target attributes in the link/button calling the modal. (tested with bootstrap button calling the modal)

Instead is not available when the modal is opened via jquery ($('#myModal').modal("show)

magatz avatar Mar 24 '15 15:03 magatz

Try with $('#myModal').modal('show', $('#myButton')); For me it works.

bigfoot90 avatar Mar 31 '15 15:03 bigfoot90

I can confirm that I'm experiencing an empty (i.e. null) value for event.relatedTarget even when clicking on an HTML button where data-toggle and data-target attributes are set correctly. It looks like the only workaround here is to manually spawn the modal via event handler scripting instead.

jimabramson avatar May 26 '15 20:05 jimabramson

Is everyone still doing a work around for this? I need this ability or an example of a workaround if possible.

aHumanBeing avatar Jul 26 '15 18:07 aHumanBeing

@aHumanBeing here's a working example https://github.com/edx/ecommerce/blob/5a6509c5fa8e43f83ffce46c4561596d19f4fe3e/ecommerce/static/oscar/js/refund_list.js#L45-L61

jimabramson avatar Jul 27 '15 13:07 jimabramson

@jimabramson many thanks. I'm already using hidden fields but I'll take a look at how you're doing this.

aHumanBeing avatar Jul 27 '15 15:07 aHumanBeing

Upgrading from Bootstrap v3.2.0 to Bootstrap v3.3.1 solved the problem for me

ewianda avatar Aug 18 '15 17:08 ewianda

Hi. I've found the way to grab the data-variable from modal.

var options = modal.data().modal.options;

This will return ALL the data-* (so look for key-dup)

marchrius avatar Oct 28 '15 11:10 marchrius

you must be missing the event argument on handler $('#myModal').on('show.bs.modal', function (THIS ONE HERE: event) { ... });

fatmanx avatar Mar 01 '17 07:03 fatmanx

I appreciate this is anold post but I found this colustion worked for me.

$(event.relatedTarget).attr('data-whatever')

jobyid avatar Feb 09 '20 20:02 jobyid

Hi. I've found the way to grab the data-variable from modal.

var options = modal.data().modal.options;

This will return ALL the data-* (so look for key-dup)

This worked for me on older version of bootstrap.

BhaveshSGupta avatar Mar 06 '23 11:03 BhaveshSGupta