Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Filterable table with dropdowns in header

I wanted to share a method for how to get a filterable table that uses dropdowns in the header to filter its rows. This method avoids the multiple tabs/sections needed for other accordion solutions presented in the CSS Wizardry wiki called  Filtering repeating section displays according to criteria . It is possible to combine several of these filter criterias at once. This is something I have been thinking about for a while and made a working example recently. My example uses only CSS. When writing up this post I stumbled across GiGs  sheetworker solution  which work with this as well. It cuts down all the CSS but introduces a slight delay. The example in the following posts deal with equipment, but this can be used for other things as well such as: Weapons that have damage type, quality level, enchantment level Spells that have spell schools, damage type, spell level Skills that have a main atribute and a certain proficiency etc Requirements: I normally make my tables from divs. There is a header row, and then a repeating section which make up the data rows. The filter categories you want to filter for needs to be select elements and the options are the values used for filtering.  Explanation This works the same way as the linked CSS wizardry solution with accordions, but expanded. Each row in the repeating section uses several hidden radio buttons and a visible select element, and they share their name. The options in the select element correspond to one radio button each. Selecting the option in the dropdown makes the radiobutton checked. In the example below this is duplicated since I have two categories. In adition to this the same thing is done with the header div, but the radiobutton is moved in the html to a possition so that CSS logic can be used, i.e they need to be able to target children of siblings so they need to be moved up to atleast the same level as the <fieldset>. The CSS is exactly like the linked example; the rows are normally hidden, but when the outer radiobutton and the inner radiobutton of the row in the repeating section match then the row is displayed. However, instead of having different tabs where each have a different outer radiobutton ticked, here is only one section and the select element in the header controls which radiobutton is checked.  When I changed to this approach, my html became much smaller and easier since only one section is required, but instead the CSS required grew some. Example In the picure below the last two columns are the select elements, the first one is the type of equiptment (cloths, treasure, potions, etc) and the second one is where the character has this equiptment (on the character, in their pack, in their house, bank, etc): Using the dropdowns in the header the list can be filtered: Here I filter the type to only show potions: Then I filter the location to only show things on the character, giving me a single result: I will post three working examples: html/css - simple. Uses one filter category html/css - complex. Uses two filter categories to show how to add more categories GiGs sheetworker - complex. Uses two filter categories
1600995895

