Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENYO-5271: Add debugging utilities to core/handle #1652

Merged
merged 9 commits into from
May 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

The following is a curated list of changes in the Enact core module, newest changes on the top.

## [unreleased]

### Added

- `core/handle.handle` utility `bindAs` to facilitate debugging and binding handlers to component instances

## [2.0.0-beta.3] - 2018-05-14

No significant changes.
Expand Down
95 changes: 66 additions & 29 deletions packages/core/handle/handle.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,31 @@
* `props` and `context`. This allows you to write consistent event handlers for components created
* either with `kind()` or ES6 classes without worrying about from where the props are sourced.
*
* Handlers can either be bound directly using the native `bind()` method or using the `bindAs()`
* utility method that is appended to the handler.
*
* ```
* import {forKey, forward, handle, preventDefault} from '@enact/core/handle';
* import React from 'react';
*
* class MyComponent extends React.Component {
* // bind handle() to the instance
* handle = handle.bind(this)
*
* // then create handlers using the bound function
* logEnter = this.handle(
* forward('onKeyDown'), // forwards the event to the function passed in the onKeyDown prop
* forKey('enter'), // if the event.keyCode maps to the enter key, allows event processing to continue
* preventDefault, // calls event.preventDefault() to prevent the `keypress` event
* (ev, props) => { // custom event handler -- in this case, logging some text
* // In the bound version, `props` will contain a reference to this.props
* // since it doesn't return `true`, no further input functions would be called after this one
* console.log('The Enter key was pressed down');
* }
* )
* constructor () {
* super();
*
* // logEnter will be bound to `this` and set as this.handleKeyDown
* //
* // Equivalent to the following with the advantage of set the function name to be displayed in
* // development tool call stacks
* //
* // this.handleKeyDown = logEnter.bind(this)
* logEnter.bindAs(this, 'handleKeyDown');
Copy link
Contributor

@webOS101 webOS101 May 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does logEnter come from? Aha, above. It was redundant here, I guess?

* }
*
* render () {
* // ...
* return (
* <div onKeyDown={this.handleKeyDown} />
* );
* }
* }
* ```
Expand Down Expand Up @@ -85,6 +88,40 @@ const hasPropsAndContext = (obj) => {
return obj && obj.hasOwnProperty && obj.hasOwnProperty('props') && obj.hasOwnProperty('context');
};

const named = (fn, name) => {
if (__DEV__) {
try {
Object.defineProperty(fn, 'name', {
value: name,
writeable: false,
enumerable: false
});
} catch (err) {
// unable to set name of function
}
}

return fn;
};

const bindAs = (fn, obj, name) => {
const namedFunction = name ? named(fn, name) : fn;
const bound = namedFunction.bind(obj);

if (name) {
obj[name] = bound;
}

return bound;
};

const decorateHandleFunction = (fn) => {
fn.named = (name) => named(fn, name);
fn.bindAs = (obj, name) => bindAs(fn, obj, name);

return fn;
};

