javascript - validacion - Cómo descargar la respuesta de búsqueda en reaccionar como archivo
validacion de formularios con javascript ejemplos (2)
La tecnología del navegador actualmente no es compatible con la descarga de un archivo directamente desde una solicitud Ajax. La solución alternativa es agregar un formulario oculto y enviarlo entre bastidores para que el navegador active el cuadro de diálogo Guardar.
Estoy ejecutando una implementación estándar de Flux, así que no estoy seguro de cuál debería ser el código exacto de Redux (Reducer), pero el flujo de trabajo que acabo de crear para la descarga de un archivo es el siguiente ...
- Tengo un componente React llamado
FileDownload. Todo lo que hace este componente es renderizar un formulario oculto y luego, dentro decomponentDidMount, enviar inmediatamente el formulario y llamarlo a laonDownloadCompleteprop. - Tengo otro componente React, lo llamaremos
Widget, con un botón / ícono de descarga (muchos en realidad ... uno para cada elemento en una tabla).Widgettiene acción correspondiente y almacenar archivos.WidgetimportaFileDownload. -
Widgettiene dos métodos relacionados con la descarga:handleDownloadyhandleDownloadComplete. -
Widgettienda deWidgettiene una propiedad llamadadownloadPath. Está establecido ennullpor defecto. Cuando su valor se establece ennull, no hay descarga de archivos en curso y el componenteWidgetno representa el componenteFileDownload. - Al hacer clic en el botón / icono en
Widgetllama al métodohandleDownloadque desencadena una accióndownloadFile. La accióndownloadFileNO hace una solicitud Ajax. Envía un eventoDOWNLOAD_FILEa la tienda que envía junto con él ladownloadPathdel archivo para descargar. La tienda guardadownloadPathy emite un evento de cambio. - Dado que ahora hay un
FileDownloaddownloadPath,Widgethará queFileDownloadpase los elementos necesarios, incluidosdownloadPathy el métodohandleDownloadCompletecomo el valor deonDownloadComplete. - Cuando se procesa
FileDownloady se envía el formulario conmethod="GET"(POST debería funcionar también) yaction={downloadPath}, la respuesta del servidor activará el diálogo Save del navegador para el archivo de descarga de destino (probado en IE 9/10 , los últimos Firefox y Chrome). - Inmediatamente después del envío del formulario, se llama a
onDownloadComplete/handleDownloadComplete. Esto desencadena otra acción que distribuye un eventoDOWNLOAD_FILE. Sin embargo, esta vez,downloadPathse establece ennull. La tienda guardadownloadPathcomonully emite un evento de cambio. - Como ya no existe una
downloadPathel componenteFileDownloadno se representa enWidgety el mundo es un lugar feliz.
Widget.js - solo código parcial
import FileDownload from ''./FileDownload'';
export default class Widget extends Component {
constructor(props) {
super(props);
this.state = widgetStore.getState().toJS();
}
handleDownload(data) {
widgetActions.downloadFile(data);
}
handleDownloadComplete() {
widgetActions.downloadFile();
}
render() {
const downloadPath = this.state.downloadPath;
return (
// button/icon with click bound to this.handleDownload goes here
{downloadPath &&
<FileDownload
actionPath={downloadPath}
onDownloadComplete={this.handleDownloadComplete}
/>
}
);
}
widgetActions.js - solo código parcial
export function downloadFile(data) {
let downloadPath = null;
if (data) {
downloadPath = `${apiResource}/${data.fileName}`;
}
appDispatcher.dispatch({
actionType: actionTypes.DOWNLOAD_FILE,
downloadPath
});
}
widgetStore.js - solo código parcial
let store = Map({
downloadPath: null,
isLoading: false,
// other store properties
});
class WidgetStore extends Store {
constructor() {
super();
this.dispatchToken = appDispatcher.register(action => {
switch (action.actionType) {
case actionTypes.DOWNLOAD_FILE:
store = store.merge({
downloadPath: action.downloadPath,
isLoading: !!action.downloadPath
});
this.emitChange();
break;
FileDownload.js
- código completo, completamente funcional listo para copiar y pegar
- Reaccionar 0.14.7 con Babel 6.x ["es2015", "reaccionar", "etapa-0"]
- formulario debe display: none que es el nombre de className "oculto" para
import React, {Component, PropTypes} from ''react'';
import ReactDOM from ''react-dom'';
function getFormInputs() {
const {queryParams} = this.props;
if (queryParams === undefined) {
return null;
}
return Object.keys(queryParams).map((name, index) => {
return (
<input
key={index}
name={name}
type="hidden"
value={queryParams[name]}
/>
);
});
}
export default class FileDownload extends Component {
static propTypes = {
actionPath: PropTypes.string.isRequired,
method: PropTypes.string,
onDownloadComplete: PropTypes.func.isRequired,
queryParams: PropTypes.object
};
static defaultProps = {
method: ''GET''
};
componentDidMount() {
ReactDOM.findDOMNode(this).submit();
this.props.onDownloadComplete();
}
render() {
const {actionPath, method} = this.props;
return (
<form
action={actionPath}
className="hidden"
method={method}
>
{getFormInputs.call(this)}
</form>
);
}
}
Aquí está el código en actions.js
export function exportRecordToExcel(record) {
return ({fetch}) => ({
type: EXPORT_RECORD_TO_EXCEL,
payload: {
promise: fetch(''/records/export'', {
credentials: ''same-origin'',
method: ''post'',
headers: {''Content-Type'': ''application/json''},
body: JSON.stringify(data)
}).then(function(response) {
return response;
})
}
});
}
La respuesta devuelta es un archivo .xlsx . Quiero que el usuario pueda guardarlo como un archivo, pero no pasa nada. Supongo que el servidor está devolviendo el tipo correcto de respuesta porque en la consola dice
Content-Disposition:attachment; filename="report.xlsx"
¿Qué me estoy perdiendo? ¿Qué debería hacer en el reductor?
Puede usar estas dos bibliotecas para descargar archivos http://danml.com/download.html https://github.com/eligrey/FileSaver.js/#filesaverjs
ejemplo
// for FileSaver
import FileSaver from ''file-saver'';
export function exportRecordToExcel(record) {
return ({fetch}) => ({
type: EXPORT_RECORD_TO_EXCEL,
payload: {
promise: fetch(''/records/export'', {
credentials: ''same-origin'',
method: ''post'',
headers: {''Content-Type'': ''application/json''},
body: JSON.stringify(data)
}).then(function(response) {
return response.blob();
}).then(function(blob) {
FileSaver.saveAs(blob, ''nameFile.zip'');
})
}
});
// for download
let download = require(''./download.min'');
export function exportRecordToExcel(record) {
return ({fetch}) => ({
type: EXPORT_RECORD_TO_EXCEL,
payload: {
promise: fetch(''/records/export'', {
credentials: ''same-origin'',
method: ''post'',
headers: {''Content-Type'': ''application/json''},
body: JSON.stringify(data)
}).then(function(response) {
return response.blob();
}).then(function(blob) {
download (blob);
})
}
});