I was kindly helped by Jonathan over here simple javascript question-linking button state to image swap?
The problem is that this makes the "active" class the same class for both list items.
Each list item needs to toggle its own active and its own inactive class (each is a button with its own css styling and background image).
Can you please help me modify the script so that I can do that?
Here is Jonathans provided code:
<li class="transcript">
<a id="transcriptionhorbutton" class="inactive"
href="javascript:void()"
onclick="getDataReturnText('/lessons/transcriptions/ajaxcalls/L1horizontal.txt', callback);make_active(this);"></a>
</li>
<li class="transcript">
<a id="transcriptionvertbutton" class="inactive"
href="javascript:void()"
onclick="getDataReturnText('/lessons/transcriptions/ajaxcalls/L1vertical.txt', callback);make_active(this);"></a>
</li>
<script>
var buttons = [ document.getElementById("transcriptionvertbutton"),
document.getElementById("transcriptionhorbutton")];
function make_active(el) {
deactivate_buttons();
el.setAttribute("class","active");
}
function deactivate_buttons() {
buttons[0].setAttribute("class","inactive");
buttons[1].setAttribute("class","inactive");
}
</script>
I understand that the problem is here:
function make_active(el) {
deactivate_buttons();
el.setAttribute("class","active");
}
but I don't know enough to separate that into two different classes.
Just add an extra parameter to the function:
function make_active(el, classname) {
deactivate_buttons();
el.setAttribute("class",classname);
}
Then change your calls just a bit. Here is the completed code. Note I changed all calls of setAttribute to .className instead. This was just so you don't run into any trouble with IE6:
<li class="transcript">
<a id="transcriptionhorbutton" class="inactive"
href="javascript:void()"
onclick="getDataReturnText('/lessons/transcriptions/ajaxcalls/L1horizontal.txt', callback);make_active(this,'active_class_1');"></a>
</li>
<li class="transcript">
<a id="transcriptionvertbutton" class="inactive"
href="javascript:void()"
onclick="getDataReturnText('/lessons/transcriptions/ajaxcalls/L1vertical.txt', callback);make_active(this,'active_class_2');"></a>
</li>
<script>
var buttons = [ document.getElementById("transcriptionvertbutton"),
document.getElementById("transcriptionhorbutton")];
function make_active(el, classname) {
deactivate_buttons();
el.className = classname;
}
function deactivate_buttons() {
buttons[0].className = "inactive_class_1";
buttons[1].className = "inactive_class_2";
}
</script>
Related
I have this website: https://www.australianathleticscalendar.com.au/
I want to make it so you can have selected an 'Events' filter and 'State' filter at once (I.e. 100m and QLD at once), and the bubbles in each filter will have styling (I.e. like :focus). With each filter, select one category. When you change categories within a filter, the previous one selected must also be unstyled (I.e. can't select 100m and 200m at once. Moving from 100m to 200m would remove styling from 100m and add it to 200m).
The problem is with css is that you can only :focus one element at a time.
How can I achieve this using javascript?
I've pulled the relevant code into a codepen here.
This is the functions which draw the two filters:
function drawCategories() {
var template = Handlebars.compile(document.getElementById("menu-template").innerHTML);
console.log('draw ', this.products);
document.getElementById('menu-container').innerHTML = template(this.events);
}
function drawStates() {
var template = Handlebars.compile(document.getElementById("menu-template-2").innerHTML);
console.log('draw ', this.products);
document.getElementById('menu-container-states').innerHTML = template(this.states);
}
function showCategory(event) {
this.title = event;
drawProducts();
}
function showState(state) {
this.titleStates = state;
drawProducts();
}
And this is the HTML for the two filters:
<!-- Events filter -->
<div class="container">
<script id="menu-template" type="text/x-handlebars-template">
<ul class="navbar-nav">
{{#each this as |event|}}
<li class="nav-item"></li>
<a class="nav-link" href="#" onclick="showCategory('{{event.name}}');">{{event.name}}</a>
{{/each}}
<a class="navbar-brand hover-color" href="#" onclick="showAllEvents();">All Events</a>
</ul>
<ul class="navbar-nav ml-auto"></ul>
<li class="nav-item">
</li>
</ul>
</div>
</script>
</div>
<!-- States filter -->
<div class="container">
<script id="menu-template-2" type="text/x-handlebars-template">
<ul class="navbar-nav">
{{#each this as |state|}}
<li class="nav-item"></li>
<a class="nav-link nav-link-states" href="#" onclick="showState('{{state.name}}');">{{state.name}}</a>
{{/each}}
<a class="navbar-brand hover-color-states" href="#" onclick="showAllStates();">All States</a>
</ul>
<ul class="navbar-nav ml-auto"></ul>
<li class="nav-item">
</li>
</ul>
</div>
</script>
</div>
As often there are many ways to achieve this. I think the easiest might be to add an extra argument to the calls made in the click handlers -- passing this -- so the handler knows exactly which DOM element needs to get styling:
<a onclick="showCategory(this, '{{event.name}}');">
<a onclick="showAllEvents(this);">
<a onclick="showState(this, '{{state.name}}');">
<a onclick="showAllStates(this);">
Then in your code define a function that will apply a CSS class to a given DOM element, but also makes sure that it will be the only element having that class. Then use this utility function in the click handlers, which must now also take that extra argument:
// Utility function that ensures only one element has the given CSS class:
function selectElem(elem, className) {
let prevSelected = document.querySelector("." + className);
if (prevSelected) prevSelected.classList.remove(className);
elem.classList.add(className);
}
function showCategory(elem, event) { // <-- extra parameter, here and below...
selectElem(elem, "selectedEvent"); // <-- add this call, here and below...
this.title = event;
drawProducts();
}
function showAllEvents(elem) {
selectElem(elem, "selectedEvent");
this.title = "All Events";
drawProducts();
}
function showState(elem, state) {
selectElem(elem, "selectedState"); // <-- different style here and below
this.titleStates = state;
drawProducts();
}
function showAllStates(elem) {
selectElem(elem, "selectedState");
this.titleStates = "All States";
drawProducts();
}
Now it is up to you to define the style of two new CSS classes: selectedEvent and selectedState. You can give them the same definition with a comma:
.selectedState, .selectedEvent {
# Your styling ...
}
I am developing a website with a mobile menu where three underlying elements should appear when hovering over another menu item (not a parent!)
I have tried css but that was not possible without altering the html structure, so I left that for now. I also tried onclick function but the mobile menu disappears after a click so that doesn't work either. Therefore, onmouseover javascript would be preferable.
The javascript code I have so far:
<script type='text/javascript'>
document.getElementsByClassName("holder").onmouseover = function(){mouseOver()};
document.getElementsByClassName("holder").onmouseout = function() {mouseOut()};
function mouseOver() {
document.getElementsByClassName("helper").style.display=="block";}
function mouseOut() {
document.getElementsByClassName("helper").style.display=="none";}
</script>
The html is as follows:
<ul class="mm-listview">
<li class="holder menu-item menu-item-type-custom menu-item-object-custom">Holder</li>
<li class="helpers menu-item menu-item-type-custom menu-item-object-custom menu-item-has-icon">Example</li>
<li class="helpers menu-item menu-item-type-custom menu-item-object-custom menu-item-has-icon">Example
</li></ul>
Modified HTML below.
<ul class="mm-listview">
<li id="holder">Holder</li>
<li id="helper1">Example</li>
<li id="helper2">Example
</li></ul>
Modified JS below.
<script type='text/javascript'>
document.getElementById("holder").onmouseover = function(){mouseOver()};
document.getElementById("holder").onmouseout = function() {mouseOut()};
function mouseOver() {
document.getElementById("helper1").style.display=="block";}
function mouseOut() {
document.getElementById("helper").style.display=="none";}
</script>
This should work.
getElementsByClassName will return an array with all elements with class helper. You need to change the display of each of those elements, something like this:
function mouseOver() {
var myList = document.getElementsByClassName("helper");
myList.forEach(function(item){
item.style.display="block";
});
}
I'm working on a basic dropdown element in HTML and jQuery and I'm trying to get better at understanding JavaScript and jQuery so this questions is a bit about code refactoring as well.
So here is what I've gotten so far:
HTML
<li class="nav-item">
<a class="nav-link" href="#">Foo</a>
<div class="subnav">
...
</div>
</li>
JavaScript
const navLink = $('.nav-link');
navLink.each(function () {
let $this = $(this);
$this.click(function (e) {
let hasSubnav = $this.parent().find('.subnav');
if(hasSubnav.length !== 0) {
e.preventDefault();
$this.toggleClass('dropdown-active');
}
hasSubnav.stop(true, true).slideToggle(200);
})
});
This solutions works fine. So what I want to do next is to check if another element in my loop is active, close is accordingly and then open the one I just clicked.
I thought about just putting a default click function before the each function like this:
navLink.click(function () {
$('.subnav').slideUp();
});
navLink.each(function () {
let $this = $(this);
$this.click(function (e) {
let hasSubnav = $this.parent().find('.subnav');
if(hasSubnav.length !== 0) {
e.preventDefault();
$this.toggleClass('dropdown-active');
}
hasSubnav.stop(true, true).slideDown(200);
})
});
But this does not seem to work. So my question is, is there a pretty way to achieve this maybe even inside of the each function? I've red about .not(this) in this post, which will maybe work (haven't tried it yet) but I thought that this would be duplicated code and that there might be a better way to get this to work.
Your code is now looping through every single nav-link and adding a click handler to them one by one. It is possible to remove the each loop, since you can just add a click handler to all nav-links at once.
All you have to do is add a click handler to the nav-link and then remove the active class and slide up all open dropdowns before executing your logic. See working code example below for reference:
// Collapse all initially
$(".subnav").slideUp();
// Add click handler to all nav-links
const navLink = $('.nav-link');
navLink.click(function(e) {
// Remove active classes on other elements & slide up
const otherLinks = navLink.not(this);
otherLinks.removeClass('dropdown-active');
otherLinks.parent().find('.subnav').slideUp();
// Slide down the subnav of selected element
let hasSubnav = $(this).parent().find('.subnav');
if (hasSubnav.length !== 0) {
e.preventDefault();
$(this).addClass('dropdown-active');
}
hasSubnav.stop(true, true).slideToggle(200);
})
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<li class="nav-item">
<a class="nav-link" href="#">Foo</a>
<div class="subnav">
Link1
Link2
Link3
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Foo</a>
<div class="subnav">
Link1
Link2
Link3
</div>
</li>
I'm rather new to coding and my understanding is limited.
But I'm using Slicknav for mobile screen sizes, however according to the author of the plug in.
"SlickNav menu items are created Dynamically" so I need to use delegated click events or attach event handlers after SlickNav is created.
My SlickNav post
I need some help with this. According to This, I tried making a delegated events.
Here is the original code which is a " direct" event handler (I think)
menuitem.eq(0).on('click', function(){
status = 1;
clearBox();
statusCheck();
});
BTW all my code is trying to do is 1. Clear out a Container which is a display window for content. And 2. Append the correct content to that window based on the menu item that is clicked.
Here is my attempt at a delegated event:
$('#navMenu').on('click',menuitem.eq(0), function(){
status = 1;
clearBox();
statusCheck();
});
Also tried replacing .on() with .delegate(), no dice.
For completeness ill including the functions that the cick events are calling
function clearBox(){
$("#display_box").children().fadeOut(1000).appendTo(".holding");
};
function statusCheck(){
if (status == 1){
displaycontent.eq(0).fadeIn(1000).appendTo("#display_box");
displaycontent.eq(0).removeClass("hide");
$("#display_box").animate({scrollTop:0},500);
} else{}
if (status == 2){
displaycontent.eq(25).fadeIn(1000).appendTo("#display_box");
displaycontent.eq(25).removeClass("hide");
$("#display_box").animate({scrollTop:0},500);
} else{}
// Etc Etc Etc
Edit: Provided HTML for the Menu
<div class ="menu_wrap">
<nav id = "navMenu">
<ul class ="clearfix menu">
<li>General
<ul class="subMenu1">
<li class ="menu_item">Introduction</li>
<li class ="menu_item"> What you need</li>
<li class ="menu_item">House Rules</li>
<li class ="menu_item">Running the Game</li>
<li class ="menu_item">Survival</li>
<li class ="menu_item">Encounters</li>
</ul>
</li>
<li>The World
<ul class ="subMenu1">
<li class ="menu_item">Nol</li>
<li class ="menu_item">Wol</li>
<li class ="menu_item">Sol</li>
<li class ="menu_item">Eol</li>
</ul>
</li>
<li>Locations and Maps</li>
<li>Races and Cultures
<ul class ="subMenu1">
<li> <a class="allow_default" href="index_npcs.html" target="blank">NPC Creatures</a></li>
<li class ="menu_item"> Voran Kingdom</li>
<li class ="menu_item">Doval Empire</li>
<li class ="menu_item">Salatai Sultanate</li>
<li class ="menu_item">Gamoran Republic</li>
<li class ="menu_item">Elandel</li>
<li class ="menu_item">Kingdom of Night</li>
<li class ="menu_item">Halflings</li>
<li class ="menu_item">Aiur' Dun</li>
<li class ="menu_item">Half-Elves</li>
<li class ="menu_item">Half-Orcs</li>
<li class ="menu_item">Dryads</li>
</ul>
</li>
<li> Organizations
<ul class ="subMenu1">
<li class="menu_item">Information</li>
<li class ="menu_item">The Green Wardens</li>
<li class ="menu_item">The Temple of Light</li>
<li class ="menu_item">The Black Hand</li>
<li class ="menu_item">The Stone Priests</li>
<li class ="menu_item">The Golden Company</li>
<li class ="menu_item">The Dread Guards</li>
</ul>
</li>
<li class ="menu_item">Character Creation
<ul class ="subMenu1">
<li class ="menu_item"> <a class="allow_default" href="index_personality_test.html" target="blank">Creation Test</a></li>
</ul>
</li>
</ul>
</nav>
</div>
Try something like this:
<nav id="navMenu">
<ul class="clearfix menu">
<li>General
<ul class="subMenu1">
<li class="menu_item" data-item="intro">Introduction</li>
<li class="menu_item" data-item="requirements">What you need</li>
<li class="menu_item" data-item="rules">House Rules</li>
...
And this for the JavaScript:
$('#navMenu, .slicknav_menu').on('click', '.menu_item', function() {
clearBox();
switch($(this).attr('data-item')) {
case 'intro': {
// Do something here.
break;
}
case 'requirements': {
// Do something here.
break;
}
case 'rules': {
// Do something here.
break;
}
// More cases...
}
});
It appears the cloned menu created by SlickNav is placed directly under the <body> element, so you cannot call .on() only on your #navMenu element. You could call it on the body element, but the code above calls it on both the #navMenu and .slicknav_menu elements. The <ul> element generated by SlickNav has the slicknav_menu class on it.
As written, the code above has to be called after you call .slicknav() since it requires the cloned menu exist when it is called. Otherwise, you would have to change to $('body').on(....
As for the rest, the selector '.menu_item' identifies all the menu item elements, so the click-event handler will execute for all of them. But each menu item element has a different value for the data-item attribute. That way you can do something different for each one.
jsfiddle
Well, I wanted to add class to all of li's of my ul, yet I don't know why it won't work.
My CSS code:
.vnav-menu li.selected {
background:#fff;
}
here's my html code for it.
<ul class="vnav-menu"> <!-- Initiates Showing/Hiding Dashboard Containers -->
<li onclick="vnav_function('current_offer');" id="current_offer_li" class="selected"> <a> CURRENT OFFERS </a></li>
<li onclick="vnav_function('draft_offer');" id="draft_offer_li"> <a> DRAFT OFFERS </a></li>
<li onclick="vnav_function('expired_offer');" id="expired_offer_li"> <a> EXPIRED OFFERS </a></li>
<li onclick="vnav_function('offer_code');" id="offer_code_li"> <a> OFFER CODE </a></li>
<li onclick="vnav_function('title');" id="title_li"> <a> TITLE </a></li>
<li onclick="vnav_function('schedule');" id="schedule_li"> <a> SCHEDULE </a></li>
<li onclick="vnav_function('offer_type');" id="offer_type_li"> <a> OFFER TYPE </a></li>
</ul>
And here is my code for the function vnav_function()
function vnav_function(data) { //Showing/Hiding Dashboard Containers
$('.vnav-menu li').each(function(i)
{
$(this).attr('id').addClass("selected");
});
}
Well, for some reason it won't work though. any ideas for this stuff?
.attr('id') returns a string. I think that you want to use the ID for comparison:
if ($(this).attr('id') == $(data.target).attr('id') {
$(this).addClass('selected');
}
However, it would be much easier to just do
function vnav_function(data) {
$(this).addClass('selected');
}
...and it would be even easier to do
<ul class="vnav-menu"> <!-- Initiates Showing/Hiding Dashboard Containers -->
<li id="current_offer_li" class="selected"> <a> CURRENT OFFERS </a></li>
<li id="draft_offer_li"> <a> DRAFT OFFERS </a></li>
<li id="expired_offer_li"> <a> EXPIRED OFFERS </a></li>
<li id="offer_code_li"> <a> OFFER CODE </a></li>
<li id="title_li"> <a> TITLE </a></li>
<li id="schedule_li"> <a> SCHEDULE </a></li>
<li id="offer_type_li"> <a> OFFER TYPE </a></li>
</ul>
$(".vnav-menu li").on('click', function () {
$(this).addClass('selected');
});
You may even want to do:
$(".vnav-menu li").on('click', function () {
$(".vnav-menu").removeClass('selected');
$(this).addClass('selected');
});
You are trying to invoke the addClass method on the string returned by $(this).attr('id'). Try using the addClass method on the jQuery collection returned by $(this):
$(this).addClass("selected");
No jQuery:
function getElementsByCSSSelector(s) {
return Array.prototype.slice.call(document.querySelectorAll(s));
}
getElementsByCSSSelector('.vnav-menu li').forEach(function(li) {
li.classList.add("selected");
});
function vnav_function(data) { //Showing/Hiding Dashboard Containers
$('.vnav-menu li').each(function()
{
$(this).addClass("selected");
});
}
Or just:
function vnav_function(data) { //Showing/Hiding Dashboard Containers
$('.vnav-menu li').addClass('extra');
}
Well no need to use .each instead $('.vnav-menu li').addClass('extra'); this can add class to every li inside ul.vnav-menu