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;