diff --git a/src/BinderApi.js b/src/BinderApi.js index b1be24e..3cd5833 100644 --- a/src/BinderApi.js +++ b/src/BinderApi.js @@ -16,6 +16,8 @@ export default class BinderApi { constructor() { this.__initializeCsrfHandling(); + + this.handleValidationErrors = this.handleValidationErrors.bind(this); } __initializeCsrfHandling() { @@ -149,20 +151,22 @@ export default class BinderApi { }); } + handleValidationErrors(err) { + if (err.response) { + err.valErrors = this.parseBackendValidationErrors(err.response); + } + throw err; + } + saveModel({ url, data, isNew, requestOptions }) { const method = isNew ? 'post' : 'patch'; - return this[method](url, data, requestOptions) + return ( + this[method](url, data, requestOptions) .then(newData => { return { data: newData }; }) - .catch(err => { - if (err.response) { - err.valErrors = this.parseBackendValidationErrors( - err.response - ); - } - throw err; - }); + .catch(this.handleValidationErrors) + ); } saveAllModels({ url, data, model, requestOptions }) { @@ -180,14 +184,7 @@ export default class BinderApi { } return res; }) - .catch(err => { - if (err.response) { - err.valErrors = this.parseBackendValidationErrors( - err.response - ); - } - throw err; - }); + .catch(this.handleValidationErrors); } deleteModel({ url, requestOptions }) { @@ -221,4 +218,27 @@ export default class BinderApi { }; }); } + + saveModelFile({ url, name, file, requestOptions }) { + const data = new FormData(); + data.append(name, file, file.name); + + return ( + this.api.post(url, data, { + ...requestOptions, + headers: { + ...(requestOptions.headers || {}), + 'Content-Type': 'multipart/form-data', + }, + }) + .catch(this.handleValidationErrors) + ); + } + + deleteModelFile({ url, name, requestOptions }) { + return ( + this.api.delete(url, {}, requestOptions) + .catch(this.handleValidationErrors) + ); + } } diff --git a/src/Model.js b/src/Model.js index 54c305b..a80291d 100644 --- a/src/Model.js +++ b/src/Model.js @@ -183,6 +183,7 @@ export default class Model { this.initialize(); this.saveFile = this.saveFile.bind(this); + this.handleValidationErrors = this.handleValidationErrors.bind(this); } @action @@ -589,29 +590,25 @@ export default class Model { saveFile(name) { const snakeName = camelToSnake(name); + const url = `${this.url}${snakeName}/`; if (this.__fileChanges[name]) { const file = this.__fileChanges[name]; - - const data = new FormData(); - data.append(name, file, file.name); - return ( - this.api.post( - `${this.url}${snakeName}/`, - data, - { headers: { 'Content-Type': 'multipart/form-data' } }, - ) + this.__getApi() + .saveModelFile({ url, name, file }) .then(action((res) => { this.__fileExists[name] = true; delete this.__fileChanges[name]; this.saveFromBackend(res); })) + .catch((err) => this.handleValidationErrors(err, true)) ); } else if (this.__fileDeletions[name]) { if (this.__fileExists[name]) { return ( - this.api.delete(`${this.url}${snakeName}/`) + this.__getApi() + .deleteModelFile({ url, name }) .then(action(() => { this.__fileExists[name] = false; delete this.__fileDeletions[name]; @@ -619,6 +616,7 @@ export default class Model { [snakeName]: null, } }); })) + .catch((err) => this.handleValidationErrors(err, true)) ); } else { delete this.__fileDeletions[name]; @@ -632,19 +630,25 @@ export default class Model { return Promise.all(this.fileFields().map(this.saveFile)); } - @action - save(options = {}) { + @action handleValidationErrors(err, append = false) { + if (err.valErrors) { + this.parseValidationErrors(err.valErrors, append); + } + throw err; + } + + @action save(options = {}) { this.clearValidationErrors(); return this.wrapPendingRequestCount( this.__getApi() .saveModel({ url: options.url || this.url, data: this.toBackend({ - data: options.data, - mapData: options.mapData, - fields: options.fields, - onlyChanges: options.onlyChanges, - }), + data: options.data, + mapData: options.mapData, + fields: options.fields, + onlyChanges: options.onlyChanges, + }), isNew: this.isNew, requestOptions: omit(options, 'url', 'data', 'mapData') }) @@ -659,14 +663,7 @@ export default class Model { return Promise.resolve(res); }); })) - .catch( - action(err => { - if (err.valErrors) { - this.parseValidationErrors(err.valErrors); - } - throw err; - }) - ) + .catch(this.handleValidationErrors) ); } @@ -674,7 +671,7 @@ export default class Model { setInput(name, value) { invariant( this.__attributes.includes(name) || - this.__activeCurrentRelations.includes(name), + this.__activeCurrentRelations.includes(name), `Field \`${name}\` does not exist on the model.` ); if (this.fileFields().includes(name)) { @@ -766,14 +763,7 @@ export default class Model { return res; }); })) - .catch( - action(err => { - if (err.valErrors) { - this.parseValidationErrors(err.valErrors); - } - throw err; - }) - ) + .catch(this.handleValidationErrors) ); } @@ -800,7 +790,7 @@ export default class Model { } @action - parseValidationErrors(valErrors) { + parseValidationErrors(valErrors, append = false) { const bname = this.constructor.backendResourceName; if (valErrors[bname]) { @@ -818,12 +808,16 @@ export default class Model { return valError.map(this.validationErrorFormatter); } ); - this.__backendValidationErrors = formattedErrors; + if (append) { + this.__backendValidationErrors.push(...formattedErrors); + } else { + this.__backendValidationErrors = formattedErrors; + } } } this.__activeCurrentRelations.forEach(currentRel => { - this[currentRel].parseValidationErrors(valErrors); + this[currentRel].parseValidationErrors(valErrors, append); }); } diff --git a/src/Store.js b/src/Store.js index 971e964..9a63c46 100644 --- a/src/Store.js +++ b/src/Store.js @@ -176,9 +176,9 @@ export default class Store { return this; } - parseValidationErrors(valErrors) { + parseValidationErrors(valErrors, append = false) { this.each(model => { - model.parseValidationErrors(valErrors); + model.parseValidationErrors(valErrors, append); }); }