MozillaZine

JavaScript change event question.

Discuss how to use and promote Web standards with the Mozilla Gecko engine.
Bethrezen
 
Posts: 405
Joined: September 13th, 2003, 11:56 am

Post Posted June 6th, 2019, 8:54 am

Hi all

So I'm trying to write a simple show hide script that will upon checking a given radio button show the container that has the same id as the radio button that was checked, however this is where I'm stuck because the process only seems to work 1 way.

I can set the default state to make things hidden and I can make each of those hidden elements visible again upon clicking the corresponding radio button the problem is once there corresponding radio button is no longer checked those elements don’t switch back to the default state and thus remain visible and I don't understand why? because I had amused that the change event would work the same way as the :checked pseudo-class does in css

So all i would have to do is set the default state and then define when happens when one of the radio buttons is checked and then each time the script is fired the targeted element would be made viable and the previously targeted element would be made invisible again.

however the change event does seem to work this way and i cant figure out how to make all the containers except the one that is selected one invisible.

ok so here is a simplified version of the page I'm working on

Code: Select all
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
div.container
{
display:none
}
</style>
</head>

<body>

<main>

  <div id="left">

    <input type="radio" name="button" id="foo">
    <label for="foo">foo</label>

    <br><input type="radio" name="button" id="bar">
    <label for="bar">bar</label>

  <div id="right">

    <div class="container" data-name='foo'>
      <p>some content that should be hidden by default</p>
    </div>

    <div class="container" data-name='bar'>
      <p>some content that should be hidden by default</p>
    </div>

  </div>

</main>

<script>

function checked(event)
{
  var id = event.target.id;
  var selected = document.querySelector('[data-name="'+ id +'"]');

  selected.style.cssText="display:block";
}

var inputs = document.querySelectorAll("input");
for (i = 0; i < inputs.length; i++)
{
  inputs[i].addEventListener("change", checked);
}

</script>

</body>
</html>


what should happen when i check the radio button with the id foo is that the box with the data-name="foo" should appear which it does

what should happen when i check the radio button with the id bar is that the box with the data-name="bar" should appear which it does however since the radio button with the id foo is no longer checked the box with the data-name="foo" should become invisible again which it doesn't and as i already mentioned I don't understand why because i though the change event was supposed to work like the :checked pseudo-class so when the radio button is no linger checked it should automatically switch back to the default state.

anyone have any idea what I'm missing ?

mightyglydd

User avatar
 
Posts: 9590
Joined: November 4th, 2006, 7:07 pm
Location: Hollywood Ca.

Post Posted June 6th, 2019, 10:07 am

Bethrezen wrote:anyone have any idea what I'm missing ?

Er the correct forum?

Which is here https://forum.palemoon.org/index.php
#KeepFightingMichael

morat
 
Posts: 3469
Joined: February 3rd, 2009, 6:29 pm

Post Posted June 6th, 2019, 12:00 pm

Add the following code snippet to the beginning of the checked function.

Code: Select all
var divs = document.querySelectorAll("div.container");
for (var i = 0; i < divs.length; i++)
  divs[i].style.cssText = "display:none";

Dynamic Tabs Example
http://jsfiddle.net/t8zbh0pa/

Bethrezen
 
Posts: 405
Joined: September 13th, 2003, 11:56 am

Post Posted June 10th, 2019, 3:37 pm

Er the correct forum?

Which is here https://forum.palemoon.org/index.php


Web Development / Standards Evangelism is the correct forum, since it's a question is related to web development, the browser that i used to post the question is irrelevant, and for the record just because i post using pale moon doesn't mean I'm developing in pale moon, in fact i use a portable version of Firefox for that.

Dynamic Tabs Example
http://jsfiddle.net/t8zbh0pa/


i had a look at the jsfiddle and that's not really what I'm trying to do.

Add the following code snippet to the beginning of the checked function.

Code: Select all
var divs = document.querySelectorAll("div.container");
    for (var i = 0; i < divs.length; i++)
      divs[i].style.cssText = "display:none";p




