Add custom sorting option

906

📘

Componens

To add a new custom sorting option, you would need to update three components, for the desktop sorting and for mobile sorting. For instance, if you want to create a custom option for alphabetical sorting, you would need to update item props in both components mentioned above. Here is the example:

/**
 * @module components/Sorting
 */
import React from 'react';
import { connectSort } from '@findify/react-connect';
import { compose, withPropsOnChange, setDisplayName, withHandlers, branch, renderNothing } from 'recompose';
import withTheme from 'helpers/withTheme';
import { is, Map } from 'immutable';

import view from 'components/Sorting/view';
import styles from 'components/Sorting/styles.css';

export default compose(
  setDisplayName('Sorting'),
  withTheme(styles),
  connectSort,
  withPropsOnChange(['config'], ({ config, selected }) => {
    const labels = config.getIn(['sorting', 'i18n', 'options']);
    if (!labels) return;

    const items = config.getIn(['sorting', 'options']).map(i =>
      i.set('label', labels.get([i.get('field'), i.get('order')].filter(i => i).join('|')))
    )
    
    // here are the two options
    // field: by which field the items should be sorted
    // order: either 'asc' or 'desc'
    // label: how those options should be named
    .push(new Map({ field: 'title', order: 'asc', label: 'Name A-Z' }))
    .push(new Map({ field: 'title', order: 'desc', label: 'Name Z-A' }));

    return { items }
  }),
  withPropsOnChange(['selected'], ({ items, selected }) => ({
    selectedItem: selected && items && items.find(i =>
      is(i.get('order'), selected.get('order')) &&
      is(i.get('field'), selected.get('field'))
    )
  })),
  withHandlers({
    onChangeSort: ({ onChangeSort }) => item => onChangeSort(item.get('field', 'default'), item.get('order', ''))
  }),

  branch(
    ({ items }) => !items,
    renderNothing
  )
)(view);
/**
 * @module components/search/MobileSorting
 */
import React from 'react';
import { compose, setDisplayName, withProps, withHandlers } from "recompose";
import { connectSort } from '@findify/react-connect';
import withTheme from 'helpers/withTheme';
import { is, Map } from 'immutable';
import pure from 'helpers/pure';

import view from 'components/search/MobileSorting/view';
import styles from 'components/search/MobileSorting/styles.css';

export default compose(
  pure,
  setDisplayName('MobileSorting'),
  withTheme(styles),
  connectSort,
  withProps(({ config, meta }) => {
    const selected = meta.getIn(['sort', 0]);
    const labels = config.getIn(['sorting', 'i18n', 'options']);
    const items = config.getIn(['sorting', 'options']).map(i => i
      .set('label',
        labels.get([i.get('field'), i.get('order')].filter(i => i).join('|'))
      )                                       
      .set('selected',
        !!selected
        ? is(i.get('order'), selected.get('order')) && is(i.get('field'), selected.get('field'))
        : i.get('field') === 'default'
      )
    )
    //add these here
    .push(new Map({ field: 'title', order: 'asc', label: 'Name A-Z', selected: selected && is('asc', selected.get('order')) && is('discount', selected.get('field'))}))
    .push(new Map({ field: 'title', order: 'desc', label: 'Name Z-A', selected: selected && is('desc', selected.get('order')) && is('discount', selected.get('field'))})); 
    return { items }
  }),

  withHandlers({
    setSorting: ({ items, onChangeSort }) => index =>
      onChangeSort(items.getIn([index, 'field'], 'default'), items.getIn([index, 'order'], '')),
  })
)(view)
/**
 * @module components/search/MobileActions
 */
import React from 'react';
import { connectSort, connectQuery } from '@findify/react-connect';
import { compose, withHandlers, withPropsOnChange } from 'recompose';
import withEvents from 'helpers/withEvents';
import withTheme from 'helpers/withTheme';

import view from 'components/search/MobileActions/view';
import styles from 'components/search/MobileActions/styles.css';

// in MobileActions we can adjust the labels to update the labels on the mobile button
const labels = {
  "default": "Popularity",
  "created_at|desc": "Newest",
  "price|desc": "Price: High to low",
  "price|asc": "Price: Low to high",
  "title|asc": "Name A-Z",
  "title|desc": "Name Z-A",
};

export default compose(
  withTheme(styles),
  connectSort,
  connectQuery,
  withEvents(),
  withHandlers({
    showFacets: ({ emit }) => () => emit('showMobileFacets'),
    showSort: ({ emit }) => () => emit('showMobileSort')
  }),
  withPropsOnChange(['selected'], ({ selected, config }) => ({
    sorting: labels[!!selected
      && [selected.get('field'), selected.get('order')].join('|')
      || 'default'
    ]
  })),
  withPropsOnChange(['query'], ({ query }) => query.get('filters') && ({
    total: query.get('filters').reduce(
      // The workaround to not sum the nested category filters
      (acc, filter, key) =>  acc + (/category[2-9]/.test(key) ? 0 : filter.size)
    , 0)
  }))
)(view);

Alternatively, you could create a new items list instead of pushing new items to the existing list within the components components/Sorting/index.ts and components/search/MobileSorting/index.ts.
Let's try it with an example were we want to change the default order of the labels:

/**
 * @module components/Sorting
 */
import React from 'react';
import { connectSort } from '@findify/react-connect';
import { compose, withPropsOnChange, setDisplayName, withHandlers, branch, renderNothing } from 'recompose';
import withTheme from 'helpers/withTheme';
// import Map and List from immutable
import { is, Map, List } from 'immutable';

