Capturing 'shown' event from bootstrap tab - javascript

I have some 'static' HTML on my page:
<div id="DIVISIONS">
<ul class="nav nav-tabs" id="DIVISIONTABS">
#* <li> nodes will be injected here by javascript *#
</ul>
<div class="tab-content" id="DIVISIONTABPANES">
#* <div class="tab-pane"> nodes will be injected here by javascript *#
</div>
</div>
On page load, I create a tab 'framework', i.e. create the bootstrap tabs and tab content containers.
I trigger the process with:
$(window).bind("load", prepareDivisionTabs);
And "prepareDivisionTabs" does this:
function prepareDivisionTabs() {
// Retrieve basic data for creating tabs
$.ajax({
url: "#Url.Action("GetDivisionDataJson", "League")",
cache: false
}).done(function (data) {
var $tabs = $('#DIVISIONTABS').empty();
var $panes = $('#DIVISIONTABPANES').empty();
for (var i = 0; i < data.length; i++) {
var d = data[i];
$tabs.append("<li>" + NMWhtmlEncode(d.Name) + "</li>");
$panes.append("<div id=\"TABPANE" + d.DivisionId + "\" class=\"tab-pane\"></div>")
}
renderDivisionTabPaneContents(data);
}).fail(function (err) {
alert("AJAX error in request: " + JSON.stringify(err, null, 2));
});
}
For info, the "renderDivisionTabPaneContents" in the above does this:
function renderDivisionTabPaneContents(data) {
for (var i = 0; i < data.length; i++) {
var d = data[i];
renderDivisionTabPaneContent(d.DivisionId);
}
}
function renderDivisionTabPaneContent(id) {
var $tabPane = $('#TABPANE' + id);
$tabPane.addClass("loader")
$.ajax({
url: "/League/GetDivisionPartialView?divisionId=" + id,
cache: false
}).done(function (html) {
$tabPane.html(html);
}).fail(function (err) {
alert("AJAX error in request: " + JSON.stringify(err, null, 2));
}).always(function () {
$tabPane.removeClass("loader")
});
}
All good so far. My page loads, my tab contents are rendered, and when I click the different tabs, the relevant content is shown.
Now, rather than loading all content at the start, I want to load tab content just-in-time by using the 'shown' event of the tabs. To test this, I've wanted to just make sure I could get a javascript alert when the tab was shown. So, I create the following to trigger the attachment of tab shown events:
$(function () {
attachTabShownEvents();
})
which calls:
function attachTabShownEvents() {
$(document).on('shown', 'a[data-toggle="tab"]', function (e) {
alert('TAB CHANGED');
})
}
I'd therefore expect so see the "TAB CHANGED" alert after the change of tab. But ... I see no alerts.
Could anybody help me out here?

The correct event binding for tab change is shown.bs.tab.
$(document).on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) {
alert('TAB CHANGED');
})
Update 11-01-2020 --- Bootstrap 4.5
This is still the correct answer however, this is a bit of additional helpful information found all the way at the bottom of the official bootstrap docs page at: https://getbootstrap.com/docs/4.5/components/navs/#tabs
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
e.target // newly activated tab
e.relatedTarget // previous active tab
})
You can determine which tab has been selected each time the code fires with e.target.
If you have unique IDs on your elements then you could do something like the following so code only runs when the appropriate tab is clicked.
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
switch (e.target.id){
case "mainTab":{
doMainTabStuff();
break;
}
case "configTab":{
doConfigTabStuff();
break;
}
}
})

<a data-toggle="tab" href="#some_special_tab_anchor">
<div id="some_special_tab_anchor" class="tab-pane fade">
special tab content
</div>
$( 'a[data-toggle="tab"]' ).on( 'shown.bs.tab', function( evt ) {
var anchor = $( evt.target ).attr( 'href' );
alert("TAB SHOWN = "+anchor);
// take action based on what tab was shown
if(anchor === "some_special_tab_anchor"){
// do my special thing :)
}
});

Use my Nuget package for lazyloading bootstrap tabs here, its very simple,
just add "lazyload" class to the "ul" element of bootstrap tabs, then add "data-url" equal to url to load to the any tabs anchor element (a). thats it.
https://www.nuget.org/packages/MT.BootstrapTabsLazyLoader.js/