after a bit of tinkering that's almost the solution i came up with, which you can see below.

Code: Select all
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<style>
div.container
{
display:none
}
</style>

</head>

<body>

<main>

  <div id="left">

    <input type="radio" name="button" id="foo">
    <label for="foo">show foo</label>

    <br><input type="radio" name="button" id="bar">
    <label for="bar">show bar</label>

    <br><input type="radio" name="button" id="choo">
    <label for="bar">show choo</label>

  <div id="right">

    <div class="container" data-name='foo'>
      <p>some content that should be hidden by default foo</p>
    </div>

    <div class="container" data-name='bar'>
      <p>some content that should be hidden by default bar</p>
    </div>

    <div class="container" data-name='choo'>
      <p>some content that should be hidden by default choo</p>
    </div>

  </div>

</main>

<script>

function checked(event)
{
  var id = event.target.id;
  var selected = document.querySelector('[data-name="'+ id +'"]');

  selected.style.cssText="display:block";

  var notchecked = document.querySelectorAll('input:not(:checked)');

  for (i = 0; i < notchecked.length; i++)
  {
    var id = notchecked[i].id;
    document.querySelector('[data-name="'+ id +'"]').style.cssText="display:none";
  }
}

var inputs = document.querySelectorAll("input");
for (i = 0; i < inputs.length; i++)
{
  inputs[i].addEventListener("change", checked);
}

</script>

</body>
</html>


while the above works fine, doing things this way is something of a blunt instrument, and not particularly efficient, and i was actually hoping for a solution with a little more finesse.

aka I want to get only the previously checked element since only the previously checked element will be set to display:block; therefore there is no reason to get every other element that is not currently checked as they are already set to be hidden by default, unfortunately my knowledge of JavaScript is pretty limited so I'm unsure how i would store which element was checked previously and then switch only that element back to hidden when its no longer checked.

Also this still doesn't explain why if i set the default state to hidden and the checked state to viable elements are not automatically switched back to there default state when they become unchecked, since i though the onchange event was supposed to fire when the element is checked and when the element is unchecked and therefore it should work like the :checked pseudo-class and automatically switch back to the default state upon becoming unchecked.

humm... and just when i start to think I'm finally beginning to understand this stuff, why there aren't just separate checked and unchecked events just like there are for hover I'll never know it would make life so much easier.

jscher2000

User avatar
 
Posts: 10752
Joined: December 19th, 2004, 12:26 am
Location: Silicon Valley, CA USA

Post Posted June 11th, 2019, 6:11 pm

Try using a unique class name for the selected/visible tab. For example add this to your style block:

Code: Select all
.currentTab {
  display: block !important;
}

Then you can shorten your script to:

Code: Select all
function checked(event){
  var id = event.target.id;
  var elOld = document.querySelector('.currentTab');
  if (elOld) elOld.classList.remove('currentTab');
  var elNew = document.querySelector('[data-name="' + id + '"]');
  if (elNew) elNew.classList.add('currentTab'); else console.log('Did not find data-name: ' + id);
}

You raise a good question about why there isn't a change fired when a radio button becomes unchecked. I don't know, but my code wouldn't work correctly in that case.

Bethrezen
 
Posts: 405
Joined: September 13th, 2003, 11:56 am

Post Posted June 24th, 2019, 9:14 am

jscher2000 wrote:Try using a unique class name for the selected/visible tab. For example add this to your style block:

Code: Select all
.currentTab {
  display: block !important;
}

Then you can shorten your script to:

Code: Select all
function checked(event){
  var id = event.target.id;
  var elOld = document.querySelector('.currentTab');
  if (elOld) elOld.classList.remove('currentTab');
  var elNew = document.querySelector('[data-name="' + id + '"]');
  if (elNew) elNew.classList.add('currentTab'); else console.log('Did not find data-name: ' + id);
}


i take it what you are suggesting is instead of controlling the css via the script setup the css in the style sheet and then just swap classes back and forth.

