CutCodeDown

Web Development => Snippet Sharing => Topic started by: jmrker on 12 Nov 2019, 03:25:24 pm

Title: Alternate select submission
Post by: jmrker on 12 Nov 2019, 03:25:24 pm
I wanted to contribute to your snippit collection section, so I created the following script. 
I wanted to have more dropdown select choices visible without constant scrolling of a single list.

It does what I intended, but it is not perfect as I would like to:
1. be more responsive to screen size or orientation changes.
(Thought it might be ralated to 'width' setting of .selectWrapper class and #selectSection element.

2. be less dependent on ID settings should I want to expand to multiple elements

3. allow for multiple selection option like the native version

4. and finally, be less dependent upon JS being available or browser incompatibilities.
(Separate question would involve how to default to normal select should JS not be available)

The following script works without the desired options above and
I am open to suggested improvements to the following script:
Code: [Select]
<!DOCTYPE html><html lang="en"><head><title> Custom Select </title>
<meta charset="UTF-8">
<meta name="viewport" content="width-device-width,initial-scale=1.0, user-scalable=yes"/>
<style>
 .flexbox {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
 }
 .item { width: 25%; text-align: left; }  /* change % for different # on row */
 .item:hover { cursor: pointer; }
 .hide { display: none; }

 .selectWrapper  {
    box-sizing: border-box; cursor: pointer;
    border: 1px solid tan;  width: 25em;   /* 25%; or 25em; should be same value as below */
 }
 .item:hover { background: tan; }

 #selectSection {
    position: absolute;     width: 25em;   /* 25%; or 25em; should be same value as above */
    background: wheat;
    z-index: 1; line-height: 1.6;
 }
 #btnSelect { font-size: 1.4em; }
 .btnSelectR::after { content: '\25bc'; } /* right arrow */
 .btnSelectD::after { content: '\25ba'; } /* down arrow */
 #btnSelect:hover { background: lime; }
 #btnClrPick { float: right; }
 #btnClrPick:hover { background: lime; }
</style>

</head><body>

<div class="selectWrapper">
 <label id="btnSelect" class="btnSelectR" data-section="selectSection"> Select: </label>
 <span id="selectedPick"></span>
 <label id="btnClrPick">Clr</label>

 <div id="selectSection" class="hide">
  <div class="flexbox">
   <div class="item"> zero </div>
   <div class="item"> one </div>
   <div class="item"> two </div>
   <div class="item"> three </div>
   <div class="item"> four </div>
   <div class="item"> five </div>
   <div class="item"> six </div>
   <div class="item"> seven </div>
   <div class="item"> eight </div>
   <div class="item"> nine </div>
   <div class="item"> ten </div>
   <div class="item"> eleven </div>
   <div class="item"> twelve </div>
   <div class="item"> thirteen </div>
   <div class="item"> fourteen </div>
   <div class="item"> fifteen</div>
  </div>
 </div>
</div>

<h2> Additional information on the page </h2>

<script>
 console.clear();

 function  init() {

/* Syntax: getOrder(spec)
   Arguments: spec: the element whose position to return
   Returns: The position of the given element within the given container,
            relative to other elements of the same type
            (0=first, -1=spec not found in main).
*/
  function getSelection(spec) {
    var p = -1, item = spec.parentNode.firstElementChild;
    if (item) do { ++p;
      if (item == spec) { break; }
    } while (item = item.nextElementSibling);
    return p;  // return position (0 .. #) or -1 if not found
  }

  function togglePicks(ids) { document.getElementById(ids).classList.toggle('hide'); }
  function clrPick(ids) { document.getElementById(ids).innerHTML = ''; }
  function togglePicksPointer() {
    var ptr = document.getElementById("btnSelect");
    if (ptr.classList.contains('btnSelectR')) { ptr.classList.toggle('btnSelectD'); }
                                        else  { ptr.classList.toggle('btnSelectR'); }
  }

  var sel = document.querySelectorAll('#selectSection .item');
  for (let i=0; i<sel.length; i++) {
    sel[i].addEventListener('click', 

      function () {  // change message as needed
        document.getElementById('selectedPick').innerHTML =
          getSelection(sel[i]) + sel[i].textContent || sel[i].innerHTML;
        togglePicks('selectSection');  // removes picks list section
        togglePicksPointer('btnSelect');
      }
    );
  }
  document.getElementById('btnClrPick')
    .addEventListener('click', function () { clrPick('selectedPick') }, false);

  document.getElementById('btnSelect')
    .addEventListener('click',
     function () {
       togglePicks(this.dataset.section);
       togglePicksPointer();
     }, false);
 }
 init();
