npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

sortabular

v1.6.0

Published

Sort utilities

Downloads

15,654

Readme

build status bitHound Score codecov

Sortabular - Sort utilities

Sortabular's helpers make it possible to manage sorting related classes and to sort rows based on them.

API

The API consists of the sort helpers, transforms, formatters, and strategies. These can be combined together to set up sort in various ways. There's control over the algorithm used as well as how it's bound to the user interface. You can also control how sorting is tracked (per column index or per property).

import * as sort from 'sortabular';

// Or you can cherry-pick
import { byColumn } from 'sortabular';
import { byColumn as sortByColumn } from 'sortabular';

Helpers

sort.byColumn({ sortingColumns: <sorting columns>, sortingOrder: {FIRST: <string>, <string>: <string>}, selectedColumn: <string> }) => <sorting colums> || {}

sort.byColumn allows you to sort per one column. It discards possible existing sorting state. If you are trying to sort the same column, it will cycle between ascending, descending, and no sorting. In case you are trying to sort some other column, it will start from the ascending state while discarding the existing sorting state.

sort.byColumns({ sortingColumns: <sorting columns>, sortingOrder: {FIRST: <string>, <string>: <string>}, selectedColumn: <string> }) => <sorting columns> || {}

sort.byColumns is like sort.byColumn except it doesn't discard possible existing sort state and instead accumulates it. This allows you to perform sorting over multiple columns while refining the results. The last-sorted column always has the highest position value, i.e. the lowest sorting priority.

sort.byColumnsPrioritizeLastSorted({ sortingColumns: <sorting columns>, sortingOrder: {FIRST: <string>, <string>: <string>}, selectedColumn: <string> }) => <sorting columns> || {}

sort.byColumnsPrioritizeLastSorted is like sort.byColumns except it always gives the last-sorted column the highest sorting priority.

sort.sorter({ columns: [<object>], sortingColumns: <sorting columns>, sort: <function>, strategy = strategies.byIndex })([<rows to sort>]) => [<sorted rows>]

sort.sorter sorts the passed rows using a sortingColumns definitions and a sort function. It has been designed to work based on lodash.orderBy signature.

If you want to evaluate columns in a reverse order instead of the default, you can reverse sort function like this:

const reverseSort = (data, columnIndexList, orderList) => (
  orderBy(data, columnIndexList.slice().reverse(), orderList.slice().reverse())
);

Transforms

sort.sort = ({ event = 'onClick', getSortingColumns = () => [], strategy = strategies.byIndex, onSort = (columnIndex) => {} } = {}) => (value, { columnIndex }, props)

sort.sort can be applied as a transform. It expects getSortingColumns and onSort callbacks. The former should return the sorting column data, the latter is called when the user sorts based on event.

sort.reset = ({ event = 'onDoubleClick', getSortingColumns = () => [], strategy = strategies.byIndex, onReset = (columnIndex) => {} } = {}) => (value, { columnIndex }, props)

sort.reset can be applied as a transform. It expects getSortingColumns and onReset callbacks. The former should return the sorting column data, the latter is called when the user sorts based on event.

Formatters

sort.header = ({ sortable, strategy = strategies.byIndex, getSortingColumns = () => [] }) => (value, { columnIndex })

sort.header can be used to sort within a header cell. This works well with sort.reset since then you can apply both reseting and sorting to the same cell without conflicts. It expects an initialized sortable (i.e., sort.sort) and getSortingColumns. If sorting is active at a column, it displays the current order number.

sortable and strategy parameters are optional. getSortingColumns is required.

You can customize props of sort.header specific portions through the following protocol:

props = {
  container: {},
  value: {},
  order: {}
}

sort.order = ({ strategy = strategies.byIndex, getSortingColumns = () => [] }) => (value, { columnIndex })

sort.order can be used to display a sorting arrow widget. It figures out the class name based on the current sorting status. sort.header uses this internally.

Strategies

Most of the functions accept a strategy. This allows you to modify their sorting behavior. By default they'll track sorting by column index. It's possible to change it to sort by property.

sort.strategies.byIndex

byIndex is the default strategy used by other functions. It literally means the system will track sorting per index.

sort.strategies.byProperty

byProperty ties sorting state to column property. This can be useful if you want to retain the sorting state within a column while moving it around.

Sorting Protocol

Sorting relies on a structure like this to describe what is being sorted and in what order:

const sortingColumns = {
  0: {
    direction: 'asc',
    position: 1
  },
  1: {
    direction: 'desc',
    position: 0
  }
};

It maps column index to sorting state and can contain multiple sorters.

Customizing Sorting Order

It is possible to customize the sorting order of sort.byColumn and sort.byColumns by passing an object describing the sorting. It should contain FIRST key to describe the starting point. The remaining key-value pairs should form a cycle.

Assuming you are using the sort transform, the order values are used for generating the classes you see at the user interface.

The default order cycles between asc, desc, and '' (no sort).

You could implement a custom order cycling between asc and desc like this:

const sortingOrder = {
  FIRST: 'asc',
  asc: 'desc',
  desc: 'asc'
};

The sort Transform