Edited 1600996090
html/css - simple Screenshot of a simple example when showing all rows: The table is now filtered by 'Other': Here is the full html code for the example: < div   class = "equipment-list" >   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"   class = "equipment-all"       value = "All types"   checked  />   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"   class = "equipment-clothes"   value = "Clothing"           />   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"   class = "equipment-potions"   value = "Potion"             />   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"   class = "equipment-other"     value = "Other"              />   < div   class = "equipment header row" >     < div   class = "equipment header field" >Equipment</ div >     < div   class = "equipment header field" >Description</ div >     < select   class = "equipment header field select type"   name = "attr_equipment_type_filter" >       < option >All types</ option >       < option >Clothing</ option >       < option >Potion</ option >       < option >Other</ option >     </ select >   </ div >   < fieldset   class = "repeating_equipment" >   < input   type = "radio"   hidden   name = "attr_equipment_type"   class = "equipment-all"       value = "All types"   checked  />   < input   type = "radio"   hidden   name = "attr_equipment_type"   class = "equipment-clothes"   value = "Clothing"           />   < input   type = "radio"   hidden   name = "attr_equipment_type"   class = "equipment-potions"   value = "Potion"             />   < input   type = "radio"   hidden   name = "attr_equipment_type"   class = "equipment-other"     value = "Other"              />   < div   class = "equipment data row" >       < input   class = "equipment data field"   type = "text"   name = "attr_equipment_item"   value = "" />       < input   class = "equipment data field"   type = "text"   name = "attr_equipment_desc"   value = "" />       < select   class = "equipment data field select"   name = "attr_equipment_type" >         < option >All types</ option >         < option >Clothing</ option >         < option >Potion</ option > < option >Other</ option >       </ select >     </ div >   </ fieldset > </ div >   Here is the css code for the example: /*============================== =                              = =      Some other styling      = =                              = ==============================*/ .charsheet  {   --background-row :   #cccccc ;   --background-header :   #999999 ;   --height-header-select :   1.38 em ;   --inter-row-space :   3 px ;   } .sheet-field  {    background-color :   var(--background-row) ;    width :   100 % ;    padding :   0 px ; } .sheet-select  {    margin-bottom :   0 px ; } .sheet-header.sheet-field  {    background-color :   var(--background-header) ; } .sheet-header.sheet-select  {    height :   var(--height-header-select) ; } .sheet-data.sheet-row  {    background-color :   var(--background-row) ; } .sheet-equipment.sheet-row  {    display :   grid ;    grid-template-columns :   1 fr  1 fr  7 em ;    margin-bottom :   var(--inter-row-space) ; } /*============================== =                              = =      Relevant section        = =                              = ==============================*/ /* Rows are hidden by default */ .sheet-data.sheet-row  {    display :   none ; } /* All rows marked with 'All types' should always display */ .repitem   input .sheet-equipment-all:checked ~ .sheet-data.sheet-row , /* When 'All types' is selected in the header, always show all rows */ input .sheet-equipment-all:checked ~ .repcontainer   .sheet-data.sheet-row , /* For the rest, when something is selected in the header, only show rows marked with the same value */ input .sheet-equipment-clothes:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-clothes:checked ~ .sheet-data.sheet-row , input .sheet-equipment-potions:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-potions:checked ~ .sheet-data.sheet-row , input .sheet-equipment-other:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-other:checked ~ .sheet-data.sheet-row  {    display :   grid ; }
html/css - complex Screenshot of a complex example when showing all the rows: Wanting to use a potion I first filter for the item type being  Potion : Then I filter for potions that are on my  Character : Here is the html code for the example: < div   class = "equipment-list" >   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"       class = "equipment-all"        value = "All types"       checked  />   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"       class = "equipment-clothes"    value = "Clothes"                />   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"       class = "equipment-potions"    value = "Potions"                />   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"       class = "equipment-other"      value = "Other"                  />   < input   type = "radio"   hidden   name = "attr_equipment_location_filter"   class = "location-all"         value = "All locations"   checked  />   < input   type = "radio"   hidden   name = "attr_equipment_location_filter"   class = "location-character"   value = "Character"              />   < input   type = "radio"   hidden   name = "attr_equipment_location_filter"   class = "location-other"       value = "Other"                  />   < div   class = "equipment header row" >     < div   class = "equipment header field" >Equipment</ div >     < div   class = "equipment header field" >Description</ div >     < select   class = "equipment header field select"   name = "attr_equipment_type_filter" >       < option >All types</ option >       < option >Clothes</ option >       < option >Potions</ option >       < option >Other</ option >     </ select >     < select   class = "equipment header field select"   name = "attr_equipment_location_filter" >       < option >All locations</ option >       < option >Character</ option >       < option >Other</ option >     </ select >   </ div >   < fieldset   class = "repeating_equipment" >     < input   type = "radio"   hidden   name = "attr_equipment_type"       class = "equipment-all"        value = "All types"       checked  />     < input   type = "radio"   hidden   name = "attr_equipment_type"       class = "equipment-clothes"    value = "Clothes"                />     < input   type = "radio"   hidden   name = "attr_equipment_type"       class = "equipment-potions"    value = "Potions"                />     < input   type = "radio"   hidden   name = "attr_equipment_type"       class = "equipment-other"      value = "Other"                  />     < input   type = "radio"   hidden   name = "attr_equipment_location"   class = "location-all"         value = "All locations"   checked  />     < input   type = "radio"   hidden   name = "attr_equipment_location"   class = "location-character"   value = "Character"              />     < input   type = "radio"   hidden   name = "attr_equipment_location"   class = "location-other"       value = "Other"                  />     < div   class = "equipment data row" >       < input   class = "equipment data field"   type = "text"   name = "attr_equipment_item"   value = "" />       < input   class = "equipment data field"   type = "text"   name = "attr_equipment_desc"   value = "" />       < select   class = "equipment data field select"   name = "attr_equipment_type" >         < option >All types</ option >         < option >Clothes</ option >         < option >Potions</ option > < option >Other</ option >       </ select >       < select   class = "equipment data field select"   name = "attr_equipment_location" >         < option >All locations</ option >         < option >Character</ option > < option >Other</ option >       </ select >     </ div >   </ fieldset > </ div > Here is the css code for the example: /*============================== =                              = =      Some other styling      = =                              = ==============================*/ .charsheet  {   --background-row :   #cccccc ;   --background-header :   #999999 ;   --height-header-select :   1.38 em ;   --inter-row-space :   3 px ;   } .sheet-field  {    background-color :   var(--background-row) ;    width : 100 % ;    padding :   0 px ; } .sheet-select  {    margin-bottom :   0 px ; } .sheet-header.sheet-field  {    background-color :   var(--background-header) ; } .sheet-header.sheet-select  {    height :   var(--height-header-select) ; } .sheet-data.sheet-row  {    background-color :   var(--background-row) ; } .sheet-equipment.sheet-row  {    display :   grid ;    grid-template-columns :   1 fr  1 fr  6 em  8 em ;    margin-bottom :   var(--inter-row-space) ; } /*============================== =                              = =      Relevant section        = =                              = ==============================*/ /* Rows are hidden by default */ .sheet-data.sheet-row  {    display :   none ; } /* All rows marked with 'All types' or 'All locations' should always display */ .repitem   input .sheet-equipment-all:checked ~ .sheet-data.sheet-row , .repitem   input .sheet-location-all:checked ~ .sheet-data.sheet-row , /* When 'All types' and 'All locations' are selected in the header, show all rows  When 'All types' is selected in the header, and character or other is selected as location, then show rows with matching location */ input .sheet-equipment-all:checked ~ input .sheet-location-all:checked ~ .repcontainer   .sheet-data.sheet-row , input .sheet-equipment-all:checked ~ input .sheet-location-character:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-location-character:checked ~ .sheet-data.sheet-row , input .sheet-equipment-all:checked ~ input .sheet-location-other:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-location-other:checked ~ .sheet-data.sheet-row , /* When 'Clothes' and 'All locations' are selected in the header, show all rows marked with 'Clothes' as Type When 'Clothes' is selected in the header, and character or other is selected as location, then show rows with a matching set of values*/ input .sheet-equipment-clothes:checked ~ input .sheet-location-all:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-clothes:checked ~ .sheet-data.sheet-row , input .sheet-equipment-clothes:checked ~ input .sheet-location-character:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-clothes:checked ~ input .sheet-location-character:checked ~ .sheet-data.sheet-row , input .sheet-equipment-clothes:checked ~ input .sheet-location-other:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-clothes:checked ~ input .sheet-location-other:checked ~ .sheet-data.sheet-row , /* When 'Potions' and 'All locations' are selected in the header, show all rows marked with 'Potions' as Type When 'Potions' is selected in the header, and character or other is selected as location, then show rows with a matching set of values*/ input .sheet-equipment-potions:checked ~ input .sheet-location-all:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-potions:checked ~ .sheet-data.sheet-row , input .sheet-equipment-potions:checked ~ input .sheet-location-character:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-potions:checked ~ input .sheet-location-character:checked ~ .sheet-data.sheet-row , input .sheet-equipment-potions:checked ~ input .sheet-location-other:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-potions:checked ~ input .sheet-location-other:checked ~ .sheet-data.sheet-row , /* When 'Other' and 'All locations' are selected in the header, show all rows marked with 'Other' as Type  When 'Other' is selected in the header, and character or other is selected as location, then show rows with matching pairs of values*/ input .sheet-equipment-other:checked ~ input .sheet-location-all:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-other:checked ~ .sheet-data.sheet-row , input .sheet-equipment-other:checked ~ input .sheet-location-character:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-other:checked ~ input .sheet-location-character:checked ~ .sheet-data.sheet-row , input .sheet-equipment-other:checked ~ input .sheet-location-other:checked ~ fieldset .repeating_equipment + .repcontainer > .repitem   input .sheet-equipment-other:checked ~ input .sheet-location-other:checked ~ .sheet-data.sheet-row  {    display :   grid ; }
1600995936

Edited 1601120442
GiGs' sheetworker The sheetworker is much easier to scale since the CSS rules in the complex example need to cover all combinations of filter values in the catogories, wheras this sheetworker only needs an extra section of  boolean checks per category, plus adding the new one to the old categories. The example looks just like the complex html/css example. Here is the html code: < div   class = "equipment-list" >   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"       class = "equipment-all"        value = "All types"       checked  />   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"       class = "equipment-clothes"    value = "Clothes"                />   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"       class = "equipment-potions"    value = "Potions"                />   < input   type = "radio"   hidden   name = "attr_equipment_type_filter"       class = "equipment-other"      value = "Other"                  />   < input   type = "radio"   hidden   name = "attr_equipment_location_filter"   class = "location-all"         value = "All locations"   checked  />   < input   type = "radio"   hidden   name = "attr_equipment_location_filter"   class = "location-character"   value = "Character"              />   < input   type = "radio"   hidden   name = "attr_equipment_location_filter"   class = "location-other"       value = "Other"                  />   < div   class = "equipment header row" >     < div   class = "equipment header field" >Equipment</ div >     < div   class = "equipment header field" >Description</ div >     < select   class = "equipment header field select"   name = "attr_equipment_type_filter" >       < option >All types</ option >       < option >Clothes</ option >       < option >Potions</ option >       < option >Other</ option >     </ select >     < select   class = "equipment header field select"   name = "attr_equipment_location_filter" >       < option >All locations</ option >       < option >Character</ option >       < option >Other</ option >     </ select >   </ div >   < fieldset   class = "repeating_equipment" >     < input   type = "radio"   hidden   name = "attr_equipment_type"       class = "equipment-all"        value = "All types"       checked  />     < input   type = "radio"   hidden   name = "attr_equipment_type"       class = "equipment-clothes"    value = "Clothes"                />     < input   type = "radio"   hidden   name = "attr_equipment_type"       class = "equipment-potions"    value = "Potions"                />     < input   type = "radio"   hidden   name = "attr_equipment_type"       class = "equipment-other"      value = "Other"                  />     < input   type = "radio"   hidden   name = "attr_equipment_location"   class = "location-all"         value = "All locations"   checked  />     < input   type = "radio"   hidden   name = "attr_equipment_location"   class = "location-character"   value = "Character"              />     < input   type = "radio"   hidden   name = "attr_equipment_location"   class = "location-other"       value = "Other"                  />     < input   type = "hidden"   class = "toggle-show"   name = "attr_show"   value = "1" >     < div   class = "equipment data row" >       < input   class = "equipment data field"   type = "text"   name = "attr_equipment_item"   value = "" />       < input   class = "equipment data field"   type = "text"   name = "attr_equipment_desc"   value = "" />       < select   class = "equipment data field select"   name = "attr_equipment_type" >         < option >All types</ option >         < option >Clothes</ option >         < option >Potions</ option >         < option >Other</ option >       </ select >       < select   class = "equipment data field select"   name = "attr_equipment_location" >         < option >All locations</ option >         < option >Character</ option >         < option >Other</ option >       </ select >     </ div >   </ fieldset > </ div > < script   type = "text/worker" >    on ( 'change:equipment_type_filter change:equipment_location_filter' , ()  =>  {      getSectionIDs ( 'repeating_equipment' , idarray  =>  {        const   fieldnames   =  [];        idarray .forEach (id  =>  {          fieldnames .push ( `repeating_equipment_ ${ id } _equipment_type` );          fieldnames .push ( `repeating_equipment_ ${ id } _equipment_location` );       });        getAttrs ([ 'equipment_type_filter' ,  'equipment_location_filter' ,  ... fieldnames ], values  =>  {          const   output   =  {};          const   type   =   values . equipment_type_filter || '' ;          const   location   =   values . equipment_location_filter || '' ;          idarray .forEach (id  =>  {            const   thisType   =   values [ `repeating_equipment_ ${ id } _equipment_type` ]  ||   '' ;            const   thisLocation   =   values [ `repeating_equipment_ ${ id } _equipment_location` ]  ||   '' ;            output [ `repeating_equipment_ ${ id } _show` ]  =  (                           // Rows with 'All types' as their type should be displayed if the Location filter matches or is unset             (( thisType   ===   'All types' )  &&  ( location   ===   'All locations'   ||   location   ===   thisLocation ))  ||              // Rows with 'All locations' as their location should be displayed if the Type filter matches or is unset             (( thisLocation   ===   'All locations' )  &&  ( type   ===   'All types'   ||   type   ===   thisType ))  ||              // For each category, when either the filter matches or is unset for that category, the rows should be displayed             (( type   ===   'All types'   ||   type   ===   thisType )  &&  ( location   ===   'All locations'   ||   location   ===   thisLocation ))                       // When either is true, set show to 1, else 0           ) ?   1   :   0 ;         });          setAttrs ( output );       });     });   }); </ script > Here is the css code: /*============================== =                              = =      Some other styling      = =                              = ==============================*/ .charsheet  {   --background-row :   #cccccc ;   --background-header :   #999999 ;   --height-header-select :   1.38 em ;   --inter-row-space :   3 px ;   } .sheet-field  {    background-color :   var(--background-row) ;    width : 100 % ;    padding :   0 px ; } .sheet-select  {    margin-bottom :   0 px ; } .sheet-header.sheet-field  {    background-color :   var(--background-header) ; } .sheet-header.sheet-select  {    height :   var(--height-header-select) ; } .sheet-data.sheet-row  {    background-color :   var(--background-row) ; } .sheet-equipment.sheet-row  {    display :   grid ;    grid-template-columns :   1 fr  1 fr  6 em  8 em ;    margin-bottom :   var(--inter-row-space) ; } /*============================== =                              = =      Relevant section        = =                              = ==============================*/ /* Rows are hidden by default */ .sheet-data.sheet-row  {    display :   none ; } /* When toggle-show is 1, show the corresponding table row*/ .sheet-toggle-show [ value = "1" ]  ~   .sheet-data.sheet-row  {    display :   grid ; }
1601008878
GiGs
Pro
Sheet Author
API Scripter
This looks interesting and cool. I'm not sure how soon I'll be able to properly check it out, but I'm definitely looking forward to trying it out (I'm leaning towards trying out the sheet worker version, for some reason).
1601058898
Andreas J.
Forum Champion
Sheet Author
Translator
Interesting, I'll keep an eye on this :D
1601121155

Edited 1601121192
GiGs said: This looks interesting and cool. I'm not sure how soon I'll be able to properly check it out, but I'm definitely looking forward to trying it out (I'm leaning towards trying out the sheet worker version, for some reason). Yes please do, I'd be honored. Haha, yeah I wonder why you would feel like that :P
📜🗡Andreas J.🏹📜 said: Interesting, I'll keep an eye on this :D Thanks. I have not been developing sheets for that long, and mostly my goal has been able to have a clean, simple and collapsible sheet. That is what made me think about alternate solutions to the solution I had copied from the CSS wizardry wiki/thread. I guess many people take the directly opposite route where they want stylish, almost artfull, sheets. In those cases this might look pretty bland when compared to having multiple stylable tabs that can really "pop". It would be interesting to hear yours and others thoughts about this.
1601129412
Andreas J.
Forum Champion
Sheet Author
Translator
Necris said: That is what made me think about alternate solutions to the solution I had copied from the CSS wizardry wiki/thread. The examples there are really basic, and only covers what people have bothered to add there, which is a really small part. The examples there serves as useful starting points for a couple of things, but people who know the stuff better won't find as much use for them. Many sheets have sophisticated features or clever implementations, but those are hidden on the sheet, and people need to dig those up themselves. I try to write down the names of sheets that have some good/clever implementation, so that if the page don't have an example, it at least mentions sheets that have the thing. I haven't had the time to digest your implementation here yet, but it would be great if/when it's finalized/streamlined, you could at least add a link to this thread to the CSS Wizardry page.
1601137300

