Sorting a table in the back office

Hi,

I am trying to upgrade an back office component, it was originaly developed for a V13 site, but I am now trying to get it to work on V17.

I can’t work out how to add a sort to it, I tried to use but I can work out how to use it.

Do I need to add some extra code to do the sorting.

i have built the following code.

import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api";
import { UMB_NOTIFICATION_CONTEXT } from "@umbraco-cms/backoffice/notification";
import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document';
import { UmbPaginationManager } from '@umbraco-cms/backoffice/utils';
import { UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui';


var _data = "";
var pagingObject;
var _root;
var _authentication;
var page_size_options = [];
var default_page_size;
var _currentPage;
var _pageSize;
var _totalPages;
var _totalItems;
var pageing;

const template = document.createElement("template");

export default class MyDashboardElement extends UmbElementMixin(HTMLElement) {
    /** @type {import('@umbraco-cms/backoffice/notification').UmbNotificationContext} */
    #notificationContext;
    #workspaceContext;
    #authContext;

    constructor() {
        super();

        this.attachShadow({ mode: "open" });
        _root = this.shadowRoot;
        page_size_options = [10, 25, 50];
        default_page_size = 10;
        _currentPage = 1;
        _pageSize = default_page_size;
        _totalPages = 0;
        _totalItems = 0;

        this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => {
            this.#notificationContext = instance;
        });

        this.consumeContext(UMB_AUTH_CONTEXT, (_auth) => {
            if (_auth) {
                _authentication = _auth;
                console.log("The document workspace context is ok.");

                displayHeader();
                displayPage();

                loadData();
            } else {
                console.log("The document workspace context is gone, I will make sure my code disassembles properly.");
            }
        });



        
    }
}
function displayHeader() {

    template.innerHTML = `
  <style>
    :host {
      padding: 20px;
      display: block;
      box-sizing: border-box;
    }

    .centerLoader {
      display: flex;
      justify-content: center;
      align-items: center;
    }
  </style>
  <!--
  <uui-box>
    <h1>Page History</h1>
  </uui-box>
  <br/><br/>
  -->
  <div class="centerLoader" id="loader">
  <uui-loader style="color: color: #006eff"></uui-loader>
  </div>
  `;
}


function displaytable(tableData) {

    template.innerHTML = `
  
  <!--Table-->
  <uui-box style="--uui-box-default-padding:0">
  <div id="DisplayTable">
    ${baseTable(tableData)}
        
    </div>
</uui-box>
<div id="paging">
${renderPagination() } 
<div>

`;
}

function renderPagination() {
    var returnvalue = "";

    returnvalue = returnvalue + `
   
    <div style="overflow: hidden; padding: 6px;">
        <uui-pagination total="${_totalPages}" current="${_currentPage}" id="pageing"></uui-pagination>
    </div>
    `

    return returnvalue;
}

function buildtable(position) {
    var startindex = (position - 1) * _pageSize;
    var endIndex = startindex + _pageSize;
    var returnArray = _data.slice(startindex, endIndex);

    return returnArray;
}


function tableHeader() {
    var returnvalue = "";

    returnvalue = `
     <uui-table-head role="row">
                    <uui-table-head-cell role="columnheader">Id</uui-table-head-cell>
					<uui-table-head-cell role="columnheader">Name</uui-table-head-cell>
                    <uui-table-head-cell role="columnheader">Date</uui-table-head-cell>
                    <uui-table-head-cell role="columnheader">Alias</uui-table-head-cell>
					 <uui-table-head-cell role="columnheader"></uui-table-head-cell>
        </uui-table-head>
        `

    return returnvalue;
}

function _render(tableData) {
    var returnvalue = "";

    for (let i = 0; i < tableData.length; i++) {

        var id = tableData[i].id;
        var name = tableData[i].name;
        var date = tableData[i].date;
        var alias = tableData[i].alias;
        var defaultDate = tableData[i].defaultDate;
        var key = tableData[i].key;

        returnvalue = returnvalue + `
        <uui-table-row>
        <uui-table-cell><a href="/section/content/workspace/document/edit/${key}/invariant">${id}</a></uui-table-cell>
        <uui-table-cell>${name}</uui-table-cell>
        <uui-table-cell>${defaultDate}</uui-table-cell>
        <uui-table-cell>${alias}</uui-table-cell>
        <uui-table-cell>
             <uui-button id="Button-${id}" pristine="" label="Edit" look="primary" href="/section/content/workspace/document/edit/${key}/invariant"></uui-button>
        </uui-table-cell>

        </uui-table-row>
        `


    }

    return returnvalue;
}

