Search code examples
sapui5

Sort table by certain parts of string only


The sorting works basically fine using the sorter. One column is the full name (e.g. "Steve Jobs"). I have only the full name in that entity set but I want to sort the entries by the last name (last word of the full name) when clicking the full name column header. Is there some way to do this?


Solution

  • You'll need to define a custom comparator for the sorter which applies only if all the entities are available client-side, for example, by having the operationMode set to 'Client' when defining the OData list binding.API

    sap.ui.getCore().attachInit(() => sap.ui.require([
      "sap/ui/model/odata/v2/ODataModel",
      "sap/ui/core/mvc/XMLView",
      "sap/ui/model/json/JSONModel",
      "sap/ui/model/Sorter",
    ], (ODataModel, XMLView, JSONModel, Sorter) => {
      "use strict";
      const odataModel = new ODataModel({
        serviceUrl: [
          "https://cors-anywhere.herokuapp.com/",
          "https://services.odata.org/V2/Northwind/Northwind.svc/",
        ].join(""),
        tokenHandling: false,
        preliminaryContext: true,
      });
      Promise.all([
        odataModel.metadataLoaded(),
        sap.ui.getCore().loadLibrary("sap.m", true),
      ]).then(() => XMLView.create({
        definition: `<mvc:View xmlns:mvc="sap.ui.core.mvc"
          xmlns="sap.m"
          xmlns:core="sap.ui.core"
          height="100%"
        >
          <App>
            <Page showHeader="false">
              <Table id="myResponsiveTable"
                items="{
                  path: '/Customers',
                  parameters: {
                    select: 'CustomerID, ContactName',
                    operationMode: 'Client'
                  }
                }"
              >
                <columns>
                  <Column id="customerIdColumn"
                    sortIndicator="{colCustomerId>/sortOrder}"
                    width="33%"
                  >
                    <Text text="Customer ID">
                      <customData>
                        <core:CustomData
                          key="sorterPath"
                          value="CustomerID"
                        />
                      </customData>
                    </Text>
                  </Column>
                  <Column id="fullNameColumn"
                    sortIndicator="{colFullName>/sortOrder}"
                    width="auto"
                  >
                    <Text text="Full Name">
                      <customData>
                        <core:CustomData
                          key="sorterPath"
                          value="ContactName"
                        />
                      </customData>
                    </Text>
                  </Column>
                </columns>
                <ColumnListItem>
                  <Text text="{CustomerID}" />
                  <Text text="{ContactName}" />
                </ColumnListItem>
              </Table>
            </Page>
          </App>
        </mvc:View>`,
        afterInit: function() { // === onInit
          const table = this.byId("myResponsiveTable");
          activateColumnPress(table, onColumnPress);
        },
        models: {
          undefined: odataModel,
          colCustomerId: new JSONModel({ sortOrder: "None" }),
          colFullName: new JSONModel({ sortOrder: "None" }),
        }
      }).then(view => view.placeAt("content")));
    
      function activateColumnPress(table, handler) {
        // Making columns clickable for the demo
        table.bActiveHeaders = true;
        table.onColumnPress = col => handler(table, col);
      }
      
      function onColumnPress(table, pressedCol) {
        table.getColumns()
          .filter(col => !(col.getSortIndicator() === pressedCol.getSortIndicator()))
          .map(col => col.setSortIndicator("None"));
        table.getBinding("items").sort(createSorter(pressedCol));
      }
    
      function createSorter(column) {
        return column.getHeader().data("sorterPath") === "ContactName"
          ? createFullNameSorter(column, toggleSort(column.getModel("colFullName")))
          : createCustomerIdSorter(column, toggleSort(column.getModel("colCustomerId")));
      }
    
      function toggleSort(colModel) {
        const descending = colModel.getProperty("/sortOrder") !== "Ascending";
        colModel.setProperty("/sortOrder", descending ? "Ascending" : "Descending");
        return !descending;
      }
    
      function createFullNameSorter(column, descending) {
        const comparator = (a, b) => a.split(" ").pop().localeCompare(b.split(" ").pop());
        return new Sorter("ContactName", descending, false, comparator);
      }
    
      function createCustomerIdSorter(column, descending) {
        return new Sorter("CustomerID", descending);
      }
    
    }));
    <script id="sap-ui-bootstrap"
      src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
      data-sap-ui-libs="sap.ui.core"
      data-sap-ui-async="true"
      data-sap-ui-compatversion="edge"
      data-sap-ui-theme="sap_belize"
      data-sap-ui-xx-waitfortheme="true"
    ></script>
    <body id="content" class="sapUiBody sapUiSizeCompact" style="height: 100%;"></body>

    Btw: the "Client" operation mode currently doesn't fetch all entities if the service has server-side paging implemented.

    As you can see in the example above, the Sorter constructor can handle custom comparator which will be invoked when the sort method is called. For comparing last parts of the full names, you can define the comparator like this:

    function compare(fullName_a, fullName_b) {
      const lastPart_a = fullName_a.split(" ").pop();
      const lastPart_b = fullName_b.split(" ").pop();
      return lastPart_a.localeCompare(lastPart_b); // 0 if equal. Otherwise a negative or positive number
    }