Reimplement feature-matrix filtering
authorMagnus Hagander <magnus@hagander.net>
Tue, 12 Aug 2025 20:13:40 +0000 (22:13 +0200)
committerMagnus Hagander <magnus@hagander.net>
Wed, 13 Aug 2025 10:15:01 +0000 (12:15 +0200)
* jQuery removed and replaced with native javascript
* Column filtering is now done in pure CSS without javascript. This also
  means that the initial rendering of the page is correct instead of
  rendering all column and then removing them (visible on slow
  connections)
* Reduce page size by moving some contents to CSS instead of repeating
  it

media/css/main.css
media/js/featurematrix.js
pgweb/featurematrix/views.py
templates/featurematrix/featurematrix.html

index 4c5c0fab1428398c7c645575fbdc2796314b3f03..0aaefb63ac3ae53c282bc4d0b6db6d001596c526 100644 (file)
@@ -1683,18 +1683,30 @@ td.fm_no {
   background-color: var(--td-fm-no-bg-color);
   color: var(--td-fm-no-fg-color);
 }
+td.fm_no::before {
+    content: "No";
+}
 td.fm_yes {
   background-color: var(--td-fm-yes-bg-color);
   color: var(--td-fm-yes-fg-color);
 }
+td.fm_yes::before {
+    content: "Yes";
+}
 td.fm_obs {
   background-color: var(--td-fm-obs-bg-color);
   color: var(--td-fm-obs-fg-color);
 }
+td.fm_obs::before {
+    content: "Obsolete";
+}
 td.fm_unk {
   background-color: var(--td-fm-unk-bg-color);
   color: var(--td-fm-unk-fg-color);
 }
+td.fm_unk::before {
+    content: "?";
+}
 
 div#feature-matrix-filter {
     border-color: var(--hr-bdr-color) !important;
index 5c9bb9a41c0f1f340adcd89a18183d1dcccd81bc..d02541a9433a91b342a1a2a0906a5e6971f4f433 100644 (file)
@@ -1,53 +1,37 @@
 /*
  * Filter feature matrix
  */