it's an interesting idea and i had actually though about that, but once again my limited knowledge of JavaScript is the stumbling block as class swapping is not really something that i understand.

it's a shame that CSS only has the general sibling selector because if there was a general selector that allowed you to select an element any where in the document regardless of its relationship to other elements then JavaScript would be completely unnecessary for this.

while doing this trick is possible with css only there is one major catch both the inputs and the thing that you are targeting must have the same parent

so for example the following will work with css only just fine because they have the same parent

Code: Select all
foo, bar, choo
{
display:none
}

input#foo:checked ~ #right div.foo,
input#bar:checked ~ #right div.bar,
input#choo:checked ~ #right div.choo
{
display:block
}


Code: Select all
<div id="left">
    <input type="radio" name="button" id="foo">
    <label for="foo">show foo</label>

    <br><input type="radio" name="button" id="bar">
    <label for="bar">show bar</label>

    <br><input type="radio" name="button" id="choo">
    <label for="bar">show choo</label>

    <div id="right">

    <div id="foo">
      <p>some content that should be hidden by default foo</p>
    </div>

    <div id="bar">
      <p>some content that should be hidden by default bar</p>
    </div>

    <div id="choo">
      <p>some content that should be hidden by default choo</p>
    </div>

    </div>

</div>


however the following wont because the contents of the left and right boxes now have different parents

Code: Select all
<div id="left">
    <input type="radio" name="button" id="foo">
    <label for="foo">show foo</label>

    <br><input type="radio" name="button" id="bar">
    <label for="bar">show bar</label>

    <br><input type="radio" name="button" id="choo">
    <label for="bar">show choo</label>
</div>

<div id="right">
    <div id="foo">
      <p>some content that should be hidden by default foo</p>
    </div>

    <div id="bar">
      <p>some content that should be hidden by default bar</p>
    </div>

    <div id="choo">
      <p>some content that should be hidden by default choo</p>
    </div>
</div>


So i would need a general selector that allow me to target any element regardless of its relationship to the other elements around it

so

Code: Select all
foo, bar, choo
{
display:none
}

input#foo:checked ~ #right div.foo,
input#bar:checked ~ #right div.bar,
input#choo:checked ~ #right div.choo
{
display:block
}


would become something like

Code: Select all
foo, bar, choo
{
display:none
}

input#foo:checked * #right div.foo,
input#bar:checked * #right div.bar,
input#choo:checked * #right div.choo
{
display:block
}


where * represents a theoretical universal selector that can get any named element anywhere in the document, regardless of its relationship to other elements within the document.

jscher2000

User avatar
 
Posts: 10752
Joined: December 19th, 2004, 12:26 am
Location: Silicon Valley, CA USA

Post Posted June 24th, 2019, 9:51 am

Bethrezen wrote:i take it what you are suggesting is instead of controlling the css via the script setup the css in the style sheet and then just swap classes back and forth.

it's an interesting idea and i had actually though about that, but once again my limited knowledge of JavaScript is the stumbling block as class swapping is not really something that i understand.


Are there parts of my code that you can't follow? These may be the less familiar bits:

* https://developer.mozilla.org/docs/Web/API/Document/querySelector
* https://developer.mozilla.org/docs/Web/API/Element/classList

Bethrezen
 
Posts: 405
Joined: September 13th, 2003, 11:56 am

Post Posted July 2nd, 2019, 2:39 pm

Are there parts of my code that you can't follow?

Code: Select all
function checked(event)
{
  var id = event.target.id;
  var elOld = document.querySelector('.currentTab');

  if (elOld)
  {
    elOld.classList.remove('currentTab');
  }

  var elNew = document.querySelector('[data-name="' + id + '"]');

  if (elNew)
  {
    elNew.classList.add('currentTab');
  }
  else
  {
    console.log('Did not find data-name: ' + id);
  }
}


yeah there are a couple of things first off I'm not really sure what those if conditions are supposed to do, for example elNew as far as i can tell is just the id of the div that is being targeted so I'm not really sure what they are for.