import view from 'components/Sorting/view';
import styles from 'components/Sorting/styles.css';

export default compose(
  setDisplayName('Sorting'),
  withTheme(styles),
  connectSort,
  withPropsOnChange(['config'], ({ config, selected }) => {
    const labels = config.getIn(['sorting', 'i18n', 'options']);
    if (!labels) return;

    // replace config items with a new List
    const items = new List([
      new Map({ field: 'default', order: '', label: 'Best Selling' }),
      new Map({ field: 'created_at', order: 'desc', label: 'Newest' }),
      new Map({ field: 'price', order: 'asc', label:  "Price $ - $" + "$" }),
      new Map({ field: 'price', order: 'desc', label: "Price $" + "$ - $" }),
    ])
    return { items }
  }),
  withPropsOnChange(['selected'], ({ items, selected }) => ({
    selectedItem: selected && items && items.find(i =>
      is(i.get('order'), selected.get('order')) &&
      is(i.get('field'), selected.get('field'))
    )
  })),
  withHandlers({
    onChangeSort: ({ onChangeSort }) => item => onChangeSort(item.get('field', 'default'), item.get('order', ''))
  }),

  branch(
    ({ items }) => !items,
    renderNothing
  )
)(view);
/**
 * @module components/search/MobileSorting
 */
import React from 'react';
import { compose, setDisplayName, withProps, withHandlers } from "recompose";
import { connectSort } from '@findify/react-connect';
import withTheme from 'helpers/withTheme';
// import List and Map from immutable
import { is, List, Map } from 'immutable';
import pure from 'helpers/pure';

import view from 'components/search/MobileSorting/view';
import styles from 'components/search/MobileSorting/styles.css';

export default compose(
  pure,
  setDisplayName('MobileSorting'),
  withTheme(styles),
  connectSort,
  withProps(({ config, meta }) => {
    const selected = meta.getIn(['sort', 0]);
    const labels = config.getIn(['sorting', 'i18n', 'options']);
    // replace config items with a new List
    const items = new List([
      new Map({ field: 'default', order: '', label: 'Best Selling' }),
      new Map({ field: 'created_at', order: 'desc', label: 'Newest' }),
      new Map({ field: 'price', order: 'asc', label: 'Price $ - $$' }),
      new Map({ field: 'price', order: 'desc', label: 'Price $$ - $' }),
    ]).map(i => i
      .set('selected',
        !!selected
        ? is(i.get('order'), selected.get('order')) && is(i.get('field'), selected.get('field'))
        : i.get('field') === 'default'
      )
    );
    return { items }
  }),

  withHandlers({
     setSorting: ({ items, onChangeSort, update }) => index =>
      update('sort', [{field: items.getIn([index, 'field'], 'default'), order: items.getIn([index, 'order'])}, {field: 'title', order: 'asc'}])
  })
)(view)


onChangeSort: ({ update }) => item => update('sort', [{field: item.get('field', 'default'), order: item.get('order', '')}, {field: 'title', order: 'asc'}])
/**
 * @module components/search/MobileActions
 */
import React from 'react';
import { connectSort, connectQuery } from '@findify/react-connect';
import { compose, withHandlers, withPropsOnChange } from 'recompose';
import withEvents from 'helpers/withEvents';
import withTheme from 'helpers/withTheme';

import view from 'components/search/MobileActions/view';
import styles from 'components/search/MobileActions/styles.css';

// // in MobileActions we can adjust the labels to update the labels on the mobile button
const labels = {
  "default": "Best Selling",
  "created_at|desc": "Newest",
  "price|desc": "Price $" + "$ - $",
  "price|asc":  "Price $ - $" + "$",
};

export default compose(
  withTheme(styles),
  connectSort,
  connectQuery,
  withEvents(),
  withHandlers({
    showFacets: ({ emit }) => () => emit('showMobileFacets'),
    showSort: ({ emit }) => () => emit('showMobileSort')
  }),
  withPropsOnChange(['selected'], ({ selected, config }) => ({
    sorting: labels[!!selected && selected.get('order') && [selected.get('field'), selected.get('order')].join('|')
      || 'default' 
    ]
  })),
  withPropsOnChange(['query'], ({ query }) => query.get('filters') && ({
    total: query.get('filters').reduce(
      // The workaround to not sum the nested category filters
      (acc, filter, key) =>  acc + (/category[2-9]/.test(key) ? 0 : filter.size)
    , 0)
  }))
)(view);

❗️

As shown in the above example, if you would like the price labels to say something like "Price $ - $$" or "Price $$ - $", please use the following snippet as a reference. It is important to use "Price $" + "$ - $" instead of putting two $ signs directly after another, which would cause the store to break.

const labels = {
“default”: “Best Selling”,
“created_at|desc”: “Newest”,
“price|desc”: “Price $” + “$ - $“,
“price|asc”: “Price $ - $” + “$”
};

If you would like to additionally sort the items within the sorting options, to, for example, sort items in ascending order of prices alphabetically, you would need to rewrite onChangeSort/setSorting functions on desktop and mobile sortings:

withHandlers({
    onChangeSort: ({ update }) => item => update('sort', [{field: item.get('field', 'default'), order: item.get('order', '')}, {field: 'title', order: 'asc'}])
  }),
withHandlers({
     setSorting: ({ items, onChangeSort, update }) => index =>
      update('sort', [{field: items.getIn([index, 'field'], 'default'), order: items.getIn([index, 'order'])}, {field: 'title', order: 'asc'}])
  })