</script>
</body></html>
Title: Re: Alternate select submission
Post by: Jason Knight on 12 Nov 2019, 06:39:15 pm
What's this random "CLR" text that's half cut off inside the faux-select even for?

On the whole, I'm not sure I can even figure out what this is supposed to do, given that it doesn't do anything here. The gibberish endless pointless div for nothing, lack of a proper selection method, LABEL tags without any input/select/textarea tags for them to.. well... LABEL.... etc, etc... It's a mess.

Or at least, nothing I'd be throwing JavaScript at. Particularly when throwing click events at DIV is basically telling users with accessibility needs to sod off.

What is this supposed to be/do?

-- edit -- It almost feels like you're trying to fake a select scripting only, telling users with accessibility needs to BOHICA. If I'm understanding what this is trying (and failing) to do, I'd probably be using radio buttons with proper labels, and quite possibly no scripting whatsoever.

Semantic markup and scripting-off functionality FIRST, THEN enhance.

Oh, and side note, the <title> should be AFTER your charset <meta> since you basically are forcing the browser to start over from the beginning.

Hence why I usually put <title> last right before </head>. First thing after <head> should be the charset <meta>.
Title: Re: Alternate select submission
Post by: jmrker on 13 Nov 2019, 11:35:24 pm
OK, I took your criticism to heart and re-wrote it more to your liking.
It was never meant to be a BOHICA submission, just something different and hopefully useful for whatever user might like to use as an alternative to the native drop down <select>.

I took out the 'Clr' button as it bothered you.  It was meant to clear the user choice without having to include an option with a value of ''.  The choices included are just 'dummy' entries as they could be links, other events or other actions.

Does it need more work?  Probably. 
But that is why I asked for a critical look so I could learn.
I came to learn how to be a better coder, so give me your best shots.

Code: [Select]
<!DOCTYPE html><html lang="en"><head><title> Custom Select </title>
<meta charset="UTF-8">
<meta name="viewport" content="width-device-width,initial-scale=1.0, user-scalable=yes"/>
<style>
 html { box-sizing: border-box; }
 *, *:before, *:after { box-sizing: inherit; }

 .flexbox {
    display: flex;
    flex: 1 0 25%;
    flex-wrap: wrap;
 }
 .hide { display: none; }
 
 .item { width: 25%; } /* min-width: 6em; }    /* change % for different # on row */
 .item:hover { cursor: pointer; background: tan; }

 .selectWrapper  {
    box-sizing: border-box; cursor: pointer;
    border: 1px solid tan;
    width: 50%;
 }

 #selectSection {
    position: relative;     width: 100%;
    background: wheat;     
    z-index: 1; line-height: 1.6;
 }
 input[type="radio"] { position: absolute; left: -99em; }

 #cbxSelect:not(:checked) ~ #selectSection { display: none; }

 #cbxSelect { position: absolute; left: -99em; }
 #cbxSelect:checked + label { background: lime; }
 #cbxSelect + #btnSelect::after { content: "\25bc"; }         /* right arrow */
 #cbxSelect:checked + #btnSelect::after { content: "\25ba"; } /* down arrow */

 #btnSelect { font-size: 1.4em; }
 #btnSelect:hover { background: lime; }
</style>

<style>
/*  @media only screen and (max-width: 80em) { .item { flex: 1 0 20%; } } */
 @media only screen and (max-width: 65em) { .item { flex: 1 0 25%; } }
 @media only screen and (max-width: 50em) { .item { flex: 1 0 33%; } }
 @media only screen and (max-width: 35em) { .item { flex: 1 0 50%; } }
 @media only screen and (max-width: 20em) { .item { flex: 1 0 100%; } }
</style>

</head><body>
<div class="selectWrapper">
 <input type="checkbox" id="cbxSelect">
 <label id="btnSelect" for="cbxSelect">Select: </label>
 <span id="selectedPick"></span>

 <div id="selectSection" class="flexbox">
<!-- // for blank selection if desired
   <input id="rb" type="radio" name="itm" value=""> <label for="rb" class="item"> </label>
