Dropdown content with rotating arrow (css/js) - javascript

I'm stuck with my dropdown content. Trying to rotate arrows next to the clicked Button Titles. The first content is opened by default, so that arrow should be turned there by default.
The current code does not turn Content 1 arrow by default and
Click rotates all arrows as I don't know how to refer correctly to
current icon in JS.
Any advices are very welcome.
let dropdownModule = {
init: function(){
this.cacheDom();
this.bindEvents();
},
cacheDom: function(){
this.$el = $('.dropdown-container');
this.$dropdownBox = this.$el.find('.dropdown-box');
this.$dropdownContent = this.$el.find('.dropdown-content');
},
bindEvents:function(){
this.$dropdownBox.on('click', function(){
let currentContent = $(this).next('.dropdown-content');
currentContent.slideToggle(600);
$('.dropdown-content').not(currentContent).slideUp(600);
$('.fa-sort-down').toggleClass('r180');
})
}}
dropdownModule.init();
<div class="dropdown-container">
<div class="dropdown-box">Button Title 1<i class="fas fa-sort-down"></i>
</div>
<div style="display: block;" class="dropdown-content">
Content 1
</div>
</div>
<div class="dropdown-container">
<div class="dropdown-box">Button Title 2<i class="fas fa-sort-down"></i>
</div>
<div class="dropdown-content">
Content 2
</div>
</div>
.fa-sort-down {
transition: all 0.5s ease;
}
.r180 {
transform: rotate(180deg);
}

Basically, when you click on the dropdown it might be the tag getting clicked and you need to find the target first.
Change your js to:
let dropdownModule = {
init: function(){
this.cacheDom();
this.bindEvents();
},
cacheDom: function(){
this.$el = $('.dropdown-container');
this.$dropdownBox = this.$el.find('.dropdown-box');
this.$dropdownContent = this.$el.find('.dropdown-content');
},
bindEvents:function(){
this.$dropdownBox.on('click', function(e){
let target = $(e.target);
if(!$(e.target).hasClass("dropdown-box")){
target = $(e.target).parent();
}
$('.fa-sort-down').removeClass('r180');
let currentContent = target.next('.dropdown-content');
currentContent.slideToggle(600);
$('.dropdown-content').not(currentContent).slideUp(600);
target.find('.fa-sort-down').toggleClass('r180');
})
}}
dropdownModule.init();

Related

Remove class if id's the correct ID

