Consolidate XHR UIExplorer examples

Summary:
The XHRExample file had been forked between iOS and Android for various reasons that are no longer valid, making maintenance unnecessarily difficult. New features demos had in fact been added in separate files to avoid further duplication. This change continues on that same trajectory and splits the entire example file into multiple files. This makes them much easier to handle. We also get rid of the iOS/Android fork.

**Test plan:** Run UIExplorer app, specifically the XHR examples, on both iOS and Android.
Closes https://github.com/facebook/react-native/pull/11901

Differential Revision: D4419882

Pulled By: ericvicenti

fbshipit-source-id: ec12836dfa8e1b9259b92a7510914857a8db58d5
This commit is contained in:
Philipp von Weitershausen 2017-01-14 18:55:42 -08:00 committed by Facebook Github Bot
parent 6d2ae35bca
commit 3c806652cf
6 changed files with 608 additions and 1024 deletions

View File

@ -354,7 +354,7 @@ const APIExamples: Array<UIExplorerExample> = [
}, },
{ {
key: 'XHRExample', key: 'XHRExample',
module: require('./XHRExample.ios'), module: require('./XHRExample'),
supportsTVOS: true, supportsTVOS: true,
}, },
]; ];

View File

@ -1,515 +0,0 @@
/**
* Copyright (c) 2013-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.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
var React = require('react');
var ReactNative = require('react-native');
var {
CameraRoll,
Image,
ProgressBarAndroid,
StyleSheet,
Switch,
Text,
TextInput,
TouchableHighlight,
View,
} = ReactNative;
var XHRExampleHeaders = require('./XHRExampleHeaders');
var XHRExampleCookies = require('./XHRExampleCookies');
var XHRExampleFetch = require('./XHRExampleFetch');
var XHRExampleOnTimeOut = require('./XHRExampleOnTimeOut');
/**
* Convert number of bytes to MB and round to the nearest 0.1 MB.
*/
function roundKilo(value: number): number {
return Math.round(value / 1000);
}
// TODO t7093728 This is a simplified XHRExample.ios.js.
// Once we have Camera roll, Toast, Intent (for opening URLs)
// we should make this consistent with iOS.
class Downloader extends React.Component {
xhr: XMLHttpRequest;
cancelled: boolean;
constructor(props) {
super(props);
this.cancelled = false;
this.state = {
status: '',
downloading: false,
// set by onreadystatechange
contentLength: 1,
responseLength: 0,
// set by onprogress
progressTotal: 1,
progressLoaded: 0,
readystateHandler: false,
progressHandler: true,
arraybuffer: false,
};
}
download() {
this.xhr && this.xhr.abort();
var xhr = this.xhr || new XMLHttpRequest();
const onreadystatechange = () => {
if (xhr.readyState === xhr.HEADERS_RECEIVED) {
const contentLength = parseInt(xhr.getResponseHeader('Content-Length'), 10);
this.setState({
contentLength,
responseLength: 0,
});
} else if (xhr.readyState === xhr.LOADING && xhr.response) {
this.setState({
responseLength: xhr.response.length,
});
}
};
const onprogress = (event) => {
this.setState({
progressTotal: event.total,
progressLoaded: event.loaded,
});
};
if (this.state.readystateHandler) {
xhr.onreadystatechange = onreadystatechange;
}
if (this.state.progressHandler) {
xhr.onprogress = onprogress;
}
if (this.state.arraybuffer) {
xhr.responseType = 'arraybuffer';
}
xhr.onload = () => {
this.setState({downloading: false});
if (this.cancelled) {
this.cancelled = false;
return;
}
if (xhr.status === 200) {
let responseType = `Response is a string, ${xhr.response.length} characters long.`;
if (typeof ArrayBuffer !== 'undefined' &&
xhr.response instanceof ArrayBuffer) {
responseType = `Response is an ArrayBuffer, ${xhr.response.byteLength} bytes long.`;
}
this.setState({status: `Download complete! ${responseType}`});
} else if (xhr.status !== 0) {
this.setState({
status: 'Error: Server returned HTTP status of ' + xhr.status + ' ' + xhr.responseText
});
} else {
this.setState({status: 'Error: ' + xhr.responseText});
}
};
xhr.open('GET', 'http://aleph.gutenberg.org/cache/epub/100/pg100.txt.utf8');
// Avoid gzip so we can actually show progress
xhr.setRequestHeader('Accept-Encoding', '');
xhr.send();
this.xhr = xhr;
this.setState({
downloading: true,
status: 'Downloading...',
});
}
componentWillUnmount() {
this.cancelled = true;
this.xhr && this.xhr.abort();
}
render() {
var button = this.state.downloading ? (
<View style={styles.wrapper}>
<View style={styles.button}>
<Text>...</Text>
</View>
</View>
) : (
<TouchableHighlight
style={styles.wrapper}
onPress={this.download.bind(this)}>
<View style={styles.button}>
<Text>Download 5MB Text File</Text>
</View>
</TouchableHighlight>
);
let readystate = null;
let progress = null;
if (this.state.readystateHandler && !this.state.arraybuffer) {
const { responseLength, contentLength } = this.state;
readystate = (
<View>
<Text style={styles.progressBarLabel}>
responseText:{' '}
{roundKilo(responseLength)}/{roundKilo(contentLength)}k chars
</Text>
<ProgressBarAndroid
progress={(responseLength / contentLength)}
styleAttr="Horizontal"
indeterminate={false}
/>
</View>
);
}
if (this.state.progressHandler) {
const { progressLoaded, progressTotal } = this.state;
progress = (
<View>
<Text style={styles.progressBarLabel}>
onprogress:{' '}
{roundKilo(progressLoaded)}/{roundKilo(progressTotal)} KB
</Text>
<ProgressBarAndroid
progress={(progressLoaded / progressTotal)}
styleAttr="Horizontal"
indeterminate={false}
/>
</View>
);
}
return (
<View>
<View style={styles.configRow}>
<Text>onreadystatechange handler</Text>
<Switch
value={this.state.readystateHandler}
onValueChange={(readystateHandler => this.setState({readystateHandler}))}
/>
</View>
<View style={styles.configRow}>
<Text>onprogress handler</Text>
<Switch
value={this.state.progressHandler}
onValueChange={(progressHandler => this.setState({progressHandler}))}
/>
</View>
<View style={styles.configRow}>
<Text>download as arraybuffer</Text>
<Switch
value={this.state.arraybuffer}
onValueChange={(arraybuffer => this.setState({arraybuffer}))}
/>
</View>
{button}
{readystate}
{progress}
<Text>{this.state.status}</Text>
</View>
);
}
}
var PAGE_SIZE = 20;
class FormUploader extends React.Component {
_isMounted: boolean;
_addTextParam: () => void;
_upload: () => void;
constructor(props) {
super(props);
this.state = {
isUploading: false,
uploadProgress: null,
textParams: [],
};
this._isMounted = true;
this._addTextParam = this._addTextParam.bind(this);
this._upload = this._upload.bind(this);
this._fetchRandomPhoto = this._fetchRandomPhoto.bind(this);
this._fetchRandomPhoto();
}
_addTextParam() {
var textParams = this.state.textParams;
textParams.push({name: '', value: ''});
this.setState({textParams});
}
_fetchRandomPhoto() {
CameraRoll.getPhotos(
{first: PAGE_SIZE}
).then(
(data) => {
if (!this._isMounted) {
return;
}
var edges = data.edges;
var edge = edges[Math.floor(Math.random() * edges.length)];
var randomPhoto = edge && edge.node && edge.node.image;
if (randomPhoto) {
this.setState({randomPhoto});
}
},
(error) => undefined
);
}
componentWillUnmount() {
this._isMounted = false;
}
_onTextParamNameChange(index, text) {
var textParams = this.state.textParams;
textParams[index].name = text;
this.setState({textParams});
}
_onTextParamValueChange(index, text) {
var textParams = this.state.textParams;
textParams[index].value = text;
this.setState({textParams});
}
_upload() {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://posttestserver.com/post.php');
xhr.onload = () => {
this.setState({isUploading: false});
if (xhr.status !== 200) {
console.log(
'Upload failed',
'Expected HTTP 200 OK response, got ' + xhr.status
);
return;
}
if (!xhr.responseText) {
console.log(
'Upload failed',
'No response payload.'
);
return;
}
var index = xhr.responseText.indexOf('http://www.posttestserver.com/');
if (index === -1) {
console.log(
'Upload failed',
'Invalid response payload.'
);
return;
}
var url = xhr.responseText.slice(index).split('\n')[0];
console.log('Upload successful: ' + url);
};
var formdata = new FormData();
this.state.textParams.forEach(
(param) => formdata.append(param.name, param.value)
);
if (this.state.randomPhoto) {
formdata.append('image', {...this.state.randomPhoto, type:'image/jpg', name: 'image.jpg'});
}
xhr.upload.onprogress = (event) => {
console.log('upload onprogress', event);
if (event.lengthComputable) {
this.setState({uploadProgress: event.loaded / event.total});
}
};
xhr.send(formdata);
this.setState({isUploading: true});
}
render() {
var image = null;
if (this.state.randomPhoto) {
image = (
<Image
source={this.state.randomPhoto}
style={styles.randomPhoto}
/>
);
}
var textItems = this.state.textParams.map((item, index) => (
<View style={styles.paramRow}>
<TextInput
autoCapitalize="none"
autoCorrect={false}
onChangeText={this._onTextParamNameChange.bind(this, index)}
placeholder="name..."
style={styles.textInput}
/>
<Text style={styles.equalSign}>=</Text>
<TextInput
autoCapitalize="none"
autoCorrect={false}
onChangeText={this._onTextParamValueChange.bind(this, index)}
placeholder="value..."
style={styles.textInput}
/>
</View>
));
var uploadButtonLabel = this.state.isUploading ? 'Uploading...' : 'Upload';
var uploadProgress = this.state.uploadProgress;
if (uploadProgress !== null) {
uploadButtonLabel += ' ' + Math.round(uploadProgress * 100) + '%';
}
var uploadButton = (
<View style={styles.uploadButtonBox}>
<Text style={styles.uploadButtonLabel}>{uploadButtonLabel}</Text>
</View>
);
if (!this.state.isUploading) {
uploadButton = (
<TouchableHighlight onPress={this._upload}>
{uploadButton}
</TouchableHighlight>
);
}
return (
<View>
<View style={[styles.paramRow, styles.photoRow]}>
<Text style={styles.photoLabel}>
Random photo from your library
(<Text style={styles.textButton} onPress={this._fetchRandomPhoto}>
update
</Text>)
</Text>
{image}
</View>
{textItems}
<View>
<Text
style={[styles.textButton, styles.addTextParamButton]}
onPress={this._addTextParam}>
Add a text param
</Text>
</View>
<View style={styles.uploadButton}>
{uploadButton}
</View>
</View>
);
}
}
exports.framework = 'React';
exports.title = 'XMLHttpRequest';
exports.description = 'Example that demonstrates upload and download requests ' +
'using XMLHttpRequest.';
exports.examples = [{
title: 'File Download',
render() {
return <Downloader/>;
}
}, {
title: 'multipart/form-data Upload',
render() {
return <FormUploader/>;
}
}, {
title: 'Fetch Test',
render() {
return <XHRExampleFetch/>;
}
}, {
title: 'Headers',
render() {
return <XHRExampleHeaders/>;
}
}, {
title: 'Cookies',
render() {
return <XHRExampleCookies/>;
}
}, {
title: 'Time Out Test',
render() {
return <XHRExampleOnTimeOut/>;
}
}];
var styles = StyleSheet.create({
wrapper: {
borderRadius: 5,
marginBottom: 5,
},
button: {
backgroundColor: '#eeeeee',
padding: 8,
},
progressBarLabel: {
marginTop: 12,
marginBottom: 8,
},
configRow: {
flexDirection: 'row',
paddingVertical: 8,
alignItems: 'center',
justifyContent: 'space-between',
},
paramRow: {
flexDirection: 'row',
paddingVertical: 8,
alignItems: 'center',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: 'grey',
},
randomPhoto: {
width: 50,
height: 50,
},
textButton: {
color: 'blue',
},
addTextParamButton: {
marginTop: 8,
},
textInput: {
flex: 1,
borderRadius: 3,
borderColor: 'grey',
borderWidth: 1,
paddingLeft: 8,
},
equalSign: {
paddingHorizontal: 4,
},
uploadButton: {
marginTop: 16,
},
uploadButtonBox: {
flex: 1,
paddingVertical: 12,
alignItems: 'center',
backgroundColor: 'blue',
borderRadius: 4,
},
uploadButtonLabel: {
color: 'white',
fontSize: 16,
fontWeight: '500',
},
});

