From 3c806652cf75d2aa93998068af84a7ba4a80d57c Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Sat, 14 Jan 2017 18:55:42 -0800 Subject: [PATCH] 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 --- Examples/UIExplorer/js/UIExplorerList.ios.js | 2 +- Examples/UIExplorer/js/XHRExample.android.js | 515 ------------------- Examples/UIExplorer/js/XHRExample.ios.js | 508 ------------------ Examples/UIExplorer/js/XHRExample.js | 68 +++ Examples/UIExplorer/js/XHRExampleDownload.js | 258 ++++++++++ Examples/UIExplorer/js/XHRExampleFormData.js | 281 ++++++++++ 6 files changed, 608 insertions(+), 1024 deletions(-) delete mode 100644 Examples/UIExplorer/js/XHRExample.android.js delete mode 100644 Examples/UIExplorer/js/XHRExample.ios.js create mode 100644 Examples/UIExplorer/js/XHRExample.js create mode 100644 Examples/UIExplorer/js/XHRExampleDownload.js create mode 100644 Examples/UIExplorer/js/XHRExampleFormData.js diff --git a/Examples/UIExplorer/js/UIExplorerList.ios.js b/Examples/UIExplorer/js/UIExplorerList.ios.js index 8d401a986..26aa40777 100644 --- a/Examples/UIExplorer/js/UIExplorerList.ios.js +++ b/Examples/UIExplorer/js/UIExplorerList.ios.js @@ -354,7 +354,7 @@ const APIExamples: Array = [ }, { key: 'XHRExample', - module: require('./XHRExample.ios'), + module: require('./XHRExample'), supportsTVOS: true, }, ]; diff --git a/Examples/UIExplorer/js/XHRExample.android.js b/Examples/UIExplorer/js/XHRExample.android.js deleted file mode 100644 index 9cc6a7564..000000000 --- a/Examples/UIExplorer/js/XHRExample.android.js +++ /dev/null @@ -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 ? ( - - - ... - - - ) : ( - - - Download 5MB Text File - - - ); - - let readystate = null; - let progress = null; - if (this.state.readystateHandler && !this.state.arraybuffer) { - const { responseLength, contentLength } = this.state; - readystate = ( - - - responseText:{' '} - {roundKilo(responseLength)}/{roundKilo(contentLength)}k chars - - - - ); - } - if (this.state.progressHandler) { - const { progressLoaded, progressTotal } = this.state; - progress = ( - - - onprogress:{' '} - {roundKilo(progressLoaded)}/{roundKilo(progressTotal)} KB - - - - ); - } - - return ( - - - onreadystatechange handler - this.setState({readystateHandler}))} - /> - - - onprogress handler - this.setState({progressHandler}))} - /> - - - download as arraybuffer - this.setState({arraybuffer}))} - /> - - {button} - {readystate} - {progress} - {this.state.status} - - ); - } -} - -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 = ( - - ); - } - var textItems = this.state.textParams.map((item, index) => ( - - - = - - - )); - var uploadButtonLabel = this.state.isUploading ? 'Uploading...' : 'Upload'; - var uploadProgress = this.state.uploadProgress; - if (uploadProgress !== null) { - uploadButtonLabel += ' ' + Math.round(uploadProgress * 100) + '%'; - } - var uploadButton = ( - - {uploadButtonLabel} - - ); - if (!this.state.isUploading) { - uploadButton = ( - - {uploadButton} - - ); - } - return ( - - - - Random photo from your library - ( - update - ) - - {image} - - {textItems} - - - Add a text param - - - - {uploadButton} - - - ); - } -} - -exports.framework = 'React'; -exports.title = 'XMLHttpRequest'; -exports.description = 'Example that demonstrates upload and download requests ' + - 'using XMLHttpRequest.'; -exports.examples = [{ - title: 'File Download', - render() { - return ; - } -}, { - title: 'multipart/form-data Upload', - render() { - return ; - } -}, { - title: 'Fetch Test', - render() { - return ; - } -}, { - title: 'Headers', - render() { - return ; - } -}, { - title: 'Cookies', - render() { - return ; - } -}, { - title: 'Time Out Test', - render() { - return ; - } -}]; - -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', - }, -}); diff --git a/Examples/UIExplorer/js/XHRExample.ios.js b/Examples/UIExplorer/js/XHRExample.ios.js deleted file mode 100644 index 75cb09d40..000000000 --- a/Examples/UIExplorer/js/XHRExample.ios.js +++ /dev/null @@ -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 ? ( - - - Downloading... - - - ) : ( - - - Download 5MB Text File - - - ); - - let readystate = null; - let progress = null; - if (this.state.readystateHandler && !this.state.arraybuffer) { - const { responseLength, contentLength } = this.state; - readystate = ( - - - responseText:{' '} - {roundKilo(responseLength)}/{roundKilo(contentLength)}k chars - - - - ); - } - if (this.state.progressHandler) { - const { progressLoaded, progressTotal } = this.state; - progress = ( - - - onprogress:{' '} - {roundKilo(progressLoaded)}/{roundKilo(progressTotal)} KB - - - - ); - } - - return ( - - - onreadystatechange handler - this.setState({readystateHandler}))} - /> - - - onprogress handler - this.setState({progressHandler}))} - /> - - - download as arraybuffer - this.setState({arraybuffer}))} - /> - - {button} - {readystate} - {progress} - - ); - } -} - -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 = ( - - ); - } - var textItems = this.state.textParams.map((item, index) => ( - - - = - - - )); - var uploadButtonLabel = this.state.isUploading ? 'Uploading...' : 'Upload'; - var uploadProgress = this.state.uploadProgress; - if (uploadProgress !== null) { - uploadButtonLabel += ' ' + Math.round(uploadProgress * 100) + '%'; - } - var uploadButton = ( - - {uploadButtonLabel} - - ); - if (!this.state.isUploading) { - uploadButton = ( - - {uploadButton} - - ); - } - return ( - - - - Random photo from your library - ( - update - ) - - {image} - - {textItems} - - - Add a text param - - - - {uploadButton} - - - ); - } -} - -exports.framework = 'React'; -exports.title = 'XMLHttpRequest'; -exports.description = 'XMLHttpRequest'; -exports.examples = [{ - title: 'File Download', - render() { - return ; - } -}, { - title: 'multipart/form-data Upload', - render() { - return ; - } -}, { - title: 'Fetch Test', - render() { - return ; - } -}, { - title: 'Headers', - render() { - return ; - } -}, { - title: 'Time Out Test', - render() { - return ; - } -}, { - title: 'Cookies', - render() { - return ; - } -}]; - -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', - }, -}); diff --git a/Examples/UIExplorer/js/XHRExample.js b/Examples/UIExplorer/js/XHRExample.js new file mode 100644 index 000000000..fdb56088b --- /dev/null +++ b/Examples/UIExplorer/js/XHRExample.js @@ -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 ; + } +}, { + title: 'multipart/form-data Upload', + render() { + return ; + } +}, { + title: 'Fetch Test', + render() { + return ; + } +}, { + title: 'Headers', + render() { + return ; + } +}, { + title: 'Time Out Test', + render() { + return ; + } +}, { + title: 'Cookies', + render() { + return ; + } +}]; diff --git a/Examples/UIExplorer/js/XHRExampleDownload.js b/Examples/UIExplorer/js/XHRExampleDownload.js new file mode 100644 index 000000000..d8c9d2907 --- /dev/null +++ b/Examples/UIExplorer/js/XHRExampleDownload.js @@ -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 ( + + ); + } + return ( + + ); + } +} + +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 ? ( + + + Downloading... + + + ) : ( + + + Download 5MB Text File + + + ); + + let readystate = null; + let progress = null; + if (this.state.readystateHandler && !this.state.arraybuffer) { + const { responseLength, contentLength } = this.state; + readystate = ( + + + responseText:{' '} + {roundKilo(responseLength)}/{roundKilo(contentLength)}k chars + + + + ); + } + if (this.state.progressHandler) { + const { progressLoaded, progressTotal } = this.state; + progress = ( + + + onprogress:{' '} + {roundKilo(progressLoaded)}/{roundKilo(progressTotal)} KB + + + + ); + } + + return ( + + + onreadystatechange handler + this.setState({readystateHandler}))} + /> + + + onprogress handler + this.setState({progressHandler}))} + /> + + + download as arraybuffer + this.setState({arraybuffer}))} + /> + + {button} + {readystate} + {progress} + + ); + } +} + +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; diff --git a/Examples/UIExplorer/js/XHRExampleFormData.js b/Examples/UIExplorer/js/XHRExampleFormData.js new file mode 100644 index 000000000..9a70b484d --- /dev/null +++ b/Examples/UIExplorer/js/XHRExampleFormData.js @@ -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 = ( + + ); + } + var textItems = this.state.textParams.map((item, index) => ( + + + = + + + )); + var uploadButtonLabel = this.state.isUploading ? 'Uploading...' : 'Upload'; + var uploadProgress = this.state.uploadProgress; + if (uploadProgress !== null) { + uploadButtonLabel += ' ' + Math.round(uploadProgress * 100) + '%'; + } + var uploadButton = ( + + {uploadButtonLabel} + + ); + if (!this.state.isUploading) { + uploadButton = ( + + {uploadButton} + + ); + } + return ( + + + + Random photo from your library + ( + update + ) + + {image} + + {textItems} + + + Add a text param + + + + {uploadButton} + + + ); + } +} + +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;