Looking to remove a class if a certain button is clicked.
<div class="slide-container">
<section class="about" id="slide-0">
<div class="menu-total">
<nav class="nav">
<button class="nav_link home" onclick="slideTo('slide-2')">HOME</button>
<button class="nav_link about" onclick="slideTo('slide-0')">ABOUT</button>
<button class="nav_link fun-stuff" onclick="slideTo('slide-1')">FUN STUFF</button>
<button class="nav_link professional" onclick="slideTo('slide-3')">PROFESSIONAL</button>
<button class="nav_link contact" onclick="slideTo('slide-4')">CONTACT</button>
</nav>
<div class="hamburger">
<span class="hamburger__patty"></span>
<span class="hamburger__patty"></span>
<span class="hamburger__patty"></span>
</div>
</div>
The one I want to remove the class on is the HOME button. So "slideTo('slide-2)". If it's clicked on the others then the class is kept. I believe someone is either wrong with my loop or not getting the ID correctly of the items/
function slideTo(slideId) {
const slide = document.getElementById(slideId);
slide.scrollIntoView({
behavior: 'smooth'
})
// above this line works fine
let nonHome = document.querySelectorAll('.slide-container section');
let nonHomeID = document.getElementById('slide-2');
var i;
setTimeout(function(){
for (i=0; i < nonHome.length; i++ ){
// i believe it's somewhere here it is wrong
if (nonHome[i].id != nonHomeID){
nonHome[i].classList.add("nav-visibility");
} else{
nonHomeID.classList.remove("nav-visibility");
}
}
}, 1000)
}
If you can use jquery library, you can write in the HTML:
<button class="nav_link" data-value="home">HOME</button>
...
and then in the JS code:
$(".nav_link").on("click", function() {
var valueClicked = $(this).data("value"); // Get the data-value clicked
$(".nav_link").each(function() { // Loop through all elements of the class 'nav-link'
var v = $(this).data("value");
if (v == valueClicked) {
$(this).removeClass("nav-visibility");
} else {
$(this).addClass("nav-visibility");
}
)
}
Not much simpler, but the HTML is cleaner.
Simpler version if it is not required to browse through all buttons at each button click:
$(".nav_link").on("click", function() {
var valueClicked = $(this).data("value"); // The value of the button clicked by the user
if (valueClicked == "home") {
$(this).removeClass("nav-visibility");
console.log('remove')
} else { $(this).addClass("nav-visibility");
console.log('add')
}
});

How do I allow only one accordion item to be open at a time on a dynamic accordion list?

I have a well functioning accordion list that allows me to add new items to the list and they will immediately have the accordion functionality. Although, I would like to have only one accordion item open at a time. All of the answers I've found regarding this have all been for static lists and don't really help with my issue. I apologize if this is a bad question, but I have spent hours on this with no luck and I am out of ideas. Thanks in advance for your help.
HTML: There is more to the HTML. I just added the relevant elements.
<body>
<div class="container">
<!-- New accordion item added here -->
</div>
<script src="main.js"></script>
</body>
Javascript:
// Get items
const addAccBtn = document.getElementById('addAccBtn');
const container = document.querySelector('.container');
addAccBtn.addEventListener('click', e => {
e.preventDefault();
let newAccItem = document.createElement('div');
newAccItem.innerHTML = `
<div class="acc-title">
<i class="fas fa-trash delete"></i>
<h3 class="header-click">${accName}</h3>
<i class="fas fa-sort-down down-arrow"></i>
</div>
<div class="acc-info hidden">
<i class="far fa-edit"></i>
<h4>Section 1</h4>
<p>${input1}</p>
<h4>Section 2</h4>
<p>${input2}</p>
</div>
`;
container.appendChild(newAccItem);
let downBtn = newAccItem.firstElementChild.childNodes[5]; // .down-arrow
let info = newAccItem.firstElementChild.nextElementSibling; // .acc-info
downBtn.addEventListener('click', () => {
if(downBtn.parentElement.nextElementSibling == info && info.classList.contains('hidden')) {
info.classList.remove('hidden');
info.classList.add('active');
}
else {
info.classList.remove('active');
info.classList.add('hidden');
}
});
});
Again, the accordion items will all open and close as they should, i would just like one item open at a time. Thanks again for your time.
I believe these lines should work
...
let downBtn = newAccItem.firstElementChild.childNodes[5]; // .down-arrow
let info = newAccItem.firstElementChild.nextElementSibling; // .acc-info
downBtn.addEventListener('click', () => {
//here you close all opened accordions ---------------
document.querySelectorAll(".acc-info.active").forEach(element => {
if (element !== info) {
element.classList.remove("active");
element.classList.add("hidden");
}
};
// --------------------------------------
if(downBtn.parentElement.nextElementSibling == info && info.classList.contains('hidden')) {
info.classList.remove('hidden');
info.classList.add('active');
}
else {
info.classList.remove('active');
info.classList.add('hidden');
}
});
});
_ edit _
added a if

How to hide an accordion tab on second click

Let's say I open an accordion tab with one click, and I want to close it with the second click.
This is the JS/jQuery I have so far:
var APP = window.APP = window.APP || {};
APP.accordion = (function () {
var $accordionHeader = $('.accordion__header');
var $accordionContent = $('.accordion__content');
var showAccordionContent = function(e) {
var $t = $(this);
$accordionHeader.removeClass('accordion__header--active');
$t.addClass('accordion__header--active');
$accordionContent.removeClass('accordion__content--expanded');
$t.next().addClass('accordion__content--expanded');
$('html, body').animate({
scrollTop: $t.offset().top - 60
}, 500);
e.preventDefault();
};
var bindEventsToUI = function () {
$accordionHeader.on('click', showAccordionContent);
};
var init = function () {
bindEventsToUI();
};
return {
init: init
};
}());
HTML:
<div class="accordion__tab">
<a href="#" class="accordion__header accordion__header--active">
Setting alerts
<span class="accordion__arrow"></span>
</a>
<div class="accordion__content">
<p>
{{!-- things --}}
</p>
<ul class="cf">
<li>
{{!-- more things --}}
</li>
</ul>
</div>
</div>
<div class="accordion__tab">
<a href="#" class="accordion__header">
Setting alerts
<span class="accordion__arrow"></span>
</a>
<div class="accordion__content">
<p>
{{!-- things --}}
</p>
<ul class="cf">
<li>
{{!-- more things --}}
</li>
</ul>
</div>
</div>
Suggestions?
I would do it like this, just some pseudos
var bindEventsToUI = function () {
$accordionHeader.on('click', toggleAccordionContent);
};
var toggleAccordionContent = function (e) {
var accordionIsOpen = $(this).classList.indexOf('accordion__header--active') >= 0; // may need to use another selector base on `this`
return accordionIsOpen ? hideAccordionContent.call(this) : showAccordionContent.call(this);
}
I guess you are looking for this. Modified showAccordionContent method logic a bit.
Hope this is what you are looking for.
This change will now close already open accordion on clicking it. And once you click on inactive accordion-header that will open. There might be a condition where user closes all the accordion after this code change.
var showAccordionContent = function(e) {
var $t = $(this);
/*Modified block logic*/
if($t.hasClass('accordion__header--active')){
$t.removeClass('accordion__header--active');
$t.next().removeClass('accordion__content--expanded');
}else{
$accordionHeader.removeClass('accordion__header--active');
$accordionContent.removeClass('accordion__content--expanded');
$t.addClass('accordion__header--active');
$t.next().addClass('accordion__content--expanded');
}
$('html, body').animate({
scrollTop: $t.offset().top - 60
}, 500);
e.preventDefault();
};

hide popover when clicked outside of it

here is my code for popover html:
<a data-placement="bottom" style="float:right;margin-right:20px;" id="new-quote-popover" popover><img src="img/user.png"/> <b>{{UserName}}</b>
<div id="popover-head" class="hide">USER DETAILS</div>
<div id="popover-content" class="hide">
<div class="row smallMargin">
<div class="col-sm-4">
varun
</div>
<div class="col-sm-8">
<select name="selectopt" style="width:80%">
<option value="001">001</option>
<option value="002">002</option>
</select>
</div>
</div>
</div>
</a>
and the directive for popover is
js:
app.directive('popover', function($compile){
return {
restrict : 'A',
link : function(scope, elem){
var content = $("#popover-content").html();
var compileContent =function() {
return $compile(content)(scope);
};
var title = $("#popover-head").html();
var options = {
content: compileContent,
html: true,
title: title
};
$(elem).popover(options);
}
}
});
It is working well..I am trying to hide the pop-over whenever cliked outside of it.But as it is in tag i am unable to do so.please help me in this.I tried with classname,id but not succeeded .
I checked this question it is not working for me
You need to inject $document service and to create new event listener mousedown on document root.
$document.on('mousedown', addClickOutsideListener);
function addClickOutsideListener(event) {
var target = event.target;
while(target != document.body) {
if(target == elem[0]) {
//user clicked on Popover, nothing to do
return;
}
if(!target.parentNode)
return;
target = target.parentNode;
}
hidePopover();
});
Don't forget add listener of scope destroying:
scope.$on('$destroy', function() {
$document.off('mousedown', addClickOutsideListener);
});

