package.src.event.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mikado Show documentation
Show all versions of mikado Show documentation
Web's fastest template library to build user interfaces.
The newest version!
// COMPILER BLOCK -->
import { DEBUG, MIKADO_EVENT_CACHE, MIKADO_TPL_ROOT, PROFILER, SUPPORT_EVENTS } from "./config.js";
import { tick } from "./profiler.js";
// <-- COMPILER BLOCK
import { EventOptions } from "./type.js";
import Mikado from "./mikado.js";
/** @type {Object} */
const events = {};
/** @type {Object} */
const event_options = {};
/** @type {Object} */
const routes = SUPPORT_EVENTS ? Object.create(null) : null;
/** @type {Object} */
const options = SUPPORT_EVENTS ? Object.create(null) : null;
// The most outer element which is covered by Mikado event system is document.body
const doc = document.documentElement || document.body.parentNode;
const has_touch = "ontouchstart" in window;
const has_pointer = !has_touch && window["PointerEvent"] && navigator["maxTouchPoints"];
// if(SUPPORT_EVENTS){
//
// // TODO replace by "$route:*"
// /** @type {boolean} */
// Mikado.eventCache = false;
//
// // TODO replace by using wildcard within colon notation "route:*" or "route:target:*"
// /** @type {boolean} */
// Mikado.eventBubble = false;
// }
let tap_fallback;
/**
* @param {!Event} event
* @param {string=} type
*/
export function handler(event, type){
const event_target = event.target;
// we just handle events which are defined somewhere in the DOM
// do we need handle events on documentElement?
if(event_target === window || event_target === doc) return;
PROFILER && tick("event.trigger");
let use_event_cache; // = Mikado.eventCache;
//let use_bubble = Mikado.eventBubble;
type || (type = event.type);
let cache;
// When the Event-Cache is used, all the assigned event route names and all the event targets exposed by bubbling
// are being cached, and therefore they can't be changed dynamically after its creation.
// Instead of: you will need then just apply logic inside the handler.
// Alternatively a route can re-defined dynamically by register a new function to it by calling Mikado.route(name, fn, options) again.
// Delete a route by Mikado.route(name, null, null)
//if(use_event_cache){
cache = event_target[MIKADO_EVENT_CACHE + type];
//}
if(typeof cache === "undefined"){
let target = event_target;
// bubble up the dom tree to find element which has assigned a handler
while(target && (target !== doc)){
PROFILER && tick("event.bubble");
let route;
if((type === "click") && tap_fallback){
route = target.getAttribute("tap");
}
if(!route){
route = target.getAttribute(type);
}
if(route){
if(route[0] === "$"){
// just enable cache when the most inner handler has set it
use_event_cache = target === event_target;
route = route.substring(1);
}
const delimiter = route.indexOf(":");
// when continue bubble it needs the original target
let root = target;
let use_bubble;
// it has a custom target, bubbling needs to continue
if(delimiter > -1){
use_bubble = route.endsWith(":*");
const handler = route.substring(0, delimiter);
const attr = route.substring(delimiter + 1, route.length - (use_bubble ? 2 : 0));
route = "";
// continue bubble up the dom tree to find the custom defined root element
while((root = root.parentElement) !== doc){
PROFILER && tick("event.bubble");
if(attr === "root" ? root[MIKADO_TPL_ROOT] : root.hasAttribute(attr)){
route = handler;
break;
}
}
if(DEBUG){
if(!route){
console.warn("Event root '" + attr + "' was not found for the event: '" + handler + "'.");
}
}
}
if(route){
if(!cache){
cache = [];
if(use_event_cache){
event_target[MIKADO_EVENT_CACHE + type] = cache;
}
}
cache.push([route, root]);
const option = options[route];
if(!use_bubble || (option && (option.stop || option.cancel))){
// stop bubbling
break;
}
}
}
// continue bubble up
target = target.parentElement;
}
if(use_event_cache){
cache || (event_target[MIKADO_EVENT_CACHE + type] = null);
}
}
else{
PROFILER && tick("event.cache");
}
if(cache) for(let i = 0, tmp; i < cache.length; i++){
tmp = cache[i];
const route = tmp[0];
const fn = routes[route];
if(fn){
const target = tmp[1];
const option = options[route];
if(option){
option.prevent && event.preventDefault();
option.stop && event.stopImmediatePropagation();
if(option.once){
routes[route] = null;
if(use_event_cache){
event_target[MIKADO_EVENT_CACHE + type] = null;
}
}
}
PROFILER && tick("route.call");
//fn(target, event, event_target);
fn(target, event);
}
else if(DEBUG){
console.warn("The route '" + route + "' is not defined for the event '" + type + "'.");
}
}
}
/**
* @param {!string} route
* @param {Function|null} fn
* @param {EventOptions=} option
*/
export function route(route, fn, option){
PROFILER && tick("route.set");
if(DEBUG){
if(!route){
throw new Error("Missing route name.");
}
if(!fn){
throw new Error("The route '" + route + "' has no function assigned to it.");
}
if(routes[route]){
console.info("A new handler was re-assigned to the route '" + route + "'.");
}
}
routes[route] = fn;
options[route] = option || null;
return this;
}
/**
* @param {!string} route
* @param {Element=} target
* @param {Event=} event
*/
export function dispatch(route, target, event){
PROFILER && tick("route.dispatch");
if(DEBUG){
if(!route){
throw new Error("Missing route name.");
}
if(!routes[route]){
throw new Error("Undefined route '" + route + "'.");
}
}
routes[route](target || this, event || window.event);
return this;
}
/**
* @param {!string} event
* @param {EventListenerOptions|boolean=} options
*/
export function listen(event, options){
if(!events[event]){
PROFILER && tick("event.listen");
register_event(1, event, handler, options);
events[event] = 1;
event_options[event] = options || null;
}
return this;
}
/**
* @param {string} event
* @returns {Mikado}
*/
export function unlisten(event){
if(events[event]){
PROFILER && tick("event.unlisten");
register_event(0, event, handler, event_options[event]);
events[event] = 0;
event_options[event] = null;
}
return this;
}
let touch_x, touch_y, register_tap;
if(has_touch || has_pointer){
/**
* @param {TouchEvent|PointerEvent} event
*/
function handler_down(event){
pointer(event, event.touches);
}
/**
* @param {TouchEvent|PointerEvent} event
*/
function handler_end(event){
const last_x = touch_x;
const last_y = touch_y;
pointer(event, event.changedTouches);
if((Math.abs(touch_x - last_x) < 15) &&
(Math.abs(touch_y - last_y) < 15)){
handler(event, "tap");
}
}
/**
* @param {TouchEvent|PointerEvent|Touch} event
* @param {TouchList} touches
*/
function pointer(event, touches){
if(touches){
event = touches[0];
}
touch_x = event.clientX;
touch_y = event.clientY;
}
/**
* @type {EventListenerOptions}
*/
const opt = {
// Note: the default click behavior should not force passive handling
passive: false,
// Capturing by default, since we need the event dispatched from window
capture: true
};
register_tap = function(add_or_remove){
register_event(add_or_remove, has_pointer ? "pointerdown" : "touchstart", handler_down, opt);
//register_event(add_or_remove, "touchmove", handler_move, false);
register_event(add_or_remove, has_pointer ? "pointerup" : "touchend", handler_end, opt);
}
}
/**
* @param {boolean|number} add_or_remove
* @param {string} type
* @param {Function} handler
* @param {EventListenerOptions|boolean=} options
*/
function register_event(add_or_remove, type, handler, options){
PROFILER && tick(add_or_remove ? "event.register" : "event.unregister");
if(type === "tap"){
if(has_touch || has_pointer){
register_tap(add_or_remove);
return;
}
else{
tap_fallback = true;
type = "click";
}
}
window[(add_or_remove ? "add": "remove") + "EventListener"](
type,
handler,
// Capturing by default, since we need the event dispatched from window
options || options === false ? options : true
);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy