# src/Whisper.js File

    import React,{PropTypes, cloneElement} from 'react';
    import ReactDOM from 'react/lib/ReactDOM';
    import Component from './utils/Component';
    import classnames from 'classnames';

    /**
     * <h5>提示模块，主要包括一下两个组件:</h5>
     * <strong><a href='../classes/Whisper.html'>Whisper</a></strong><br/>
     * <strong><a href='../classes/Popover.html'>Popover</a></strong><br>
     * <h6>点击以上链接或者左侧导航栏的组件名称链接进行查看</h6>
     * @module 提示组件
     * @main 提示组件
     * @static
     *
     */

    /**
     * 倾听者组件<br/>
     * - 配合提示组件使用, 通过target设置显示的目标。
     * - 可通过placement设置目标物的显示位置, 可选top、bottom、left、right、top left、top right、bottom left、bottom right。
     * - 可通过distance设置目标物到点击对象(倾听者)的位置。
     * - 可通过onTargetChange定义目标物显隐时额外的回调函数。
     *
     * 主要属性和接口：
     * - target:目标物。
     * - placement:目标物的显示位置, 默认bottom。
     * - distance:目标物到点击对象(倾听者)的位置, 默认15。
     * - onTargetChange:目标物显隐时额外的回调函数。
     *
     * 示例:
     * ```code
     *     const popover = ( // 可以通过style自定义位置
     *         <Popover>
     *             <ul className="ph-popover-list">
     *                 <li className="ph-popover-item">未上线单店</li>
     *                 <li className="ph-popover-item">未上线连锁店</li>
     *             </ul>
     *         </Popover>
     *     );
     * ```
     * ```code
     *     <Whisper placement="top" onTargetChange={()=>{console.log('气泡出现消失时额外的执行函数');}} target={popover} distance={10} >Top</Whisper>
     * ```
     *
     * @class Whisper
     * @module 提示组件
     * @extends Component
     * @constructor
     * @since 1.0.0
     * @demo popover|popover.js {展示}
     * @show true
     * */
    export default class Whisper extends Component{

        static propTypes = {
            /**
             * 样式前缀
             * @property classPrefix
             * @type String
             * @default 'whisper'
             * */
            classPrefix: PropTypes.string,
            /**
             * 标签tagName
             * @property componentTag
             * @type String
             * */
            componentTag: PropTypes.string,
            /**
             * 显示的目标气泡
             * @property target
             * @type Object
             * */
            target: PropTypes.object,
            /**
             * 气泡的位置,默认bottom
             * @property placement
             * @type String
             * */
            placement: PropTypes.string,
            /**
             * 气泡距离点击物的位置,默认15
             * @property distance
             * @type Number
             * */
            distance: PropTypes.number,
            /**
             * 气泡显隐时可执行的额外函数,自定义
             * @method onTargetChange
             * @type Function
             * */
            onTargetChange: PropTypes.func
        };

        static defaultProps = {
            distance: 15,
            placement: 'bottom',
            classPrefix:'whisper',
            componentTag: 'div',
            classMapping : {}
        };

        constructor(props, context) {
            super(props, context);

            this.visible = false;
            this._layer = document.createElement('div');
        }

        componentDidMount(){
            setTimeout(()=>{
                this.getWhisperPosition();
            },0);
        }

        getWhisperPosition(){
            this.position = {};
            this.size = {};

            this.position.x = parseInt(this.whisper.offsetLeft);
            this.position.y = parseInt(this.whisper.offsetTop);

            this.size.width = parseInt(this.whisper.offsetWidth);
            this.size.height = parseInt(this.whisper.offsetHeight);

            this.calcTooltipPosition();
        }

        calcTooltipPosition(){
            const ARROW_SIZE = this.props.distance;
            let winWidth = parseInt(document.body.clientWidth),
                winHeight = parseInt(document.body.clientHeight);

            document.body.style.position = 'relative';
            this.style = {};

            switch(this.props.placement){
                case 'top':
                    this.style.bottom = winHeight - this.position.y + ARROW_SIZE;
                    this.style.left = this.position.x + this.size.width/2;
                    break;
                case 'bottom':
                    this.style.top = this.position.y + this.size.height + ARROW_SIZE;
                    this.style.left = this.position.x + this.size.width/2;
                    break;
                case 'left':
                    this.style.right = winWidth - this.position.x + ARROW_SIZE;
                    this.style.top = this.position.y + this.size.height/2;
                    break;
                case 'right':
                    this.style.left = this.position.x + this.size.width + ARROW_SIZE;
                    this.style.top = this.position.y + this.size.height/2;
                    break;
                case 'top left':
                    this.style.bottom = winHeight - this.position.y + ARROW_SIZE;
                    this.style.left = this.position.x;
                    break;
                case 'top right':
                    this.style.bottom = winHeight - this.position.y + ARROW_SIZE;
                    this.style.right = winWidth - this.position.x - this.size.width;
                    break;
                case 'bottom left':
                    this.style.top = this.position.y + this.size.height + ARROW_SIZE;
                    this.style.left = this.position.x;
                    break;
                case 'bottom right':
                    this.style.top = this.position.y + this.size.height + ARROW_SIZE;
                    this.style.right = winWidth - this.position.x - this.size.width;
                    break;
                default:
                    this.style.top = 0;
                    this.style.left = 0;
            }
        }

        onToggle(){
            this.visible = !this.visible;

            if(this.visible){
                this.renderTarget();
            }else{
                this.removeTarget();
            }

            if(this.props.onTargetChange) this.props.onTargetChange();
        }

        onClose(){
            this.visible = false;
            this.removeTarget();
        }

        getTarget(){
            return cloneElement(this.props.target, {
                styles: this.style,
                placement: this.props.placement,
                onClose: this.onClose.bind(this),
                whisper: this.whisper,
                setVisible: this.setVisible
            });
        }

        renderTarget(){
            let newTarget = this.getTarget();

            ReactDOM.unstable_renderSubtreeIntoContainer(this, newTarget, this._layer);
            document.body.appendChild(this._layer);
        }

        removeTarget(){
            ReactDOM.unmountComponentAtNode(this._layer);
            document.body.removeChild(this._layer);
        }

        componentWillUnmount(){
            if(this.visible) this.onClose();
        }

        render(){
            let {className, children} = this.props;

            return (
                <div {...this.props}
                    className={classnames(this.getProperty(true), className)}
                    onClick={::this.onToggle}
                    ref={(whisper)=>{this.whisper = whisper}}
                >
                    {children}
                </div>
            );
        }
    }