Bootstrap accordion jumps when expanding - javascript

I am using a snippet to load external divs to Bootstrap accordion items.
Initially, the divs were loaded on page load, but then I modified the code so that they would load on button click.
I have used two different snippets to make this happen, and they both result in a rather jumpy transition of the accordion items when expanding. However, this didn't happen when the divs where loaded on page load.
I have searched for a solution to this problem, I have tried many things that worked for other people (such as zero margin and padding or enclosing both the button and the panel-body inside a div), but none of them worked in this case.
If you have any suggestions, please tell me.
Thank you in advance
HTML
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title">
<a data-href="/path/to.html #firstdiv" class="ajax-link" data-ajaxtarget="#first" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Read more
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingOne">
<div class="panel-body" id="first"></div>
</div>
</div>
</div>
JS/JQuery
(function($){
$(document).ready(function(){
ajaxLink();
linkPreventDefault();
});
function ajaxLink(){
$("a.ajax-link").on('click', function(event){
var thisLink = $(this);
var targetUrl = thisLink.attr("data-href");
var target = thisLink.data('ajaxtarget');
console.log("loading via ajax: "+ targetUrl + ",in div: "+ target);
$(target).load( targetUrl, function( response, status, xhr ) {
if ( status == "error" ) {
var msg = "Sorry but there was an error: ";
console.error (msg + xhr.status + " " + xhr.statusText );
}else if(status="success"){
console.log("successfully loaded");
//OPTIONAL: unbind the links click event and bind a new one
noMoreAjax(thisLink);
}
});
event.preventDefault();
});
}
function linkPreventDefault(){
$('a.link-prevent').click(function(event){
console.log
event.preventDefault();
});
}
function noMoreAjax(item){
var linkItem = $(item);
console.log("No more Ajax for this link");
linkItem.removeClass('ajax-link');
linkItem.addClass('link-prevent');
linkItem.unbind('click');
$(linkItem).click(function(event){
console.log("Preventing after ajax");
event.preventDefault();
});
}
})(jQuery);
(OLD JS:
$(document).ready(function(){
$('#accordion').click(function(){
$('#first').load('/path/to.html #firstdiv');
});
})
)

You should expand accordion item once received response for the ajax call. So remove data-toggle="collapse" href="#collapseOne" attributes from your link and call $('#collapseOne').collapse() in callback function, like this:
$(target).load( targetUrl, function( response, status, xhr ) {
if ( status == "error" ) {
var msg = "Sorry but there was an error: ";
console.error (msg + xhr.status + " " + xhr.statusText );
}else if(status="success"){
$('#collapseOne').collapse();
console.log("successfully loaded");
//OPTIONAL: unbind the links click event and bind a new one
noMoreAjax(thisLink);
}
});

See a working sample here
I've used setTimeout() to fake ajax call, so you'll need to update that part, but other than that, everything is there.

Related

How to make text popup in a side panel when hover over an image (using modal code)?

