bootstrap-modal
bootstrap-modal copied to clipboard
event.relatedTarget undefined
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
});
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
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 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 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.
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.
Does this still not work properly (without hacks)?
I have the same problem with
<a class="btn" data-toggle="modal" data-target="#testmodal" >test</a>
this is pretty much a show stopper for me. Any ideas?
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.
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.
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.
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)
Try with $('#myModal').modal('show', $('#myButton'));
For me it works.
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.
Is everyone still doing a work around for this? I need this ability or an example of a workaround if possible.
@aHumanBeing here's a working example https://github.com/edx/ecommerce/blob/5a6509c5fa8e43f83ffce46c4561596d19f4fe3e/ecommerce/static/oscar/js/refund_list.js#L45-L61
@jimabramson many thanks. I'm already using hidden fields but I'll take a look at how you're doing this.
Upgrading from Bootstrap v3.2.0 to Bootstrap v3.3.1 solved the problem for me
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)
you must be missing the event argument on handler
$('#myModal').on('show.bs.modal', function (THIS ONE HERE: event) { ... });
I appreciate this is anold post but I found this colustion worked for me.
$(event.relatedTarget).attr('data-whatever')
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.