# Handler 类

# 概述

MVC 结构中的 controller

接受 DOM 的鼠标事件(如果存在 HandleProxy ,则已经规范简化为 mouseEvent) , 根据鼠标经过、点击等位置变化,将事件分发给 Element

# 构造函数

入参:

  • storage Sotrage 类实例, 存储元素的内容仓库
  • painter Painter 类实例,绘制场景
  • proxy handlerProxy 事件抽象
  • painterRoot 实例化时的dom

# 关系

# 继承

# Eventful

Eventful 提供事件的绑定和触发特性

# Draggable

Draggable 提供可拖动的特性

疑问: handler 并无实体,何来的拖动呢?

# 方法

# setHandlerProxy

监听 HandlerProxy 中的事件, 事件列表为 handlerNames

疑问: Handler 全局只实例化了一次, 应该不存在 this.proxy, 并且 setHandlerProxy 没有其他的调用的地方, 除非用户自己调用。

setHandlerProxy: function (proxy) {
    if (this.proxy) {
        this.proxy.dispose();
    }

    if (proxy) {
        util.each(handlerNames, function (name) {
            proxy.on && proxy.on(name, this[name], this);
        }, this);
        // Attach handler
        proxy.handler = this;
    }
    this.proxy = proxy;
}
var handlerNames = [
    'click', 'dblclick', 'mousewheel', 'mouseout',
    'mouseup', 'mousedown', 'mousemove', 'contextmenu'
];

# mousemove

监听 HandlerProxymousemove 方法并进行处理

  • 获取鼠标经过的元素
  • 根据元素的选项 opts.cursor 来设置鼠标样式
  • 如果从一个元素离开,触发元素的 mouseout
  • 触发元素的 mousemove
  • 如果到了一个新元素, 触发元素的 mouseover
mousemove: function (event) {
    var x = event.zrX;
    var y = event.zrY;

    var isOutside = isOutsideBoundary(this, x, y);

    var lastHovered = this._hovered;
    var lastHoveredTarget = lastHovered.target;

    if (lastHoveredTarget && !lastHoveredTarget.__zr) {
        lastHovered = this.findHover(lastHovered.x, lastHovered.y);
        lastHoveredTarget = lastHovered.target;
    }

    var hovered = this._hovered = isOutside ? {x: x, y: y} : this.findHover(x, y);
    var hoveredTarget = hovered.target;

    var proxy = this.proxy;
    proxy.setCursor && proxy.setCursor(hoveredTarget ? hoveredTarget.cursor : 'default');

    if (lastHoveredTarget && hoveredTarget !== lastHoveredTarget) {
        this.dispatchToElement(lastHovered, 'mouseout', event);
    }

    this.dispatchToElement(hovered, 'mousemove', event);
    if (hoveredTarget && hoveredTarget !== lastHoveredTarget) {
        this.dispatchToElement(hovered, 'mouseover', event);
    }
}

# mouseout

触发 mouseout 事件,是整个 HandlerProxy 的 mouseout ,不是元素的

  • 触发 mouseout 事件
  • 触发 globalout 事件,从多余的区域移动到真正的外部是触发
mouseout: function (event) {
    var eventControl = event.zrEventControl;
    var zrIsToLocalDOM = event.zrIsToLocalDOM;

    if (eventControl !== 'only_globalout') {
        this.dispatchToElement(this._hovered, 'mouseout', event);
    }

    if (eventControl !== 'no_globalout') {
        // FIXME: if the pointer moving from the extra doms to realy "outside",
        // the `globalout` should have been triggered. But currently not.
        !zrIsToLocalDOM && this.trigger('globalout', {type: 'globalout', event: event});
    }
}

# resize

接受 zrender 的 resize 调用, 清除 _hovered

resize: function (event) {
    this._hovered = {};
}

# dispatch

事件派发(代码中未调动)

 dispatch: function (eventName, eventArgs) {
    var handler = this[eventName];
    handler && handler.call(this, eventArgs);
}

# dispose

销毁

dispose: function () {
    this.proxy.dispose();
    this.storage =
    this.proxy =
    this.painter = null;
}

# setCursorStyle

设置鼠标样式

setCursorStyle: function (cursorStyle) {
    var proxy = this.proxy;
    proxy.setCursor && proxy.setCursor(cursorStyle);
}

# dispatchToElement

事件分发个 Element

  • 判断 opts.silent 选项
  • 根据 el.parent 获取元素的父元素(Group IncrementalDisplayable)
  • 如果元素使用了 onmouseover 这类方式监听数据, 这派发事件
  • 触发事件
  • cancelBubble 是否停止冒泡, 为什么不用 ev.stopPropagation() 呢
  • 触发事件到 Handler 的监听
  • layer 也可以被派发事件,前提是 builtin 不为 true