I currently have a modal that shows information on various images on my page. When I click on an image, lots of information pops up. However, for quick access, I would also like to have an information box on the side quickly show when hovered over. I don't know how to make this work because of how I structured by data.
//this displays the information in the modal
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="myModalLabel">IMAGE</h4>
</div>
<div class="modal-name">
</div>
<div class="modal-rarity">
</div>
<div class="modal-effect">
</div>
<div class="modal-description">
</div>
<div class="modal-stack">
</div>
<div class="modal-tags">
</div>
<div class="modal-body">
</div>
</div>
</div>
</div>
//script for getting the information from the modal
<script>
$('#myModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var Title = button.data('title');
var Rarity = button.data('rarity');
var Effect = button.data('effect');
var Description = button.data('description');
var Stack = button.data('stack');
var Tags = button.data('tags');
var image = button.data('image');
var modal = $(this);
modal.find('.modal-title').text(Title);
modal.find('.modal-rarity').text(Rarity);
modal.find('.modal-effect').text(Effect);
modal.find('.modal-description').text(Description);
modal.find('.modal-stack').text(Stack);
modal.find('.modal-tags').text(Tags);
modal.find('.modal-body').html('<img src="' + image + '" alt="' + Title + '" class="center-block">');
})
</script>
// this is an example item that i would be clicking on (modal works) however, i don't know how to trigger this information to show up in a side panel when hovered over.
<div id="items">
<!--Tier 1 Offence-->
<li id="item">
<!--Barbed Wire-->
<a href="#" data-toggle="modal" data-target="#myModal"
data-title="Barbed Wire"
data-rarity="Common"
data-description="In Range: Mobs take 50% DPS. Barbed wire will inflict on only one mob at a time."
data-stack="On Stack: +20% radius and +10% DPS."
data-tags="barbed, wire, barbed wire"
data-image="images/items/Barbed Wire.png">
<img src="images/items/Barbed Wire.png" alt="Barbed Wire" class="img-thumbnail">
</a>
</li>
</div>
I suggest creating a JavaScript function that grabs the desired values and concatenates them into one big string which you can output into your sidebar. For each item <a> tag, add an onmouseover= tag pointing to that function, and an onmouseleave= tag pointing to a function or expression to hide the sidebar away again.
To get the value from each of the data tags, concatenate them with some labels and line breaks, and display that in an element with id="sidebar" that is previously set to display: none or display: hidden (in the CSS), you'd set this function in the onmouseover= tag:
function displayTags() { //there are many other ways to structure this, and this is probably not the most efficient way
var tags = [];
tags.push(this.getAttribute("data-title"));
tags.push(this.getAttribute("data-rarity"));
tags.push(this.getAttribute("data-effect"));
tags.push(this.getAttribute("data-description"));
tags.push(this.getAttribute("data-stack"));
tags.push(this.getAttribute("data-tags"));
var sidebar = document.getElementById("sidebar"); //or whatever you've named your sidebar
var out = "Title: " + tags[0] + "<br />Rarity: " + tags[1] + "<br />Effect: " + tags[2]; //and so on
sidebar.innerHTML = out;
sidebar.style.display = "visible";
}
If the function is triggered by an event, the this keyword should refer to the element that fired the event.
Then to hide the sidebar away again, you'd put a function like this
function hideSideBar() {
document.getElementById("sidebar").style.display = "none"; //or "hidden", depending on how you want it's presence to affect the layout of the entire page
}
Into the onmouseleave= on each item <a> tag as well.
What I've written may need a bit (a lot) of tweaking to work with what you have, but I think I've made the general idea here clear enough.

Call a Action method from a $scope function()

