Nick Lockwood 60db876f66 Wrapped UIManager native module for better abstraction
Summary: public

RCTUIManager is a public module with several useful methods, however, unlike most such modules, it does not have a JS wrapper that would allow it to be required directly.

Besides making it more cumbersome to use, this also makes it impossible to modify the UIManager API, or smooth over differences between platforms in the JS layer without breaking all of the call sites.

This diff adds a simple JS wrapper file for the UIManager module to make it easier to work with.

Reviewed By: tadeuzagallo

Differential Revision: D2700348

fb-gh-sync-id: dd9030eface100b1baf756da11bae355dc0f266f
2015-11-27 07:00:32 -08:00

204 lines
5.5 KiB
JavaScript

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule WebView
*/
'use strict';
var EdgeInsetsPropType = require('EdgeInsetsPropType');
var React = require('React');
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
var StyleSheet = require('StyleSheet');
var UIManager = require('UIManager');
var View = require('View');
var keyMirror = require('keyMirror');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var PropTypes = React.PropTypes;
var RCT_WEBVIEW_REF = 'webview';
var WebViewState = keyMirror({
IDLE: null,
LOADING: null,
ERROR: null,
});
/**
* Note that WebView is only supported on iOS for now,
* see https://facebook.github.io/react-native/docs/known-issues.html
*/
var WebView = React.createClass({
propTypes: {
...View.propTypes,
renderError: PropTypes.func, // view to show if there's an error
renderLoading: PropTypes.func, // loading indicator to show
url: PropTypes.string,
html: PropTypes.string,
automaticallyAdjustContentInsets: PropTypes.bool,
contentInset: EdgeInsetsPropType,
onNavigationStateChange: PropTypes.func,
startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
style: View.propTypes.style,
javaScriptEnabledAndroid: PropTypes.bool,
/**
* Sets the JS to be injected when the webpage loads.
*/
injectedJavaScript: PropTypes.string,
/**
* Sets the user-agent for this WebView. The user-agent can also be set in native through
* WebViewConfig, but this can and will overwrite that config.
*/
userAgent: PropTypes.string,
/**
* Used to locate this view in end-to-end tests.
*/
testID: PropTypes.string,
},
getInitialState: function() {
return {
viewState: WebViewState.IDLE,
lastErrorEvent: null,
startInLoadingState: true,
};
},
componentWillMount: function() {
if (this.props.startInLoadingState) {
this.setState({viewState: WebViewState.LOADING});
}
},
render: function() {
var otherView = null;
if (this.state.viewState === WebViewState.LOADING) {
otherView = this.props.renderLoading && this.props.renderLoading();
} else if (this.state.viewState === WebViewState.ERROR) {
var errorEvent = this.state.lastErrorEvent;
otherView = this.props.renderError && this.props.renderError(
errorEvent.domain,
errorEvent.code,
errorEvent.description);
} else if (this.state.viewState !== WebViewState.IDLE) {
console.error('RCTWebView invalid state encountered: ' + this.state.loading);
}
var webViewStyles = [styles.container, this.props.style];
if (this.state.viewState === WebViewState.LOADING ||
this.state.viewState === WebViewState.ERROR) {
// if we're in either LOADING or ERROR states, don't show the webView
webViewStyles.push(styles.hidden);
}
var webView =
<RCTWebView
ref={RCT_WEBVIEW_REF}
key="webViewKey"
style={webViewStyles}
url={this.props.url}
html={this.props.html}
injectedJavaScript={this.props.injectedJavaScript}
userAgent={this.props.userAgent}
javaScriptEnabledAndroid={this.props.javaScriptEnabledAndroid}
contentInset={this.props.contentInset}
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
onLoadingStart={this.onLoadingStart}
onLoadingFinish={this.onLoadingFinish}
onLoadingError={this.onLoadingError}
testID={this.props.testID}
/>;
return (
<View style={styles.container}>
{webView}
{otherView}
</View>
);
},
goForward: function() {
UIManager.dispatchViewManagerCommand(
this.getWebWiewHandle(),
UIManager.RCTWebView.Commands.goForward,
null
);
},
goBack: function() {
UIManager.dispatchViewManagerCommand(
this.getWebWiewHandle(),
UIManager.RCTWebView.Commands.goBack,
null
);
},
reload: function() {
UIManager.dispatchViewManagerCommand(
this.getWebWiewHandle(),
UIManager.RCTWebView.Commands.reload,
null
);
},
/**
* We return an event with a bunch of fields including:
* url, title, loading, canGoBack, canGoForward
*/
updateNavigationState: function(event) {
if (this.props.onNavigationStateChange) {
this.props.onNavigationStateChange(event.nativeEvent);
}
},
getWebWiewHandle: function() {
return React.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
},
onLoadingStart: function(event) {
this.updateNavigationState(event);
},
onLoadingError: function(event) {
event.persist(); // persist this event because we need to store it
console.error('Encountered an error loading page', event.nativeEvent);
this.setState({
lastErrorEvent: event.nativeEvent,
viewState: WebViewState.ERROR
});
},
onLoadingFinish: function(event) {
this.setState({
viewState: WebViewState.IDLE,
});
this.updateNavigationState(event);
},
});
var RCTWebView = requireNativeComponent('RCTWebView', WebView);
var styles = StyleSheet.create({
container: {
flex: 1,
},
hidden: {
height: 0,
flex: 0, // disable 'flex:1' when hiding a View
},
});
module.exports = WebView;