From e5b3f9bc20b0603cd4d4890bb447fb866c961e8d Mon Sep 17 00:00:00 2001 From: kir4ik Date: Mon, 19 Jul 2021 18:55:02 +0300 Subject: [PATCH] added support multipart form data for form model & added support update filed with type file --- src/common/utils.js | 6 +++-- src/form/FormExpressApi.js | 55 +++++++++++++++++++++++++++----------- src/form/FormXhrModel.js | 25 ++++++++++++++--- 3 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/common/utils.js b/src/common/utils.js index 96d7da9e..5ad02db2 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -179,11 +179,13 @@ export function parseValueFromEvent(event) { event.target && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(event.target.tagName) >= 0 ) { switch (event.target.type) { - case 'checkbox': - return event.target.checked; + case 'checkbox': return event.target.checked; + case 'file': return event.target.files[0]; } + return event.target.value; } + return event; } diff --git a/src/form/FormExpressApi.js b/src/form/FormExpressApi.js index 7426a49f..e7668a10 100644 --- a/src/form/FormExpressApi.js +++ b/src/form/FormExpressApi.js @@ -8,14 +8,23 @@ import ValidationErrors from '../common/validation/ValidationErrors'; import express from 'express'; -import {asyncHandler} from '../common/utils'; +import multer from 'multer'; +import {asyncHandler, parseJson} from '../common/utils'; + +const DEFAULT_MAX_FILE_SIZE = 104857600; // 100 MB class FormExpressApi { - static create() { - return new FormExpressApi(); + static create(multipartFormData, maxFileSize) { + return new FormExpressApi(multipartFormData, maxFileSize); } - constructor() { + constructor(multipartFormData = false, maxFileSize = DEFAULT_MAX_FILE_SIZE) { + const upload = multer({ + limits: { + fileSize: maxFileSize + } + }); + this.middlewares = { getData: [asyncHandler(async (req, res, next) => { const fields = req.query.fields ? JSON.parse(req.query.fields) : null; @@ -25,19 +34,33 @@ class FormExpressApi { const fields = req.body.fields || null; this._commonGetDataMiddleware(req, res, next, fields); })], - submit: [asyncHandler(async (req, res, next) => { - const model = this._getModel(req, res); - try { - const data = await model.submit(req.body); - this._result(null, {data: data, error: null}, req, res, next); - } catch (err) { - if (err && !(err instanceof ValidationErrors)) { - this._result(err, null, req, res, next); - return; + submit: [ + ...(multipartFormData ? [upload.any()] : []), + asyncHandler(async (req, res, next) => { + const model = this._getModel(req, res); + let body = req.body; + + if (multipartFormData) { + body = parseJson(body.rest); + + for (const {fieldname, buffer} of req.files) { + const parsedFieldName = parseJson(decodeURI(fieldname), 'Invalid JSON in field name'); + body[parsedFieldName] = buffer; + } } - this._result(null, {data: null, error: err}, req, res, next); - } - })], + + try { + const data = await model.submit(body); + this._result(null, {data: data, error: null}, req, res, next); + } catch (err) { + if (err && !(err instanceof ValidationErrors)) { + this._result(err, null, req, res, next); + return; + } + this._result(null, {data: null, error: err}, req, res, next); + } + }) + ], validate: [asyncHandler(async (req, res, next) => { const model = this._getModel(req, res); try { diff --git a/src/form/FormXhrModel.js b/src/form/FormXhrModel.js index 930de31f..f64314c5 100644 --- a/src/form/FormXhrModel.js +++ b/src/form/FormXhrModel.js @@ -22,6 +22,7 @@ class FormXhrModel extends EventsModel { throw new Error('Initialization problem: \'api\' must be specified.'); } + this._multipartFormDataEncoded = settings.multipartFormData || false; this._validator = settings.validator || new Validator(); this._xhr = settings.xhr || defaultXhr; this._apiUrl = settings.api @@ -46,14 +47,30 @@ class FormXhrModel extends EventsModel { return JSON.parse(response); } - async submit(changes) { + async submit(record) { + const formData = new FormData(); + + if (this._multipartFormDataEncoded) { + const ordinaryData = {}; + for (const [prop, value] of Object.entries(record)) { + if (value instanceof File) { + formData.append(JSON.stringify(prop), value); + } else { + ordinaryData[prop] = value; + } + } + formData.append('rest', JSON.stringify(ordinaryData)); + } + let body = await this._xhr({ method: 'POST', - headers: { - 'Content-type': 'application/json' + ...!this._multipartFormDataEncoded && { + headers: { + 'Content-type': 'application/json' + } }, uri: this._apiUrl, - body: JSON.stringify(changes) + body: this._multipartFormDataEncoded ? formData : JSON.stringify(record) }); body = JSON.parse(body);