-->
   <input id="rb0"  type="radio" name="itm"  value="0"> <label for="rb0"  class="item"> zero </label>
   <input id="rb1"  type="radio" name="itm"  value="1"> <label for="rb1"  class="item"> one </label>
   <input id="rb2"  type="radio" name="itm"  value="2"> <label for="rb2"  class="item"> two </label>
   <input id="rb3"  type="radio" name="itm"  value="3"> <label for="rb3"  class="item"> three </label>
   <input id="rb4"  type="radio" name="itm"  value="4"> <label for="rb4"  class="item"> four </label>
   <input id="rb5"  type="radio" name="itm"  value="5"> <label for="rb5"  class="item"> five </label>
   <input id="rb6"  type="radio" name="itm"  value="6"> <label for="rb6"  class="item"> six </label>
   <input id="rb7"  type="radio" name="itm"  value="7"> <label for="rb7"  class="item"> seven </label>
   <input id="rb8"  type="radio" name="itm"  value="8"> <label for="rb8"  class="item"> eight </label>
   <input id="rb9"  type="radio" name="itm"  value="9"> <label for="rb9"  class="item"> nine </label>
   <input id="rb10" type="radio" name="itm" value="10"> <label for="rb10" class="item"> ten </label>
   <input id="rb11" type="radio" name="itm" value="11"> <label for="rb11" class="item"> eleven </label>
   <input id="rb12" type="radio" name="itm" value="12"> <label for="rb12" class="item"> twelve </label>
   <input id="rb13" type="radio" name="itm" value="13"> <label for="rb13" class="item"> thirteen </label>
   <input id="rb14" type="radio" name="itm" value="14"> <label for="rb14" class="item"> fourteen </label>
   <input id="rb15" type="radio" name="itm" value="15"> <label for="rb15" class="item"> fifteen </label>
 </div>
</div>

<h2> Additional information on the page </h2>

<script>
 console.clear();

 function  init() {
   var sel = document.querySelectorAll("#selectSection input");
   for (let i=0; i<sel.length; i++) {
     sel[i].addEventListener(
       "click", 
       function () {  // change message as needed

// allow multiple selection display if needed
//         document.getElementById("selectedPick").innerHTML += sel[i].value+' ';

// single selection display until changed for user/submission needs
         document.getElementById("selectedPick").innerHTML = sel[i].value
           + ' ' + this.nextElementSibling.textContent;

// optional display action [ retain image or automatic clear of choices]
         document.getElementById('cbxSelect').click();
       }
     );
   }
 } init();
</script>
</body></html>

What would be your suggestions to remove the remaining JS code?  And why?
Title: Re: Alternate select submission
Post by: Jason Knight on 14 Nov 2019, 07:13:35 am
Well first off, if you want the possibility of multiple selections, that's checkbox's job, not radio buttons. THAT would require JavaScript for the "select none" option.

The first step is to get the proper semantic markup for the functionality in question. That would most likely be:

Code: [Select]
<fieldset class="fauxSelect">
<legend>Number:</legend>

<input type="radio" id="numbers_zero" name="numbers" value="0">
<label for="numbers_zero">zero</label><br>

<input type="radio" id="numbers_one" name="numbers" value="1">
<label for="numbers_one">one</label><br>

<input type="radio" id="numbers_two" name="numbers" value="2">
<label for="numbers_two">two</label><br>

<input type="radio" id="numbers_three" name="numbers" value="3">
<label for="numbers_three">three</label><br>

<input type="radio" id="numbers_four" name="numbers" value="4">
<label for="numbers_four">four</label><br>

<input type="radio" id="numbers_five" name="numbers" value="5">
<label for="numbers_five">five</label><br>

<input type="radio" id="numbers_six" name="numbers" value="6">
<label for="numbers_six">six</label><br>

<input type="radio" id="numbers_seven" name="numbers" value="7">
<label for="numbers_seven">seven</label><br>

<input type="radio" id="numbers_eight" name="numbers" value="8">
<label for="numbers_eight">eight</label><br>

<input type="radio" id="numbers_nine" name="numbers" value="9">
<label for="numbers_nine">nine</label><br>

<input type="radio" id="numbers_ten" name="numbers" value="10">
<label for="numbers_ten">ten</label><br>

<input type="radio" id="numbers_eleven" name="numbers" value="11">
<label for="numbers_eleven">eleven</label><br>

<input type="radio" id="numbers_twelve" name="numbers" value="12">
<label for="numbers_twelve">twelve</label><br>

<input type="radio" id="numbers_thirteen" name="numbers" value="13">
<label for="numbers_thirteen">thirteen</label><br>

<input type="radio" id="numbers_fourteen" name="numbers" value="14">
<label for="numbers_fourteen">fourteen</label><br>

<input type="radio" id="numbers_fifteen" name="numbers" value="15">
<label for="numbers_fifteen">fifteen</label><br>