-$(document).ready(function(){
-    // Show/hide column based on whether supplied checkbox is checked.
-    function filter_version(checkbox)
-    {
-        var total_checked = $('form#featurematrix_version_filter .featurematrix_version:checked').length;
-        var column=$("table tr:first th").index($("table tr:first th:contains('" + checkbox.val() + "')")) + 1;
-        if (total_checked) {
-            $('.feature-version-col').css('width', (70 / total_checked) + '%');
+
+function hide_unchanged(toggled) {
+    /* Unfortunately, can't figure out how to do this part in pure CSS */
+
+    if (!document.getElementById('hide_unchanged').checked) {
+        /* Unchanged filter is unchecked. If we just made it unchecked we have to display everything, otherwise we have nothing to do here. */
+        if (toggled) {
+            document.querySelectorAll('table.matrix tbody tr').forEach((tr) => {
+                tr.style.display = 'table-row';
+            });
         }
-        $("table th:nth-child(" + column + "), table td:nth-child(" + column + ")").toggle(checkbox.is(":checked")).toggleClass('hidden');
-        hide_unchanged();
-        // Lastly, if at this point an entire row is obsolete, then hide
-        $('tbody tr').each(function(i, el) {
-            var $tr = $(el),
-                visible_count = $tr.find('td:not(.hidden)').length,
-                obsolete_count = $tr.find('td.fm_obs:not(.hidden)').length;
-            // if visible count matches obsolete count, then hide this row
-            $tr.toggle(visible_count !== obsolete_count);
-        });
+        return;
     }
 
-    // Show/hide rows if all values are the same in displayed cells
-    function hide_unchanged()
-    {
-        var hide_unchanged=$('form#featurematrix_version_filter input#hide_unchanged').is(':checked');
-        $('table tr').has('td').each(function(){
-            var row_values=$(this).children('td').not('.colFirst, .hidden');
-            var yes_count=row_values.filter(":contains('Yes')").length;
-            var no_count=row_values.filter(":contains('No')").length;
-            var obsolete_count=row_values.filter(":contains('Obsolete')").length;
-            $(this).toggle(hide_unchanged == false || (yes_count != row_values.length && no_count != row_values.length && obsolete_count != row_values.length));
-        });
-    }
+    /* Get indexes of checked version checkboxes */
+    const vercols = [...document.querySelectorAll('#featurematrix_version_filter input.featurematrix_version')].map((cb, i) => cb.checked ? i : null).filter((i) => i != null);
 
-    // Upon loading the page, apply the filter based on EOL versions that are
-    // unchecked.
-    $('form#featurematrix_version_filter input.featurematrix_version').not(':checked').each(function(){
-        filter_version($(this));
+    document.querySelectorAll('table.matrix tbody tr').forEach((tr) => {
+        /* Get classes of all relevant td's (based on vercols), and see if there is more than one unique class. Assumes each td only has one explicit class. */
+        const changed = new Set([...tr.querySelectorAll('td')].filter((td, i) => vercols.indexOf(i) > -1).map((td) => td.classList[0])).size > 1;
+        tr.style.display = changed ? "table-row" : "none";
     });
+}
 
-    // Apply filter based on change in check status of clicked version filter.
-    $('form#featurematrix_version_filter input.featurematrix_version').on("change", function(){
-        filter_version($(this));
+document.addEventListener('DOMContentLoaded', () => {
+    document.getElementById('hide_unchanged').addEventListener('change', (e) => {
+        hide_unchanged(true);
     });
-
-    // Show/hide unchanged feature rows when checkbox clicked.
-    $('form#featurematrix_version_filter input#hide_unchanged').on("change", function(){
-        hide_unchanged();
+    document.querySelectorAll('input.featurematrix_version').forEach((c) => {
+        c.addEventListener('change', (e) => {
+            hide_unchanged(false);
+        });
     });
 });
index 6798240536cc2b8bf2ae5e3e5b9130a55bef53e8..582b393b73b8543cfdfe3975e68172466924f49b 100644 (file)
@@ -1,11 +1,13 @@
 from django.shortcuts import get_object_or_404
 
 from pgweb.util.contexts import render_pgweb
+from pgweb.util.decorators import content_sources
 
 from pgweb.core.models import Version
 from .models import Feature
 
 
+@content_sources('style', "'unsafe-inline'")
 def root(request):
     features = Feature.objects.all().select_related().order_by('group__groupsort', 'group__groupname', 'featurename')
     groups = []
index bb39fc5d175033d45339621c15df22c81c945bd3..5e46c95cb6e71422182d9c91572326fcdc4bf5d2 100644 (file)
@@ -1,6 +1,26 @@
 {%extends "base/page.html"%}
 {%block title%}Feature Matrix{%endblock%}
 
+{% block extrahead %}
+{{ block.super }}
+<style>
+table.matrix tr th,
+table.matrix tr td {
+    display: none;
+}
+table.matrix tr th:nth-child(1),
+table.matrix tr td:nth-child(1) {
+    display: table-cell;
+}
+{% for version in versions %}
+body:has(input#toggle_{{ version.numtree|cut:"." }}:checked) table.matrix th:nth-child({{forloop.counter|add:1}}),
+body:has(input#toggle_{{ version.numtree|cut:"." }}:checked) table.matrix td:nth-child({{forloop.counter|add:1}}) {
+    display: table-cell;
+}
+{% endfor %}
+</style>
+{%endblock%}
+
 {%block extrascript%}
 <script type="text/javascript" src="/media/js/featurematrix.js?{{gitrev}}"></script>
 {%endblock%}
@@ -38,12 +58,12 @@ the text.
   <h2>
     <a name="{{group.group.groupname|slugify}}">{{ group.group.groupname }}</a>
   </h2>
-  <table class="table table-striped table-sm">
+  <table class="table table-striped table-sm matrix">
     <thead>
       <tr>
         <th scope="col" width="30%">&nbsp;</th>
         {% for col in group.group.columns %}
-          <th scope="col" class="feature-version-col">{{col}}</th>
+          <th scope="col">{{col}}</th>
         {% endfor %}
       </tr>
     </thead>
@@ -58,7 +78,7 @@ the text.
             {%endif%}
           </th>
           {%for col in feature.columns%}
-            <td class="fm_{{col.class}}">{{col.str}}</td>
+            <td class="fm_{{col.class}}"></td>
           {%endfor%}
         </tr>
       {%endfor%}