builtin 何时不为 true , getRenderedCanvas 方法调用创建 layer 的时候,可是只有 Zrender.prototype.toDataURL 的时候才会被调用, 代码被注释掉了。

dispatchToElement: function (targetInfo, eventName, event) {
    targetInfo = targetInfo || {};
    var el = targetInfo.target;
    if (el && el.silent) {
        return;
    }
    var eventHandler = 'on' + eventName;
    var eventPacket = makeEventPacket(eventName, targetInfo, event);

    while (el) {
        el[eventHandler]
            && (eventPacket.cancelBubble = el[eventHandler].call(el, eventPacket));

        el.trigger(eventName, eventPacket);

        el = el.parent;

        if (eventPacket.cancelBubble) {
            break;
        }
    }

    if (!eventPacket.cancelBubble) {
        // 冒泡到顶级 zrender 对象
        this.trigger(eventName, eventPacket);
        // 分发事件到用户自定义层
        // 用户有可能在全局 click 事件中 dispose,所以需要判断下 painter 是否存在
        this.painter && this.painter.eachOtherLayer(function (layer) {
            if (typeof (layer[eventHandler]) === 'function') {
                layer[eventHandler].call(layer, eventPacket);
            }
            if (layer.trigger) {
                layer.trigger(eventName, eventPacket);
            }
        });
    }
}

# findHover

查找鼠标经过时,经过的元素

findHover: function (x, y, exclude) {
    var list = this.storage.getDisplayList();
    var out = {x: x, y: y};
    for (var i = list.length - 1; i >= 0; i--) {
        var hoverCheckResult;
        if (list[i] !== exclude
            && !list[i].ignore
            && (hoverCheckResult = isHover(list[i], x, y))
        ) {
            !out.topTarget && (out.topTarget = list[i]);
            if (hoverCheckResult !== SILENT) {
                out.target = list[i];
                break;
            }
        }
    }
    return out;
}

# processGesture

手势, 由 HandlerProxytouchstart touchmove touchend 来触发

留到后面研究手势

processGesture: function (event, stage) {
    if (!this._gestureMgr) {
        this._gestureMgr = new GestureMgr();
    }
    var gestureMgr = this._gestureMgr;

    stage === 'start' && gestureMgr.clear();

    var gestureInfo = gestureMgr.recognize(
        event,
        this.findHover(event.zrX, event.zrY, null).target,
        this.proxy.dom
    );

    stage === 'end' && gestureMgr.clear();

    // Do not do any preventDefault here. Upper application do that if necessary.
    if (gestureInfo) {
        var type = gestureInfo.type;
        event.gestureEvent = type;

        this.dispatchToElement({target: gestureInfo.target}, type, gestureInfo.event);
    }
}

# 通用事件

util.each(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
    Handler.prototype[name] = function (event) {
        var x = event.zrX;
        var y = event.zrY;
        var isOutside = isOutsideBoundary(this, x, y);

        var hovered;
        var hoveredTarget;

        if (name !== 'mouseup' || !isOutside) {
            hovered = this.findHover(x, y);
            hoveredTarget = hovered.target;
        }

        if (name === 'mousedown') {
            this._downEl = hoveredTarget;
            this._downPoint = [event.zrX, event.zrY];
            this._upEl = hoveredTarget;
        }
        else if (name === 'mouseup') {
            this._upEl = hoveredTarget;
        }
        else if (name === 'click') {
            if (this._downEl !== this._upEl
                || !this._downPoint
                || vec2.dist(this._downPoint, [event.zrX, event.zrY]) > 4
            ) {
                return;
            }
            this._downPoint = null;
        }

        this.dispatchToElement(hovered, name, event);
    };
});

# 通用方法

# isOutsideBoundary

是否在 Painter 的包围盒外, 如果拖动事件的话,可能就已经移出了范围,需要停止事件吧。

function isOutsideBoundary(handlerInstance, x, y) {
    var painter = handlerInstance.painter;
    return x < 0 || x > painter.getWidth() || y < 0 || y > painter.getHeight();
}

# isHover

判断坐标是否包含在元素中, opts.rectHover 选项决定方法。暂时不深究了

function isHover(displayable, x, y) {
    if (displayable[displayable.rectHover ? 'rectContain' : 'contain'](x, y)) {
        var el = displayable;
        var isSilent;
        while (el) {
            if (el.clipPath && !el.clipPath.contain(x, y)) {
                return false;
            }
            if (el.silent) {
                isSilent = true;
            }
            el = el.parent;
        }
        return isSilent ? SILENT : true;
    }
    return false;
}