Add logic to React FormBuilder to utilise validation schema generated

This commit is contained in:
Damian Mooyman 2016-10-31 18:25:22 +13:00 committed by Christopher Joe
parent 1142757c21
commit 67f00302f9
9 changed files with 190 additions and 98 deletions

View File

@ -59,64 +59,69 @@ return this.fetch(e,{method:"put",credentials:"same-origin",body:s(t),headers:n}
return this.fetch(e,{method:"delete",credentials:"same-origin",body:s(t),headers:n}).then(a)}}]),e}(),O=new P
t["default"]=O},function(e,t,n){n(8),e.exports=self.fetch.bind(self)},function(e,t){!function(e){"use strict"
function t(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name")
return e.toLowerCase()}function n(e){return"string"!=typeof e&&(e=String(e)),e}function i(e){this.map={},e instanceof i?e.forEach(function(e,t){this.append(t,e)},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){
this.append(t,e[t])},this)}function r(e){return e.bodyUsed?Promise.reject(new TypeError("Already read")):void(e.bodyUsed=!0)}function o(e){return new Promise(function(t,n){e.onload=function(){t(e.result)
return e.toLowerCase()}function n(e){return"string"!=typeof e&&(e=String(e)),e}function i(e){var t={next:function(){var t=e.shift()
return{done:void 0===t,value:t}}}
return m.iterable&&(t[Symbol.iterator]=function(){return t}),t}function r(e){this.map={},e instanceof r?e.forEach(function(e,t){this.append(t,e)},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){
this.append(t,e[t])},this)}function o(e){return e.bodyUsed?Promise.reject(new TypeError("Already read")):void(e.bodyUsed=!0)}function a(e){return new Promise(function(t,n){e.onload=function(){t(e.result)
},e.onerror=function(){n(e.error)}})}function a(e){var t=new FileReader
return t.readAsArrayBuffer(e),o(t)}function s(e){var t=new FileReader
return t.readAsText(e),o(t)}function l(){return this.bodyUsed=!1,this._initBody=function(e){if(this._bodyInit=e,"string"==typeof e)this._bodyText=e
else if(h.blob&&Blob.prototype.isPrototypeOf(e))this._bodyBlob=e
else if(h.formData&&FormData.prototype.isPrototypeOf(e))this._bodyFormData=e
else if(e){if(!h.arrayBuffer||!ArrayBuffer.prototype.isPrototypeOf(e))throw new Error("unsupported BodyInit type")}else this._bodyText=""
this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type&&this.headers.set("content-type",this._bodyBlob.type))
},e.onerror=function(){n(e.error)}})}function s(e){var t=new FileReader
return t.readAsArrayBuffer(e),a(t)}function l(e){var t=new FileReader
return t.readAsText(e),a(t)}function u(){return this.bodyUsed=!1,this._initBody=function(e){if(this._bodyInit=e,"string"==typeof e)this._bodyText=e
else if(m.blob&&Blob.prototype.isPrototypeOf(e))this._bodyBlob=e
else if(m.formData&&FormData.prototype.isPrototypeOf(e))this._bodyFormData=e
else if(m.searchParams&&URLSearchParams.prototype.isPrototypeOf(e))this._bodyText=e.toString()
else if(e){if(!m.arrayBuffer||!ArrayBuffer.prototype.isPrototypeOf(e))throw new Error("unsupported BodyInit type")}else this._bodyText=""
this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):m.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))
},h.blob?(this.blob=function(){var e=r(this)
},m.blob?(this.blob=function(){var e=o(this)
if(e)return e
if(this._bodyBlob)return Promise.resolve(this._bodyBlob)
if(this._bodyFormData)throw new Error("could not read FormData body as blob")
return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this.blob().then(a)},this.text=function(){var e=r(this)
return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this.blob().then(s)},this.text=function(){var e=o(this)
if(e)return e
if(this._bodyBlob)return s(this._bodyBlob)
if(this._bodyBlob)return l(this._bodyBlob)
if(this._bodyFormData)throw new Error("could not read FormData body as text")
return Promise.resolve(this._bodyText)}):this.text=function(){var e=r(this)
return e?e:Promise.resolve(this._bodyText)},h.formData&&(this.formData=function(){return this.text().then(d)}),this.json=function(){return this.text().then(JSON.parse)},this}function u(e){var t=e.toUpperCase()
return Promise.resolve(this._bodyText)}):this.text=function(){var e=o(this)
return e?e:Promise.resolve(this._bodyText)},m.formData&&(this.formData=function(){return this.text().then(f)}),this.json=function(){return this.text().then(JSON.parse)},this}function c(e){var t=e.toUpperCase()
return m.indexOf(t)>-1?t:e}function c(e,t){t=t||{}
return g.indexOf(t)>-1?t:e}function d(e,t){t=t||{}
var n=t.body
if(c.prototype.isPrototypeOf(e)){if(e.bodyUsed)throw new TypeError("Already read")
this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new i(e.headers)),this.method=e.method,this.mode=e.mode,n||(n=e._bodyInit,e.bodyUsed=!0)}else this.url=e
if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new i(t.headers)),this.method=u(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null,
if(d.prototype.isPrototypeOf(e)){if(e.bodyUsed)throw new TypeError("Already read")
this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new r(e.headers)),this.method=e.method,this.mode=e.mode,n||(n=e._bodyInit,e.bodyUsed=!0)}else this.url=e
if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new r(t.headers)),this.method=c(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null,
("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests")
this._initBody(n)}function d(e){var t=new FormData
this._initBody(n)}function f(e){var t=new FormData
return e.trim().split("&").forEach(function(e){if(e){var n=e.split("="),i=n.shift().replace(/\+/g," "),r=n.join("=").replace(/\+/g," ")
t.append(decodeURIComponent(i),decodeURIComponent(r))}}),t}function f(e){var t=new i,n=e.getAllResponseHeaders().trim().split("\n")
t.append(decodeURIComponent(i),decodeURIComponent(r))}}),t}function p(e){var t=new r,n=(e.getAllResponseHeaders()||"").trim().split("\n")
return n.forEach(function(e){var n=e.trim().split(":"),i=n.shift().trim(),r=n.join(":").trim()
t.append(i,r)}),t}function p(e,t){t||(t={}),this.type="default",this.status=t.status,this.ok=this.status>=200&&this.status<300,this.statusText=t.statusText,this.headers=t.headers instanceof i?t.headers:new i(t.headers),
this.url=t.url||"",this._initBody(e)}if(!e.fetch){i.prototype.append=function(e,i){e=t(e),i=n(i)
t.append(i,r)}),t}function h(e,t){t||(t={}),this.type="default",this.status=t.status,this.ok=this.status>=200&&this.status<300,this.statusText=t.statusText,this.headers=t.headers instanceof r?t.headers:new r(t.headers),
this.url=t.url||"",this._initBody(e)}if(!e.fetch){var m={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,
!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e}
r.prototype.append=function(e,i){e=t(e),i=n(i)
var r=this.map[e]
r||(r=[],this.map[e]=r),r.push(i)},i.prototype["delete"]=function(e){delete this.map[t(e)]},i.prototype.get=function(e){var n=this.map[t(e)]
return n?n[0]:null},i.prototype.getAll=function(e){return this.map[t(e)]||[]},i.prototype.has=function(e){return this.map.hasOwnProperty(t(e))},i.prototype.set=function(e,i){this.map[t(e)]=[n(i)]},i.prototype.forEach=function(e,t){
Object.getOwnPropertyNames(this.map).forEach(function(n){this.map[n].forEach(function(i){e.call(t,i,n,this)},this)},this)}
var h={blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e},m=["DELETE","GET","HEAD","OPTIONS","POST","PUT"]
c.prototype.clone=function(){return new c(this)},l.call(c.prototype),l.call(p.prototype),p.prototype.clone=function(){return new p(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new i(this.headers),
url:this.url})},p.error=function(){var e=new p(null,{status:0,statusText:""})
r||(r=[],this.map[e]=r),r.push(i)},r.prototype["delete"]=function(e){delete this.map[t(e)]},r.prototype.get=function(e){var n=this.map[t(e)]
return n?n[0]:null},r.prototype.getAll=function(e){return this.map[t(e)]||[]},r.prototype.has=function(e){return this.map.hasOwnProperty(t(e))},r.prototype.set=function(e,i){this.map[t(e)]=[n(i)]},r.prototype.forEach=function(e,t){
Object.getOwnPropertyNames(this.map).forEach(function(n){this.map[n].forEach(function(i){e.call(t,i,n,this)},this)},this)},r.prototype.keys=function(){var e=[]
return this.forEach(function(t,n){e.push(n)}),i(e)},r.prototype.values=function(){var e=[]
return this.forEach(function(t){e.push(t)}),i(e)},r.prototype.entries=function(){var e=[]
return this.forEach(function(t,n){e.push([n,t])}),i(e)},m.iterable&&(r.prototype[Symbol.iterator]=r.prototype.entries)
var g=["DELETE","GET","HEAD","OPTIONS","POST","PUT"]
d.prototype.clone=function(){return new d(this)},u.call(d.prototype),u.call(h.prototype),h.prototype.clone=function(){return new h(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new r(this.headers),
url:this.url})},h.error=function(){var e=new h(null,{status:0,statusText:""})
return e.type="error",e}
var g=[301,302,303,307,308]
p.redirect=function(e,t){if(g.indexOf(t)===-1)throw new RangeError("Invalid status code")
return new p(null,{status:t,headers:{location:e}})},e.Headers=i,e.Request=c,e.Response=p,e.fetch=function(e,t){return new Promise(function(n,i){function r(){return"responseURL"in a?a.responseURL:/^X-Request-URL:/m.test(a.getAllResponseHeaders())?a.getResponseHeader("X-Request-URL"):void 0
var v=[301,302,303,307,308]
h.redirect=function(e,t){if(v.indexOf(t)===-1)throw new RangeError("Invalid status code")
return new h(null,{status:t,headers:{location:e}})},e.Headers=r,e.Request=d,e.Response=h,e.fetch=function(e,t){return new Promise(function(n,i){function r(){return"responseURL"in a?a.responseURL:/^X-Request-URL:/m.test(a.getAllResponseHeaders())?a.getResponseHeader("X-Request-URL"):void 0
}var o
o=c.prototype.isPrototypeOf(e)&&!t?e:new c(e,t)
o=d.prototype.isPrototypeOf(e)&&!t?e:new d(e,t)
var a=new XMLHttpRequest
a.onload=function(){var e=1223===a.status?204:a.status
if(e<100||e>599)return void i(new TypeError("Network request failed"))
var t={status:e,statusText:a.statusText,headers:f(a),url:r()},o="response"in a?a.response:a.responseText
n(new p(o,t))},a.onerror=function(){i(new TypeError("Network request failed"))},a.open(o.method,o.url,!0),"include"===o.credentials&&(a.withCredentials=!0),"responseType"in a&&h.blob&&(a.responseType="blob"),
o.headers.forEach(function(e,t){a.setRequestHeader(t,e)}),a.send("undefined"==typeof o._bodyInit?null:o._bodyInit)})},e.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)},function(e,t,n){var i;(function(t,r){
!function(t,n){e.exports=n()}(this,function(){"use strict"
a.onload=function(){var e={status:a.status,statusText:a.statusText,headers:p(a),url:r()},t="response"in a?a.response:a.responseText
n(new h(t,e))},a.onerror=function(){i(new TypeError("Network request failed"))},a.ontimeout=function(){i(new TypeError("Network request failed"))},a.open(o.method,o.url,!0),"include"===o.credentials&&(a.withCredentials=!0),
"responseType"in a&&m.blob&&(a.responseType="blob"),o.headers.forEach(function(e,t){a.setRequestHeader(t,e)}),a.send("undefined"==typeof o._bodyInit?null:o._bodyInit)})},e.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)
},function(e,t,n){var i;(function(t,r){!function(t,n){e.exports=n()}(this,function(){"use strict"
function e(e){return"function"==typeof e||"object"==typeof e&&null!==e}function o(e){return"function"==typeof e}function a(e){K=e}function s(e){Y=e}function l(){return function(){return t.nextTick(p)}}
function u(){return function(){Q(p)}}function c(){var e=0,t=new ee(p),n=document.createTextNode("")
return t.observe(n,{characterData:!0}),function(){n.data=e=++e%2}}function d(){var e=new MessageChannel
@ -342,17 +347,17 @@ e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,wri
value:!0}),t.schemaPropType=t.basePropTypes=void 0
var l=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]
for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e},u=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n]
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),c=n(4),d=i(c),f=n(20),p=i(f),h=n(30),m=i(h),g=n(31),v=i(g),y=n(32),b=i(y),w=n(16),_=i(w),C=function(e){
i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),c=n(4),d=i(c),f=n(16),p=i(f),h=n(30),m=i(h),g=n(20),v=i(g),y=n(31),b=i(y),w=n(32),_=i(w),C=function(e){
function t(e){o(this,t)
var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e)),i=e.schema.schema
return n.state={submittingAction:null},n.submitApi=v["default"].createEndpointFetcher({url:i.attributes.action,method:i.attributes.method}),n.mapActionsToComponents=n.mapActionsToComponents.bind(n),n.mapFieldsToComponents=n.mapFieldsToComponents.bind(n),
return n.state={submittingAction:null},n.submitApi=b["default"].createEndpointFetcher({url:i.attributes.action,method:i.attributes.method}),n.mapActionsToComponents=n.mapActionsToComponents.bind(n),n.mapFieldsToComponents=n.mapFieldsToComponents.bind(n),
n.handleSubmit=n.handleSubmit.bind(n),n.handleAction=n.handleAction.bind(n),n.buildComponent=n.buildComponent.bind(n),n}return s(t,e),u(t,[{key:"handleAction",value:function n(e){"function"==typeof this.props.handleAction&&this.props.handleAction(e,(0,
m["default"])())
var t=e.currentTarget.name
e.isPropagationStopped()||this.setState({submittingAction:t})}},{key:"handleSubmit",value:function i(e){var t=this,n=this.state.submittingAction?this.state.submittingAction:this.props.schema.schema.actions[0].name,i=l({},e,r({},n,1)),o={
"X-Formschema-Request":"state,schema","X-Requested-With":"XMLHttpRequest"},a=function s(e){return t.submitApi(e||i,o).then(function(e){return t.setState({submittingAction:null}),e})["catch"](function(e){
throw t.setState({submittingAction:null}),e})}
return"function"==typeof this.props.handleSubmit?this.props.handleSubmit(i,n,a):a()}},{key:"buildComponent",value:function c(e){var t=e,n=null!==t.schemaComponent?b["default"].getComponentByName(t.schemaComponent):b["default"].getComponentByDataType(t.type)
return"function"==typeof this.props.handleSubmit?this.props.handleSubmit(i,n,a):a()}},{key:"buildComponent",value:function c(e){var t=e,n=null!==t.schemaComponent?_["default"].getComponentByName(t.schemaComponent):_["default"].getComponentByDataType(t.type)
if(null===n)return null
@ -362,21 +367,21 @@ var i=this.props.createFn
return"function"==typeof i?i(n,t):d["default"].createElement(n,l({key:t.id},t))}},{key:"mapFieldsToComponents",value:function f(e){var t=this,n=this.props.baseFieldComponent
return e.map(function(e){var i=e
return e.children&&(i=l({},e,{children:t.mapFieldsToComponents(e.children)})),"Structural"===e.type||e.readOnly===!0?t.buildComponent(i):d["default"].createElement(n,l({key:i.id},i,{component:t.buildComponent
}))})}},{key:"mapActionsToComponents",value:function p(e){var t=this
}))})}},{key:"mapActionsToComponents",value:function h(e){var t=this
return e.map(function(e){var n=l({},e)
return e.children?n.children=t.mapActionsToComponents(e.children):(n.handleClick=t.handleAction,t.props.submitting&&t.state.submittingAction===e.name&&(n.loading=!0)),t.buildComponent(n)})}},{key:"mergeFieldData",
value:function h(e,t){return"undefined"==typeof t?e:_["default"].recursive(!0,e,{data:t.data,source:t.source,message:t.message,valid:t.valid,value:t.value})}},{key:"normalizeFields",value:function g(e,t){
value:function g(e,t){return"undefined"==typeof t?e:p["default"].recursive(!0,e,{data:t.data,source:t.source,message:t.message,valid:t.valid,value:t.value})}},{key:"normalizeFields",value:function v(e,t){
var n=this
return e.map(function(e){var i=t&&t.fields?t.fields.find(function(t){return t.id===e.id}):{},r=_["default"].recursive(!0,n.mergeFieldData(e,i),{schemaComponent:e.component})
return e.map(function(e){var i=t&&t.fields?t.fields.find(function(t){return t.id===e.id}):{},r=p["default"].recursive(!0,n.mergeFieldData(e,i),{schemaComponent:e.component})
return e.children&&(r.children=n.normalizeFields(e.children,t)),r})}},{key:"normalizeActions",value:function y(e){var t=this
return e.map(function(e){var n=_["default"].recursive(!0,e,{schemaComponent:e.component})
return e.map(function(e){var n=p["default"].recursive(!0,e,{schemaComponent:e.component})
return e.children&&(n.children=t.normalizeActions(e.children)),n})}},{key:"render",value:function w(){var e=this.props.schema.schema,t=this.props.schema.state,n=this.props.baseFormComponent,i=l({},e.attributes,{
className:e.attributes["class"],encType:e.attributes.enctype})
delete i["class"],delete i.enctype
var r=this.props,o=r.asyncValidate,a=r.onSubmitFail,s=r.onSubmitSuccess,u=r.shouldAsyncValidate,c=r.touchOnBlur,f=r.touchOnChange,p=r.persistentSubmitErrors,h=r.validate,g=r.form,v={form:g,fields:this.normalizeFields(e.fields,t),
actions:this.normalizeActions(e.actions),attributes:i,data:e.data,initialValues:(0,m["default"])(e,t),onSubmit:this.handleSubmit,valid:t&&t.valid,messages:t&&Array.isArray(t.messages)?t.messages:[],mapActionsToComponents:this.mapActionsToComponents,
mapFieldsToComponents:this.mapFieldsToComponents,asyncValidate:o,onSubmitFail:a,onSubmitSuccess:s,shouldAsyncValidate:u,touchOnBlur:c,touchOnChange:f,persistentSubmitErrors:p,validate:h}
return d["default"].createElement(n,v)}}]),t}(p["default"]),T=c.PropTypes.shape({id:c.PropTypes.string.isRequired,schema:c.PropTypes.shape({attributes:c.PropTypes.shape({"class":c.PropTypes.string,enctype:c.PropTypes.string
return d["default"].createElement(n,v)}}]),t}(v["default"]),T=c.PropTypes.shape({id:c.PropTypes.string.isRequired,schema:c.PropTypes.shape({attributes:c.PropTypes.shape({"class":c.PropTypes.string,enctype:c.PropTypes.string
}),fields:c.PropTypes.array.isRequired}).isRequired,state:c.PropTypes.shape({fields:c.PropTypes.array})}),E={createFn:c.PropTypes.func,handleSubmit:c.PropTypes.func,handleAction:c.PropTypes.func,asyncValidate:c.PropTypes.func,
onSubmitFail:c.PropTypes.func,onSubmitSuccess:c.PropTypes.func,shouldAsyncValidate:c.PropTypes.func,touchOnBlur:c.PropTypes.bool,touchOnChange:c.PropTypes.bool,persistentSubmitErrors:c.PropTypes.bool,validate:c.PropTypes.func,
baseFormComponent:c.PropTypes.func.isRequired,baseFieldComponent:c.PropTypes.func.isRequired}

View File

@ -26,6 +26,7 @@ require('expose?ReactAddonsCssTransitionGroup!react-addons-css-transition-group'
require('expose?ReactAddonsTestUtils!react-addons-test-utils');
require('expose?Page!page.js');
require('expose?BootstrapCollapse!bootstrap/dist/js/umd/collapse.js');
require('expose?approvejs!approvejs');
require('../../../thirdparty/jquery-ondemand/jquery.ondemand.js');
require('../../../thirdparty/jquery-ui/jquery-ui.js');

View File

@ -12,7 +12,7 @@ function fieldHolder(Field) {
*
* @returns {Component}
*/
getDescription() {
renderDescription() {
if (this.props.description === null) {
return null;
}
@ -29,10 +29,11 @@ function fieldHolder(Field) {
*
* @returns {Component}
*/
getMessage() {
const message = (this.props.meta) ? this.props.meta.error : null;
renderMessage() {
const meta = this.props.meta;
const message = (meta) ? meta.error : null;
if (!message) {
if (!message || (meta && !meta.touched)) {
return null;
}
@ -46,7 +47,7 @@ function fieldHolder(Field) {
*
* @returns {Component}
*/
getLeftTitle() {
renderLeftTitle() {
const labelText = this.props.leftTitle !== null
? this.props.leftTitle
: this.props.title;
@ -67,7 +68,7 @@ function fieldHolder(Field) {
*
* @returns {Component}
*/
getRightTitle() {
renderRightTitle() {
if (!this.props.rightTitle || this.props.hideLabels) {
return null;
}
@ -108,13 +109,13 @@ function fieldHolder(Field) {
render() {
return (
<FormGroup {...this.getHolderProps()}>
{this.getLeftTitle()}
{this.renderLeftTitle()}
<div className="form__field-holder">
<Field {...this.props} />
{this.getMessage()}
{this.getDescription()}
{this.renderMessage()}
{this.renderDescription()}
</div>
{this.getRightTitle()}
{this.renderRightTitle()}
</FormGroup>
);
}

View File

@ -1,9 +1,10 @@
import React, { PropTypes } from 'react';
import approve from 'approvejs';
import merge from 'merge';
import schemaFieldValues, { findField } from 'lib/schemaFieldValues';
import SilverStripeComponent from 'lib/SilverStripeComponent';
import schemaFieldValues from 'lib/schemaFieldValues';
import backend from 'lib/Backend';
import injector from 'lib/Injector';
import merge from 'merge';
class FormBuilder extends SilverStripeComponent {
@ -21,6 +22,82 @@ class FormBuilder extends SilverStripeComponent {
this.handleSubmit = this.handleSubmit.bind(this);
this.handleAction = this.handleAction.bind(this);
this.buildComponent = this.buildComponent.bind(this);
this.validateForm = this.validateForm.bind(this);
}
/**
* Run validation for every field on the form and return an object which list issues while
* validating
*
* @param values
* @returns {*}
*/
validateForm(values) {
if (typeof this.props.validate === 'function') {
return this.props.validate(values);
}
const schema = this.props.schema && this.props.schema.schema;
if (!schema) {
return {};
}
return Object.entries(values).reduce((prev, curr) => {
const [key, value] = curr;
const field = findField(this.props.schema.schema.fields, key);
if (!field.validation) {
return prev;
}
const error = approve.value(value, this.getFieldValidationRules(field, values));
if (error.approved) {
return prev;
}
// so if there are multiple errors, it will be listed in html spans
const errorHtml = `<span>${error.errors.join('</span><span>')}</span>`;
return Object.assign({}, prev, {
[key]: {
type: 'error',
value: { html: errorHtml },
},
});
}, {});
}
/**
* Generates validation rules for a given field
*
* @param {object} field
* @param {object} otherValues
* @returns {object}
*/
getFieldValidationRules(field, otherValues) {
if (!field.validation) {
return {};
}
const rules = Object.assign({},
field.validation,
{
title: (field.leftTitle !== null) ? field.leftTitle : field.title,
}
);
// mutate rules for equality check
// currently assumes server provides field name to check against
if (typeof rules.equal === 'string') {
const equalField = findField(this.props.schema.schema.fields, rules.equal);
rules.equal = {
value: otherValues[rules.equal],
field: (equalField.leftTitle !== null) ? equalField.leftTitle : equalField.title,
};
}
return rules;
}
/**
@ -32,14 +109,12 @@ class FormBuilder extends SilverStripeComponent {
handleAction(event) {
// Custom handlers
if (typeof this.props.handleAction === 'function') {
this.props.handleAction(event, schemaFieldValues());
this.props.handleAction(event, this.props.values);
}
const name = event.currentTarget.name;
// Allow custom handlers to cancel event
if (!event.isPropagationStopped()) {
this.setState({ submittingAction: name });
this.setState({ submittingAction: event.currentTarget.name });
}
}
@ -72,6 +147,7 @@ class FormBuilder extends SilverStripeComponent {
return formSchema;
})
.catch((reason) => {
// TODO Generic CMS error reporting
this.setState({ submittingAction: null });
throw reason;
});
@ -274,7 +350,6 @@ class FormBuilder extends SilverStripeComponent {
touchOnBlur,
touchOnChange,
persistentSubmitErrors,
validate,
form,
} = this.props;
@ -297,7 +372,7 @@ class FormBuilder extends SilverStripeComponent {
touchOnBlur,
touchOnChange,
persistentSubmitErrors,
validate,
validate: this.validateForm,
};
return <BaseFormComponent {...props} />;
@ -330,6 +405,8 @@ const basePropTypes = {
touchOnChange: PropTypes.bool,
persistentSubmitErrors: PropTypes.bool,
validate: PropTypes.func,
values: PropTypes.object,
submitting: PropTypes.bool,
baseFormComponent: PropTypes.func.isRequired,
baseFieldComponent: PropTypes.func.isRequired,
};
@ -337,7 +414,6 @@ const basePropTypes = {
FormBuilder.propTypes = Object.assign({}, basePropTypes, {
form: PropTypes.string.isRequired,
schema: schemaPropType.isRequired,
submitting: PropTypes.bool,
});
export { basePropTypes, schemaPropType };

View File

@ -82,9 +82,9 @@ describe('FormBuilder', () => {
{ id: 'fieldTwo', name: 'fieldTwo' },
];
props.schema.state.fields = [
{ id: 'fieldOne', value: 'valOne' },
{ id: 'fieldTwo', value: null },
{ id: 'notInSchema', value: 'invalid' },
{ id: 'fieldOne', name: 'fieldOne', value: 'valOne' },
{ id: 'fieldTwo', name: 'fieldTwo', value: null },
{ id: 'notInSchema', name: 'notInSchema', value: 'invalid' },
];
fieldValues = schemaFieldValues(props.schema.schema, props.schema.state);
expect(fieldValues).toEqual({
@ -110,9 +110,9 @@ describe('FormBuilder', () => {
{ id: 'actionTwo', name: 'actionTwo' },
];
props.schema.state.fields = [
{ id: 'fieldOne', value: 'valOne' },
{ id: 'fieldTwo', value: null },
{ id: 'notInSchema', value: 'invalid' },
{ id: 'fieldOne', name: 'fieldOne', value: 'valOne' },
{ id: 'fieldTwo', name: 'fieldTwo', value: null },
{ id: 'notInSchema', name: 'notInSchema', value: 'invalid' },
];
});
@ -156,35 +156,35 @@ describe('FormBuilder', () => {
it('should retrieve the field in the shallow fields list', () => {
fields = [
{ id: 'fieldOne' },
{ id: 'fieldTwo' },
{ id: 'fieldThree' },
{ id: 'fieldFour' },
{ id: 'fieldOne', name: 'fieldOne' },
{ id: 'fieldTwo', name: 'fieldTwo' },
{ id: 'fieldThree', name: 'fieldThree' },
{ id: 'fieldFour', name: 'fieldFour' },
];
const field = findField(fields, 'fieldThree');
expect(field).toBeTruthy();
expect(field.id).toBe('fieldThree');
expect(field.name).toBe('fieldThree');
});
it('should retrieve the field that is a grandchild in the fields list', () => {
fields = [
{ id: 'fieldOne' },
{ id: 'fieldTwo', children: [
{ id: 'fieldTwoOne' },
{ id: 'fieldTwoTwo', children: [
{ id: 'fieldTwoOne' },
{ id: 'fieldTwoTwo' },
{ id: 'fieldTwoThree' },
{ id: 'fieldOne', name: 'fieldOne' },
{ id: 'fieldTwo', name: 'fieldTwo', children: [
{ id: 'fieldTwoOne', name: 'fieldTwoOne' },
{ id: 'fieldTwoTwo', name: 'fieldTwoTwo', children: [
{ id: 'fieldTwoOne', name: 'fieldTwoOne' },
{ id: 'fieldTwoTwo', name: 'fieldTwoTwo' },
{ id: 'fieldTwoThree', name: 'fieldTwoThree' },
] },
] },
{ id: 'fieldThree' },
{ id: 'fieldFour' },
{ id: 'fieldThree', name: 'fieldThree' },
{ id: 'fieldFour', name: 'fieldFour' },
];
const field = findField(fields, 'fieldTwoThree');
expect(field).toBeTruthy();
expect(field.id).toBe('fieldTwoThree');
expect(field.name).toBe('fieldTwoThree');
});
});
});

View File

@ -174,11 +174,15 @@ FormBuilderLoader.defaultProps = {
export default connect(
(state, ownProps) => {
const schema = state.schemas[ownProps.schemaUrl];
const form = schema ? schema.id : null;
const submitting = state.form
&& state.form[ownProps.schemaUrl]
&& state.form[ownProps.schemaUrl].submitting;
return { schema, form, submitting };
const form = schema && schema.id;
const reduxFormState = state.form
&& state.form[ownProps.schemaUrl];
const submitting = reduxFormState
&& reduxFormState.submitting;
const values = reduxFormState
&& reduxFormState.values;
return { schema, form, submitting, values };
},
(dispatch) => ({
schemaActions: bindActionCreators(schemaActions, dispatch),

View File

@ -3,22 +3,22 @@
* schema's deep nesting of fields.
*
* @param fields
* @param id
* @param name
* @returns {object|undefined}
*/
export function findField(fields, id) {
export function findField(fields, name) {
let result = null;
if (!fields) {
return result;
}
result = fields.find(field => field.id === id);
result = fields.find(field => field.name === name);
for (const field of fields) {
if (result) {
break;
}
result = findField(field.children, id);
result = findField(field.children, name);
}
return result;
}
@ -36,7 +36,7 @@ export default function schemaFieldValues(schema, state) {
return state.fields
.reduce((prev, curr) => {
const match = findField(schema.fields, curr.id);
const match = findField(schema.fields, curr.name);
if (!match) {
return prev;

4
npm-shrinkwrap.json generated
View File

@ -4,6 +4,10 @@
"npm-shrinkwrap-version": "6.0.1",
"node-version": "v4.5.0",
"dependencies": {
"approvejs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/approvejs/-/approvejs-1.1.2.tgz"
},
"autoprefixer": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.4.1.tgz",

View File

@ -33,6 +33,7 @@
},
"homepage": "https://github.com/silverstripe/silverstripe-framework#readme",
"dependencies": {
"approvejs": "^1.1.2",
"babel-polyfill": "^6.7.4",
"blueimp-file-upload": "6.0.3",
"blueimp-load-image": "1.1.3",