I have a multi-level repeating accordion. On page load , the data for the first accordion will be bind. When I click on each of the level-1 accordion, the unique ID should be passed to a Action method and the data for the next-level accordion should bind.
I am not able to pass this ID and call the Action method from angular js controller. Can anyone suggest what I am doing wrong here.
CSHTML
<div id="itineraryAccordion" ng-repeat="itinerary in item.Itineraries">
<div>
<a data-toggle="collapse" data-parent="#itineraryAccordion" href="#collapseItinerary" aria-expanded="false" class="collapsed" ng-bind="itinerary.Name" data-ng-click="GetSchedule(itinerary.Name)"></a>
</div>
<div id="collapseItinerary" class="panel-collapse collapse" aria-expanded="false">
<div class="box-body">
<div class="box-group" id="scheduleAccordion" ng-repeat="schedule in scheList">
<div>
<a data-toggle="collapse" data-parent="#scheduleAccordion" href="#collapseSchedule" aria-expanded="false" class="collapsed" ng-bind="schedule.ScheduleName" > </a>
</div>
SCRIPT
myApp.controller('layoutController', function ($scope, $http) {
$scope.GetSchedule = function (itineraryName, $scope, $http) {
alert("Itinerary Id is " + itineraryName);
$http.get("../SchedulePlan/GetSchedule?itineraryName=" + itineraryName).success(function (response) {
$scope.scheList = response;
})
};
Here , I am getting the itineraryName in the Alert(), but in the next line, $http.get() is not working.
try this. you don't need to put $scope and $http service in function.
myApp.controller('layoutController', function ($scope, $http) {
$scope.GetSchedule = function (itineraryName) {
alert("Itinerary Id is " + itineraryName);
$http.get("../SchedulePlan/GetSchedule?itineraryName=" + itineraryName).success(function (response) {
$scope.scheList = response;
})
};

Disable collapsing animation in bootstrap on "close" click

I have made some collapsing panels with bootstrap. Then added a method to "close" and remove the panels using angular. Only problem is that when I close the panel, the collapsing, open/close functionality still works. So the panel might open and then disappear, when it really should just disappear.
Here is a simplified version of the HTML
<div class="panel-group">
<div class="panel panel-info fadein fadeout" ng-repeat="p in panels">
<div class="panel-heading" data-toggle="collapse" data-target="#test_{{p}}" style="cursor:pointer">
<h4 class="panel-title">
open / close - {{p}}
<span class="glyphicon glyphicon-remove pull-right" ng-click="close(p)"></span>
</h4>
</div>
<div id="test_{{p}}" class="panel-collapse collapse" aria-expanded="false">
<div class="panel-body">
hello {{p}}
</div>
</div>
</div>
</div>
and angularJS
angular.module('app', ['ngAnimate'])
.controller('controller', function($scope){
var i = 4;
$scope.panels = ['panel_1', 'panel_2', 'panel_3']
$scope.add = function(){
console.log('add');
$scope.panels.push('panel_' + i++);
}
$scope.close = function (p) {
console.log('close ' + p);
for (var i = 0; i < $scope.panels.length; i++){
if ($scope.panels[i] == p){
$scope.panels.splice(i, 1);
}
}
}
});
Made a fiddle to illustrate:
https://jsfiddle.net/fiddlejan/82bmcyt0/
When you click on close, the cross on the right. The panel will "open", then disappear (the fadeOut animation does not seem to work here). I would like the close button to just remove the panel when you click close. Not "open" or "close" the panel.
So in case of clicking the close button, the panels collapsing, opening/close function should be disabled.
Add the event object to your ng-click definition:
<span class="glyphicon glyphicon-remove pull-right" ng-click="close(p, $event)"></span>
And use that object in your event handler:
$scope.close = function (p, e) {
console.log('close ' + p);
for (var i = 0; i < $scope.panels.length; i++){
if ($scope.panels[i] == p){
$scope.panels.splice(i, 1);
}
}
e.stopPropagation();
}

Show Bootstrap tab loaded using Ajax doesn't work the first time it's clicked

I'm trying to load the content of a given tab using Ajax.
The first tab I render on the server as being active, and the others I active and load using Ajax.
I've isolated the problem (without Ajax) here.
HTML:
<h1>Hello, tabs!</h1>
<div>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active">Home
</li>
<li role="presentation">Profile
</li>
<li role="presentation">Messages
</li>
<li role="presentation">Settings
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="home">...</div>
</div>
</div>
Javascript:
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
e.preventDefault();
var ref = $(this).attr('href').replace('#', '');
var html = '<div role="tabpanel" class="tab-pane" id="' + ref + '">Brace yourself SW7 is coming. ' + ref + '</div>';
var tabContent = $('.tab-content');
if (!tabContent.find('.tab-pane#' + ref).length) {
tabContent.append(html);
}
tabContent.find('.tab-pane#' + ref).tab('show');
});
The Home tab is rendered already activated.
Click in the Profile tab for example, it will append the content but won't activate the tab. Now, if you click in the Home tab and in the Profile tab again, it'll work correctly.
I'm doing something wrong or this is an 'expected' behavior?
The problem is that the content DIVs don't exist the first time you click on a tab, so when the Bootstrap code executes it doesn't find the DIV it needs to activate. The second time you click, the DIV exists, so Bootstrap finds it. You can get around this by hiding all of the tab content DIVs at the end of the script, and then showing the one that was specified.
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
e.preventDefault();
var ref = $(this).attr('href').replace('#', '');
var tabContent = $('.tab-content');
if (!$('#' + ref).length) {
// Execute the AJAX here to get the tab content
var html = '<div role="tabpanel" class="tab-pane" id="' + ref + '">Brace yourself SW7 is coming. ' + ref + '</div>';
tabContent.append(html);
}
tabContent.find('.tab-pane').hide();
tabContent.find('#' + ref).show();
});
I believe this could be further optimized, but I'm out of time right now and this works...
What was happening was as you said, the following:
The ajax request was created, and the click handler for the tab was also fired.
The handler was executed while there's no target content to be shown but it added the 'active' class to the 'li' element.
When the ajax request was completed I executed the "tab('show')" code but as the 'li' tab was already activated the event was not handled properly.
The said line is this:
https://github.com/twbs/bootstrap/blob/master/dist/js/bootstrap.js#L2081
Adding removeClass before the execution of the tab show worked:
el.parent().removeClass('active');
el.tab('show');
Where el is the 'a' element.
I solved just
$('#mydivid').addClass('show');

