react-native/RNTester/js/WebViewExample.js

469 lines
11 KiB
JavaScript
Raw Normal View History

2015-03-14 08:22:25 +00:00
/**
* 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.
*
2015-03-23 18:36:57 +00:00
* @flow
* @providesModule WebViewExample
2015-03-14 08:22:25 +00:00
*/
'use strict';
var React = require('react');
var ReactNative = require('react-native');
2015-03-14 08:22:25 +00:00
var {
StyleSheet,
Text,
TextInput,
TouchableWithoutFeedback,
2015-03-14 08:22:25 +00:00
TouchableOpacity,
View,
WebView
} = ReactNative;
2015-03-14 08:22:25 +00:00
var HEADER = '#3b5998';
var BGWASH = 'rgba(255,255,255,0.8)';
var DISABLED_WASH = 'rgba(255,255,255,0.25)';
var TEXT_INPUT_REF = 'urlInput';
var WEBVIEW_REF = 'webview';
var DEFAULT_URL = 'https://m.facebook.com';
class WebViewExample extends React.Component<{}, $FlowFixMeState> {
state = {
url: DEFAULT_URL,
status: 'No Page Loaded',
backButtonEnabled: false,
forwardButtonEnabled: false,
loading: true,
scalesPageToFit: true,
};
inputText = '';
handleTextInputChange = (event) => {
var url = event.nativeEvent.text;
if (!/^[a-zA-Z-_]+:/.test(url)) {
url = 'http://' + url;
}
this.inputText = url;
};
2015-03-14 08:22:25 +00:00
render() {
2015-03-14 08:22:25 +00:00
this.inputText = this.state.url;
return (
<View style={[styles.container]}>
<View style={[styles.addressBarRow]}>
[Animated][BREAKING_CHANGE] Convert <TouchableOpacity> to Animated Summary: Because we don't want to integrate Animated inside of the core of React, we can only pass Animated.Value to styles of <Animated.View>. TouchableOpacity unfortunately used cloneElement. This means that we should have asked every single call site to replace their children to Animated.View. This isn't great. The other solution is to stop using cloneElement and instead wrap the children inside of an <Animated.View>. This has many advantages: - We no longer use cloneElement so we're no longer messing up with elements that are not our own. - Refs are now working correctly for children elements - No longer need to enforce that there's only one child and that this child is a native element The downside is that we're introducing a <View> into the hierarchy. Sadly with CSS there is no way to have a View that doesn't affect layout. What we need to do is to remove the inner <View> and transfer all the styles to the TouchableOpacity. It is annoying but fortunately a pretty mechanical process. I think that having a wrapper is the best solution. I will investigate to see if we can make wrappers on TouchableHighliht and TouchableWithoutFeedback as well. **Upgrade Path:** If the child is a View, move the style of the View to TouchableOpacity and remove the View itself. ``` <TouchableOpacity onPress={...}> <View style={...}> ... </View> </TouchableOpacity> --> <TouchableOpacity onPress={...} style={...}> ... </TouchableOpacity> ``` If the child is an Image or Text, on all the examples at Facebook it worked without any change. But it is a great idea to double check them anyway.
2015-07-20 23:29:40 +00:00
<TouchableOpacity
onPress={this.goBack}
style={this.state.backButtonEnabled ? styles.navButton : styles.disabledButton}>
<Text>
{'<'}
</Text>
2015-03-14 08:22:25 +00:00
</TouchableOpacity>
[Animated][BREAKING_CHANGE] Convert <TouchableOpacity> to Animated Summary: Because we don't want to integrate Animated inside of the core of React, we can only pass Animated.Value to styles of <Animated.View>. TouchableOpacity unfortunately used cloneElement. This means that we should have asked every single call site to replace their children to Animated.View. This isn't great. The other solution is to stop using cloneElement and instead wrap the children inside of an <Animated.View>. This has many advantages: - We no longer use cloneElement so we're no longer messing up with elements that are not our own. - Refs are now working correctly for children elements - No longer need to enforce that there's only one child and that this child is a native element The downside is that we're introducing a <View> into the hierarchy. Sadly with CSS there is no way to have a View that doesn't affect layout. What we need to do is to remove the inner <View> and transfer all the styles to the TouchableOpacity. It is annoying but fortunately a pretty mechanical process. I think that having a wrapper is the best solution. I will investigate to see if we can make wrappers on TouchableHighliht and TouchableWithoutFeedback as well. **Upgrade Path:** If the child is a View, move the style of the View to TouchableOpacity and remove the View itself. ``` <TouchableOpacity onPress={...}> <View style={...}> ... </View> </TouchableOpacity> --> <TouchableOpacity onPress={...} style={...}> ... </TouchableOpacity> ``` If the child is an Image or Text, on all the examples at Facebook it worked without any change. But it is a great idea to double check them anyway.
2015-07-20 23:29:40 +00:00
<TouchableOpacity
onPress={this.goForward}
style={this.state.forwardButtonEnabled ? styles.navButton : styles.disabledButton}>
<Text>
{'>'}
</Text>
2015-03-14 08:22:25 +00:00
</TouchableOpacity>
<TextInput
ref={TEXT_INPUT_REF}
autoCapitalize="none"
2015-08-27 21:04:59 +00:00
defaultValue={this.state.url}
2015-03-14 08:22:25 +00:00
onSubmitEditing={this.onSubmitEditing}
onChange={this.handleTextInputChange}
clearButtonMode="while-editing"
style={styles.addressBarTextInput}
/>
<TouchableOpacity onPress={this.pressGoButton}>
<View style={styles.goButton}>
<Text>
Go!
</Text>
</View>
</TouchableOpacity>
</View>
<WebView
ref={WEBVIEW_REF}
automaticallyAdjustContentInsets={false}
style={styles.webView}
source={{uri: this.state.url}}
javaScriptEnabled={true}
domStorageEnabled={true}
decelerationRate="normal"
2015-03-14 08:22:25 +00:00
onNavigationStateChange={this.onNavigationStateChange}
onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
2015-03-14 08:22:25 +00:00
startInLoadingState={true}
scalesPageToFit={this.state.scalesPageToFit}
2015-03-14 08:22:25 +00:00
/>
<View style={styles.statusBar}>
<Text style={styles.statusBarText}>{this.state.status}</Text>
</View>
</View>
);
}
2015-03-14 08:22:25 +00:00
goBack = () => {
2015-03-14 08:22:25 +00:00
this.refs[WEBVIEW_REF].goBack();
};
2015-03-14 08:22:25 +00:00
goForward = () => {
2015-03-14 08:22:25 +00:00
this.refs[WEBVIEW_REF].goForward();
};
2015-03-14 08:22:25 +00:00
reload = () => {
2015-03-14 08:22:25 +00:00
this.refs[WEBVIEW_REF].reload();
};
2015-03-14 08:22:25 +00:00
onShouldStartLoadWithRequest = (event) => {
// Implement any custom loading logic here, don't forget to return!
return true;
};
onNavigationStateChange = (navState) => {
2015-03-14 08:22:25 +00:00
this.setState({
backButtonEnabled: navState.canGoBack,
forwardButtonEnabled: navState.canGoForward,
url: navState.url,
status: navState.title,
loading: navState.loading,
scalesPageToFit: true
2015-03-14 08:22:25 +00:00
});
};
2015-03-14 08:22:25 +00:00
onSubmitEditing = (event) => {
2015-03-14 08:22:25 +00:00
this.pressGoButton();
};
2015-03-14 08:22:25 +00:00
pressGoButton = () => {
2015-03-14 08:22:25 +00:00
var url = this.inputText.toLowerCase();
if (url === this.state.url) {
this.reload();
} else {
this.setState({
url: url,
});
}
// dismiss keyboard
2015-03-14 08:22:25 +00:00
this.refs[TEXT_INPUT_REF].blur();
};
}
2015-03-14 08:22:25 +00:00
class Button extends React.Component<$FlowFixMeProps> {
_handlePress = () => {
if (this.props.enabled !== false && this.props.onPress) {
this.props.onPress();
}
};
render() {
return (
<TouchableWithoutFeedback onPress={this._handlePress}>
<View style={styles.button}>
<Text>{this.props.text}</Text>
</View>
</TouchableWithoutFeedback>
);
}
}
class ScaledWebView extends React.Component<{}, $FlowFixMeState> {
state = {
scalingEnabled: true,
};
render() {
return (
<View>
<WebView
style={{
backgroundColor: BGWASH,
height: 200,
}}
source={{uri: 'https://facebook.github.io/react/'}}
scalesPageToFit={this.state.scalingEnabled}
/>
<View style={styles.buttons}>
{ this.state.scalingEnabled ?
<Button
text="Scaling:ON"
enabled={true}
onPress={() => this.setState({scalingEnabled: false})}
/> :
<Button
text="Scaling:OFF"
enabled={true}
onPress={() => this.setState({scalingEnabled: true})}
/> }
</View>
</View>
);
}
}
class MessagingTest extends React.Component<{}, $FlowFixMeState> {
webview = null
state = {
messagesReceivedFromWebView: 0,
message: '',
}
onMessage = e => this.setState({
messagesReceivedFromWebView: this.state.messagesReceivedFromWebView + 1,
message: e.nativeEvent.data,
})
postMessage = () => {
if (this.webview) {
this.webview.postMessage('"Hello" from React Native!');
}
}
render(): React.Node {
const {messagesReceivedFromWebView, message} = this.state;
return (
<View style={[styles.container, { height: 200 }]}>
<View style={styles.container}>
<Text>Messages received from web view: {messagesReceivedFromWebView}</Text>
<Text>{message || '(No message)'}</Text>
<View style={styles.buttons}>
<Button text="Send Message to Web View" enabled onPress={this.postMessage} />
</View>
</View>
<View style={styles.container}>
<WebView
ref={webview => { this.webview = webview; }}
style={{
backgroundColor: BGWASH,
height: 100,
}}
source={require('./messagingtest.html')}
onMessage={this.onMessage}
/>
</View>
</View>
);
}
}
class InjectJS extends React.Component<{}> {
webview = null;
injectJS = () => {
const script = 'document.write("Injected JS ")';
if (this.webview) {
this.webview.injectJavaScript(script);
}
}
render() {
return (
<View>
<WebView
ref={webview => { this.webview = webview; }}
style={{
backgroundColor: BGWASH,
height: 300,
}}
source={{uri: 'https://www.facebook.com'}}
scalesPageToFit={true}
/>
<View style={styles.buttons}>
<Button text="Inject JS" enabled onPress={this.injectJS} />
</View>
</View>
);
}
}
2015-03-14 08:22:25 +00:00
var styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: HEADER,
},
addressBarRow: {
flexDirection: 'row',
padding: 8,
},
webView: {
backgroundColor: BGWASH,
height: 350,
},
addressBarTextInput: {
backgroundColor: BGWASH,
borderColor: 'transparent',
borderRadius: 3,
borderWidth: 1,
height: 24,
paddingLeft: 10,
paddingTop: 3,
paddingBottom: 3,
flex: 1,
fontSize: 14,
},
navButton: {
width: 20,
padding: 3,
marginRight: 3,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: BGWASH,
borderColor: 'transparent',
borderRadius: 3,
},
disabledButton: {
width: 20,
padding: 3,
marginRight: 3,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: DISABLED_WASH,
borderColor: 'transparent',
borderRadius: 3,
},
goButton: {
height: 24,
padding: 3,
marginLeft: 8,
alignItems: 'center',
backgroundColor: BGWASH,
borderColor: 'transparent',
borderRadius: 3,
alignSelf: 'stretch',
},
statusBar: {
flexDirection: 'row',
alignItems: 'center',
paddingLeft: 5,
height: 22,
},
statusBarText: {
color: 'white',
fontSize: 13,
},
spinner: {
width: 20,
marginRight: 6,
},
buttons: {
flexDirection: 'row',
height: 30,
backgroundColor: 'black',
alignItems: 'center',
justifyContent: 'space-between',
},
button: {
flex: 0.5,
width: 0,
margin: 5,
borderColor: 'gray',
borderWidth: 1,
backgroundColor: 'gray',
},
2015-03-14 08:22:25 +00:00
});
const HTML = `
<!DOCTYPE html>\n
<html>
<head>
<title>Hello Static World</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=320, user-scalable=no">
<style type="text/css">
body {
margin: 0;
padding: 0;
font: 62.5% arial, sans-serif;
background: #ccc;
}
h1 {
padding: 45px;
margin: 0;
text-align: center;
color: #33f;
}
</style>
</head>
<body>
<h1>Hello Static World</h1>
</body>
</html>
`;
exports.displayName = (undefined: ?string);
2015-03-14 08:22:25 +00:00
exports.title = '<WebView>';
exports.description = 'Base component to display web content';
exports.examples = [
{
title: 'Simple Browser',
render(): React.Element<any> { return <WebViewExample />; }
},
{
title: 'Scale Page to Fit',
render(): React.Element<any> { return <ScaledWebView/>; }
},
{
title: 'Bundled HTML',
render(): React.Element<any> {
return (
<WebView
style={{
backgroundColor: BGWASH,
height: 100,
}}
source={require('./helloworld.html')}
scalesPageToFit={true}
/>
);
}
},
{
title: 'Static HTML',
render(): React.Element<any> {
return (
<WebView
style={{
backgroundColor: BGWASH,
height: 100,
}}
source={{html: HTML}}
scalesPageToFit={true}
/>
);
}
},
{
title: 'POST Test',
render(): React.Element<any> {
return (
<WebView
style={{
backgroundColor: BGWASH,
height: 100,
}}
source={{
uri: 'http://www.posttestserver.com/post.php',
method: 'POST',
body: 'foo=bar&bar=foo'
}}
scalesPageToFit={false}
/>
);
}
},
{
title: 'Messaging Test',
render(): React.Element<any> { return <MessagingTest />; }
},
{
title: 'Inject JavaScript',
render(): React.Element<any> { return <InjectJS />; }
},
2015-03-14 08:22:25 +00:00
];