1 <?xml version='1.0' encoding='UTF-8' ?>
3 Nick's web site: XHTML+CSS sortable clicky table headers.
5 Copyright © 2021 Nick Bowler
7 This implements the gory markup for clicky table headers, creating
8 input and related label elements to implement the clicking part.
10 To use: add the "clicky" attribute to each sortable header th element.
11 Class names corresponding to that header will be generated based on
12 the value of the attribute. For example, a header with clicky='date'
13 will add class="clicky-date" to the header and generate two input
14 elements: one with class="clicky-date" to indicate when that column
15 is selected for sorting, and another with class="clicky-date-rev" to
16 indicate when the reverse ordering is selected for that column.
18 The input table itself must have two tbody sections. The first is a
19 completely normal table body in the default sort order. The second
20 encodes all other possible orderings.
22 This program is free software: you can redistribute it and/or modify
23 it under the terms of the GNU General Public License as published by
24 the Free Software Foundation, either version 3 of the License, or
25 (at your option) any later version.
27 This program is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
32 You should have received a copy of the GNU General Public License
33 along with this program. If not, see <https://www.gnu.org/licenses/>
35 <xsl:stylesheet version='1.0'
36 xmlns='http://www.w3.org/1999/xhtml'
37 xmlns:xhtml='http://www.w3.org/1999/xhtml'
38 xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
40 <xsl:template match='xhtml:table/@clicky|xhtml:th/@clicky' />
41 <xsl:template match='xhtml:table[@clicky]'>
42 <xsl:variable name='group' select='generate-id(@clicky)' />
43 <xsl:variable name='clicky' select='@clicky' />
46 <script type='x'><![CDATA[]]x><!--]]></script>
47 <xsl:for-each select='xhtml:thead/*/xhtml:th/@clicky'>
48 <!-- hoist default element to be first in document order -->
49 <xsl:sort select='number(.!=$clicky)' data-type='number' />
51 <input style='display: none' id='sort-{generate-id(.)}'
52 type='radio' name='{$group}' class='clicky-{.}'>
53 <xsl:if test='.=$clicky'>
54 <xsl:attribute name='checked'>checked</xsl:attribute>
58 <xsl:for-each select='xhtml:thead/*/xhtml:th/@clicky'>
59 <input style='display: none' id='rev-{generate-id(.)}'
60 type='checkbox' class='clicky-{.}-rev' />
62 <script type='x'>--></script>
65 <xsl:apply-templates select='node()|@*' />
70 <xsl:template match='xhtml:table[@clicky]/xhtml:thead[*/xhtml:th/@clicky]'>
72 <xsl:attribute name='class'>
73 <xsl:if test='@class'>
74 <xsl:value-of select='concat(@class, " ")' />
76 <xsl:text>clicky</xsl:text>
78 <xsl:apply-templates select='node()|@*[local-name() != "class"]' />
82 <xsl:template match='xhtml:table[@clicky]/xhtml:thead/*/xhtml:th[@clicky]'>
84 <xsl:attribute name='class'>
85 <xsl:if test='@class'>
86 <xsl:value-of select='concat(@class, " ")' />
88 <xsl:value-of select='concat("clicky-", @clicky)' />
90 <xsl:apply-templates select='@*[local-name() != "class"]' />
92 <label for='sort-{generate-id(@clicky)}'>
93 <xsl:text>⁠</xsl:text>
94 <span><xsl:apply-templates select='node()' /></span>
97 <script type='x'><![CDATA[]]x><!--]]></script>
98 <label for='rev-{generate-id(@clicky)}' style='display: none'>
99 <xsl:text>⁠</xsl:text>
100 <span><xsl:apply-templates select='node()' /></span>
101 <img alt='FWD' width='16' height='16' src='/icons/down.svg' />
102 <img alt='REV' width='16' height='16' src='/icons/up.svg' style='display: none' />
104 <script type='x'>--></script>
108 <xsl:template match='xhtml:table[@clicky]/xhtml:tbody[last()]'>
110 <xsl:attribute name='style'>
111 <xsl:if test='@style'>
112 <xsl:value-of select='concat(@style, "; ")' />
114 <xsl:text>display: none</xsl:text>
116 <xsl:apply-templates select='@*[local-name() != "style"]' />
119 <xsl:for-each select='../*/xhtml:tr'>
120 <xsl:sort select='count(*)' data-type='number' order='descending' />
121 <xsl:if test='position()=1'>
122 <xsl:attribute name='colspan'>
123 <xsl:value-of select='count(*)' />
131 <xsl:for-each select='../xhtml:thead[1]/xhtml:tr[1]/*'>
133 <xsl:value-of select='.' />
137 <xsl:apply-templates select='node()' />
141 <!-- Insert script hack around the second <tbody> -->
142 <xsl:template match='xhtml:table[@clicky]/xhtml:tbody[1]/*[last()]/*[last()]'>
144 <xsl:apply-templates select='node()|@*' />
145 <script type='x'><![CDATA[]]x><!--]]></script>
149 <!-- Note that it is not allowed for <tr> to have no child elements. -->
151 match='xhtml:table[@clicky]/xhtml:tbody[last()]/*[last()]/*[last()]'>
153 <xsl:apply-templates select='node()|@*' />
154 <script type='x'>--></script>