Bootstrap 3 expand accordion from URL

Using Bootstrap 3, I'm trying to use sub-navigation anchor links (ie, index.php#wnsh) to expand a specified accordion and anchor down the page to the content. I've tried searching for examples but with little luck, likely because my accordion structure is different from the given BS3 example. Here's my HTML:
UPDATE:
Made a few updates to the code, but it still isn't opening the accordion specified by the hash. Any further thoughts?
<div id="accordion" class="accordion-group">
<div class="panel">
<h4 id="cs" class="accordion-title"><a data-toggle="collapse" data-parent="#accordion" data-target="#cs_c">Child Survival: Boosting Immunity and Managing Diarrhoea</a></h4>
<div id="cs_c" class="accordion-collapse collapse in">
<p>...</p>
</div>
<h4 id="chgd" class="accordion-title"><a data-toggle="collapse" data-parent="#accordion" data-target="#chgd_c">Child Health, Growth and Development: Preventing Mental Impairment with Iodine and Iron</a></h4>
<div id="chgd_c" class="accordion-collapse collapse">
<p>...</p>
</div>
<h4 id="wmnh" class="accordion-title"><a data-toggle="collapse" data-parent="#accordion" data-target="#wmnh_c">Women’s and Newborn Survival and Health: Iron Supplementation and Food Fortification</a></h4>
<div id="wmnh_c" class="accordion-collapse collapse">
<p>...</p>
</div>
</div>
</div>
JS
var elementIdToScroll = window.location.hash;
if(window.location.hash != ''){
$("#accordion .in").removeClass("in");
$(elementIdToScroll).addClass("in");
$('html,body').animate({scrollTop: $(elementIdToScroll).offset().top},'slow');
}
Thanks in advance. Any help would be appreciated.
Tested and working in Bootstrap 3.3.5.
<script type="text/javascript">
$(document).ready(function () {
if(location.hash != null && location.hash != ""){
$('.collapse').removeClass('in');
$(location.hash + '.collapse').collapse('show');
}
});
</script>
I encountered the same problem just few minutes ago. The solution appears to be straight forward - you need to parse an URL and add class in to the matchable accordion, using its id:
// Opening accordion based on URL
var url = document.location.toString();
if ( url.match('#') ) {
$('#'+url.split('#')[1]).addClass('in');
}
Tested and working in Bootstrap 3.1.1.
With Bootstrap 4.4, just adding this single line to my JS code (inside a document ready clause) seems to do the trick:
if(location.hash != null && location.hash != ""){$(location.hash + '.collapse').collapse('show');}
I'm using this in Yii2 with the Collapse widget.
Assign an id to your panels.
If you have plain html you can just add an id to your a-tag and update the selector.
$(function(){
var hash = document.location.hash;
if (hash) {
$(hash).find('a').click();
}
});
Just a response, to Ilia R's. His solution worked great! The only thing, though, was that the panel title style wasn't updating (the .collapsed class needed to be removed from the panel title link), so I tweaked Ilia R's code a tad. Someone probably has a better / cleaner solution, but here's a start.
$(document).ready(function() {
var url = document.location.toString();
if ( url.match('#') ) {
$('#'+url.split('#')[1]).addClass('in');
var cPanelBody = $('#'+url.split('#')[1]);
var cPanelHeading = cPanelBody.prev();
cPanelHeading.find( ".panel-title a" ).removeClass('collapsed');
}
});
For me worked:
$(document).ready(function() {
var url = document.location.toString();
if ( url.match('#') ) {
var cPanelBody = $('#'+url.split('#')[1]);
cPanelBody.find(".panel-title a")[0].click();
}
});

Categories

Resources