Initial ReactInput work
This commit is contained in:
parent
1d20129397
commit
5ff8fdc86b
7 changed files with 335 additions and 23 deletions
49
javascripts/src/components/common/form/ReactForm.tsx
Normal file
49
javascripts/src/components/common/form/ReactForm.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
import { observer, Provider } from 'mobx-react';
|
||||
import {Field, Form} from "mobx-react-form";
|
||||
import {observable, action, toJS} from 'mobx';
|
||||
|
||||
export interface ReactFormProps
|
||||
{
|
||||
form:Form
|
||||
}
|
||||
|
||||
///Mostly useless class but, at some point, will replace all our form elements
|
||||
class ReactForm extends React.Component<ReactFormProps, {}> {
|
||||
|
||||
|
||||
|
||||
@observable
|
||||
form:Form
|
||||
|
||||
@action.bound
|
||||
componentWillMount()
|
||||
{
|
||||
this.form = this.props.form
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
componentDidUpdate(){
|
||||
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
return <form >
|
||||
{this.props.children}
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
export default observer(ReactForm)
|
||||
|
||||
|
||||
|
162
javascripts/src/components/common/form/ReactInput.spec.tsx
Normal file
162
javascripts/src/components/common/form/ReactInput.spec.tsx
Normal file
|
@ -0,0 +1,162 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
import 'jest';
|
||||
import ReactInput from './ReactInput'
|
||||
import {Form} from "mobx-react-form";
|
||||
import ReactForm from "./ReactForm";
|
||||
import {mount} from 'enzyme';
|
||||
import {toJS, observable, action, runInAction} from 'mobx';
|
||||
import {observer} from 'mobx-react';
|
||||
import {InputHTMLAttributes} from 'react';
|
||||
|
||||
|
||||
|
||||
@observer
|
||||
class TestChange extends React.Component{
|
||||
@observable
|
||||
remove:boolean
|
||||
@observable
|
||||
form: Form
|
||||
|
||||
@action.bound
|
||||
componentWillMount(){
|
||||
this.form = new Form({fields:[{
|
||||
name: 'name',
|
||||
extra: null}
|
||||
]})
|
||||
}
|
||||
|
||||
|
||||
|
||||
@action.bound
|
||||
onClick(){
|
||||
this.remove = true
|
||||
}
|
||||
render() {
|
||||
let reactInput = !this.remove ? <ReactInput field={this.form.$('name')} label={'label1'} placeholder={"holder"}>
|
||||
{this.props.children}
|
||||
</ReactInput> : undefined
|
||||
|
||||
return <ReactForm form={this.form}>
|
||||
|
||||
{reactInput}
|
||||
<button onClick={() => this.onClick()}/>
|
||||
</ReactForm>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class WrappedInput extends React.Component<InputHTMLAttributes<HTMLInputElement>>{
|
||||
|
||||
render(){
|
||||
let notChildren = {...this.props}
|
||||
delete notChildren.children
|
||||
return <div>
|
||||
<input {...notChildren} />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
describe('ReactInput', () => {
|
||||
|
||||
let form: Form
|
||||
beforeEach(() => {
|
||||
form = new Form({
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
extra: null
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
describe('no children passed in', () => {
|
||||
test('gets added properly', () => {
|
||||
let res = mount(<ReactForm form={form}>
|
||||
<ReactInput field={form.$('name')} label={"label"}
|
||||
placeholder={"holder"} value={'snapshot'} aria-required={true}/>
|
||||
|
||||
</ReactForm>)
|
||||
|
||||
|
||||
//Did the attributes settings work as expected back to the objects
|
||||
expect(form.$('name').label).toEqual('label')
|
||||
expect(form.$('name').placeholder).toEqual('holder')
|
||||
expect(form.$('name').value).toEqual('')
|
||||
|
||||
//is the aria attribute passted through to the input
|
||||
let input = res.find('input')
|
||||
expect(input.prop('aria-required')).toEqual(true)
|
||||
|
||||
|
||||
// is the input properly bound?
|
||||
input.simulate('change', {target: { value: 'something' } })
|
||||
expect(form.$('name').value).toEqual('something')
|
||||
})
|
||||
|
||||
test('gets removed properly', () => {
|
||||
|
||||
let res = mount(<TestChange/>)
|
||||
|
||||
let f = res.find('ReactForm').instance() as ReactForm
|
||||
expect(f.form.size).toEqual(1)
|
||||
|
||||
res.find('input').simulate('change', {target: { value: 'something' } })
|
||||
|
||||
expect(f.form.$('name').value).toEqual('something')
|
||||
|
||||
res.find('button').simulate('click')
|
||||
expect(f.form.size).toEqual(1)
|
||||
|
||||
expect(toJS(res.find('form'))).toMatchSnapshot()
|
||||
|
||||
expect(f.form.$('name').label).toEqual('label1')
|
||||
expect(f.form.$('name').placeholder).toEqual('holder')
|
||||
})
|
||||
})
|
||||
|
||||
describe('children passed in', () => {
|
||||
test('gets added properly', () => {
|
||||
let res = mount(<ReactForm form={form}>
|
||||
<ReactInput field={form.$('name')} label={"label"}
|
||||
placeholder={"holder"} value={'snapshot'} aria-required={true}>
|
||||
<WrappedInput/>
|
||||
</ReactInput>
|
||||
|
||||
</ReactForm>)
|
||||
|
||||
//Did the attributes settings work as expected back to the objects
|
||||
expect(form.$('name').label).toEqual('label')
|
||||
expect(form.$('name').placeholder).toEqual('holder')
|
||||
expect(form.$('name').value).toEqual('')
|
||||
|
||||
//is the aria attribute passted through to the input
|
||||
let input = res.find('input')
|
||||
expect(input.prop('aria-required')).toEqual(true)
|
||||
|
||||
|
||||
// is the input properly bound?
|
||||
input.simulate('change', {target: { value: 'something' } })
|
||||
expect(form.$('name').value).toEqual('something')
|
||||
})
|
||||
|
||||
test('gets removed properly', () => {
|
||||
|
||||
let res = mount(<TestChange>
|
||||
<WrappedInput/>
|
||||
</TestChange>)
|
||||
let f = res.find('ReactForm').instance() as ReactForm
|
||||
res.find('input').simulate('change', {target: { value: 'something' } })
|
||||
|
||||
expect(f.form.$('name').value).toEqual('something')
|
||||
expect(f.form.size).toEqual(1)
|
||||
res.find('button').simulate('click')
|
||||
expect(f.form.size).toEqual(1)
|
||||
|
||||
expect(f.form.$('name').label).toEqual('label1')
|
||||
expect(f.form.$('name').placeholder).toEqual('holder')
|
||||
})
|
||||
|
||||
})
|
||||
})
|
89
javascripts/src/components/common/form/ReactInput.tsx
Normal file
89
javascripts/src/components/common/form/ReactInput.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
// License: LGPL-3.0-or-later
|
||||
import * as React from 'react';
|
||||
import { observer, inject, Provider } from 'mobx-react';
|
||||
import {InjectedIntlProps, injectIntl} from 'react-intl';
|
||||
import {Field} from "mobx-react-form";
|
||||
import {observable, action, toJS, runInAction} from 'mobx';
|
||||
import {InputHTMLAttributes} from 'react';
|
||||
|
||||
export interface ReactInputProps
|
||||
{
|
||||
field:Field
|
||||
label?:string
|
||||
placeholder?:string
|
||||
children: React.ReactElement<InputHTMLAttributes<HTMLInputElement>>
|
||||
}
|
||||
|
||||
function castToNullIfUndef(i:any){
|
||||
return i === undefined ? null : i
|
||||
}
|
||||
|
||||
|
||||
class ReactInput extends React.Component<ReactInputProps & InputHTMLAttributes<HTMLInputElement>, {}> {
|
||||
|
||||
constructor(props:ReactInputProps){
|
||||
super(props)
|
||||
}
|
||||
|
||||
@observable
|
||||
field:Field
|
||||
|
||||
|
||||
@action.bound
|
||||
componentWillMount(){
|
||||
|
||||
this.field = this.props.field
|
||||
|
||||
|
||||
this.updateProps()
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
}
|
||||
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<ReactInputProps>, prevState: Readonly<{}>): void {
|
||||
this.updateProps()
|
||||
}
|
||||
|
||||
@action.bound
|
||||
updateProps() {
|
||||
this.field.set('label', castToNullIfUndef(this.props.label))
|
||||
this.field.set('placeholder', castToNullIfUndef(this.props.placeholder))
|
||||
}
|
||||
|
||||
@action.bound
|
||||
renderChildren(){
|
||||
let ourProps = this.winnowProps()
|
||||
let elem = React.cloneElement(this.props.children as React.ReactElement<any>,
|
||||
{...ourProps, ...this.field.bind() })
|
||||
return elem
|
||||
|
||||
}
|
||||
|
||||
///Removes the properties we don't want to put into the input element
|
||||
@action.bound
|
||||
winnowProps(): ReactInputProps & InputHTMLAttributes<HTMLInputElement> {
|
||||
let ourProps = {...this.props}
|
||||
delete ourProps.field
|
||||
delete ourProps.value
|
||||
return ourProps
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
if (this.props.children)
|
||||
{
|
||||
return this.renderChildren()
|
||||
}
|
||||
else {
|
||||
return <input {...this.winnowProps()} {...this.field.bind()}/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default observer(ReactInput)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ReactInput no children passed in gets removed properly 1`] = `
|
||||
<form>
|
||||
<button
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</form>
|
||||
`;
|
30
package-lock.json
generated
30
package-lock.json
generated
|
@ -5697,9 +5697,9 @@
|
|||
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
|
||||
},
|
||||
"hoist-non-react-statics": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz",
|
||||
"integrity": "sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w==",
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
||||
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==",
|
||||
"dev": true
|
||||
},
|
||||
"home-or-tmp": {
|
||||
|
@ -8900,19 +8900,19 @@
|
|||
}
|
||||
},
|
||||
"mobx": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/mobx/-/mobx-4.2.1.tgz",
|
||||
"integrity": "sha1-3UGQ2vG0PUGjoihYUlP5lwsKJ90=",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mobx/-/mobx-4.3.1.tgz",
|
||||
"integrity": "sha1-M05aq0kWsdQ/D682BaZLG0s8y40=",
|
||||
"dev": true
|
||||
},
|
||||
"mobx-react": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.1.2.tgz",
|
||||
"integrity": "sha1-7FwtKaHfgj29GzfiFPo2oJBwVOI=",
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.2.3.tgz",
|
||||
"integrity": "sha512-OuSlF2nJEa1PGookZcZnINbvEK4iWNNYiqUh6aebk2AkWxj3sG8OafDOQMcMYApQALTHRsrBIjOx/K8TFxcz7w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hoist-non-react-statics": "2.5.0",
|
||||
"react-lifecycles-compat": "3.0.3"
|
||||
"hoist-non-react-statics": "2.5.5",
|
||||
"react-lifecycles-compat": "3.0.4"
|
||||
}
|
||||
},
|
||||
"mobx-react-devtools": {
|
||||
|
@ -8922,7 +8922,7 @@
|
|||
"dev": true
|
||||
},
|
||||
"mobx-react-form": {
|
||||
"version": "github:houdiniproject/mobx-react-form#63a163397102d86745b7ac381d42b794ec172231",
|
||||
"version": "github:houdiniproject/mobx-react-form#8421c456a88cd3d7f887168a8228bb1a04911e7d",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "4.17.10"
|
||||
|
@ -11584,9 +11584,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.3.tgz",
|
||||
"integrity": "sha512-bOr65SSYgxDgDNqLnDqt+gropXGPNB1Wbyys4tOYiNuP/qYWC4qFM9XH1ruzq+tT6EjE29pJsCr19rclKtpUEg==",
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
|
||||
"dev": true
|
||||
},
|
||||
"react-reconciler": {
|
||||
|
|
|
@ -54,8 +54,8 @@
|
|||
"less": "^3.0.4",
|
||||
"less-loader": "^4.1.0",
|
||||
"lodash": "^4.17.5",
|
||||
"mobx": "^4.2.0",
|
||||
"mobx-react": "^5.0.0",
|
||||
"mobx": "^4.3.1",
|
||||
"mobx-react": "^5.2.3",
|
||||
"mobx-react-devtools": "^5.0.1",
|
||||
"mobx-react-form": "github:houdiniproject/mobx-react-form#our_fix",
|
||||
"no-scroll": "^2.1.0",
|
||||
|
|
15
types/mobx-react-form/index.d.ts
vendored
15
types/mobx-react-form/index.d.ts
vendored
|
@ -72,7 +72,7 @@ export declare class Field implements Base, FieldProperties, FieldMethods, Field
|
|||
|
||||
$(fieldName: string): Field;
|
||||
|
||||
add(obj: any): any;
|
||||
add(obj:{FieldDefinition}): any;
|
||||
|
||||
bind(): object;
|
||||
|
||||
|
@ -82,7 +82,7 @@ export declare class Field implements Base, FieldProperties, FieldMethods, Field
|
|||
|
||||
container(): Form |Field
|
||||
|
||||
del(key: any);
|
||||
del(path?: string);
|
||||
|
||||
each(callback: (i: Field) => void);
|
||||
|
||||
|
@ -173,6 +173,7 @@ interface FieldHandlers {
|
|||
|
||||
interface FieldDefinition {
|
||||
name: string
|
||||
key?: string
|
||||
label?: string
|
||||
value?: any
|
||||
default?: any
|
||||
|
@ -253,12 +254,13 @@ interface FormInitializer{
|
|||
|
||||
interface initializationDefinition {
|
||||
fields?:FieldDefinitions[]
|
||||
hooks?: FormHooks
|
||||
}
|
||||
|
||||
export class Form implements Base {
|
||||
|
||||
|
||||
constructor(definition:initializationDefinition, options?:any)
|
||||
constructor(definition?:initializationDefinition, options?:any)
|
||||
plugins(): void
|
||||
setup(): any
|
||||
onInit(): void
|
||||
|
@ -269,7 +271,7 @@ export class Form implements Base {
|
|||
|
||||
$(fieldName: string): Field;
|
||||
|
||||
add(obj: any): any;
|
||||
add(obj:FieldDefinition): any;
|
||||
|
||||
check(computed: string, deep?: boolean): boolean;
|
||||
|
||||
|
@ -292,7 +294,7 @@ export class Form implements Base {
|
|||
|
||||
map(callback: (i: Field) => void);
|
||||
|
||||
observe(obj: any);
|
||||
observe(...obj: any)
|
||||
|
||||
select(path: string): Field;
|
||||
|
||||
|
@ -304,6 +306,7 @@ export class Form implements Base {
|
|||
update(obj: any): void;
|
||||
|
||||
readonly submitting: boolean;
|
||||
readonly isValid:boolean;
|
||||
|
||||
protected validator :any
|
||||
|
||||
|
@ -327,7 +330,7 @@ interface SharedFieldFormMethods {
|
|||
has(key:string):boolean
|
||||
map(callback:(i:Field) => void)
|
||||
each(callback:(i:Field) => void)
|
||||
add(obj:any):any
|
||||
add(obj:FieldDefinition): any;
|
||||
del(key:any)
|
||||
observe(obj:any)
|
||||
intercept(obj:any)
|
||||
|
|
Loading…
Reference in a new issue