Home Reference Source

scripts/django_cradmin/components/CradminSelectableList.jsx

import React from "react";
import CradminSelectableListItem from "./CradminSelectableListItem";


export default class CradminSelectableList extends React.Component {

  static get defaultProps() {
    return {
      className: 'selectable-list',
      keyAttribute: 'id',
      renderSelected: true,
      signalNameSpace: null,
      itemComponentProps: {},
      loadMoreTreshold: 3
    }
  }

  constructor(props) {
    super(props);
    this._name = `django_cradmin.components.CradminSelectableList.${this.props.signalNameSpace}`;
    this.logger = new window.ievv_jsbase_core.LoggerSingleton().getLogger(
      'django_cradmin.components.CradminSelectableList');
    if(this.props.signalNameSpace == null) {
      throw new Error('The signalNameSpace prop is required.');
    }
    this._onDataChangeSignal = this._onDataChangeSignal.bind(this);
    this._onSelectionChangeSignal = this._onSelectionChangeSignal.bind(this);
    this._onFocusOnSelectableItemSignal = this._onFocusOnSelectableItemSignal.bind(this);
    this._onFocusOnFirstSelectableItemSignal = this._onFocusOnFirstSelectableItemSignal.bind(this);
    this._onFocusOnLastSelectableItemSignal = this._onFocusOnLastSelectableItemSignal.bind(this);

    this._focusOnItemData = null;
    this.state = {
      dataList: [],
      renderableDataList: [],
      hasMorePages: false,
      selectedItemsMap: new Map()
    };

    this.initializeSignalHandlers();
  }

  initializeSignalHandlers() {
    new window.ievv_jsbase_core.SignalHandlerSingleton().addReceiver(
      `${this.props.signalNameSpace}.DataChange`,
      this._name,
      this._onDataChangeSignal
    );
    new window.ievv_jsbase_core.SignalHandlerSingleton().addReceiver(
      `${this.props.signalNameSpace}.SelectionChange`,
      this._name,
      this._onSelectionChangeSignal
    );
    new window.ievv_jsbase_core.SignalHandlerSingleton().addReceiver(
      `${this.props.signalNameSpace}.FocusOnSelectableItem`,
      this._name,
      this._onFocusOnSelectableItemSignal
    );
    new window.ievv_jsbase_core.SignalHandlerSingleton().addReceiver(
      `${this.props.signalNameSpace}.FocusOnFirstSelectableItem`,
      this._name,
      this._onFocusOnFirstSelectableItemSignal
    );
    new window.ievv_jsbase_core.SignalHandlerSingleton().addReceiver(
      `${this.props.signalNameSpace}.FocusOnLastSelectableItem`,
      this._name,
      this._onFocusOnLastSelectableItemSignal
    );
  }

  componentWillUnmount() {
    new window.ievv_jsbase_core.SignalHandlerSingleton()
      .removeAllSignalsFromReceiver(this._name);
  }

  render() {
    return <div className={this.props.className}>
      {this.renderItems()}
    </div>;
  }

  _loadMoreIfNeeded() {
    if(this.state.hasMorePages && this.state.renderableDataList.length < this.props.loadMoreTreshold) {
      this.logger.debug('Automatically sending the LoadMore signal because we are below the loadMoreTreshold');
      new window.ievv_jsbase_core.SignalHandlerSingleton().send(
        `${this.props.signalNameSpace}.LoadMore`);
    }
  }

  _makeRenderableDataList(dataList) {
    let renderableDataList = dataList;
    if(!this.props.renderSelected) {
      renderableDataList = [];
      for(let itemData of dataList) {
        let itemKey = itemData[this.props.keyAttribute];
        let isSelected = this.state.selectedItemsMap.has(itemKey);
        if(!isSelected) {
          renderableDataList.push(itemData);
        }
      }
    }
    return renderableDataList;
  }

  _onDataChangeSignal(receivedSignalInfo) {
    if(this.logger.isDebug) {
      this.logger.debug(receivedSignalInfo.toString(), receivedSignalInfo.data);
    }
    const dataList = receivedSignalInfo.data.results;
    this.setState({
      dataList: dataList,
      renderableDataList: this._makeRenderableDataList(dataList),
      hasMorePages: receivedSignalInfo.data.next != null
    });
    this._loadMoreIfNeeded();
  }

  _onSelectionChangeSignal(receivedSignalInfo) {
    if(this.logger.isDebug) {
      this.logger.debug(receivedSignalInfo.toString(), receivedSignalInfo.data);
    }
    this.setState({
      selectedItemsMap: receivedSignalInfo.data.selectedItemsMap,
      renderableDataList: this._makeRenderableDataList(this.state.dataList),
    });
    this._loadMoreIfNeeded();
  }

  _onFocusOnSelectableItemSignal(receivedSignalInfo) {
    if(this.logger.isDebug) {
      this.logger.debug(receivedSignalInfo.toString(), receivedSignalInfo.data);
    }
    this._focusOnItemData = receivedSignalInfo.data;
    this.forceUpdate();
  }

  _onFocusOnFirstSelectableItemSignal(receivedSignalInfo) {
    if(this.logger.isDebug) {
      this.logger.debug(receivedSignalInfo.toString(), receivedSignalInfo.data);
    }
    if(this.state.renderableDataList.length > 0) {
      this._focusOnItemData = this.state.renderableDataList[0];
      this.forceUpdate();
    }
  }

  _onFocusOnLastSelectableItemSignal(receivedSignalInfo) {
    if(this.logger.isDebug) {
      this.logger.debug(receivedSignalInfo.toString(), receivedSignalInfo.data);
    }
    if(this.state.renderableDataList.length > 0) {
      this._focusOnItemData = this.state.renderableDataList[this.state.renderableDataList.length - 1];
      this.forceUpdate();
    }
  }

  renderItem(itemKey, props) {
    return <CradminSelectableListItem key={itemKey} {...props} />;
  }

  renderItems() {
    const items = [];
    let previousItemData = null;
    for(let index=0; index < this.state.renderableDataList.length; index++) {
      let itemData = this.state.renderableDataList[index];
      let itemKey = itemData[this.props.keyAttribute];
      let isSelected = this.state.selectedItemsMap.has(itemKey);

      let nextItemData = null;
      let isLast = index == (this.state.renderableDataList.length - 1);
      if(!isLast) {
        nextItemData = this.state.renderableDataList[index + 1];
      }

      let props = Object.assign({
        focusClosestSiblingOnSelect: !this.props.renderSelected
      }, this.props.itemComponentProps, {
        data: itemData,
        itemKey: itemKey,
        isSelected: isSelected,
        signalNameSpace: this.props.signalNameSpace,
        previousItemData: previousItemData,
        nextItemData: nextItemData
      });
      items.push(this.renderItem(itemKey, props));
      previousItemData = itemData;
    }
    return items;
  }

  _sendFocusOnItemSignal(itemData) {
    const itemKey = itemData[this.props.keyAttribute];
    new window.ievv_jsbase_core.SignalHandlerSingleton().send(
      `${this.props.signalNameSpace}.FocusOnSelectableItem.${itemKey}`);
  }

  componentDidUpdate() {
    if(this._focusOnItemData != null) {
      this._sendFocusOnItemSignal(this._focusOnItemData);
      this._focusOnItemData = null;
    }
  }
}