superdesk/superdesk-client-core

View on GitHub
scripts/apps/contacts/components/ItemList.tsx

Summary

Maintainability
B
5 hrs
Test Coverage
import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {Item} from 'apps/contacts/components';
import {gettext} from 'core/utils';

import {IContact} from '../Contacts';

interface IProps {
    svc: {
        $timeout: any;
        Keys: {
            right: any;
            down: any;
            left: any;
            up: any;
        };
    };
    scope: {
        preview(item: IContact): void;
        $apply: any;
        viewColumn: boolean;
        loading: boolean;
    };
}

interface IState {
    itemsList: Array<any>;
    itemsById: {[key: string]: IContact};
    selected: string;
    view: string;
}

export class ItemList extends React.Component<IProps, IState> {
    static propTypes: any;
    static defaultProps: any;

    updateTimeout: any;

    constructor(props) {
        super(props);

        this.state = {
            itemsList: [],
            itemsById: {},
            selected: null,
            view: 'photogrid',
        };

        this.select = this.select.bind(this);
        this.setSelectedItem = this.setSelectedItem.bind(this);
        this.getSelectedItem = this.getSelectedItem.bind(this);
        this.handleKey = this.handleKey.bind(this);
    }

    select(item, event?) {
        const {$timeout} = this.props.svc;
        const {scope} = this.props;

        this.setSelectedItem(item);

        $timeout.cancel(this.updateTimeout);
        this.updateTimeout = $timeout(() => {
            if (item && scope.preview) {
                scope.$apply(() => {
                    scope.preview(item);
                });
            }
        }, 0, false);
    }

    setSelectedItem(item) {
        this.setState({selected: item ? item._id : null});
    }

    getSelectedItem() {
        const selected = this.state.selected;

        return this.state.itemsById[selected];
    }

    handleKey(event) {
        const {scope} = this.props;
        const {Keys} = this.props.svc;

        let diff;

        switch (event.keyCode) {
        case Keys.right:
        case Keys.down:
            diff = 1;
            break;

        case Keys.left:
        case Keys.up:
            diff = -1;
            break;
        }

        const highlightSelected = () => {
            for (let i = 0; i < this.state.itemsList.length; i++) {
                if (this.state.itemsList[i] === this.state.selected) {
                    const next = Math.min(this.state.itemsList.length - 1, Math.max(0, i + diff));

                    this.select(this.state.itemsById[this.state.itemsList[next]]);
                    return;
                }
            }
        };

        const checkRemaining = () => {
            event.preventDefault();
            event.stopPropagation();

            if (this.state.selected) {
                highlightSelected();
            } else {
                this.select(this.state.itemsById[this.state.itemsList[0]]);
            }
        };

        // This function is to bring the selected item (by key press) into view if it is out of container boundary.
        const scrollSelectedItemIfRequired = (_event, _scope) => {
            const container = _scope.viewColumn ? $(document).find('.content-list') : $(_event.currentTarget);

            const selectedItemElem = $(_event.currentTarget.firstChild).children('.list-item-view.active');

            if (selectedItemElem.length > 0) {
                // The following line translated to: top_Of_Selected_Item (minus) top_Of_Scrollable_Div

                const distanceOfSelItemFromVisibleTop = $(selectedItemElem[0]).offset().top - $(document).scrollTop() -
                $(container[0]).offset().top - $(document).scrollTop();

                // If the selected item goes beyond container view, scroll it to middle.
                if (distanceOfSelItemFromVisibleTop >= container[0].clientHeight ||
                    distanceOfSelItemFromVisibleTop < 0) {
                    container.scrollTop(container.scrollTop() + distanceOfSelItemFromVisibleTop -
                    container[0].offsetHeight * 0.5);
                }
            }
        };

        if (!_.isNil(diff)) {
            checkRemaining();
            scrollSelectedItemIfRequired(event, scope);
        }
    }

    render() {
        const {scope, svc} = this.props;

        const _createItem = function createItem(itemId) {
            const item = this.state.itemsById[itemId];

            const flags = {selected: this.state.selected === itemId};

            return (
                <Item
                    key={itemId}
                    item={item}
                    view={this.state.view}
                    flags={flags}
                    onSelect={this.select}
                    svc={svc}
                    scope={scope}
                />
            );
        }.bind(this);
        const isEmpty = !this.state.itemsList.length;

        const cssClass = classNames(
            this.state.view === 'photogrid' ?
                'sd-grid-list sd-grid-list--large sd-grid-list--small-margin' :
                (this.state.view || 'compact') + '-view list-view',
            {'list-without-items': isEmpty},
        );

        const listItems = isEmpty && !scope.loading ?
            <span key="no-items">{gettext('There are currently no items')}</span>
            : this.state.itemsList.map(_createItem);

        return (
            <ul className={cssClass}>
                {listItems}
            </ul>
        );
    }
}

ItemList.propTypes = {
    svc: PropTypes.object.isRequired,
    scope: PropTypes.any.isRequired,
};