rltbl 0.1.0

Relatable (rltbl) is a tool for cleaning and connecting your data.
Documentation
{% extends "page.html" %}

{% set range = result.range %}
{% set table = result.table %}
{% set columns = result.columns %}
{% set rows = result.rows %}

{% block styles %}
<link href="{{site.root}}/static/main.css" rel="stylesheet">
{% endblock %}

{% block content %}
<div class="float-end mt-1">
  <a class="btn btn-outline-primary" href="{{site.root}}/{{page.path}}/{{table.name}}">
    <i class="bi bi-funnel"></i>
    Clear Filters
  </a>
  <div class="btn-group">
    <a class="btn btn-outline-primary dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
      aria-expanded="false">
      Format
    </a>
    <ul class="dropdown-menu">
      {% for name, url in page.formats|items %}
      <li><a class="dropdown-item" href="{{url}}">{{name}}</a></li>
      {% endfor %}
    </ul>
  </div>
  <a class="btn btn-outline-primary" href="{{site.root}}/add-row/{{table.name}}">
    <i class="bi bi-plus"></i>
    Append Row
  </a>
</div>
<h1>{{ table.name }}</h1>

{% if page.tabs %}
<ul class="nav nav-tabs mb-2">
  {% for tab in page.tabs %}
  <li class="nav-item">
    <a class="nav-link {{'active' if tab['active'] == true}} {{'disabled' if tab['disabled'] == true}}"
      href="{{tab['url']}}">
      {{tab['table']}}
      <span id="tab-{{tab['table']}}-count" class="badge rounded-pill text-bg-secondary">...</span>
    </a>
  </li>
  {% endfor %}
</ul>
{% endif %}

<div id="portal" style="width: 100%; min-width: 400px; left: auto; right: auto"></div>

<p class="range">Rows {{ range.start }}-{{ range.end }} of {{ range.total }}</p>
<noscript>
  <table class="table">
    <thead>
      <tr>
        {% for column in columns %}
        <th>{{ column.name }}</th>
        {% endfor %}
      </tr>
    </thead>
    <tbody>
      {% for row in rows %}
      <tr>
        {% for column, cell in row.cells|items %}
        <td>{{ cell.text }}</td>
        {% endfor %}
      </tr>
      {% endfor %}
    </tbody>
  </table>
</noscript>
{% endblock %}

{% block scripts %}
<script>
  window.rltbl = {};
  window.rltbl.site = {{site | tojson}};
  window.rltbl.page = {{page | tojson}};
  window.rltbl.result = {{result | tojson}};
  window.rltbl.grid = {};

  for (var i = 0; i < window.rltbl.page.tabs.length; i++) {
    var tab = window.rltbl.page.tabs[i];
    // console.log("fetch " + i, tab);
    fetch_count(tab);
  }

  function fetch_count(tab) {
    fetch(tab['count'])
      .catch(e => console.log("tab count error", tab, e))
      .then(response => response.json())
      .then(data => {
        // console.log("tab count result", tab, data);
        document.getElementById("tab-" + tab['table'] + "-count").innerHTML = data;
      });
  }


  // Example: Use an autocomplete dropdown for 'penguin.island' column.
  const table = {{result.table.name | tojson}};
  var columns = {{result.columns | tojson}};
  for (var i = 0; i < columns.length; i++) {
    var column_name = columns[i].name;
    var kind = "text";
    if (table === "penguin") {
      if (column_name === "island") {
        kind = "dropdown";
      } else if (column_name === "review") {
        kind = "image";
      }
    }
    window.rltbl.result.columns[i].kind = kind;
  }

  if (table === "penguin") {
    window.rltbl.grid.freezeColumns = 1;
  }

  window.rltbl.loadOptions = (row, column, inputValue, callback) => {
    const url = `{{site.root}}/cell-options/${table}/${row}/${column}?input=${inputValue}`;
    const response = fetch(url)
      .catch(e => console.log("loadOptions", e))
      .then(response => response.json())
      .then(data => callback(data));
  };

  document.body.addEventListener("click", function (e) {
    if (e.target.id === "row-insert-above") {
      console.log("ROW INSERT ABOVE");
    }
  });

  document.body.addEventListener("click", function (e) {
    var column = document.getElementById("filter-value");
    if (column) {
      // TODO: This should handle multiple columns.
      column = column.name;
      var order = "";
      if (e.target.id === "sort-asc") {
        order = column;
      } else if (e.target.id === "sort-desc") {
        order = `${column}.desc`;
      }
      if (order) {
        e.preventDefault();
        const params = new URLSearchParams(document.location.search);
        params.set("order", order);
        document.location = `?${params.toString()}`;
        return false;
      } else if (e.target.id === "sort-none") {
        e.preventDefault();
        const params = new URLSearchParams(document.location.search);
        params.delete("order");
        document.location = `?${params.toString()}`;
        return false;
      }
    }
  });

  document.body.addEventListener("submit", function (e) {
    if (e.target.id === "filter-column") {
      e.preventDefault();
      if (e.submitter.id === "filter-clear") {
        document.location = '{{site.root}}/{{page.path}}/{{table.name}}';
      } else {
        const column = document.getElementById("filter-value").name;
        var operator = document.getElementById("filter-operator").value;
        var value = document.getElementById("filter-value").value;
        if (operator === "contains") {
          operator = "like";
          value = `*${value}*`;
        } else if (operator === "is") {
          value = 'NULL';
        } else if (operator === "is_not") {
          value = 'NULL';
        }
        document.location = `?${column}=${operator}.${value}`;
      }
      return false;
    }
  });
</script>
<script defer="defer" src="{{site.root}}/static/main.js"></script>
{% endblock %}