# events.js

    import * as d3 from "d3";

    /**
     * Custom Dispatch Events
     *
     * @type {d3.dispatch}
     */
    export const dispatch = d3.dispatch("d3X3dClick", "d3X3dMouseOver", "d3X3dMouseOut");

    /**
     * Attach Event Listeners to Shape
     *
     * Detect X3DOM events and convert them into D3 dispatch events.
     *
     * @param el
     */
    export function attachEventListners(el) {
    	el.attr("onclick", "d3.x3d.events.forwardEvent(event);")
    		.on("click", function(e) { dispatch.call("d3X3dClick", this, e); });

    	el.attr("onmouseover", "d3.x3d.events.forwardEvent(event);")
    		.on("mouseover", function(e) { dispatch.call("d3X3dMouseOver", this, e); });

    	el.attr("onmouseout", "d3.x3d.events.forwardEvent(event);")
    		.on("mouseout", function(e) { dispatch.call("d3X3dMouseOut", this, e); });
    }

    /**
     * Forward X3DOM Event to D3
     *
     * In X3DOM, it is the canvas which captures onclick events, therefore defining a D3 event handler
     * on an single X3DOM element does not work. A workaround is to define an onclick handler which then
     * forwards the call to the D3 "click" event handler with the event.
     * Note: X3DOM and D3 event members slightly differ, so d3.mouse() function does not work.
     *
     * @param {event} event
     * @see https://bl.ocks.org/hlvoorhees/5376764
     */
    export function forwardEvent(event) {
    	let type = event.type;
    	let target = d3.select(event.target);
    	target.on(type)(event);
    }

    /**
     * Show Alert With Event Coordinate
     *
     * @param {event} event
     * @returns {{canvas: {x: (*|number), y: (*|number)}, world: {x: *, y: *, z: *}, page: {x: number, y: number}}}
     */
    export function getEventCoordinates(event) {
    	let pagePoint = getEventPagePoint(event);

    	return {
    		world: { x: event.hitPnt[0], y: event.hitPnt[1], z: event.hitPnt[2] },
    		canvas: { x: event.layerX, y: event.layerY },
    		page: { x: pagePoint.x, y: pagePoint.y }
    	}
    }

    /**
     * Inverse of coordinate transform defined by function mousePosition(evt) in x3dom.js
     *
     * @param {event} event
     * @returns {{x: number, y: number}}
     */
    export function getEventPagePoint(event) {
    	let pageX = -1;
    	let pageY = -1;

    	let convertPoint = window.webkitConvertPointFromPageToNode;

    	if ("getBoundingClientRect" in document.documentElement) {
    		let holder = getX3domHolder(event);
    		let computedStyle = document.defaultView.getComputedStyle(holder, null);
    		let paddingLeft = parseFloat(computedStyle.getPropertyValue("padding-left"));
    		let borderLeftWidth = parseFloat(computedStyle.getPropertyValue("border-left-width"));
    		let paddingTop = parseFloat(computedStyle.getPropertyValue("padding-top"));
    		let borderTopWidth = parseFloat(computedStyle.getPropertyValue("border-top-width"));
    		let box = holder.getBoundingClientRect();
    		let scrolLeft = window.pageXOffset || document.body.scrollLeft;
    		let scrollTop = window.pageYOffset || document.body.scrollTop;
    		pageX = Math.round(event.layerX + (box.left + paddingLeft + borderLeftWidth + scrolLeft));
    		pageY = Math.round(event.layerY + (box.top + paddingTop + borderTopWidth + scrollTop));
    	} else if (convertPoint) {
    		let pagePoint = convertPoint(event.target, new WebKitPoint(0, 0));
    		pageX = Math.round(pagePoint.x);
    		pageY = Math.round(pagePoint.y);
    	} else {
    		x3dom.debug.logError("Unable to find getBoundingClientRect or webkitConvertPointFromPageToNode");
    	}

    	return { x: pageX, y: pageY };
    }

    /**
     * Return the x3d Parent Holder
     *
     * Find clicked element, walk up DOM until we find the parent x3d.
     * Then return the x3d's parent.
     *
     * @param event
     * @returns {*}
     */
    export function getX3domHolder(event) {
    	let target = d3.select(event.target);

    	let x3d = target.select(function() {
    		let el = this;
    		while (el.nodeName.toLowerCase() !== "x3d") {
    			el = el.parentElement;
    		}

    		return el;
    	});

    	return x3d.select(function() {
    		return this.parentNode;
    	}).node();
    }