View File

@ -1,508 +0,0 @@
/**
* Copyright (c) 2013-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.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
var React = require('react');
var ReactNative = require('react-native');
var {
AlertIOS,
CameraRoll,
Image,
Linking,
ProgressViewIOS,
StyleSheet,
Switch,
Text,
TextInput,
TouchableHighlight,
View,
} = ReactNative;
var XHRExampleHeaders = require('./XHRExampleHeaders');
var XHRExampleFetch = require('./XHRExampleFetch');
var XHRExampleOnTimeOut = require('./XHRExampleOnTimeOut');
var XHRExampleCookies = require('./XHRExampleCookies');
/**
* Convert number of bytes to MB and round to the nearest 0.1 MB.
*/
function roundKilo(value: number): number {
return Math.round(value / 1000);
}
class Downloader extends React.Component {
state: any;
xhr: XMLHttpRequest;
cancelled: boolean;
constructor(props) {
super(props);
this.cancelled = false;
this.state = {
downloading: false,
// set by onreadystatechange
contentLength: 1,
responseLength: 0,
// set by onprogress
progressTotal: 1,
progressLoaded: 0,
readystateHandler: false,
progressHandler: true,
arraybuffer: false,
};
}
download() {
this.xhr && this.xhr.abort();
var xhr = this.xhr || new XMLHttpRequest();
const onreadystatechange = () => {
if (xhr.readyState === xhr.HEADERS_RECEIVED) {
const contentLength = parseInt(xhr.getResponseHeader('Content-Length'), 10);
this.setState({
contentLength,
responseLength: 0,
});
} else if (xhr.readyState === xhr.LOADING) {
this.setState({
responseLength: xhr.responseText.length,
});
}
};
const onprogress = (event) => {
this.setState({
progressTotal: event.total,
progressLoaded: event.loaded,
});
};
if (this.state.readystateHandler) {
xhr.onreadystatechange = onreadystatechange;
}
if (this.state.progressHandler) {
xhr.onprogress = onprogress;
}
if (this.state.arraybuffer) {
xhr.responseType = 'arraybuffer';
}
xhr.onload = () => {
this.setState({downloading: false});
if (this.cancelled) {
this.cancelled = false;
return;
}
if (xhr.status === 200) {
let responseType = `Response is a string, ${xhr.response.length} characters long.`;
if (typeof ArrayBuffer !== 'undefined' &&
xhr.response instanceof ArrayBuffer) {
responseType = `Response is an ArrayBuffer, ${xhr.response.byteLength} bytes long.`;
}
alert(`Download complete! ${responseType}`);
} else if (xhr.status !== 0) {
alert('Error: Server returned HTTP status of ' + xhr.status + ' ' + xhr.responseText);
} else {
alert('Error: ' + xhr.responseText);
}
};
xhr.open('GET', 'http://aleph.gutenberg.org/cache/epub/100/pg100.txt.utf8');
xhr.send();
this.xhr = xhr;
this.setState({downloading: true});
}
componentWillUnmount() {
this.cancelled = true;
this.xhr && this.xhr.abort();
}
render() {
var button = this.state.downloading ? (
<View style={styles.wrapper}>
<View style={styles.button}>
<Text>Downloading...</Text>
</View>
</View>
) : (
<TouchableHighlight
style={styles.wrapper}
onPress={this.download.bind(this)}>
<View style={styles.button}>
<Text>Download 5MB Text File</Text>
</View>
</TouchableHighlight>
);
let readystate = null;
let progress = null;
if (this.state.readystateHandler && !this.state.arraybuffer) {
const { responseLength, contentLength } = this.state;
readystate = (
<View>
<Text style={styles.progressBarLabel}>
responseText:{' '}
{roundKilo(responseLength)}/{roundKilo(contentLength)}k chars
</Text>
<ProgressViewIOS
progress={(responseLength / contentLength)}
/>
</View>
);
}
if (this.state.progressHandler) {
const { progressLoaded, progressTotal } = this.state;
progress = (
<View>
<Text style={styles.progressBarLabel}>
onprogress:{' '}
{roundKilo(progressLoaded)}/{roundKilo(progressTotal)} KB
</Text>
<ProgressViewIOS
progress={(progressLoaded / progressTotal)}
/>
</View>
);
}
return (
<View>
<View style={styles.configRow}>
<Text>onreadystatechange handler</Text>
<Switch
value={this.state.readystateHandler}
onValueChange={(readystateHandler => this.setState({readystateHandler}))}
/>
</View>
<View style={styles.configRow}>
<Text>onprogress handler</Text>
<Switch
value={this.state.progressHandler}
onValueChange={(progressHandler => this.setState({progressHandler}))}
/>
</View>
<View style={styles.configRow}>
<Text>download as arraybuffer</Text>
<Switch
value={this.state.arraybuffer}
onValueChange={(arraybuffer => this.setState({arraybuffer}))}
/>
</View>
{button}
{readystate}
{progress}
</View>
);
}
}
var PAGE_SIZE = 20;
class FormUploader extends React.Component {
state: any;
_isMounted: boolean;
_fetchRandomPhoto: () => void;
_addTextParam: () => void;
_upload: () => void;
constructor(props) {
super(props);
this.state = {
isUploading: false,
uploadProgress: null,
randomPhoto: null,
textParams: [],
};
this._isMounted = true;
this._fetchRandomPhoto = this._fetchRandomPhoto.bind(this);
this._addTextParam = this._addTextParam.bind(this);
this._upload = this._upload.bind(this);
this._fetchRandomPhoto();
}
_fetchRandomPhoto() {
CameraRoll.getPhotos(
{first: PAGE_SIZE}
).then(
(data) => {
if (!this._isMounted) {
return;
}
var edges = data.edges;
var edge = edges[Math.floor(Math.random() * edges.length)];
var randomPhoto = edge && edge.node && edge.node.image;
if (randomPhoto) {
this.setState({randomPhoto});
}
},
(error) => undefined
);
}
_addTextParam() {
var textParams = this.state.textParams;
textParams.push({name: '', value: ''});
this.setState({textParams});
}
componentWillUnmount() {
this._isMounted = false;
}
_onTextParamNameChange(index, text) {
var textParams = this.state.textParams;
textParams[index].name = text;
this.setState({textParams});
}
_onTextParamValueChange(index, text) {
var textParams = this.state.textParams;
textParams[index].value = text;
this.setState({textParams});
}
_upload() {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://posttestserver.com/post.php');
xhr.onload = () => {
this.setState({isUploading: false});
if (xhr.status !== 200) {
AlertIOS.alert(
'Upload failed',
'Expected HTTP 200 OK response, got ' + xhr.status
);
return;
}
if (!xhr.responseText) {
AlertIOS.alert(
'Upload failed',
'No response payload.'
);
return;
}
var index = xhr.responseText.indexOf('http://www.posttestserver.com/');
if (index === -1) {
AlertIOS.alert(
'Upload failed',
'Invalid response payload.'
);
return;
}
var url = xhr.responseText.slice(index).split('\n')[0];
Linking.openURL(url);
};
var formdata = new FormData();
if (this.state.randomPhoto) {
formdata.append('image', {...this.state.randomPhoto, name: 'image.jpg'});
}
this.state.textParams.forEach(
(param) => formdata.append(param.name, param.value)
);
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
this.setState({uploadProgress: event.loaded / event.total});
}
};
xhr.send(formdata);
this.setState({isUploading: true});
}
render() {
var image = null;
if (this.state.randomPhoto) {
image = (
<Image
source={this.state.randomPhoto}
style={styles.randomPhoto}
/>
);
}
var textItems = this.state.textParams.map((item, index) => (
<View style={styles.paramRow}>
<TextInput
autoCapitalize="none"
autoCorrect={false}
onChangeText={this._onTextParamNameChange.bind(this, index)}
placeholder="name..."
style={styles.textInput}
/>
<Text style={styles.equalSign}>=</Text>
<TextInput
autoCapitalize="none"
autoCorrect={false}
onChangeText={this._onTextParamValueChange.bind(this, index)}
placeholder="value..."
style={styles.textInput}
/>
</View>
));
var uploadButtonLabel = this.state.isUploading ? 'Uploading...' : 'Upload';
var uploadProgress = this.state.uploadProgress;
if (uploadProgress !== null) {
uploadButtonLabel += ' ' + Math.round(uploadProgress * 100) + '%';
}
var uploadButton = (
<View style={styles.uploadButtonBox}>
<Text style={styles.uploadButtonLabel}>{uploadButtonLabel}</Text>
</View>
);
if (!this.state.isUploading) {
uploadButton = (
<TouchableHighlight onPress={this._upload}>
{uploadButton}
</TouchableHighlight>
);
}
return (
<View>
<View style={styles.paramRow}>
<Text style={styles.photoLabel}>
Random photo from your library
(<Text style={styles.textButton} onPress={this._fetchRandomPhoto}>
update
</Text>)
</Text>
{image}
</View>
{textItems}
<View>
<Text
style={[styles.textButton, styles.addTextParamButton]}
onPress={this._addTextParam}>
Add a text param
</Text>
</View>
<View style={styles.uploadButton}>
{uploadButton}
</View>
</View>
);
}
}
exports.framework = 'React';
exports.title = 'XMLHttpRequest';
exports.description = 'XMLHttpRequest';
exports.examples = [{
title: 'File Download',
render() {
return <Downloader/>;
}
}, {
title: 'multipart/form-data Upload',
render() {
return <FormUploader/>;
}
}, {
title: 'Fetch Test',
render() {
return <XHRExampleFetch/>;
}
}, {
title: 'Headers',
render() {
return <XHRExampleHeaders/>;
}
}, {
title: 'Time Out Test',
render() {
return <XHRExampleOnTimeOut/>;
}
}, {
title: 'Cookies',
render() {
return <XHRExampleCookies />;
}
}];
var styles = StyleSheet.create({
wrapper: {
borderRadius: 5,
marginBottom: 5,
},
button: {
backgroundColor: '#eeeeee',
padding: 8,
},
progressBarLabel: {
marginTop: 12,
marginBottom: 8,
},
configRow: {
flexDirection: 'row',
paddingVertical: 8,
alignItems: 'center',
justifyContent: 'space-between',
},
paramRow: {
flexDirection: 'row',
paddingVertical: 8,
alignItems: 'center',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: 'grey',
},
photoLabel: {
flex: 1,
},
randomPhoto: {
width: 50,
height: 50,
},
textButton: {
color: 'blue',
},
addTextParamButton: {
marginTop: 8,
},
textInput: {
flex: 1,
borderRadius: 3,
borderColor: 'grey',
borderWidth: 1,
height: 30,
paddingLeft: 8,
},
equalSign: {
paddingHorizontal: 4,
},
uploadButton: {
marginTop: 16,
},
uploadButtonBox: {
flex: 1,
paddingVertical: 12,
alignItems: 'center',
backgroundColor: 'blue',
borderRadius: 4,
},
uploadButtonLabel: {
color: 'white',
fontSize: 16,
fontWeight: '500',
},
});

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) 2013-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.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
var React = require('react');
var XHRExampleDownload = require('./XHRExampleDownload');
var XHRExampleFormData = require('./XHRExampleFormData');
var XHRExampleHeaders = require('./XHRExampleHeaders');
var XHRExampleFetch = require('./XHRExampleFetch');
var XHRExampleOnTimeOut = require('./XHRExampleOnTimeOut');
var XHRExampleCookies = require('./XHRExampleCookies');
exports.framework = 'React';
exports.title = 'XMLHttpRequest';
exports.description = 'Example that demonstrates upload and download ' +
'requests using XMLHttpRequest.';
exports.examples = [{
title: 'File Download',
render() {
return <XHRExampleDownload/>;
}
}, {
title: 'multipart/form-data Upload',
render() {
return <XHRExampleFormData/>;
}
}, {
title: 'Fetch Test',
render() {
return <XHRExampleFetch/>;
}
}, {
title: 'Headers',
render() {
return <XHRExampleHeaders/>;
}
}, {
title: 'Time Out Test',
render() {
return <XHRExampleOnTimeOut/>;
}
}, {
title: 'Cookies',
render() {
return <XHRExampleCookies/>;
}
}];