</fieldset>

Then we add two more input at the very start, one for clearing it, one for showing the submenu, and then wrap the children in a DIV for the show/hide wrapper.

Code: [Select]
<fieldset class="fauxSelect">
<legend>Number:</legend>

<div>

<input
type="radio"
name="numbers"
id="numbers_clear"
class="fauxSelect_clear"
hidden
><label for="numbers_clear">Clear</label>

<input
type="radio"
name="numbers"
id="numbers_show"
class="fauxSelect_show"
hidden
>
<div>

<input type="radio" id="numbers_zero" name="numbers" value="0">
<label for="numbers_zero">zero</label><br>

<input type="radio" id="numbers_one" name="numbers" value="1">
<label for="numbers_one">one</label><br>

<input type="radio" id="numbers_two" name="numbers" value="2">
<label for="numbers_two">two</label><br>

<input type="radio" id="numbers_three" name="numbers" value="3">
<label for="numbers_three">three</label><br>

<input type="radio" id="numbers_four" name="numbers" value="4">
<label for="numbers_four">four</label><br>

<input type="radio" id="numbers_five" name="numbers" value="5">
<label for="numbers_five">five</label><br>

<input type="radio" id="numbers_six" name="numbers" value="6">
<label for="numbers_six">six</label><br>

<input type="radio" id="numbers_seven" name="numbers" value="7">
<label for="numbers_seven">seven</label><br>

<input type="radio" id="numbers_eight" name="numbers" value="8">
<label for="numbers_eight">eight</label><br>

<input type="radio" id="numbers_nine" name="numbers" value="9">
<label for="numbers_nine">nine</label><br>

<input type="radio" id="numbers_ten" name="numbers" value="10">
<label for="numbers_ten">ten</label><br>

<input type="radio" id="numbers_eleven" name="numbers" value="11">
<label for="numbers_eleven">eleven</label><br>

<input type="radio" id="numbers_twelve" name="numbers" value="12">
<label for="numbers_twelve">twelve</label><br>

<input type="radio" id="numbers_thirteen" name="numbers" value="13">
<label for="numbers_thirteen">thirteen</label><br>

<input type="radio" id="numbers_fourteen" name="numbers" value="14">
<label for="numbers_fourteen">fourteen</label><br>

<input type="radio" id="numbers_fifteen" name="numbers" value="15">
<label for="numbers_fifteen">fifteen</label><br>

<label for="numbers_show" class="fauxSelect_showLabel"></label>

</div>

</div>

</fieldset>

Label order and placement might seem a bit funky, particularly the last one, but that's to leverage how CSS can only "look down and forward".

The magic here would be to have the DIV set in its default state to display:inline and nothing else, to float the first label right, the second one left, and then to set ALL labels and inputs to hidden / absolute positioned off screen.

Then you just .fauxSelect > div > input:checked + label { display:block; position:static; } which would make the selected label pop up inbetween the two floats.

Boom, you don't need JavaScript if it's single selection only. Multiple selections you'd use checkbox instead of radio, and the only thing your clear button would have to do is use JS to walk the DOM setting all checkboxes to unchecked -- I'd use button instead of the select + label for the clear.

Note, there was nothing wrong with having a clear button, but your initial code didn't work and all I saw was the C and half the L as the rest was cut off by the containing box.

Something like:

Code: [Select]

.fauxSelect label {
cursor:pointer;
}

.fauxSelect:after {
/* since we can't use overflow:hidden with a dropdown menu */
content:"";
display:block;
clear:both;
}

.fauxSelect > div {
position:relative;
display:inline-block;
width:100%;
max-width:16em;
border:2px solid rgba(0,0,0,0.5);
border-radius:0.5em;
}

.fauxSelect legend {
float:left; /* sneaky trick to make legends behave "normally" */
padding:0.25em 0.4em 0.25em 0;
margin:2px 0;
}

.fauxSelect label {
display:inline-block;
padding:0.25em 0.5em;
}

.fauxSelect_clear + label {
position:relative;
z-index:999;
float:right;
background:#EEE;
border-left:2px solid rgba(0,0,0,0.5);
box-shadow:inset 0 0 0.5em rgba(0,0,0,0.3);
border-radius:0 0.5em 0.5em 0;
}

.fauxSelect .fauxSelect_showLabel {
position:relative;
display:block;
text-align:right;
}

.fauxSelect_showLabel:before {
float:left;
content:"Select";
}