The sort transform has been designed to track when the user requests sorting and render possibly matching sorting condition as a class for styling. In addition you will need to use specific sort helpers to handle the sorting logic. The helpers have been encapsulated within the sort module.

Example:

...
import * as sort from 'sortabular';

...

const sortable = sort.sort({
  // Point the transform to your rows. React state can work for this purpose
  // but you can use a state manager as well.
  getSortingColumns: () => this.state.sortingColumns || [],

  // The user requested sorting, adjust the sorting state accordingly.
  // This is a good chance to pass the request through a sorter.
  onSort: selectedColumn => {
    this.setState({
      sortingColumns: sort.byColumns({ // sort.byColumn would work too
        sortingColumns: this.state.sortingColumns,
        selectedColumn
      })
    });
  }
});

...

// Mark a header as sortable
columns: [
  {
    property: 'name',
    header: {
      label: 'name',
      transforms: [sortable]
    }
  }
]

How to Use?

The general workflow goes as follows:

  1. Set up the sort transform. Its purpose is to track when the user requests sorting and render possibly matching sorting condition as a class for styling.
  2. Set up a sort helper. There are helpers for sorting per one column (sort.byColumn) and one for sorting per multiple columns (sort.byColumns). The helpers handle managing sorting conditions and actual sorting. If you have a back-end, you can skip the latter.
  3. Sort the rows before rendering.
  4. Feed the sorted rows to a Table.

You can find suggested default styling for the package at style.css in the package root.

Example:

/*
import React from 'react';
import orderBy from 'lodash/orderBy';
import * as resolve from 'table-resolver';
import * as Table from 'reactabular-table';
import * as sort from 'sortabular';
import { compose } from 'redux';
*/

const initialRows = [
  {
    id: 100,
    name: {
      first: 'Adam',
      last: 'West'
    },
    age: 10
  },
  {
    id: 101,
    name: {
      first: 'Brian',
      last: 'Eno'
    },
    age: 43
  },
  {
    id: 102,
    name: {
      first: 'Brian',
      last: 'Wilson'
    },
    age: 23
  },
  {
    id: 103,
    name: {
      first: 'Jake',
      last: 'Dalton'
    },
    age: 33
  },
  {
    id: 104,
    name: {
      first: 'Jill',
      last: 'Jackson'
    },
    age: 63
  }
];

class SortTable extends React.Component {
  constructor(props) {
    super(props);

    const getSortingColumns = () => this.state.sortingColumns || {};
    const sortable = sort.sort({
      // Point the transform to your rows. React state can work for this purpose
      // but you can use a state manager as well.
      getSortingColumns,

      // The user requested sorting, adjust the sorting state accordingly.
      // This is a good chance to pass the request through a sorter.
      onSort: selectedColumn => {
        this.setState({
          sortingColumns: sort.byColumns({ // sort.byColumn would work too
            sortingColumns: this.state.sortingColumns,
            selectedColumn
          })
        });
      },

      // Use property strategy over index one given we have nested data
      strategy: sort.strategies.byProperty
    });
    const resetable = sort.reset({
      event: 'onDoubleClick',
      getSortingColumns,
      onReset: ({ sortingColumns }) => this.setState({ sortingColumns }),
      strategy: sort.strategies.byProperty
    });

    this.state = {
      // Sort the first column in a descending way by default.
      // "asc" would work too and you can set multiple if you want.
      sortingColumns: {
        'name.first': {
          direction: 'desc',
          position: 0
        }
      },
      columns: [
        {
          header: {
            label: 'Name'
          },
          children: [
            {
              property: 'name.first',
              header: {
                label: 'First Name',
                transforms: [resetable],
                formatters: [
                  sort.header({
                    sortable,
                    getSortingColumns,
                    strategy: sort.strategies.byProperty
                  })
                ]
              }
            },
            {
              property: 'name.last',
              header: {
                label: 'Last Name',
                transforms: [resetable],
                formatters: [
                  sort.header({
                    sortable,
                    getSortingColumns,
                    strategy: sort.strategies.byProperty
                  })
                ]
              }
            }
          ]
        },
        {
          property: 'age',
          header: {
            label: 'Age',
            transforms: [resetable],
            formatters: [
              sort.header({
                sortable,
                getSortingColumns,
                strategy: sort.strategies.byProperty
              })
            ]
            // Alternative if you don't need reset.
            // transforms: [sortable]
          }
        }
      ],
      rows: initialRows
    };
  }
  render() {
    const { rows, sortingColumns, columns } = this.state;
    const resolvedColumns = resolve.columnChildren({ columns });
    const sortedRows = compose(
      sort.sorter({
        columns: resolvedColumns,
        sortingColumns,
        sort: orderBy,
        strategy: sort.strategies.byProperty
      }),
      resolve.resolve({
        columns: resolvedColumns,
        method: resolve.nested
      })
    )(rows);

    return (
      <div>
        <Table.Provider columns={resolvedColumns}>
          <Table.Header
            headerRows={resolve.headerRows({ columns })}
          />

          <Table.Body rows={sortedRows} rowKey="id" />
        </Table.Provider>
      </div>
    );
  }
}

<SortTable />

License

MIT. See LICENSE for details.