i would of expected a something like if (somthing !== somthing) or what ever I'm not really sure how the value of the data attribute can qualify as a condition ?!?!

also I'm not really sure why id need to add .currentTab or even where I'm supposed to add it, I'm just not following what this is supposed to do

Code: Select all
var id = event.target.id;


now that i get you are retrieving the id value of which ever radio button was checked.

Code: Select all
var elOld = document.querySelector('.currentTab');

  if (elOld)
  {
    elOld.classList.remove('currentTab');
  }


this I'm not really sure what you are doing, I'm assuming judging from the classList.remove that this is removing the class currentTab, although I'm not really sure where you would even add that in the first place

Code: Select all
var elNew = document.querySelector('[data-name="' + id + '"]');


this i get that's retrieving the div that has the same data attribute as the radio button that was clicked on so if the radio button foo was clicked then this is retrieving the the div with the data-name='foo'

Code: Select all
if (elNew)
  {
    elNew.classList.add('currentTab');
  }


and again here I'm not really sure what you are doing here, I'm assuming from classList.add that you are adding the class currentTab but again i have no idea where nor am i clear on exactly what this is supposed to achieve

are you setting up the css to do the showing and hiding in the style sheet ? and rather than letting the script control the css as i was doing are you simply switching back and forth between

Code: Select all
.show
{
display:block
}

.hide
{
display:none
}


also i had a look at the pages you linked to but they are about as clear as mud and i think i could probably do with a slightly simpler explanation that is a bit easer to understand or perhaps more accurately one that is less filled with jargon so i can actually follow what they are talking about.

jscher2000

User avatar
 
Posts: 10752
Joined: December 19th, 2004, 12:26 am
Location: Silicon Valley, CA USA

Post Posted July 2nd, 2019, 3:48 pm


Bethrezen
 
Posts: 405
Joined: September 13th, 2003, 11:56 am

Post Posted July 7th, 2019, 2:20 pm



That makes a little more sense though I'm not really sure how i would apply that to the page i'm working on, here is a cut down version of the page in question

Code: Select all
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<title>X Beyond the Frontier - Info</title>

<style>
*
{
box-sizing:border-box;
}

html,body
{
margin:0;
padding:0;
}

body
{
background-color:#172331;
font-size:0;
}

input#equipment,
input#stations,
label[for="equipment"],
label[for="stations"]
{
display:inline-block;
vertical-align:middle;
}

input#equipment,
input#stations
{
margin:6px 6px 8px 6px
}

label[for="equipment"],
label[for="stations"]
{
color:white;
font-size:1rem;
padding:3px 0 4px 0;
margin-top:-1px;
color:#bfc6e5;
}

main
{
height:calc(100vh - 27px);
width:100vw;
display:inline-flex;
font-family: Arial;
font-size:0.85rem;
}

#left
{
height:100%;
min-width:328px;
overflow-y:scroll;
}

.wrapper div
{
color:#bfc6e5;
background-color:#0b111a;

padding:10px;
margin:16px;

border:2px solid #000001;
border-radius: 6px;

min-width:280px
}

.wrapper div p
{
padding:2px;
margin:0;
font-weight:bold;
}

.wrapper div input,
.wrapper div label
{
margin-top:4px;
margin-bottom:4px;
margin-left:0;
}

#right
{
width:calc(100% - 328px);
height:100%;
overflow:auto;
}

div.container
{
width:544px;
padding:10px;
margin:10%;

color:#bfc6e5;
background-color:#0b111a;
border:2px solid #000001;
border-radius: 6px;

font-size:0;
}

div.container img
{
margin-right:10px;
}

div.container div
{
width:calc(100% - 150px);
}

div.container img,
div.container div
{
display:inline-block;
vertical-align:top
}

div.container p,
div.container span,
div.container div p,
div.container div span
{
margin:0;
font-size:0.85rem;
}

div.container > p:nth-of-type(1),
div.container > p:nth-of-type(2),
div.container > p:nth-of-type(3),
div.container div > span
{
margin-bottom:16px;
}

