From e7306bc0bbfea36e6414684b00ad4ef1c7269aed Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Wed, 17 Feb 2021 23:03:33 -0500 Subject: [PATCH] Improve clicky table generation. Disentangle the CSS rules for clicky tables from the file listing table type, and eliminate all id selectors in favour of class selectors. Then, move all the support markup out of the eruby program and into a dedicated XSLT program which will perform the necessary transformation based on a couple attributes added to the table elements. The inputs, labels, and the links between them are now automatically generated. While it's more code overall, hopefully this reduces the complexity of the implementation by clearly separating the various different parts. Additionally, this should (if ever needed) make it easy to add clicky headers to other tables, as well as supporting more than one clicky table on a page. --- Rules | 3 +- content/style.scss | 171 +++++++++++++++++-------- layouts/clickytable.xsl | 125 ++++++++++++++++++ layouts/default.xsl | 4 +- layouts/{listing.xhtml => listing.erb} | 58 ++------- 5 files changed, 258 insertions(+), 103 deletions(-) create mode 100644 layouts/clickytable.xsl rename layouts/{listing.xhtml => listing.erb} (59%) diff --git a/Rules b/Rules index 2460c14..7a9773b 100644 --- a/Rules +++ b/Rules @@ -129,8 +129,7 @@ postprocess do end compile '/**/index.lst' do - layout '/listing.xhtml' - filter :erb + layout '/listing.erb' layout '/default.xml' layout '/default.xsl' filter :relativize_paths, type: :xml diff --git a/content/style.scss b/content/style.scss index 22df5d9..dc306d7 100644 --- a/content/style.scss +++ b/content/style.scss @@ -1,7 +1,7 @@ /* * Nick's web site: default stylesheet * - * Copyright © 2018-2020 Nick Bowler + * Copyright © 2018-2021 Nick Bowler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -118,6 +118,7 @@ td, th { thead>tr, tbody>tr { border: solid $ruledefaultcolour; } th, thead>tr { border-bottom: 1px solid $rulestrongcolour; } +tbody+tbody { border-bottom: 1px solid $ruledefaultcolour; } *>table, *>th { border: none; } thead>tr { border-width: 1px; } tbody>tr { border-width: 0 1px; } @@ -137,13 +138,47 @@ table.cc { } } -$sortcols: name, date, size; -@each $col in $sortcols { - #filelist-#{$col}-sort { +// CSS rules for stortable clicky table headers: Update the display of +// the /table based on the current state. Each column has its own set +// nearly-identical rules, only the class names differ. +// +// The clickytables.xsl stylesheet generates two inputs for each column. +// These inputs are siblings of the table and all precede it in document +// order. Moreover, the inputs for a column are ordered with respect to +// each other, in this sequence: +// +// input.clicky-NAME -- checked iff NAME is selected for sorting. +// input.clicky-NAME-rev -- checked to select reverse order. +// +// One of the column selection inputs will have a 'checked' attribute to +// indicate the default order. This input is always first in document +// order. No other inputs begin checked. +// +// The table itself consists of a thead (where the header labels are +// located) and two tbody elements. The bulk of these rules relate +// to updating the headers to visually indicate the current state. +// +// A sortable column's th element has the .clicky-NAME class, matching +// its corresponding inputs, and has two label children. The first label +// is visible only when the column is unselected, and is linked to the +// .clicky-NAME input to activate that column. The second label is visible +// only on the selected column and is linked to the .clicky-NAME-rev input +// to toggle the reverse order. +// +// For the table body, the first tbody contains the default ordering +// and is not styled by these rules (except to hide it when alternate +// orderings are selected). The second tbody contains rows for all +// alternate orderings, and is revealed by these rules. When revealed, +// rows with the NAMEfwd or NAMErev class (for the forward and reverse +// orderings, respectively) are shown and other rows are hidden. + +$clickynames: name, date, size; +@each $col in $clickynames { + input.clicky-#{$col} { &:checked { - & ~ table.filelist { - /* Update table header state */ - th.#{$col} { + &~table { + // Update table header state + & th.clicky-#{$col} { label~label { display: -moz-inline-box !important; display: inline-block !important; @@ -151,60 +186,110 @@ $sortcols: name, date, size; label { display: none; } } - /* Show only appropriate items from the sort body (forward) */ - tbody+tbody>tr.#{$col} { display: table-row; } - tbody+tbody>tr { display: none; } + // Show only appropriate items from the sort body (forward) + &>tbody+tbody>tr.#{$col}fwd { display: table-row; } + &>tbody+tbody>tr { display: none; } + + // Unhide sort body + &>tbody { + &+tbody { display: table-row-group !important; } + display: none; + } } - & ~ #filelist-#{$col}-rev:checked ~ table.filelist { - /* Show only appropriate items from sort body (reversed) */ - tbody+tbody>tr.#{$col}rev { display: table-row; } - tbody+tbody>tr { display: none; } + // reverse state for selected sort column + &~input.clicky-#{$col}-rev { + &:checked ~ table { + // Show only appropriate items from sort body (reversed) + &>tbody+tbody>tr.#{$col}rev { display: table-row; } + &>tbody+tbody>tr { display: none; } + + // Unhide sort body + &>tbody { + &+tbody { display: table-row-group !important; } + display: none; + } + } + + // Unhide to allow keyboard navigation to this input + display: block !important; } + } - /* Unhide associated checkbox for keyboard navigation */ - & ~ #filelist-#{$col}-rev { display: block !important; } + // If default input element is the only one selected, match it to + // return to default view (overriding the changes above). It is + // always the first input element among all the sibling elements. + // This seems to interoperate better than using the [checked] or + // :first-of-type selectors. + @at-root &:first-child, :not(input)+& { + &:checked~table>tbody { + &+tbody { display: none !important; } + display: table-row-group; + } } - &:focus ~ table.filelist th>label~label>span { - border: 1px dotted; - padding: 0; + &:focus ~ table th.clicky-#{$col}>label~label>span { + border-color: $foregroundcolour; } + // Unhide to allow keyboard navigation display: block !important; + pointer-events: none; position: absolute; - z-index: -1; opacity: 0; + z-index: -1; } - #filelist-#{$col}-rev { - &:checked ~ table.filelist { - /* Update table header state */ - th.#{$col} { + input.clicky-#{$col}-rev { + &:checked ~ table { + // Update table header state + & th.clicky-#{$col} { img+img { display: -moz-inline-box !important; - display: inline !important; + display: inline-block !important; } img { display: none; } } } - &:focus ~ table.filelist th>label~label>img { - border: 1px dotted; - padding: 0; + &:focus ~ table th.clicky-#{$col}>label~label>img { + border-color: $foregroundcolour; } + pointer-events: none; position: absolute; - z-index: -2; opacity: 0; + z-index: -2; } -} -/* Enable the sorted tables only when non-default option is selected */ -#filelist-name-rev, #filelist-date-sort, #filelist-size-sort { - &:checked~table.filelist>tbody { - &+tbody { display: table-row-group !important; } - display: none; + th.clicky-#{$col}>label { + &, &>* { + white-space: nowrap; + vertical-align: middle; + display: -moz-inline-box; + display: inline-block; + cursor: pointer; + } + + &>* { border: 1px dotted transparent; } + + // Expand the first label a bit so the table (hopefully) + // does not reshape as columns are selected. + &:first-child { + margin-right: 1.75em; + padding-right: 2px; + } + + &:active { color: $linkactivecolour; } + &:first-child:active>span, &~label:active>img { + border-color: $linkactivecolour; + } + + img { + margin-left: 0.25em; + width: 1.5em; + height: auto; + } } } @@ -214,21 +299,7 @@ table.filelist { width: 0; } - th>label>* { padding: 1px; } - th>label, th>label>* { - white-space: nowrap; - vertical-align: middle; - display: -moz-inline-box; - display: inline-block; - cursor: pointer; - } - th img { margin-left: 0.5ex; } - - tbody+tbody { - border-bottom: solid 1px $ruledefaultcolour; - } - - img { + tbody img { display: block; height: 1.5em; width: auto; diff --git a/layouts/clickytable.xsl b/layouts/clickytable.xsl new file mode 100644 index 0000000..f442fa4 --- /dev/null +++ b/layouts/clickytable.xsl @@ -0,0 +1,125 @@ + + + + + + + + + +
+ + + + + + + + checked + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + display: none + + + + + + + + + + + + + + + + + + + + + +
diff --git a/layouts/default.xsl b/layouts/default.xsl index 84dcf9d..5a6c3ee 100644 --- a/layouts/default.xsl +++ b/layouts/default.xsl @@ -2,7 +2,7 @@ - +
- - + + + @@ -124,18 +88,14 @@ end %> <% by_name = files.keys.sort{ |a, b| strverscmp(a, b) } -by_name.each_index do |i| - entry = render_entry(files, by_name[i]) - if i+1 == by_name.length - entry.sub!(%r{(.*)}m, - "\\1") - end +by_name.each do |key| + entry = render_entry(files, key) %> <%= entry %> <% end %> - + <% def meta_cmp(files, key, a, b) av, bv = files[a][key], files[b][key] @@ -146,7 +106,7 @@ end by_date = files.keys.sort { |a, b| meta_cmp(files, :sorttime, a, b) } by_size = files.keys.sort { |a, b| meta_cmp(files, :size, a, b) } -listnames = [ "namerev", "date", "daterev", "size", "sizerev" ] +listnames = [ "namerev", "datefwd", "daterev", "sizefwd", "sizerev" ] lists = [ by_name.reverse, by_date, by_date.reverse, by_size, by_size.reverse ] if parentrow %> @@ -186,7 +146,5 @@ while not (elems = lists.map(&:first)).compact.empty? even ^= true end %> -
- - - - - - - - - - - - - - - - NameLast ModifiedSize
- -- 2.43.0