Skip to content
Snippets Groups Projects
selectList.js 5.33 KiB
Newer Older
import m from 'mithril';
import Stream from 'mithril/stream';
import {
  List, ListTile, Search, IconButton, Button, Toolbar,
  ToolbarTitle,
} from 'polythene-mithril';
import infinite from 'mithril-infinite';
import { icons, BackButton, ClearButton, SearchIcon } from './elements';
Hermann's avatar
Hermann committed
class SearchField {
  oninit() {
    this.value = Stream('');
    this.setInputState = Stream();
Hermann's avatar
Hermann committed
    // const clear = () => setInputState()({ value: '', focus: false});
    this.clear = () => this.value('');
    this.leave = () => this.value('');
  }
Hermann's avatar
Hermann committed
  view({ state, attrs }) {
    // incoming value and focus added for result list example:
    const value = attrs.value !== undefined ? attrs.value : state.value();
    return m(Search, Object.assign(
      {},
      {
        textfield: {
          label: 'type here',
          onChange: (newState) => {
            state.value(newState.value);
            state.setInputState(newState.setInputState);
            // onChange callback added for result list example:
            if (attrs.onChange) attrs.onChange(newState, state.setInputState);
Hermann's avatar
Hermann committed
          value,
          defaultValue: attrs.defaultValue,
        },
        buttons: {
          focus: {
            before: m(BackButton, { leave: state.leave }),
          },
          focus_dirty: {
            before: m(BackButton, { leave: state.leave }),
            after: m(ClearButton, { clear: state.clear }),
          },
          dirty: {
            before: m(BackButton, { leave: state.leave }),
            after: m(ClearButton, { clear: state.clear }),
Hermann's avatar
Hermann committed
      },
      attrs,
    ));
  }
}
Hermann's avatar
Hermann committed

export default class SelectList {
  constructor({ attrs: { listTileAttrs, onSelect = false } }) {
Hermann's avatar
Hermann committed
    this.showList = false;
    this.searchValue = '';
    this.listTileAttrs = listTileAttrs;
    this.onSelect = onSelect;
    // initialize the Selection
    this.selected = null;
  }

  onupdate({ attrs: { selection = null } }) {
    // make it possible to change the selection from outside, e.g. to set the field to an
    // existing group moderator
    if (selection) this.selected = selection;
Hermann's avatar
Hermann committed
  }

  item() {
    return (data) => {
      const attrs = {
        compactFront: true,
        hoverable: true,
        className: 'themed-list-tile',
        events: {
          onclick: () => {
            if (this.onSelect) { this.onSelect(data); }
            this.selected = data;
            this.showList = false;
          },
        },
      };
      // Overwrite default attrs
      Object.assign(attrs, this.listTileAttrs(data));
      return m(ListTile, attrs);
    };
  }
Hermann's avatar
Hermann committed

Hermann's avatar
Hermann committed
  view({
    attrs: {
      controller,
      onSubmit = false,
      onCancel = false,
Hermann's avatar
Hermann committed
      selectedText,
    },
  }) {
      m(Toolbar, { compact: true, style: { background: 'rgb(78, 242, 167)' } }, this.selected ? [
          icon: { svg: m.trust(icons.clear) },
          events: {
            onclick: () => {
              if (this.onSelect) { this.onSelect(null); }
              this.selected = null;
            },
          },
        m(ToolbarTitle, { text: selectedText(this.selected) }),
        onSubmit ? m(Button, {
          label: 'Submit',
          className: 'blue-button',
          events: {
            onclick: () => {
              onSubmit(this.selected);
              this.selected = null;
              controller.setSearch('');
              controller.refresh();
Hermann's avatar
Hermann committed
            },
        }) : '',
      ] : [
        m(SearchField, Object.assign({}, {
          style: { background: 'rgb(78, 242, 167)' },
          onChange: ({ value, focus }) => {
            // onChange is called either if the value or focus fo the SearchField
            // changes.
            // At value change we want to update the search
            // at focus changt we hie the list of results. As focus change also
            // happens while clicking on an item in the list of results, the list
            // is hidden after a short Timeout that has to be sufficiently long
            // to register the onclick of the listitem. Can be a problem for different
            // OS and browsers.
            if (focus) {
              this.showList = true;
            } else if (!focus) {
              // don't close the list immidiately, as 'out of focus' could
              // also mean that the user is clicking on a list item
              setTimeout(() => { this.showList = false; m.redraw(); }, 500);
            }
            if (value !== this.searchValue) {
              // if we always update the search value, this would also happen
              // immidiately in the moment where we click on the listitem.
              // Then, the list get's updated before the click is registered.
              // So, we make sure this state change is due to value change and
              // not due to focus change.
              this.searchValue = value;
              controller.debouncedSearch(value);
            }
          },
        })),
        onCancel ? m(Button, {
          label: 'cancel',
          className: 'blue-button',
          events: { onclick: onCancel },
        }) : '',
      ]),
      (this.showList && !this.selected) ? m(List, {
        style: { height: '400px', 'background-color': 'white' },
        tiles: m(infinite, controller.infiniteScrollParams(this.item())),
      }) : '',
Hermann's avatar
Hermann committed
    ]);
  }
}