.fauxSelect_showLabel:after {
padding-right:0.5em;
content:"\25BC";
}

.fauxSelect .fauxSelect_show:checked + div .fauxSelect_showLabel:before,
.fauxSelect > div > div input:checked ~ .fauxSelect_showLabel:before {
display:none;
}

.fauxSelect > div > div {
display:inline;
}

.fauxSelect > div > div br,
.fauxSelect .fauxSelect_show:checked + div .fauxSelect_showLabel {
display:none;
}

.fauxSelect > div > div input,
.fauxSelect > div > div input + label {
position:absolute;
left:-999em;
}

.fauxSelect > div > div input:checked + label {
position:static;
float:left;
padding-right:2em; /* so it doesn't pass under the down-triangle */
cursor:default;
}

.fauxSelect_show + div {
overflow:hidden;
max-height:1.5em;
transition:max-height 0.5s;
}

.fauxSelect_show:checked + div {
position:absolute;
top:2.25em;
left:0;
max-height:100vh;
width:100%;
display:flex;
flex-direction:row;
flex-wrap:wrap;
border:2px solid rgba(0,0,0,0.5);
background:#CCC;
}

.fauxSelect .fauxSelect_show:checked + div input + label {
position:static;
display:block;
flex:1 0 auto;
width:26%;
margin:0.25em;
text-align:center;
background:#EEE;
}

Live demo here:

https://cutcodedown.com/for_others/jmerker/fauxSelect/

Mind you untested beyond FF and Vivaldi, but should run fine most browsers IE 10/newer.

The advantages to this approach is it works without any JS so you don't even have to THINK about accessibility violations, AND it gracefully degrades to still be functional scripting off, AND you can use it in a normal form as it's all normal form elements. Things that also should be considered minimums for accessibility.

As to IE9/earlier, my current deployment plan for those browsers is to send them neither CSS or JavaScript whatsoever. GOOD scripting AND good CSS should enhance an already working semantic page... so it's not leaving them out in the cold entirely, it's just giving those who refuse to join us in this century a degraded experience.

Title: Re: Alternate select submission
Post by: mmerlinn on 15 Nov 2019, 01:13:44 am
Jason, what is the earliest FF that your demo works with?
Title: Re: Alternate select submission
Post by: Jason Knight on 15 Nov 2019, 04:47:22 am
Jason, what is the earliest FF that your demo works with?
No clue, you'd probably have to go back a decade or more to find one that doesn't support :checked and/or CSS3 animations. It would probably be contemporary to Opera 11.

With FF it's really not an issue that should even come up... unless it's that one 70 year old still living in his mothers basement with a windows 98 machine patched-up with KernelEx and the hacked userenv.dll that lets most 2k/XP software run.

Basically it would have to be old enough a copy that flex isn't supported anyways... and really that's become the support cutoff for most developers now.
Title: Re: Alternate select submission
Post by: mmerlinn on 15 Nov 2019, 12:31:14 pm

Basically it would have to be old enough a copy that flex isn't supported anyways... and really that's become the support cutoff for most developers now.

Since flex seems to be the defining issue, then flex started being supported with IE11, FF28, Chrome29, Safair9, & Opera17 according to the following link:

https://www.w3schools.com/cssref/css3_browsersupport.asp
Title: Re: Alternate select submission
Post by: Jason Knight on 15 Nov 2019, 02:17:55 pm
according to the following link:
... and as always W3Fools doesn't quite give accurate information.

https://caniuse.com/#feat=flexbox

Note that Opera 12.1 supported it, but then LOST it when they went to -webkit unless you used the prefix. It was part of why many people (myself included) felt the abandonment of Presto was a step backwards as Webkit was slower, used more memory, and supported less things properly. Technically support goes back to IE10 with the -o- prefix, but nobody talks about that now that most developers are telling browser prefixes to sod off.

The loss of flex would only mean it doesn't have multiple columns, the main functionality is provided by :checked:

https://caniuse.com/#feat=mdn-css_selectors_checked

Somehow I thought that :checked had partial IE7 support, but I can't find any proof of that now apart from the fact it works in IE11's "emulation" mode on SOME machines but not others. (which typically means nothing, IE across different OS versions never behaves the same)

So if you can live with the dropdown going to a single column, it's deployable back to IE9, FF2, Opera 10, Chrome 4, and Safari 3.1

And the advanced combinators:

https://caniuse.com/#feat=mdn-css_selectors_general_sibling
https://caniuse.com/#feat=mdn-css_selectors_adjacent_sibling

Going back even further.