/**
* Allows generating event handlers by chaining input functions to filter or short-circuit the
* handling flow. Any input function that returns a falsey value will stop the chain.
Expand All @@ -107,7 +144,7 @@ const handle = function (...handlers) {
// context if fn() doesn't have its own `this`.
const _outer = this;

const fn = function (ev, props, context) {
const fn = function prepareHandleArgs (ev, props, context) {
let caller = null;

// if handle() was bound to a class, use its props and context. otherwise, we accept
Expand All @@ -126,7 +163,7 @@ const handle = function (...handlers) {
};

fn.finally = function (cleanup) {
return function (ev, props, context) {
return decorateHandleFunction(function handleWithFinally (ev, props, context) {
let result = false;

if (hasPropsAndContext(this)) {
Expand All @@ -141,10 +178,10 @@ const handle = function (...handlers) {
}

return result;
};
});
};

return fn;
return decorateHandleFunction(fn);
};

/**
Expand Down Expand Up @@ -195,11 +232,11 @@ const oneOf = handle.oneOf = function (...handlers) {
*/
const returnsTrue = handle.returnsTrue = function (handler) {
if (handler) {
return function (...args) {
return named(function (...args) {
handler.apply(this, args);

return true;
};
}, 'returnsTrue');
}

return true;
Expand Down Expand Up @@ -279,14 +316,14 @@ const forEventProp = handle.forEventProp = curry((prop, value, ev) => {
* @param {Object} props Props object
* @returns {Boolean} Always returns `true`
*/
const forward = handle.forward = curry((name, ev, props) => {
const forward = handle.forward = curry(named((name, ev, props) => {
const fn = props && props[name];
if (typeof fn === 'function') {
fn(ev);
}

return true;
});
}, 'forward'));

/**
* Calls `event.preventDefault()` and returns `true`.
Expand Down Expand Up @@ -329,7 +366,7 @@ const preventDefault = handle.preventDefault = callOnEvent('preventDefault');
* @param {Object} props Props object
* @returns {Boolean} Returns `true` if default action is prevented
*/
const forwardWithPrevent = handle.forwardWithPrevent = curry((name, ev, props) => {
const forwardWithPrevent = handle.forwardWithPrevent = curry(named((name, ev, props) => {
let prevented = false;
const wrappedEvent = Object.assign({}, ev, {
preventDefault: () => {
Expand All @@ -340,7 +377,7 @@ const forwardWithPrevent = handle.forwardWithPrevent = curry((name, ev, props) =
forward(name, wrappedEvent, props);

return !prevented;
});
}, 'forwardWithPrevent'));

/**
* Calls `event.stopPropagation()` and returns `true`
Expand All @@ -359,7 +396,7 @@ const forwardWithPrevent = handle.forwardWithPrevent = curry((name, ev, props) =
* @param {Object} ev Event
* @returns {Boolean} Always returns `true`
*/
const stop = handle.stop = callOnEvent('stopPropagation');
const stop = handle.stop = named(callOnEvent('stopPropagation'), 'stop');

/**
* Calls `event.stopImmediatePropagation()` and returns `true`
Expand Down Expand Up @@ -513,13 +550,13 @@ const log = handle.log = curry((message, ev, ...args) => {
* does not exist
*/
const call = function (method) {
return function (...args) {
return named(function (...args) {
if (this && this[method]) {
return this[method](...args);
}

return false;
};
}, 'call');
};

/**
Expand Down Expand Up @@ -550,9 +587,9 @@ const call = function (method) {
* @returns {Object} New event payload
*/
const adaptEvent = handle.adaptEvent = curry(function (adapter, handler) {
return function (ev, ...args) {
return named(function (ev, ...args) {
return handler.call(this, adapter.call(this, ev, ...args), ...args);
};
}, 'adaptEvent');
});

export default handle;
Expand Down
46 changes: 22 additions & 24 deletions packages/ui/Touchable/Touchable.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* @module ui/Touchable
*/

import {call, forward, forwardWithPrevent, forProp, handle, oneOf, preventDefault, returnsTrue} from '@enact/core/handle';
import {adaptEvent, call, forward, forwardWithPrevent, forProp, handle, oneOf, preventDefault, returnsTrue} from '@enact/core/handle';
import hoc from '@enact/core/hoc';
import {Job} from '@enact/core/util';
import {on, off} from '@enact/core/dispatcher';
Expand Down Expand Up @@ -35,7 +35,7 @@ const getEventCoordinates = (ev) => {
};

// Establish a standard payload for onDown/onUp/onTap events and pass it along to a handler
const makeTouchableEvent = (type, fn) => (ev, ...args) => {
const makeTouchableEvent = (type) => (ev) => {
const {target, currentTarget} = ev;
let {clientX, clientY, pageX, pageY} = ev;

Expand All @@ -46,7 +46,7 @@ const makeTouchableEvent = (type, fn) => (ev, ...args) => {
pageY = ev.changedTouches[0].pageY;
}

const payload = {
return {
type,
target,
currentTarget,
Expand All @@ -55,34 +55,32 @@ const makeTouchableEvent = (type, fn) => (ev, ...args) => {
pageX,
pageY
};

return fn(payload, ...args);
};

const isEnabled = forProp('disabled', false);

const handleDown = handle(
isEnabled,
makeTouchableEvent('onDown', forwardWithPrevent('onDown')),
adaptEvent(makeTouchableEvent('onDown'), forwardWithPrevent('onDown')),
call('activate'),
call('startGesture')
);
).named('handleDown');

const handleUp = handle(
isEnabled,
call('endGesture'),
call('isTracking'),
makeTouchableEvent('onUp', forwardWithPrevent('onUp')),
makeTouchableEvent('onTap', forward('onTap'))
).finally(call('deactivate'));
adaptEvent(makeTouchableEvent('onUp'), forwardWithPrevent('onUp')),
adaptEvent(makeTouchableEvent('onTap'), forward('onTap'))
).finally(call('deactivate')).named('handleUp');

const handleEnter = handle(
isEnabled,
forProp('noResume', false),
call('enterGesture'),
call('isPaused'),
call('activate')
);
).named('handleEnter');

const handleLeave = handle(
isEnabled,
Expand All @@ -91,7 +89,7 @@ const handleLeave = handle(
[forProp('noResume', false), call('pause')],
[returnsTrue, call('deactivate')]
)
);
).named('handleLeave');

// Mouse event handlers

Expand Down Expand Up @@ -149,7 +147,7 @@ const handleTouchMove = handle(
// detecting when the touch leaves the boundary. oneOf returns the value of whichever
// branch it follows so we append moveHold to either to handle moves that aren't
// entering or leaving
makeTouchableEvent('onMove', forward('onMove')),
adaptEvent(makeTouchableEvent('onMove'), forward('onMove')),
oneOf(
[call('hasTouchLeftTarget'), handleLeave],
[returnsTrue, handleEnter]
Expand Down Expand Up @@ -397,17 +395,17 @@ const Touchable = hoc(defaultConfig, (config, Wrapped) => {

this.clickAllow = new ClickAllow();

this.handleClick = handleClick.bind(this);
this.handleMouseDown = handleMouseDown.bind(this);
this.handleMouseEnter = handleMouseEnter.bind(this);
this.handleMouseMove = handleMouseMove.bind(this);
this.handleMouseLeave = handleMouseLeave.bind(this);
this.handleMouseUp = handleMouseUp.bind(this);
this.handleTouchStart = handleTouchStart.bind(this);
this.handleTouchMove = handleTouchMove.bind(this);
this.handleTouchEnd = handleTouchEnd.bind(this);
this.handleGlobalUp = handleGlobalUp.bind(this);
this.handleGlobalMove = handleGlobalMove.bind(this);
handleClick.bindAs(this, 'handleClick');
handleMouseDown.bindAs(this, 'handleMouseDown');
handleMouseEnter.bindAs(this, 'handleMouseEnter');
handleMouseMove.bindAs(this, 'handleMouseMove');
handleMouseLeave.bindAs(this, 'handleMouseLeave');
handleMouseUp.bindAs(this, 'handleMouseUp');
handleTouchStart.bindAs(this, 'handleTouchStart');
handleTouchMove.bindAs(this, 'handleTouchMove');
handleTouchEnd.bindAs(this, 'handleTouchEnd');
handleGlobalUp.bindAs(this, 'handleGlobalUp');
handleGlobalMove.bindAs(this, 'handleGlobalMove');
}

componentDidMount () {
Expand Down