'show' and 'shown' events didn't work for me. My solution is not exactly specifically OP's situation, but the general concepts are there.
I had the same issue with bootstrap forcing its own onclick events
on tabs (menu buttons and content panels). I wanted to lazy load stuff into a panel depending on what menu button was clicked, and some buttons show a panel on the current page, others were to load a page into an iframe.
At first, I stuffed data into a hidden form field tag, which was the same issue. The trick is to detect some sort of change and act on that. I solved the problem by forcing a change and using an alternate event listening on the buttons without having to touch bootstrap.
1) stash iframe target in button as data attribute:
$('#btn_for_iframe').attr('data-url',iframeurl);
2) bind alternate event onto fire off thingy,
and inside, swap out the iframe source
$('#btn_for_iframe').on('mouseup',function(){
console.log(this+' was activated');
$('#iframe').attr('src',$('#btn_for_iframe').attr('data-url'));
});
3) force 'change' event on panel shows, then load iframe src
$('#iframe_panel_wrapper').show().trigger('change');
or you can put the change trigger in the mouseup above.

$(document).ready(function(){
$(".nav-tabs a").click(function(){
$(this).tab('show');
});
$('.nav-tabs a').on('shown.bs.tab', function(event){
alert('tab shown');
});
});

Related

Using similar/same Javascript twice

First time posting, I wouldn't call my Javascript knowledge even rudimentary so please forgive my ignorance.
I found a snippet of code here last week (shown in first code example) and I'm trying to get a tweaked version of the same to work as well alongside.
Both are loaded into the footer together as shown below, but the one that is first works, the one that follows doesn't run
I've swapped their order in the footer and confirmed that only the first call functions correctly, I think it's just something conflicting, but I don't know what.
// First function call
$(function() {
var items = $('#v-nav>ul>li').each(function() {
$(this).click(function() {
//remove previous class and add it to clicked tab
items.removeClass('current');
$(this).addClass('current');
//hide all content divs and show current one
$('#v-nav>div.tab-content').hide().eq(items.index($(this))).show();
window.location.hash = $(this).attr('tab');
});
});
if (location.hash) {
showTab(location.hash);
} else {
showTab("tab1");
}
function showTab(tab) {
$("#v-nav ul li:[tab*=" + tab + "]").click();
}
// Bind the event hashchange, using jquery-hashchange-plugin
$(window).hashchange(function() {
showTab(location.hash.replace("#", ""));
})
// Trigger the event hashchange on page load, using jquery-hashchange-plugin
$(window).hashchange();
});
// Second function call
$(function() {
var items = $('#h-nav>ul>li').each(function() {
$(this).click(function() {
//remove previous class and add it to clicked tab
items.removeClass('current');
$(this).addClass('current');
//hide all content divs and show current one
$('#h-nav>div.tab-content').hide().eq(items.index($(this))).show();
window.location.hash = $(this).attr('tab');
});
});
if (location.hash) {
showTab(location.hash);
} else {
showTab("tab1");
}
function showTab(tab) {
$("#h-nav ul li:[tab*=" + tab + "]").click();
}
// Bind the event hashchange, using jquery-hashchange-plugin
$(window).hashchange(function() {
showTab(location.hash.replace("#", ""));
})
// Trigger the event hashchange on page load, using jquery-hashchange-plugin
$(window).hashchange();
});
https://codepen.io/adouglas1880/pen/RzMqBV

jquery Ajax call seems to work only once?

I have a bunch of images that people can click on a heart on top to "love" or "unlove". I figured out how to make it work with Ajax / jQuery but for some reason, it only works once.
The image underneath has a link to it, too ...so I need to do a preventDefault on the heart div overlayed on top. Which again, works once.
The structure is like:
<a href="...">
<div class="image">
<img src="...">
<div class="love-response love"></div>
</div>
</a>
So if they click the heart, it works correctly ...but if they click it again, it then goes to the link of the image underneath. If they however reload the page after clicking the link, they can again click it and it works to unlove it. But then again, if they click again without reloading it goes to the image underneath again.
I think it has to do with the fact that the json data is returned and then updates the content, but on further clicks it somehow doesn't do a preventDefault anymore.
What could be wrong here?
jQuery(document).ready(function($){
$('.love').click(function (e) {
e.preventDefault();
var id = $(this).data("id");
$("#love-response-" + id).hide();
$("#love-waiting-" + id).show();
$.ajax({
url: "https://www.domain.com/love.php?id=" + id,
type: "GET",
dataType: 'json',
success: function(json) {
if(json.valid == 1) {
$("#love-waiting-" + id).hide();
$("#love-response-" + id).replaceWith(json.message);
$("#love-response-" + id).show();
}
else {
$("#love-waiting-" + id).hide();
$("#love-response-" + id).replaceWith(json.message);
$("#love-response-" + id).show();
}
},
error: function (xhr, ajaxOptions, thrownError) {
$("#love-waiting-" + id).hide();
$("#love-response-" + id).html('ERROR');
},
timeout: 15000
});
});
});
The json is pretty basic. It's either:
{
"valid":"0",
"message":"<div class=\"love-response love\" id=\"love-response-782\" data-id=\"782\"><span class=\"icon love-black\"><\/span><\/div>"
}
or
{
"valid":"1",
"message":"<div class=\"love-response love\" id=\"love-response-782\" data-id=\"782\"><span class=\"icon love-red\"><\/span><\/div>"
}
I really feel like the problem is somehow with the preventDefault not executing anymore after it received the response back from json.
delegate the click event handler to sth. thats always in your DOM
as the .love is manipulated dom (loaded via ajax) so it triggers only once because the element was not there when the script was executed
simply change
$('.love').click(function (e) {
to
$(document).on('click','.love',function (e) {
When you do this:
$('.love').click(function (e) {
You're attaching that function to the click event of those matched elements. Not to the selector, but to the elements which are selected at that time. Then when you do this:
$("#love-response-" + id).replaceWith(json.message);
You remove those elements and add new ones. Those new ones don't have click events associated with them. The old ones did.
You can correct this by using event delegation. What this means is binding to the click event of some common parent element and filtering the origin of the click based on some selector. At its simplest, it would look like this:
$(document).on('click', '.love', function (e) {
// your code
});

Hide div when user clicks outside unless closing lightbox

I'm currently using the following code to allow a user to show/hide a div on click.
When clicking anywhere outside of the div, it closes the div.
However, there is a link within the div which can open a lightbox. When a user goes to close that lightbox, it also closes the div that the link was contained. Is there anything I can add into the script to stop that from happening?
$(document).ready(function(){
$("a.dropdown-link").click(function(evt) {
evt.preventDefault();
var $div = $(this).next('.info-container');
$(".info-container").not($div).slideUp();
if ($div.is(":visible")) {
$div.slideUp()
} else {
$div.slideDown();
}
});
$(document).click(function(e){
var p = $(e.target).closest('.dropdown').length
if (!p) {
$(".info-container").slideUp();
}
});
$('.movie-link').magnificPopup({type:'iframe'});
});
<a class="dropdown-link" href="#"><div class="dropdown dropdown-processed">More info</div></a>
<div class="info-container" style="display: none;">Video preview: <a class="movie-link" href="videourl"></a></div>
I'm using Magnific Popup for the lightbox: http://dimsemenov.com/plugins/magnific-popup/
My JavaScript knowledge is pretty basic so any help is appreciated.
In the "click to close div function, you can check if the lightbox is on or not. A simple if ($("#lightbox").css("display") == "none") should be able to do the trick
EDIT: put this line after the $(document).ready line
var state = 0; // default state
$('.movie-link').click(function() { state = 1; }); // state = 1, lightbox on
in the source code, on line 384, insert this code
state = 2; //state = 2, lightbox close button clicked
the idea is not firing the "close div" function when the state is 1 (lightbox is on and clicking random stuffs inside, or outside the lightbox) or 2 (lightbox's close button got clicked), and return state to 0 when it was 2
so instead of the if I provided in the comment use this
if (state == 2) {
state = 0;
} else if (state == 0) {
//rest of the code
}
this is just something I put together and haven't tested yet, so I don't actually know if it works or not so just back up your js files just in case.
EDIT 2:
remove all the changes in edit 1 and use this on instead of the if (state == 2) {
if (e.target != $('.mfp-bg')[0] and e.target != $('.mfp-wrap')[0]) {
EDIT 3
var e_class = $(e.target).attr('class');
if (e_class != 'mfp-close' && e_class != 'mfp-container') {
working example: http://imgcrash.comeze.com/test.html
I'm not 100% without actually testing this out but you may be running into issues with $(document).click(...); since clicking anywhere on the document would trigger this event.
When you close the popup you're probably triggering this event and sliding up the info-container div.
It seems that you're looking for clicks on the divs with the class .dropdown. Why not use something like:
$('.dropdown').click(function(e) { ... });
Try this:
$("a.dropdown-link").click(function(evt) {
evt.preventDefault();
evt.stopPropagation(); //We stop the propagation of the event
//Changed it to slideToggle and added stop to prevent weird animation
//on multiple clicks
$(this).next('.info-container').stop().slideToggle()
});
$(document).click(function(e){
//Check if it has the class info-container
if (!$(e.target).hasClass("info-container")) {
$(".info-container").slideUp();
}
});
$('.movie-link').magnificPopup({type:'iframe'});
Fiddle

bpopup multiple

I'm using a lightweight jQuery popup plugin called 'bPopup'. I'm using it on my website at the moment to load multiple popup windows when clicked. I was recently told that my code was inefficient as I was loading multiple popups with multiple JavaScript 'listeners', i.e.:
<script type="text/javascript">
;(function($) {
$(function() {
$('#my-button_1').bind('click', function(e) {
e.preventDefault();
$('#element_to_pop_up_32754925023').bPopup();
});
});
})(jQuery);
</script>
<script type="text/javascript">
;(function($) {
$(function() {
$('#my-button_2').bind('click', function(e) {
e.preventDefault();
$('#element_to_pop_up_95031153149').bPopup();
});
});
})(jQuery);
^^ The multiple JavaScript 'listeners'. And, for the Popups:
<!-- Button that triggers the popup -->
<a class="main" id="my-button_1" href="#">Popup 1</a></b><br />
<!-- Element to pop up -->
<div id="element_to_pop_up_1">
// ...
</div>
<!-- Button that triggers the popup -->
<a class="main" id="my-button_1" href="#">Popup 1</a></b><br />
<!-- Element to pop up -->
<div id="element_to_pop_up_1">
// ...
</div>
He's probably right (sure of it), but not sure how to implement this, or whether this is even possible (small chance he's wrong).
Help? And thanks!
Since you are using jquery, you should use it's on() method to attach a single listener to the parent DOM element, and use the selector parameter to properly delegate the event to it's children (the button/popups).
If this sounds confusing, a simple example might help:
HTML:
<div id="parent">
Show popup 1
<div id="popup1" class="popup">1</div>
Show popup 2
<div id="popup2" class="popup">2</div>
Show popup 3
<div id="popup3" class="popup">3</div>
Non-popup link
</div>
JS:
$('#parent').on('click', 'a.button', function (event) {
event.stopPropagation();
event.preventDefault();
var popup = $(this).attr('href');
$('#'+popup).bPopup();
});
This adds a single event listener on the parent element, which only gets triggered if the child element which triggered the event matches the selector (in this case a.button). It determines which popup to show by retreiving the popup's id from the href attribute.
You can see this example working here.
The below function ( myFunction() ) takes the Id of anchor/div tag which is clicked and another id of div content to be display. And applies the same style for all popup models. And also it hides the old popup which already opened when u open new popup. All popup properties you can change.
Here i used only for two popups but you can use it for many as same did here.
<script type="text/javascript">
function myFunction(whId,whtDivContent,e) {
//var totWidth = $(document).width();
//var marTop = position.top;
var elt = $(whId);
var position = elt.position();
var marLeft = position.left - 130;
if(marLeft <= 1) {
marLeft = 10;
}
var openModal_profile ='#openModal_profile';
var openModal_menu ='#openModal_menu';
// Prevents the default action to be triggered.
e.preventDefault();
$(whtDivContent).bPopup({
position: [marLeft, 0] //x, y
,opacity: 0.9
,closeClass : 'b-close'
,zIndex: 2
,positionStyle: 'fixed' //'fixed' or 'absolute' 'relative'
,follow: [false,false] //x, y
,onOpen: function() {
if(openModal_profile == whtDivContent) {
$(openModal_menu).bPopup().close();
}
else if(openModal_menu == whtDivContent) {
$(openModal_profile).bPopup().close();
}
$(whId).css({'background-color':"#DFDFDF"});
}
,onClose: function() { $('.close').click(); $(whId).css({'background-color':""}); }
});
}
;(function($) {
// DOM Ready
$(function() {
// From jQuery v.1.7.0 use .on() instead of .bind()
//$(id_menu).on('click',function(e) {}
var id_menu = '#id_menu';
var openModal_menu ='#openModal_menu';
$(id_menu).toggle(function(e) {
//$(id_menu).css({'background-color':"#DFDFDF"});
myFunction(id_menu,openModal_menu,e);
},function(e){
//$(id_menu).css({'background-color':""});
$('.close').click();
$(openModal_menu).bPopup().close();
});
var id_profile = '#id_profile';
var openModal_profile ='#openModal_profile';
$(id_profile).toggle(function(e) {
//$(id_profile).css({'background-color':"#DFDFDF"});
myFunction(id_profile,openModal_profile,e);
},function(e){
//$(id_profile).css({'background-color':""});
$(openModal_profile).bPopup().close();
});
//ENDS HERE
});
})(jQuery);
</script>

Does dojo TabContainer have an event thats triggered when changing tabs?

does DOJO TabContainer have an event thats triggered when changing tabs?
I imagine it would but I couldn't find anything about it in the documentation. :(
SOLVED: It looks like I found a solution here:
Dijit TabContainer Events - onFocus
not the most searchable topic title :/
Connect aspect.after to TabContainer's selectChild method:
var tabContainer1 = registry.byId("tabContainer1");
aspect.after(tabContainer1, "selectChild", function() {
console.log("tab changed");
});
Or if you are interested in a particular tab, connect to its ContentPane's _onShow:
var contentPane1 = registry.byId("contentPane1");
aspect.after(contentPane1, "_onShow", function() {
console.log("[first] tab selected");
});
See it in action at jsFiddle: http://jsfiddle.net/phusick/Mdh4w/
From the docs;
var tabs = registry.byId('someTabs');
tabs.watch("selectedChildWidget", function(name, oval, nval){
console.log("selected child changed from ", oval, " to ", nval);
});
In addition to #phusick's answer, which is correct, all StackContainers, including the TabContainer publish on topics that you can subscribe to.
http://dojotoolkit.org/reference-guide/1.7/dijit/layout/StackContainer.html#published-topics
[widgetId]-addChild,
[widgetId]-removeChild
[widgetId]-selectChild
http://dojotoolkit.org/reference-guide/1.7/dojo/subscribe.html#dojo-subscribe
Here's a complete code sample that works in Dojo 1.8, I've tested it. It's not an event that fires just on changing tabs, I couldn't get any of their events in the API to fire, but at least it works on the Click event.
require(["dijit/registry", "dojo/on", "dojo/ready", "dojo/domReady!"], function (registry, on, ready) {
ready(function () { //wait till dom is parsed into dijits
var panel = registry.byId('mainTab'); //get dijit from its source dom element
on(panel, "Click", function (event) { //for some reason onClick event doesn't work
$('.hidden_field_id').val(panel.selectedChildWidget.id); //on click, save the selected child to a hidden field somewhere. this $ is jquery, just change it to 'dojo.query()'
});
});
});
//include this function if you want to reselect the tab on page load after a postback
require(["dijit/registry", "dojo/ready", "dojo/domReady!"], function (registry, ready) {
ready(function () {
var tabId = $('.hidden_field_id').val();
if (tabId == null || tabId == "")
return;
var panel = registry.byId('mainTab');
var tab = registry.byId(tabId);
panel.selectChild(tab);
});
});

Categories