0

In an XSL file that parses an XML and view it as a table, I'm supposed to make the table head of one field clickable and making that change the sort of the table. How can that be done? I've attempted to make the table head contains a link with an onclick="f1()" function to go to a JavaScript function that changes a div's innerHTML. But that did not work. Is it possible to add JavaScript to an XSL file? and what other way can I make it happen.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html> 
<body>
<script>
function p2(){
        var pp1 = document.getElementById("p1");
        pp1.innerHTML = "<xsl:for-each select=\"catalog/cd\">\n" +
        "<xsl:sort select=\"artist\"/>\n" +
        "<tr>\n" +
        "<td><xsl:value-of select=\"title\"/></td>\n" +
        "<td><xsl:value-of select=\"artist\" sort=\"ascending\" />\n" +
        "</tr>\n" +
        "</xsl:for-each>";
}
</script>
  <h2>My CD Collection</h2>
  <table border="1">
    <tr bgcolor="#9acd32">
      <th style="text-align:left">Title</th>
      <th style="text-align:left">
       <a href="#" onclick="p2()">Artist </a></th>
    </tr>
    <div id="p1">
    <xsl:for-each select="catalog/cd">
    <tr>
      <td><xsl:value-of select="title"/></td>
      <td><xsl:value-of select="artist" />
    </tr>
    </xsl:for-each>
   </div>
  </table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

so far I'm parsing using https://www.w3schools.com/xml/tryxslt.asp?xmlfile=cdcatalog&xsltfile=cdcatalog

dipper
  • 15
  • 5
  • 1
    Do the table row sorting with Javascript, doing a table row sort in HTML generated by XSLT is not different from doing it in static HTML. – Martin Honnen Apr 24 '19 at 07:41
  • As @MartinHonnen had writen, [HTML table sort](https://stackoverflow.com/questions/10683712/html-table-sort) is tangencial to XSLT transforming XML to HTML. Besides that, you could use XSLT in some sence as scripting language like I proposed for this [closed question](https://stackoverflow.com/questions/55723318/how-can-i-turn-the-table-headers-in-an-xml-file-into-data-sorting-links#comment98259827_55723318) and explained in this [old post](http://alejandroaraneda.blogspot.com/2011/05/fndocument-uri-en-xslt-10.html) [in Spanish]. – Alejandro Apr 24 '19 at 14:51

1 Answers1

0

You can delegate sorting to Javascript, stuff the rows to be sorted into a Javascript array so that you can use its sort method (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort):

        function sort(headerCell) {
           var table = headerCell.parentNode.parentNode.parentNode;
           var tbody = table.tBodies[0];
           var rows = Array.from(tbody.rows);
           var cellIndex = headerCell.cellIndex;

           if ('oldIndex' in table.dataset) {
             table.tHead.rows[0].cells[table.dataset.oldIndex].classList.remove('sorted-asc', 'sorted-desc');
           }

           table.dataset.oldIndex = cellIndex;

           var prefix = headerCell.dataset.sortOrder === 'ascending' ? 1 : -1;

           if (headerCell.dataset.sortOrder === 'ascending') {
             headerCell.classList.remove('sorted-desc');
             headerCell.classList.add('sorted-asc');
           }
           else {
             headerCell.classList.remove('sorted-asc');
             headerCell.classList.add('sorted-desc');               
           }

           headerCell.dataset.sortOrder = headerCell.dataset.sortOrder === 'ascending' ? 'descending' : 'ascending';

           var numeric = headerCell.dataset.type === 'number';

           rows.sort(
             function(a, b) { 
               return prefix * a.cells[cellIndex].textContent.localeCompare(b.cells[cellIndex].textContent, undefined, { 'numeric' : numeric });
             }
           );
           rows.forEach(function(row) { tbody.appendChild(row); });
        }

Then you can just use <th onclick="sort(this);">...</th> on any table header cell you want to support sorting, for instance if you want to do it for all header cells you can write a template for them:

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    xmlns:msxml="urn:schemas-microsoft-com:xslt"
    exclude-result-prefixes="exsl msxml"
    version="1.0">

  <xsl:output method="html" indent="yes" version="5" doctype-system="about:legacy-doctype"/>

  <xsl:template match="/">
    <html>
      <head>
        <title>.NET XSLT Fiddle Example</title>
        <style>
            .sorted-asc::after { content : '↑' }
            .sorted-desc::after { content : '↓' }
        </style>
        <script>
            function sort(headerCell) {
               var table = headerCell.parentNode.parentNode.parentNode;
               var tbody = table.tBodies[0];
               var rows = Array.from(tbody.rows);
               var cellIndex = headerCell.cellIndex;

               if ('oldIndex' in table.dataset) {
                 table.tHead.rows[0].cells[table.dataset.oldIndex].classList.remove('sorted-asc', 'sorted-desc');
               }

               table.dataset.oldIndex = cellIndex;

               var prefix = headerCell.dataset.sortOrder === 'ascending' ? 1 : -1;

               if (headerCell.dataset.sortOrder === 'ascending') {
                 headerCell.classList.remove('sorted-desc');
                 headerCell.classList.add('sorted-asc');
               }
               else {
                 headerCell.classList.remove('sorted-asc');
                 headerCell.classList.add('sorted-desc');               
               }

               headerCell.dataset.sortOrder = headerCell.dataset.sortOrder === 'ascending' ? 'descending' : 'ascending';

               var numeric = headerCell.dataset.type === 'number';

               rows.sort(
                 function(a, b) { 
                   return prefix * a.cells[cellIndex].textContent.localeCompare(b.cells[cellIndex].textContent, undefined, { 'numeric' : numeric });
                 }
               );
               rows.forEach(function(row) { tbody.appendChild(row); });
            }
        </script>
      </head>
      <body>
          <h1>Example</h1>
          <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="catalog">
      <table>
          <thead>
              <tr>
                  <xsl:apply-templates select="*[1]/*" mode="header"/>
              </tr>
          </thead>
          <tbody>
              <xsl:apply-templates/>
          </tbody>
      </table>
  </xsl:template>

  <xsl:template match="*" mode="header">
      <th onclick="sort(this);" data-sort-order="ascending">
          <xsl:attribute name="data-type">
              <xsl:choose>
                  <xsl:when test="number() != number()">text</xsl:when>
                  <xsl:otherwise>number</xsl:otherwise>
              </xsl:choose>
          </xsl:attribute>
          <xsl:value-of select="local-name()"/>
      </th>
  </xsl:template>

  <xsl:template match="catalog/*">
      <tr>
          <xsl:apply-templates/>
      </tr>
  </xsl:template>

  <xsl:template match="catalog/*/*">
      <td>
          <xsl:apply-templates/>
      </td>
  </xsl:template>

</xsl:stylesheet>

Online example at https://xsltfiddle.liberty-development.net/6r5Gh3r/3, used Javascript like Array.from should work in all modern browsers, only for IE you would need to use a polyfill to be able to use that method, as done in https://xsltfiddle.liberty-development.net/6r5Gh3r/4.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110