I'm trying to create an accordion able to expand multiple panels at once. I have tried to find it in the jQuery UI API, but I haven't yet found the proper way.
Please let me know if there is a way of doing this using jQuery UI accordion.
As others have noted, the Accordion widget does not have an API option to do this directly. However, if you must use the widget, it is possible to achieve this by using the beforeActivate event handler option to subvert and emulate the default behavior of the widget.
For example:
$('#accordion').accordion({
collapsible:true,
beforeActivate: function(event, ui) {
// The accordion believes a panel is being opened
if (ui.newHeader[0]) {
var currHeader = ui.newHeader;
var currContent = currHeader.next('.ui-accordion-content');
// The accordion believes a panel is being closed
} else {
var currHeader = ui.oldHeader;
var currContent = currHeader.next('.ui-accordion-content');
}
// Since we've changed the default behavior, this detects the actual status
var isPanelSelected = currHeader.attr('aria-selected') == 'true';
// Toggle the panel's header
currHeader.toggleClass('ui-corner-all',isPanelSelected).toggleClass('accordion-header-active ui-state-active ui-corner-top',!isPanelSelected).attr('aria-selected',((!isPanelSelected).toString()));
// Toggle the panel's icon
currHeader.children('.ui-icon').toggleClass('ui-icon-triangle-1-e',isPanelSelected).toggleClass('ui-icon-triangle-1-s',!isPanelSelected);
// Toggle the panel's content
currContent.toggleClass('accordion-content-active',!isPanelSelected)
if (isPanelSelected) { currContent.slideUp(); } else { currContent.slideDown(); }
return false; // Cancel the default action
}
});
See a jsFiddle demo
You could write multiple accordions that are stacked and each accordion have only one panel. This way the panels could be individually toggled.
An accordion is, by definition, a set of expanding elements that toggle in a certain way. You don't want that. You just want a set of expanding elements. It's extremely easy to build that with jQuery. It often needs nothing more than this:
$('.my-heading-class').on('click', function() {
$(this).next('.my-content-class').slideToggle();
});
<div class="my-heading-class">My Heading</div>
<div class="my-content-class">My Content</div>
Related
I am trying to get on click function to close an accordion item and remove classes from all other elements except the one I am interacting with
I have tried various if statements with different parameters.
// Container For Each Accordion Item
var $expandeditemcontainer = $('.expand-wrapper');
// Accordion/Card Head - Summary Box - Once Clicked Expands .expanded-box
var $expandingsummarybox = $('.expander');
// Accordion/Card Body - Expanded Box That Appears Underneath - Absolutely Positioned - Out Of Document Flow
var $expandedbox = $('.expanded-box');
// Ghost Div replicating Accordion/Card Body's Height - Relatively Positioned - In Document Flow
var $expandedboxspacer = $('.expand-box-height-clearance');
// When $expandingsummarybox is clicked
$($expandingsummarybox).click(function() {
/** .expand-focus creates outline on $expandingsummarybox once accordion is opened **/
// If any $expandingsummarybox has a class .expander-focus applied
if ($($expandingsummarybox).hasClass('expander-focus')) {
// Remove .expander-focus from all $expandingsummarybox's
$($expandingsummarybox).removeClass('expander-focus');
// Close all Accordion Item's
$($expandingsummarybox).closest($expandeditemcontainer).find($expandedboxspacer).slideUp(200, 'swing');
// If any $expandingsummarybox does NOT have .expander-focus applied
} else if (!$($expandingsummarybox).hasClass('expander-focus')) {
// Add .expander-focus to this specific $expandingsummarybox
$(this).addClass('expander-focus');
// Open this specific Accordion Item
$(this).closest($expandeditemcontainer).find($expandedboxspacer).slideDown(200, 'swing');
} else {
// These were the parameters I was originally using affecting that specific item
$(this).toggleClass('expander-focus');
$(this).closest($expandeditemcontainer).find($expandedboxspacer).slideToggle(200, 'swing');
}
});
Works when interacting with the initial clicked item but when trying to close via a different method or clicking on another accordion item it breaks.
I can see some logic errors in your click handler but without some more detail on what/how it is breaking this might not be a complete solution.
Your problem is the control flow in if/else-if block. The first time an .expander is clicked it will open properly. However, if you click a different .expander it will hit the if statement block and close all .expander-box but never actually opens the .expander that was clicked.
To solve this you should first determine if the .expander that was clicked is open or closed. Then you can close all the .expander boxes. Finally if the .expander that was clicked was originally closed you can open it. Otherwise if the expander was opened then closing all of the .expander boxes closed it for you.
const $expandeditemcontainer = $('.expand-wrapper');
const $expandingsummarybox = $('.expander');
const $expandedbox = $('.expanded-box');
const $expandedboxspacer = $('.expand-box-height-clearance');
$expandingsummarybox.click(function() {
const isCurrentExpanded = $(this).hasClass("expander-focus");
// Remove .expander-focus from all $expandingsummarybox's
$expandingsummarybox.removeClass('expander-focus');
// Close all Accordion Item's
$expandingsummarybox.closest($expandeditemcontainer).find($expandedboxspacer).slideUp(200, 'swing');
if (!isCurrentExpanded) {
// The clicked expander was closed originally lets open it
$(this).addClass('expander-focus');
$(this).closest($expandeditemcontainer).find($expandedboxspacer).slideDown(200, 'swing');
}
});
I have an iron-list in which I have a settings icon which, when clicked causes a panel to slide out with settings options in. However when I have one open, I am wanting it to close upon opening the panel for another row. Currently I have it where they can all be open at the same time which is not optimal.
Please see the gif for the problem I am facing.
GIF OF THE PROBLEM
HTML/Polymer
<div class="container horizontal layout">
<div class="settingsIconContainer">
<paper-icon-button class="settingIcon" icon="settings" on-click="toggleSettings"></paper-icon-button>
</div>
<div id="edit" class="settings">
<paper-icon-button icon="delete"></paper-icon-button>
<paper-icon-button icon="create"></paper-icon-button>
<paper-icon-button icon="clear" on-click="toggleSettings"></paper-icon-button>
</div>
</div>
Polymer JS
toggleSettings : function(e) {
this.$.edit.classList.toggle('settingsMove');
},
You should not access the parent element from the child element. There are two ways of doing this.
1) In the toggle class, fire an event as below
toggleSettings : function(e) {
this.fire('settings-icon-toggle');
}
In the parent element add a listener and listen to the fired event.
listeners:{
'settings-icon-toggle': '_onSettingsIconToggle'
},
_onSettingsIconToggle: function(e){
//Using e.target.id, loop through all the settings and close them except the current one.
}
2) Add a boolean property in the object that you're passing to the iron-list, pass it to the settins component and set the property to true in the tolggleSetings method.
toggleSettings : function(e) {
this._isOpen = false;
}
In the Parent component, add an observer to this property and set all the rest of them to false.
observers:['_listChanged(arrayToIronList.*)'],
_listChanged:function(){
var isOpenSubPath = e.path.indexOf('._isOpen')
if( isOpenSubPath >=0){
var index = parseInt(e.path.match(/\d+/g)[0]);
//loop through the array and set all the _isOpen properties to false except the current one.
//You can find the current one using the index.
}
}
Did i misunderstood your question or is this question that simple?
You are trying to have only 1 opened settings at a time, right? so when user presses one settings, all others needs to be closed.
Just find all elements with settingsMove class and then remove that class.
toggleSettings : function(e) {
var elems = Polymer.dom(this.root).querySelectorAll(".settingsMove");
for(var i = 0; i < elems.length; i++){
this.toggleClass("settingsMove", false, elems[i]);
}
this.toggleClass("settingsMove", true, e.target.parentNode.parentNode.querySelector(".settings"))
}
i don't know what element you need to set class settingsMove on. So edit e.target.parentNode.parentNode.querySelector(".settings")) to suit your code
I used Polymer native function toggleClass. More info you can find here https://www.polymer-project.org/1.0/docs/api/Polymer.Base#method-toggleClass
I have an element $('#anElement') with a potential popover attached, like
<div id="anElement" data-original-title="my title" data-trigger="manual" data-content="my content" rel="popover"></div>
I just would like to know how to check whether the popover is visible or not: how this can be accomplished with jQuery?
If this functionality is not built into the framework you are using (it's no longer twitter bootstrap, just bootstrap), then you'll have to inspect the HTML that is generated/modified to create this feature of bootstrap.
Take a look at the popupver documentation. There is a button there that you can use to see it in action. This is a great place to inspect the HTML elements that are at work behind the scene.
Crack open your chrome developers tools or firebug (of firefox) and take a look at what it happening. It looks like there is simply a <div> being inserted after the button -
<div class="popover fade right in" style="... />
All you would have to do is check for the existence of that element. Depending on how your markup is written, you could use something like this -
if ($("#popoverTrigger").next('div.popover:visible').length){
// popover is visible
}
#popoverTrigger is the element that triggered that popover to appear in the first place and as we noticed above, bootstrap simply appends the popover div after the element.
There is no method implemented explicitly in the boostrap popover plugin so you need to find a way around that. Here's a hack that will return true or false wheter the plugin is visible or not.
var isVisible = $('#anElement').data('bs.popover').tip().hasClass('in');
console.log(isVisible); // true or false
It accesses the data stored by the popover plugin which is in fact a Popover object, calls the object's tip() method which is responsible for fetching the tip element, and then checks if the element returned has the class in, which is indicative that the popover attached to that element is visible.
You should also check if there is a popover attached to make sure you can call the tip() method:
if ($('#anElement').data('bs.popover') instanceof Popover) {
// do your popover visibility check here
}
In the current version of Bootstrap, you can check whether your element has aria-describedby set. The value of the attribute is the id of the actual popover.
So for instance, if you want to change the content of the visible popover, you can do:
var popoverId = $('#myElement').attr('aria-describedby');
$('#myElement').next(popoverid, '.popover-content').html('my new content');
This checks if the given div is visible.
if ($('#div:visible').length > 0)
or
if ($('#div').is(':visible'))
Perhaps the most reliable option would be listening to shown/hidden events, as demonstrated below. This would eliminate the necessity of digging deep into the DOM that could be error prone.
var isMyPopoverVisible = false;//assuming popovers are hidden by default
$("#myPopoverElement").on('shown.bs.popover',function(){
isMyPopoverVisible = true;
});
$("#myPopoverElement").on('hidden.bs.popover',function(){
isMyPopoverVisible = false;
});
These events seem to be triggered even if you hide/show/toggle the popover programmatically, without user interaction.
P. S. tested with BS3.
Here is simple jQuery plugin to manage this. I've added few commented options to present different approaches of accessing objects and left uncommented that of my favor.
For current Bootstrap 4.0.0 you can take bundle with Popover.js: https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js
// jQuery plugins
(function($)
{
// Fired immiedately
$.fn.isPopover = function (options)
{
// Is popover?
// jQuery
//var result = $(this).hasAttr("data-toggle");
// Popover API
var result = !!$(this).data('bs.popover');
if (!options) return result;
var $tip = this.popoverTip();
if (result) switch (options)
{
case 'shown' :
result = $tip.is(':visible');
break;
default:
result = false;
}
return result;
};
$.fn.popoverTip = function ()
{
// jQuery
var tipId = '#' + this.attr('aria-describedby');
return $(tipId);
// Popover API by id
//var tipId = this.data('bs.popover').tip.id;
//return $(tipId);
// Popover API by object
//var tip = this.data('bs.popover').tip; // DOM element
//return $(tip);
};
// Load indicator
$.fn.loadIndicator = function (action)
{
var indicatorClass = 'loading';
// Take parent if no container has been defined
var $container = this.closest('.loading-container') || this.parent();
switch (action)
{
case 'show' :
$container.append($('<div>').addClass(indicatorClass));
break;
case 'hide' :
$container.find('.' + indicatorClass).remove();
break;
}
};
})(jQuery);
// Usage
// Assuming 'this' points to popover object (e.g. an anchor or a button)
// Check if popover tip is visible
var isVisible = $(this).isPopover('shown');
// Hide all popovers except this
if (!isVisible) $('[data-toggle="popover"]').not(this).popover('hide');
// Show load indicator inside tip on 'shown' event while loading an iframe content
$(this).on('shown.bs.popover', function ()
{
$(this).popoverTip().find('iframe').loadIndicator('show');
});
Here a way to check the state with Vanilla JS.
document.getElementById("popover-dashboard").nextElementSibling.classList.contains('popover');
This works with BS4:
$(document).on('show.bs.tooltip','#anElement', function() {
$('#anElement').data('isvisible', true);
});
$(document).on('hidden.bs.tooltip','#anElement', function() {
$('#anElement').data('isvisible', false);
});
if ($('#anElement').data('isvisible'))
{
// popover is visible
$('#tipUTAbiertas').tooltip('hide');
$('#tipUTAbiertas').tooltip('show');
}
Bootstrap 5:
const toggler = document.getElementById(togglerId);
const popover = bootstrap.Popover.getInstance(toggler);
const isShowing = popover && popover.tip && popover.tip.classList.contains('show');
Using a popover with boostrap 4, tip() doesn't seem to be a function anymore. This is one way to check if a popover is enabled, basically if it has been clicked and is active:
if ($('#element').data('bs.popover')._activeTrigger.click == true){
...do something
}
I don't completely understand how javascript works in an OOP model, so I come to stack overflow for wisdom.
My example code:
(function($) {
var $container = $('#container');
var $sidebar = $('#sidebar');
// Sidebar
var currTab = $('#s1');
if(currTab) {
currTab.parent().parent().parent().addClass('selectedTop');
currTab.find(".sideContent").delay(300).slideToggle("slow");
currTab.addClass('selected');
}
$('#sideTop').delegate('li', 'hover', function(event) {
var $this = $(this);
if (event.type == 'mouseenter') {
if(!$this.hasClass("selected")){
$this.siblings(".selected").children(".sideContent").toggle();
$this.siblings(".selected").removeClass('selected');
$this.find(".sideContent").toggle().addClass('selected');
$this.addClass('selected');
}
}
});
})(this.jQuery);
This code caches my container and sidebar div and controls the hovering of tabs on my sidebar. These will be on every page, so I originally just included the js file on each page and it works as usual. Now I've gotten to a point where I want to customize each page with a specific tab of the sidebar open by default (defined by the currTab variable). When set, it will open by default, and stay open after the mouse leaves the sidebar.
I haven't found a way to customize currTab on each page without having to completely re-paste all the code associated with the sidebar, making any updates to the script cumbersome.
How should I be approaching this? Thanks
I'm sorry to have caused confusion with my lack of understanding, but one of the related questions answered mine in a way I didn't know how to search for:
He setup a "class" first, which could be included as a seperate JS, then communicated using jQuery.ClassName(options)
I've tried it and it works perfectly, seperating the code that is consistent, with the values that will change on each page.
(function($){
var undefined;
$.ClassName = function(options){
var self = this;
var cfg = $.extend(true, {}, this.defaults, options);
// ********************
// start:private
// ********************
function _init(){
};
// ********************
// start:public
// ********************
this.methodName = function(){
};
_init();
};
$.ClassName.prototype.defaults = {};
})(jQuery);
With classes. Just add a class such as "currTab" to whichever tab is active. In your JS, check for that class on the tab, and when the tab is changed, remove that class from the old one and add it to the new one.
Add a class to the item you want to be active by default. Use JS to detect the class and react accordingly.
One way is, to declare currTab differently inside each HTML page, and remove "var currTab = $('#s1');" from your JavaScript file. The rest of currTab occurences in the JavaScript file are still able to reference it.
I'm working on writing a drop-down menu with jQuery and I have a question. My menu is composed of two part span.menu_head (which is in the menu bar) and a ul.menu_body (which contains the drop-down items). I have the following jQuery code:
$("span.menu_head").hover(function(){
$(this).next().slideDown('medium');
}, function(){
});
$("ul.menu_body").hover(function(){
}, function(){
$(this).slideUp('medium');
});
So, when I hover over the span.menu_head, the ul.menu_body slides down and when I leave the ul.menu_body, it slide up. This is working as expected. However, there's one more piece I'm trying to add: When the mouse leaves the span.menu_head, I want the ul.menu_body to slideUp, UNLESS the mouse is over the ul.menu_body. Is there a way in jQuery to determine if the mouse is over a certain element? Or, is there a better way to acheive this effect?
Thanks,
Paul
I found a solution using hoverIntent (http://cherne.net/brian/resources/jquery.hoverIntent.html).
I set a flag when the mouse hovers over the ul.menu_body, so that I can check this flag before closing the menu.
var overBody = false;
function mnuOpen() {
$(this).next().slideDown('medium');
}
function mnuClose() {
if (!overBody) {
$(this).next().slideUp('medium');
}
}
var headConfig = {
over: mnuOpen,
timeout: 250,
out: mnuClose
}
$("span.menu_head").hoverIntent(config);
$("ul.menu_body").hover(function(){
overBody = true;
}, function(){
overBody = false;
$(this).slideUp('medium');
});
This is producing the desired behavior. There's one more thing I need to consider: if the mouse goes from the sub menu back to the header, we don't want to close and re-open, so this might involve setting another flag to detect this behavior.
Paul