Skip to content

Commit

Permalink
Merge pull request #1 from jevakallio/fix/touchable
Browse files Browse the repository at this point in the history
Custom touchable components
  • Loading branch information
jevakallio committed Apr 23, 2016
2 parents b1c9ffb + 8398083 commit 7bbfa6c
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 45 deletions.
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

0 comments on commit 7bbfa6c

Please sign in to comment.