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

Custom touchable components #1

Merged
merged 4 commits into from
Apr 23, 2016
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
61 changes: 61 additions & 0 deletions src/Touchable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use strict';

import React from 'react-native';
import {findHandler} from './driver';
const {
View,
PropTypes
} = React;

const ACTION_TYPES = {
onPress: 'press',
onPressIn: 'pressIn',
onPressOut: 'pressOut',
onLongPress: 'longPress'
};

function createTouchableClass(className) {
return React.createClass({
displayName: 'Cycle' + className,
propTypes: {
selector: PropTypes.string.isRequired,
payload: PropTypes.any
},
setNativeProps(props) {
this._touchable.setNativeProps(props);
},
render() {
const TouchableClass = React[className];
const {selector, ...props} = this.props;

// find all defined touch handlers
const handlers = Object.keys(ACTION_TYPES)
.map(name => [name, findHandler(ACTION_TYPES[name], selector)])
.filter(([_, handler]) => !!handler)
.reduce((memo, [name, handler]) => {
// pass payload to event handler if defined
memo[name] = () => handler(this.props.payload || null);
return memo;
}, {});


return (
<TouchableClass
ref={view => this._touchable = view}
{...handlers}
>
<View {...props}>
{this.props.children}
</View>
</TouchableClass>
);
}
});
}

export default {
TouchableOpacity: createTouchableClass('TouchableOpacity'),
TouchableWithoutFeedback: createTouchableClass('TouchableWithoutFeedback'),
TouchableHighlight: createTouchableClass('TouchableHighlight'),
TouchableNativeFeedback: createTouchableClass('TouchableNativeFeedback')
};
88 changes: 43 additions & 45 deletions src/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,52 @@ import React from 'react-native'
import Rx from 'rx'
const {AppRegistry, View} = React

const BACK_ACTION = '@@back';
const backHandler = new Rx.Subject

let handlers = {
[BACK_ACTION]: createHandler()
};

function createHandler() {
const handler = new Rx.Subject();
handler.send = function sendIntoSubject(...args) {
handler.onNext(...args)
}
return handler;
}

export function getBackHandler() {
return handlers[BACK_ACTION];
}

export function registerHandler(selector, evType) {
handlers[selector] = handlers[selector] || {};
handlers[selector][evType] = handlers[selector][evType] || createHandler();
return handlers[selector][evType];
};

export function findHandler(evType, selector) {
if (evType === BACK_ACTION && !selector) {
return handlers[BACK_ACTION];
}

if (handlers[selector].hasOwnProperty(evType)) {
return handlers[selector][evType].send
}
}

function isChildReactElement(child) {
return !!child && typeof child === `object` && child._isReactElement
}

function makeReactNativeDriver(appKey) {
return function reactNativeDriver(vtree$) {
let handlers = {}

function augmentVTreeWithHandlers(vtree, index = null) {
if (typeof vtree === `string` || typeof vtree === `number`) {
return vtree
}
let newProps = {}
if (!vtree.props.selector && !!index) {
newProps.selector = index
}
let wasTouched = false
if (handlers[vtree.props.selector]) {
for (let evType in handlers[vtree.props.selector]) {
if (handlers[vtree.props.selector].hasOwnProperty(evType)) {
let handlerFnName = `on${evType.charAt(0).toUpperCase()}${evType.slice(1)}`
newProps[handlerFnName] = handlers[vtree.props.selector][evType].send
wasTouched = true
}
}
}
let children = vtree.props.children
if (Array.isArray(vtree.props.children)) {
return React.cloneElement(vtree, newProps,
...children.map(augmentVTreeWithHandlers))
} else if (isChildReactElement(vtree.props.children)) {
return React.cloneElement(vtree, newProps,
augmentVTreeWithHandlers(children))
} else if (wasTouched) {
return React.cloneElement(vtree, newProps, children)
}
return vtree
}

function componentFactory() {
return React.createClass({
componentWillMount() {
vtree$.subscribe(rawVTree => {
let replacedVTree = augmentVTreeWithHandlers(rawVTree)
this.setState({vtree: replacedVTree})
vtree$.subscribe(newVTree => {
this.setState({vtree: newVTree})
})
},
getInitialState() {
Expand All @@ -59,21 +60,18 @@ function makeReactNativeDriver(appKey) {
}

let response = {
select: function select(selector) {
select(selector) {
return {
observable: Rx.Observable.empty(),
events: function events(evType) {
handlers[selector] = handlers[selector] || {}
handlers[selector][evType] = handlers[selector][evType] || new Rx.Subject()
handlers[selector][evType].send = function sendIntoSubject(...args) {
const props = this
const event = {currentTarget: {props}, args}
handlers[selector][evType].onNext(event)
}
return handlers[selector][evType]
return registerHandler(selector, evType);
},
}
},

navigateBack() {
return findHandler(BACK_ACTION);
}
}

AppRegistry.registerComponent(appKey, componentFactory)
Expand Down