Edited 1601137371
GiGs
Pro
Sheet Author
API Scripter
Necris said: I guess many people take the directly opposite route where they want stylish, almost artfull, sheets. In those cases this might look pretty bland when compared to having multiple stylable tabs that can really "pop". It would be interesting to hear yours and others thoughts about this. I have a fondness for lean, clean sheets too. That said, I think most of the really pretty sheets are made by people who know CSS pretty well, and so the sheets function well while also being pretty. One thing I dont think is done enough though is acknowledging that roll20 is a different medium than paper. Having a copy of a paper sheet generally takes up too much space on screen and has too much on each tab. I'd like to see designers build sheets that take full advantage of the medium and are designed to be kept on screen while players are also interacting with the battlemap. But its easier to copy the paper character sheet - players are  familiar with it, and gives you a design to build towards without having to come up with something new (design is hard). I know - I've done it this way, too!
1601162744
Finderski
Pro
Sheet Author
Compendium Curator
GiGs said: But its easier to copy the paper character sheet - players are  familiar with it, and gives you a design to build towards without having to come up with something new (design is hard). I know - I've done it this way, too! This . is exactly why I tend to copy the paper sheets...I'm crap at UI/UX design and still want things to be somewhat aesthetically pleasing...so, I have to steal my design from others... :-/ I agree with you, GiGs, I'd love to be able to design a sheet that can be left open while characters are on the battle mat. I've started stretching in that direction with the latest sheet I've been working on, but it's still not enough for what I'd like to be able to do... This does have some interesting potential applications, though, so I'm definitely watching this thread so I can try it out some time.