div.container > p:nth-of-type(2),
div.container div > p:nth-of-type(4)
{
margin-top:16px;
}

div.container > p:nth-of-type(1),
div.container > p:nth-of-type(2),
div.container div > p:nth-of-type(1),
div.container div > p:nth-of-type(4),
div.container div > span:nth-of-type(1),
div.container p span
{
font-weight:bold
}

div.container div span
{
display:inline-block;
padding-right: 3px;
}
</style>

</head>

<body>

<input type="checkbox" name="button" id="equipment">
<label for="equipment">Weapons and Equipment</label>

<input type="checkbox" name="button" id="stations">
<label for="stations">Stations</label>

<main>

  <div id="left">

    <div class="wrapper target" data-name="equipment">

      <div>
        <p>Weapons</p>
        <input type="radio" name="button" id="aire">
        <label for="aire">Alpha Impulse Ray Emitter</label>
      </div>

    </div>

    <div class="wrapper target" data-name="stations">

      <div>
        <p>Power</p>
        <input type="radio" name="button" id="solar_power_plant">
        <label for="solar_power_plant">Solar Power Plant</label>
      </div>

    </div>

  </div>

  <div id="right">

    <div class="container target" data-name="aire">
      <p>Alpha Impulse Ray Emitter</p>

      <img src="images/alpha_impulse_ray_emitter.png" alt="Alpha Impulse Ray Emitter Image">

      <div>
        <p>Price:</p>
        <p>Laser Forge: ??? cr</p>
        <p>Equipment Dock: 720 cr</p>

        <p>Description:</p>
        <p>This is a basic laser that is used in many light weapons and smaller fighter craft.</p>
      </div>

      <p>Available from:</p>
      <p>Laser Forge: Three Worlds, Menelaus' Frontier</p>
      <p>Equipment Dock: Ceo's Buckzoid, Seizewell, Company Pride</p>
    </div>

    <div class="container target" data-name="solar_power_plant">
      <p>Solar Power Plant</p>

      <img src="images/placeholder.png" alt="Solar Power Plant Image">

      <div>
        <span>Price:</span> <span>7,200 cr</span>

        <p>Description:</p>
        <p>Solar Power Plants produce energy, they are found everywhere in the known universe and are operated and controlled by all races.</p>
      </div>

    </div>

  </div>

</main>

<script>

function checked(event)
{
  var id = event.target.id;
  document.querySelector('[data-name="'+ id +'"]').style.cssText="display:block";

  var notchecked = document.querySelectorAll('input:not(:checked)');

  for (i = 0; i < notchecked.length; i++)
  {
    var id = notchecked[i].id;
    document.querySelector('[data-name="'+ id +'"]').style.cssText="display:none";
  }

  if(document.querySelector('input#equipment:not(:checked)')
     && document.querySelector('[data-name="equipment"] input:checked'))
  {
    var id = document.querySelector('[data-name="equipment"] input:checked').id;
   
    document.querySelector('[data-name="'+ id +'"]').style.cssText="display:none";
    document.querySelector('[data-name="equipment"] input:checked').checked = false;
  }

  if(document.querySelector('input#stations:not(:checked)')
     && document.querySelector('[data-name="stations"] input:checked'))
  {
    var id = document.querySelector('[data-name="stations"] input:checked').id;
   
    document.querySelector('[data-name="'+ id +'"]').style.cssText="display:none";
    document.querySelector('[data-name="stations"] input:checked').checked = false;
  }
}

var inputs = document.querySelectorAll("input");
var targets = document.querySelectorAll('.target');

for (i = 0; i < targets.length && inputs.length; i++)
{
  inputs[i].checked = false;
  inputs[i].addEventListener("change", checked);
  targets[i].style.cssText="display:none";
}

</script>

</body>
</html>


while everything works fine I'm sure the scripting could be better, although the fact i got this working at all is a minor miracle given my limited knowledge of javascript.

Return to Web Development / Standards Evangelism


Who is online

Users browsing this forum: No registered users and 1 guest