function displayPage() {
    _root.appendChild(template.content.cloneNode(true));
}

function displayLoaderAndTable() {
    var table = _root.getElementById("DisplayTable");
    var loader = _root.getElementById("loader");
    table.style.display = "block";
    loader.style.display = "none";
}

function baseTable(tableData) {
    var returnvalue = "";

    returnvalue = `
    <uui-table id="pages">

                <!--Header-->
                ${tableHeader()}

				<!--Content here-->
                ${_render(tableData)} 
        </uui-table>
    
    `

    return returnvalue
}

function setupPaging() {
    pageing = _root.getElementById("pageing");
    pageing.addEventListener("click", function (e) {

        var position = e.target._current
        var newArray = buildtable(position);
        clearTable(newArray);
    });
}

function clearTable(newArray) {
    var table = _root.getElementById("DisplayTable");

    table.innerHTML = "";
    var newTable = baseTable(newArray);

    table.innerHTML = newTable;
}

async function loadData() {
    const TOKEN = await _authentication?.getLatestToken();
    const response = await fetch('/umbraco/management/api/v1/emeraled/pagehistory', {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + TOKEN,
            'credentials': 'include'
        }
    });

    if (!response.ok) {
        console.log("Error getting data.");
        throw new Error(`HTTP error! ${response.status}`);
    } else {
        console.log("Data ok!");

        const data = await response.json();
        console.log(data);
        _data = data;

        _totalItems = _data.length;
        _totalPages = Math.round(_totalItems / _pageSize);
        var position = 1
        displaytable(buildtable(position))
        displayPage();
        displayLoaderAndTable();
        setupPaging();
    }
}

customElements.define("pagehistory-extension", MyDashboardElement);


hey @darrenhunterEmerald

uui-table is just a display component it renders rows/cells but doesn’t have any built-in sorting, so you’ll need to wire that up yourself.

If you tried using the Sorter and couldn’t get it working, that’s probably because it’s not actually for this — UmbSorterController is for drag-and-drop reordering of a list, not for sorting a column by clicking its header. Different problem, so it won’t help here.

For click-to-sort-a-column, handle it the same way you handled paging by attaching listeners after the markup is in the DOM, not inside the string-building functions themselves:

  1. Give each header cell an identifiable attribute in tableHeader(), e.g. <uui-table-head-cell data-field="name" role="columnheader">Name</uui-table-head-cell>.
  2. After render (alongside your setupPaging() call), add a setupSorting() function that grabs the header cells from _root and attaches click listeners:

js

function setupSorting() {
    var headers = _root.querySelectorAll("uui-table-head-cell[data-field]");
    headers.forEach(function (header) {
        header.addEventListener("click", function () {
            sortData(header.getAttribute("data-field"));
        });
    });
}
  1. Write sortData(field) to toggle direction and sort _data directly:

js

let _sortColumn = null;
let _sortDirection = "asc";

function sortData(field) {
    if (_sortColumn === field) {
        _sortDirection = _sortDirection === "asc" ? "desc" : "asc";
    } else {
        _sortColumn = field;
        _sortDirection = "asc";
    }

    _data.sort((a, b) => {
        const valA = a[field];
        const valB = b[field];
        if (valA < valB) return _sortDirection === "asc" ? -1 : 1;
        if (valA > valB) return _sortDirection === "asc" ? 1 : -1;
        return 0;
    });

    _currentPage = 1;
    clearTable(buildtable(_currentPage));
}
  1. Call setupSorting() again after clearTable() rebuilds the markup, since the old header elements (and their listeners) get wiped out along with the rest of the table’s innerHTML.
  2. Also call setupSorting() once in loadData(), right next to your existing setupPaging() call otherwise the first render won’t have any listeners attached at all.