Collapsable panel with bootstrap and knockout

I can't make an accordion with KnockoutJS, and Bootstrap to work properly. I have defined it like so:
<div class="panel-group" id="accordion" data-bind="foreach: Advertisers()">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<span data-toggle="collapse" data-bind="html: $data, attr: { 'data-target': '#' + $data }"></span>
</h4>
</div>
</div>
<div class="panel-collapse collapse" data-parent="#accordion" data-bind="attr: { id: $data }">
<div class="panel-body">
...content...
"Advertisers" is an observable array of strings, and hence $data is a string. I get one "row" for each advertiser.
All rows are initially collapsed, and clicking a row expands the content below. So far so good.
The problem is that when I click another row I would expect the previous expanded to collapse, but that's not happening. (I couldn't make a fiddle to work either, with Bootstrap and KnockoutJS...)
Edited the code.
What about a simple custom binding, which also allows you to unclutter your view a bit:
ko.bindingHandlers.bootstrapAccordion = {
init: function(elem, value, allBindings) {
var options = ko.utils.unwrapObservable(value()),
handleClass = '[data-toggle]',
contentClass = '.collapse',
openItem = ko.utils.unwrapObservable(options.openItem) || false,
itemClass = '.' + ko.utils.unwrapObservable(options.item) || '.accordion-group',
items = $(elem).find(contentClass);
// toggle: false required to hide items on load
items.collapse({ parent: elem, toggle: false });
if (openItem > -1) items.eq(openItem).collapse('show');
// if the array is dynamic, the collapse should be re-initiated to work properly
var list = allBindings.get('foreach');
if (ko.isObservable(list)) {
list.subscribe(function() {
$(elem).find(contentClass).collapse({ parent: elem, toggle: false });
});
}
$(elem).on('click', handleClass, function() {
$(elem).find(contentClass).collapse('hide');
$(this).closest(itemClass).find(contentClass).collapse('show');
});
}
};
This binding takes 2 parameters (className for container, and optionally, an item to open on load), eg: bootstrapAccordion: {item: 'panel-group', openItem: 0}, and should be set on the same element which has a foreach binding. It assumes that collapsible sections have a collapse class, and the handles to toggle them have a data-toggle attribute.
See it in action here:
http://jsfiddle.net/pkvn79h8/22/
I extended Tyblitz's example above to include support for changing an icon (eg, +/-, up/down arrow) and support for moving to the next panel by applying data-open-next attribute to whatever should move to next panel on click.
ko.bindingHandlers.bootstrapAccordion = {
init: function (elem, value, allBindings) {
var options = ko.utils.unwrapObservable(value()),
handleClass = '[data-toggle]',
contentClass = '.collapse',
openedClass = ko.utils.unwrapObservable(options.openedClass) || 'fa-minus',
closedClass = ko.utils.unwrapObservable(options.closedClass) || 'fa-plus',
openCloseToggleClasses = openedClass + ' ' + closedClass,
openItem = ko.utils.unwrapObservable(options.openItem) || false,
itemClass = '.' + (ko.utils.unwrapObservable(options.item) || 'accordion-group'),
items = $(elem).find(contentClass);
var initializeItems = function(items) {
// toggle: false required to hide items on load
items.collapse({ parent: elem, toggle: false });
if (openItem > -1) {
items.eq(openItem).collapse('show');
items.eq(openItem).closest(itemClass).find('.panel-heading').find('i').toggleClass(openCloseToggleClasses);
items.eq(openItem).closest(itemClass).find('.panel-heading').addClass('active');
}
}
initializeItems(items);
// if the array is dynamic, the collapse should be re-initiated to work properly
var list = allBindings.get('foreach');
if (ko.isObservable(list)) {
list.subscribe(function () {
initializeItems($(elem).find(contentClass));
});
}
$(elem).on('click', handleClass, function () {
$(elem).find(contentClass).collapse('hide');
$(this).closest(itemClass).find(contentClass).collapse('show');
$(this).closest(itemClass).parent().find('.panel-heading i').removeClass(openCloseToggleClasses);
$(this).closest(itemClass).parent().find('.panel-heading i').addClass(closedClass);
$(this).closest(itemClass).parent().find('.panel-heading').removeClass('active');
if ($(this).closest(itemClass).find('.panel-collapse').attr('aria-expanded') === "true") {
$(this).closest(itemClass).find('.panel-heading i').toggleClass(openCloseToggleClasses);
$(this).closest(itemClass).find('.panel-heading').addClass('active');
}
});
$(elem).on('click', '[data-open-next]', function () {
$next = $(this).closest(itemClass).next(itemClass).find(handleClass);
if ($next.length) {
$next.click();
} else {
$same = $(this).closest(itemClass).find(contentClass);
$same.collapse('hide');
$same.parent().find('.panel-heading i').removeClass(openCloseToggleClasses);
$same.parent().find('.panel-heading i').addClass(closedClass);
$same.parent().find('.panel-heading').removeClass('active');
}
});
}
};
Sample markup to use with this binding:
<div data-bind="foreach: listOfThings, bootstrapAccordion: { openItem: 0 }">
<div class="accordion-group">
<div class="panel panel-default" style="cursor: pointer;" data-toggle>
<div class="panel-heading">
<i class="fa fa-plus fa-pull-left fa-2x"></i>
<h3 data-bind="text: name">Title of expander</h3>
</div>
</div>
<div class="panel-collapse collapse">
<div class="panel-body">
<div class="clearfix" data-accordion-content>
<!-- content goes here -->
<!-- ko if: $index() < $parent.listOfThings().length -1 -->
<button data-open-next>Next Thing</button>
<!-- /ko -->
</div>
</div>
</div>
</div>
</div>
I would feel bad not contributing back :)

Categories