View File

@ -0,0 +1,258 @@
/**
* Copyright (c) 2013-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.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
const React = require('react');
const ReactNative = require('react-native');
const {
Alert,
Platform,
ProgressBarAndroid,
ProgressViewIOS,
StyleSheet,
Switch,
Text,
TouchableHighlight,
View,
} = ReactNative;
/**
* Convert number of bytes to MB and round to the nearest 0.1 MB.
*/
function roundKilo(value: number): number {
return Math.round(value / 1000);
}
class ProgressBar extends React.Component {
render() {
if (Platform.OS === 'android') {
return (
<ProgressBarAndroid
progress={this.props.progress}
styleAttr="Horizontal"
indeterminate={false}
/>
);
}
return (
<ProgressViewIOS
progress={this.props.progress}
/>
);
}
}
class XHRExampleDownload extends React.Component {
state: Object = {
downloading: false,
// set by onreadystatechange
contentLength: 1,
responseLength: 0,
// set by onprogress
progressTotal: 1,
progressLoaded: 0,
readystateHandler: false,
progressHandler: true,
arraybuffer: false,
};
xhr: ?XMLHttpRequest = null;
cancelled: boolean = false;
_download = () => {
let xhr;
if (this.xhr) {
xhr = this.xhr;
xhr.abort();
} else {
xhr = this.xhr = new XMLHttpRequest();
}
const onreadystatechange = () => {
if (xhr.readyState === xhr.HEADERS_RECEIVED) {
const contentLength =
parseInt(xhr.getResponseHeader('Content-Length'), 10);
this.setState({
contentLength,
responseLength: 0,
});
} else if (xhr.readyState === xhr.LOADING && xhr.response) {
this.setState({
responseLength: xhr.response.length,
});
}
};
const onprogress = (event) => {
this.setState({
progressTotal: event.total,
progressLoaded: event.loaded,
});
};
if (this.state.readystateHandler) {
xhr.onreadystatechange = onreadystatechange;
}
if (this.state.progressHandler) {
xhr.onprogress = onprogress;
}
if (this.state.arraybuffer) {
xhr.responseType = 'arraybuffer';
}
xhr.onload = () => {
this.setState({downloading: false});
if (this.cancelled) {
this.cancelled = false;
return;
}
if (xhr.status === 200) {
let responseType =
`Response is a string, ${xhr.response.length} characters long.`;
if (xhr.response instanceof ArrayBuffer) {
responseType =
`Response is an ArrayBuffer, ${xhr.response.byteLength} bytes long.`;
}
Alert.alert('Download complete!', responseType);
} else if (xhr.status !== 0) {
Alert.alert(
'Error',
`Server returned HTTP status of ${xhr.status}: ${xhr.responseText}`
);
} else {
Alert.alert('Error', xhr.responseText);
}
};
xhr.open('GET', 'http://aleph.gutenberg.org/cache/epub/100/pg100.txt.utf8');
// Avoid gzip so we can actually show progress
xhr.setRequestHeader('Accept-Encoding', '');
xhr.send();
this.setState({downloading: true});
}
componentWillUnmount() {
this.cancelled = true;
this.xhr && this.xhr.abort();
}
render() {
const button = this.state.downloading ? (
<View style={styles.wrapper}>
<View style={styles.button}>
<Text>Downloading...</Text>
</View>
</View>
) : (
<TouchableHighlight
style={styles.wrapper}
onPress={this._download}>
<View style={styles.button}>
<Text>Download 5MB Text File</Text>
</View>
</TouchableHighlight>
);
let readystate = null;
let progress = null;
if (this.state.readystateHandler && !this.state.arraybuffer) {
const { responseLength, contentLength } = this.state;
readystate = (
<View>
<Text style={styles.progressBarLabel}>
responseText:{' '}
{roundKilo(responseLength)}/{roundKilo(contentLength)}k chars
</Text>
<ProgressBar
progress={(responseLength / contentLength)}
/>
</View>
);
}
if (this.state.progressHandler) {
const { progressLoaded, progressTotal } = this.state;
progress = (
<View>
<Text style={styles.progressBarLabel}>
onprogress:{' '}
{roundKilo(progressLoaded)}/{roundKilo(progressTotal)} KB
</Text>
<ProgressBar
progress={(progressLoaded / progressTotal)}
/>
</View>
);
}
return (
<View>
<View style={styles.configRow}>
<Text>onreadystatechange handler</Text>
<Switch
value={this.state.readystateHandler}
onValueChange={(readystateHandler => this.setState({readystateHandler}))}
/>
</View>
<View style={styles.configRow}>
<Text>onprogress handler</Text>
<Switch
value={this.state.progressHandler}
onValueChange={(progressHandler => this.setState({progressHandler}))}
/>
</View>
<View style={styles.configRow}>
<Text>download as arraybuffer</Text>
<Switch
value={this.state.arraybuffer}
onValueChange={(arraybuffer => this.setState({arraybuffer}))}
/>
</View>
{button}
{readystate}
{progress}
</View>
);
}
}
const styles = StyleSheet.create({
wrapper: {
borderRadius: 5,
marginBottom: 5,
},
button: {
backgroundColor: '#eeeeee',
padding: 8,
},
progressBarLabel: {
marginTop: 12,
marginBottom: 8,
},
configRow: {
flexDirection: 'row',
paddingVertical: 8,
alignItems: 'center',
justifyContent: 'space-between',
},
});
module.exports = XHRExampleDownload;

