Home Reference Source Repository

src/Pager.js

/**
 * @file melon/Pager
 * @author cxtom<[email protected]>
 */

import React, {Component, PropTypes} from 'react';
import Icon from './Icon';
import {create} from 'melon-core/classname/cxBuilder';
import {range} from 'melon-core/util/array';

const cx = create('Pager');

export default class Pager extends Component {

    constructor(props) {

        super(props);

        const page = props.page;

        this.state = {page};

        this.onMainClick = this.onMainClick.bind(this);

    }

    componentWillReceiveProps({page, total}) {

        if (page < 0) {
            page = 0;
        }
        else if (page > total) {
            page = total - 1;
        }

        if (page !== this.state.page) {
            this.setState({page});
        }
    }

    onMainClick(e) {

        e.preventDefault();

        const target = e.target;

        const role = target.getAttribute('data-role');

        if (role !== 'pager-item') {
            return;
        }

        const {first, onChange} = this.props;

        const page = +target.getAttribute('data-page') + first;

        if (this.state.page === page) {
            return;
        }

        // 被控制的
        if (onChange) {
            onChange({
                target: this,
                page: page
            });
            return;
        }

        this.setState({page});

    }

    /**
     * 生成一个页码数组, 如果需要ellipsis, 那么ellpsis用负数表示它;
     * 即ellipsis在5号位置, 那么他就是-5
     * 输入: start 0, stop 10, paddingLeft 3 paddingRight 3
     * 输出: 0, 1, 2, -3, 8, 9, 10
     *
     * @param  {number} start        起始页码
     * @param  {number} stop         结束页面(不包含)
     * @param  {number} paddingLeft  起始页码之后, 应展开的页码个数
     * @param  {number} paddingRight 结束页面之前, 应展开的页码个数
     * @return {Array.number}        [start, paddingLeft, .., paddingRight, stop]
     */
    range(start, stop, paddingLeft, paddingRight) {
        return start + paddingLeft < stop - paddingRight
            ? [
                ...range(start, start + paddingLeft),
                -start - paddingLeft,
                ...range(stop - paddingRight, stop)
            ]
            : range(start, stop);
    }

    renderItem(conf) {

        const {
            page,
            part,
            states
        } = conf;

        const {
            lang,
            useLang
        } = this.props;

        const classNames = cx()
            .part('item')
            .addStates(states)
            .build();

        let pageText;

        if (!useLang && part && part !== 'ellipsis') {
            pageText = (<Icon icon={Pager.ICONS[part]} data-role="pager-item" data-page={page} />);
        }
        else {
            pageText = lang[part] || page + 1;
        }

        return (
            <li
                className={classNames}
                key={part + page}
                data-role="pager-item"
                data-page={page}>
                {pageText}
            </li>
        );
    }

    render() {

        const {props, state} = this;

        let {
            total,
            first,
            padding,
            showCount,
            useLang,
            showAlways,
            ...others
        } = props;

        let page = state.page;

        showCount = showCount > total ? total : showCount;
        page = page - first;

        const className = cx(props).addVariants(useLang ? 'lang' : null).build();

        if (!showAlways && total <= 1) {
            return (
                <ul
                    {...others}
                    className={className}>
                </ul>
            );
        }

        const wing = Math.floor(showCount / 2);

        const paddingLeft = padding;
        const paddingRight = padding;
        const reduceLeftToRight = page - wing;

        let wingLeft = wing;
        let wingRight = wing;

        // 如果wingLeft小于0, 那么把小于0的部分移动到wingRight
        if (reduceLeftToRight < 0) {
            wingLeft += reduceLeftToRight;
            wingRight -= reduceLeftToRight;
        }

        const reduceRightToLeft = page + wing + 1 - total;

        // 如果wingRight大于total, 那么把超长的部分移动到wingLeft
        if (reduceRightToLeft > 0) {
            wingLeft += reduceRightToLeft;
            wingRight -= reduceRightToLeft;
        }

        // 生成左半端页码
        const left = this.range(0, page, paddingLeft, wingLeft);
        // 生成右半端页码
        const right = this.range(page + 1, total, wingRight, paddingRight);

        const result = [{
            page: Math.max(page - 1, 0),
            states: {
                prev: true,
                disabled: page === 0
            },
            part: 'prev'
        }]
        .concat(left)
        .concat({
            page: page,
            states: {
                current: true
            },
            part: ''
        })
        .concat(right)
        .concat({
            page: Math.min(page + 1, total - 1),
            states: {
                next: true,
                disabled: page >= total - 1
            },
            part: 'next'
        })
        .map(conf => {

            if (typeof conf === 'number') {
                const part = conf >= 0 ? '' : 'ellipsis';
                conf = {
                    page: Math.abs(conf),
                    states: {
                        ellipsis: !!part
                    },
                    part
                };
            }

            return this.renderItem(conf);

        });

        return (
            <ul
                {...others}
                className={className}
                onClick={this.onMainClick}>
                {result}
            </ul>
        );

    }

}

Pager.displayName = 'Pager';

Pager.defaultProps = {

    // 当前页,第一页从0开始
    page: 0,

    // 起始页码
    first: 0,

    // 首尾显示的页码个数
    padding: 1,

    // 是否一直显示分页控件
    showAlways: false,

    // 当页数较多时,中间显示页码的个数
    showCount: 5,

    // 总页数
    total: 0,

    // 是否可用
    disabled: false,

    useLang: false,

    // 上下页显示文字
    lang: {

        // 上一页显示文字
        prev: '上一页',

        // 下一页显示文字
        next: '下一页',

        // 省略号
        ellipsis: '...'
    }
};

Pager.propTypes = {
    disabled: PropTypes.bool,
    type: PropTypes.string,
    page: PropTypes.number,
    first: PropTypes.number,
    padding: PropTypes.number,
    showAlways: PropTypes.bool,
    showCount: PropTypes.number,
    total: PropTypes.number,
    useLang: PropTypes.bool,
    lang: PropTypes.shape({
        prev: PropTypes.string,
        ellipsis: PropTypes.string,
        next: PropTypes.string
    })
};

Pager.ICONS = {
    prev: 'navigate-before',
    next: 'navigate-next'
};