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 laonDownloadComplete
prop. - Tengo otro componente React, lo llamaremos
Widget
, con un botón / ícono de descarga (muchos en realidad ... uno para cada elemento en una tabla).Widget
tiene acción correspondiente y almacenar archivos.Widget
importaFileDownload
. -
Widget
tiene dos métodos relacionados con la descarga:handleDownload
yhandleDownloadComplete
. -
Widget
tienda deWidget
tiene una propiedad llamadadownloadPath
. Está establecido ennull
por defecto. Cuando su valor se establece ennull
, no hay descarga de archivos en curso y el componenteWidget
no representa el componenteFileDownload
. - Al hacer clic en el botón / icono en
Widget
llama al métodohandleDownload
que desencadena una accióndownloadFile
. La accióndownloadFile
NO hace una solicitud Ajax. Envía un eventoDOWNLOAD_FILE
a la tienda que envía junto con él ladownloadPath
del archivo para descargar. La tienda guardadownloadPath
y emite un evento de cambio. - Dado que ahora hay un
FileDownload
downloadPath
,Widget
hará queFileDownload
pase los elementos necesarios, incluidosdownloadPath
y el métodohandleDownloadComplete
como el valor deonDownloadComplete
. - Cuando se procesa
FileDownload
y 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,downloadPath
se establece ennull
. La tienda guardadownloadPath
comonull
y emite un evento de cambio. - Como ya no existe una
downloadPath
el componenteFileDownload
no se representa enWidget
y 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);
})
}
});