View File

@ -0,0 +1,281 @@
/**
* Copyright (c) 2013-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.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';
const React = require('react');
const ReactNative = require('react-native');
const {
Alert,
CameraRoll,
Image,
ImageEditor,
Linking,
Platform,
StyleSheet,
Text,
TextInput,
TouchableHighlight,
View,
} = ReactNative;
const PAGE_SIZE = 20;
class XHRExampleFormData extends React.Component {
state: Object = {
isUploading: false,
uploadProgress: null,
randomPhoto: null,
textParams: [],
};
_isMounted: boolean = true;
constructor(props: Object) {
super(props);
this._fetchRandomPhoto();
}
componentWillUnmount() {
this._isMounted = false;
}
_fetchRandomPhoto = () => {
CameraRoll.getPhotos(
{first: PAGE_SIZE}
).then(
(data) => {
if (!this._isMounted) {
return;
}
var edges = data.edges;
var edge = edges[Math.floor(Math.random() * edges.length)];
var randomPhoto = edge && edge.node && edge.node.image;
if (randomPhoto) {
let {width, height} = randomPhoto;
width *= 0.25;
height *= 0.25;
ImageEditor.cropImage(
randomPhoto.uri,
{offset: {x: 0, y: 0}, size: {width, height}},
(uri) => this.setState({randomPhoto: {uri}}),
(error) => undefined
);
}
},
(error) => undefined
);
};
_addTextParam = () => {
var textParams = this.state.textParams;
textParams.push({name: '', value: ''});
this.setState({textParams});
};
_onTextParamNameChange(index, text) {
var textParams = this.state.textParams;
textParams[index].name = text;
this.setState({textParams});
}
_onTextParamValueChange(index, text) {
var textParams = this.state.textParams;
textParams[index].value = text;
this.setState({textParams});
}
_upload = () => {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://posttestserver.com/post.php');
xhr.onload = () => {
this.setState({isUploading: false});
if (xhr.status !== 200) {
Alert.alert(
'Upload failed',
'Expected HTTP 200 OK response, got ' + xhr.status
);
return;
}
if (!xhr.responseText) {
Alert.alert(
'Upload failed',
'No response payload.'
);
return;
}
var index = xhr.responseText.indexOf('http://www.posttestserver.com/');
if (index === -1) {
Alert.alert(
'Upload failed',
'Invalid response payload.'
);
return;
}
var url = xhr.responseText.slice(index).split('\n')[0];
console.log('Upload successful: ' + url);
Linking.openURL(url);
};
var formdata = new FormData();
if (this.state.randomPhoto) {
formdata.append('image', {
...this.state.randomPhoto,
type: 'image/jpg',
name: 'image.jpg',
});
}
this.state.textParams.forEach(
(param) => formdata.append(param.name, param.value)
);
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
this.setState({uploadProgress: event.loaded / event.total});
}
};
xhr.send(formdata);
this.setState({isUploading: true});
};
render() {
var image = null;
if (this.state.randomPhoto) {
image = (
<Image
source={this.state.randomPhoto}
style={styles.randomPhoto}
/>
);
}
var textItems = this.state.textParams.map((item, index) => (
<View style={styles.paramRow}>
<TextInput
autoCapitalize="none"
autoCorrect={false}
onChangeText={this._onTextParamNameChange.bind(this, index)}
placeholder="name..."
style={styles.textInput}
/>
<Text style={styles.equalSign}>=</Text>
<TextInput
autoCapitalize="none"
autoCorrect={false}
onChangeText={this._onTextParamValueChange.bind(this, index)}
placeholder="value..."
style={styles.textInput}
/>
</View>
));
var uploadButtonLabel = this.state.isUploading ? 'Uploading...' : 'Upload';
var uploadProgress = this.state.uploadProgress;
if (uploadProgress !== null) {
uploadButtonLabel += ' ' + Math.round(uploadProgress * 100) + '%';
}
var uploadButton = (
<View style={styles.uploadButtonBox}>
<Text style={styles.uploadButtonLabel}>{uploadButtonLabel}</Text>
</View>
);
if (!this.state.isUploading) {
uploadButton = (
<TouchableHighlight onPress={this._upload}>
{uploadButton}
</TouchableHighlight>
);
}
return (
<View>
<View style={styles.paramRow}>
<Text style={styles.photoLabel}>
Random photo from your library
(<Text style={styles.textButton} onPress={this._fetchRandomPhoto}>
update
</Text>)
</Text>
{image}
</View>
{textItems}
<View>
<Text
style={[styles.textButton, styles.addTextParamButton]}
onPress={this._addTextParam}>
Add a text param
</Text>
</View>
<View style={styles.uploadButton}>
{uploadButton}
</View>
</View>
);
}
}
const styles = StyleSheet.create({
paramRow: {
flexDirection: 'row',
paddingVertical: 8,
alignItems: 'center',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: 'grey',
},
photoLabel: {
flex: 1,
},
randomPhoto: {
width: 50,
height: 50,
},
textButton: {
color: 'blue',
},
addTextParamButton: {
marginTop: 8,
},
textInput: {
flex: 1,
borderRadius: 3,
borderColor: 'grey',
borderWidth: 1,
height: Platform.OS === 'android' ? 50 : 30,
paddingLeft: 8,
},
equalSign: {
paddingHorizontal: 4,
},
uploadButton: {
marginTop: 16,
},
uploadButtonBox: {
flex: 1,
paddingVertical: 12,
alignItems: 'center',
backgroundColor: 'blue',
borderRadius: 4,
},
uploadButtonLabel: {
color: 'white',
fontSize: 16,
fontWeight: '500',
},
});
module.exports = XHRExampleFormData;