diff --git a/src/Scoped.tsx b/src/Scoped.tsx index 850fc5fb..437b838f 100644 --- a/src/Scoped.tsx +++ b/src/Scoped.tsx @@ -13,7 +13,8 @@ import {RendererEnv, RendererProps} from './factory'; import {noop, autobind, qsstringify} from './utils/helper'; import {RendererData, Action} from './types'; -interface ScopedComponentType extends React.Component { +export interface ScopedComponentType extends React.Component { + focus?: () =>void; doAction?: (action: Action, data: RendererData, throwErrors?: boolean) => void; receive?: (values: RendererData, subPath?: string) => void; reload?: (subPath?: string, query?: RendererData | null, ctx?: RendererData) => void; diff --git a/src/components/Select.tsx b/src/components/Select.tsx index 8171a7c4..4bf9e33a 100644 --- a/src/components/Select.tsx +++ b/src/components/Select.tsx @@ -156,6 +156,7 @@ interface SelectProps { checkAllLabel?: string; defaultCheckAll?: boolean; simpleValue?: boolean; + } interface SelectState { diff --git a/src/renderers/Form/Control.tsx b/src/renderers/Form/Control.tsx index 0eb5f7ba..87c11a05 100644 --- a/src/renderers/Form/Control.tsx +++ b/src/renderers/Form/Control.tsx @@ -10,7 +10,7 @@ import {Schema} from '../../types'; import {IIRendererStore} from '../../store'; import {ScopedContext, IScopedContext} from '../../Scoped'; -export interface FormControlProps extends RendererProps { +export interface ControlProps extends RendererProps { control: { id?: string; name?: string; @@ -36,13 +36,13 @@ export interface FormControlProps extends RendererProps { removeHook: (fn: () => any) => void; } -export default class FormControl extends React.Component { +export default class FormControl extends React.Component { public model: IFormItemStore | undefined; control: any; hook?: () => any; hook2?: () => any; - static defaultProps: Partial = {}; + static defaultProps: Partial = {}; lazyValidate: Function; componentWillMount() { @@ -134,7 +134,7 @@ export default class FormControl extends React.Component } } - componentWillReceiveProps(nextProps: FormControlProps) { + componentWillReceiveProps(nextProps: ControlProps) { const props = this.props; const form = nextProps.formStore; @@ -204,7 +204,7 @@ export default class FormControl extends React.Component } } - componentDidUpdate(prevProps: FormControlProps) { + componentDidUpdate(prevProps: ControlProps) { const { store, formStore: form, diff --git a/src/renderers/Form/Item.tsx b/src/renderers/Form/Item.tsx index 395f357d..0e38159b 100644 --- a/src/renderers/Form/Item.tsx +++ b/src/renderers/Form/Item.tsx @@ -1,11 +1,12 @@ import React from 'react'; import hoistNonReactStatic = require('hoist-non-react-statics'); -import {IFormItemStore} from '../../store/form'; +import {IFormItemStore, IFormStore} from '../../store/form'; import {reaction} from 'mobx'; import {RendererProps, registerRenderer, TestFunc, RendererConfig, HocStoreFactory} from '../../factory'; import {anyChanged, ucFirst, getWidthRate} from '../../utils/helper'; import {observer} from 'mobx-react'; +import {FormHorizontal, FormSchema} from '.'; export interface FormItemBasicConfig extends Partial { type?: string; @@ -26,28 +27,40 @@ export interface FormItemBasicConfig extends Partial { validate?: (values: any, value: any) => string | boolean; } -export interface FormControlProps extends RendererProps { - // error string - error?: string; - inputOnly?: boolean; - - // error 详情 - errors?: { - [propName: string]: string; - }; +export interface FormItemState { + isFocused: boolean; +} +export interface FormItemProps extends RendererProps { + name?: string; + formStore?: IFormStore; + formInited: boolean; + formMode: 'normal' | 'horizontal' | 'inline' | 'row' | 'default'; + formHorizontal: FormHorizontal; + defaultSize?: 'xs' | 'sm' | 'md' | 'lg' | 'full'; + size?: 'xs' | 'sm' | 'md' | 'lg' | 'full'; + disabled: boolean; + btnDisabled: boolean; defaultValue: any; value: any; - onChange: (value: any, submitOnChange?: boolean) => void; - onBulkChange: (values: any, submitOnChange?: boolean) => void; - prinstine: any; setPrinstineValue: (value: any) => void; - formMode?: 'default' | 'inline' | 'horizontal' | 'row'; - formItem?: IFormItemStore; - strictMode?: boolean; + onChange: (value: any, submitOnChange?: boolean) => void; + onBulkChange: (values: {[propName: string]: any}, submitOnChange?: boolean) => void; + addHook: (fn: Function, mode?: 'validate' | 'init') => void; + removeHook: (fn: Function, mode?: 'validate' | 'init') => void; + renderFormItems: (schema: FormSchema, region: string, props: any) => JSX.Element; + onFocus: (e: any) => void; + onBlur: (e: any) => void; - renderControl?: (props: RendererProps) => JSX.Element; + formItemValue: any; // 不建议使用 为了兼容 v1 + getValue: () => any; // 不建议使用 为了兼容 v1 + setValue: (value: any, key: string) => void; // 不建议使用 为了兼容 v1 + + inputClassName?: string; + renderControl?: (props: FormControlProps) => JSX.Element; + + inputOnly?: boolean; renderLabel?: boolean; renderDescription?: boolean; sizeMutable?: boolean; @@ -55,23 +68,43 @@ export interface FormControlProps extends RendererProps { hint?: string; description?: string; descriptionClassName?: string; + // error 详情 + errors?: { + [propName: string]: string; + }; + // error string + error?: string; } -export interface FormControlState { - isFocused: boolean; -} +export type FormControlProps = RendererProps & + Exclude< + FormItemProps, + | 'inputClassName' + | 'renderControl' + | 'defaultSize' + | 'size' + | 'error' + | 'errors' + | 'hint' + | 'descriptionClassName' + | 'inputOnly' + | 'renderLabel' + | 'renderDescription' + | 'sizeMutable' + | 'wrap' + >; -export type FormItemComponent = React.ComponentType; +export type FormItemComponent = React.ComponentType; export type FormControlComponent = React.ComponentType; export interface FormItemConfig extends FormItemBasicConfig { component: FormControlComponent; } -export class FormItemWrap extends React.Component { +export class FormItemWrap extends React.Component { reaction: any; - constructor(props: FormControlProps) { + constructor(props: FormItemProps) { super(props); this.state = { @@ -603,7 +636,7 @@ export function registerFormItem(config: FormItemConfig): RendererConfig { ref: any; - constructor(props: FormControlProps) { + constructor(props: FormItemProps) { super(props); this.refFn = this.refFn.bind(this); } diff --git a/src/renderers/Form/Options.tsx b/src/renderers/Form/Options.tsx index ec55cba5..49e99afc 100644 --- a/src/renderers/Form/Options.tsx +++ b/src/renderers/Form/Options.tsx @@ -1,4 +1,4 @@ -import {Api} from '../../types'; +import {Api, Schema} from '../../types'; import {buildApi, isEffectiveApi, isValidApi, isApiOutdated} from '../../utils/api'; import {anyChanged} from '../../utils/helper'; import {reaction} from 'mobx'; @@ -30,15 +30,33 @@ export interface OptionsControlProps extends FormControlProps, OptionProps { setOptions: (value: Array) => void; setLoading: (value: boolean) => void; reloadOptions: () => void; + addable?: boolean; + onAdd?: () => void; + editable?: boolean; + onEdit?: (value: Option) => void; + removable?: boolean; + onDelete?: (value: Option) => void; +} + +export interface OptionsProps extends FormControlProps, OptionProps { + sourcce?: Api; + addApi?: Api; + addMode?: 'dialog' | 'normal'; + addDialog?: Schema; + editApi?: Api; + editMode?: 'dialog' | 'normal'; + editDialog?: Schema; + deleteApi?: Api; + deleteConfirmText?: string; } export function registerOptionsControl(config: OptionsConfig) { const Control = config.component; // @observer - class FormOptionsItem extends React.Component { + class FormOptionsItem extends React.Component { static displayName = `OptionsControl(${config.type})`; - static defaultProps: Partial = { + static defaultProps = { delimiter: ',', labelField: 'label', valueField: 'value', @@ -55,7 +73,7 @@ export function registerOptionsControl(config: OptionsConfig) { reaction: any; input: any; - constructor(props: FormControlProps) { + constructor(props: OptionsProps) { super(props); const formItem = props.formItem as IFormItemStore; @@ -114,7 +132,7 @@ export function registerOptionsControl(config: OptionsConfig) { this.normalizeValue(); } - shouldComponentUpdate(nextProps: FormControlProps) { + shouldComponentUpdate(nextProps: OptionsProps) { if (config.strictMode === false || nextProps.strictMode === false) { return true; } @@ -156,7 +174,7 @@ export function registerOptionsControl(config: OptionsConfig) { return false; } - componentWillReceiveProps(nextProps: OptionsControlProps) { + componentWillReceiveProps(nextProps: OptionsProps) { const props = this.props; const formItem = nextProps.formItem as IFormItemStore; diff --git a/src/renderers/Form/index.tsx b/src/renderers/Form/index.tsx index 1672a39e..ddf21d54 100644 --- a/src/renderers/Form/index.tsx +++ b/src/renderers/Form/index.tsx @@ -11,7 +11,7 @@ import {promisify, difference, until, noop, isObject, isVisible} from '../../uti import debouce = require('lodash/debounce'); import flatten = require('lodash/flatten'); import find = require('lodash/find'); -import Scoped, {ScopedContext, IScopedContext} from '../../Scoped'; +import Scoped, {ScopedContext, IScopedContext, ScopedComponentType} from '../../Scoped'; import {IComboStore} from '../../store/combo'; import qs = require('qs'); import {dataMapping} from '../../utils/tpl-builtin'; @@ -145,7 +145,7 @@ export default class Form extends React.Component { asyncCancel: () => void; disposeOnValidate: () => void; shouldLoadInitApi: boolean = false; - timer: number; + timer: NodeJS.Timeout; mounted: boolean; constructor(props: FormProps) { super(props); @@ -940,8 +940,8 @@ export class FormRenderer extends Form { if (this.props.autoFocus) { const scoped = this.context as IScopedContext; const inputs = scoped.getComponents(); - let focuableInput = find(inputs, (input: any) => input.focus); - focuableInput && setTimeout(() => focuableInput.focus(), 200); + let focuableInput = find(inputs, input => input.focus) as ScopedComponentType; + focuableInput && setTimeout(() => focuableInput.focus!(), 200); } } diff --git a/src/store/formItem.ts b/src/store/formItem.ts index 933b34e1..a4f58440 100644 --- a/src/store/formItem.ts +++ b/src/store/formItem.ts @@ -5,7 +5,7 @@ import {Api, Payload, fetchOptions} from '../types'; import {ComboStore, IComboStore, IUniqueGroup} from './combo'; import {evalExpression} from '../utils/tpl'; import findIndex = require('lodash/findIndex'); -import {isArrayChilrenModified, hasOwnProperty, isObject} from '../utils/helper'; +import {isArrayChilrenModified, hasOwnProperty, isObject, createObject} from '../utils/helper'; import {flattenTree} from '../utils/helper'; import {IRendererStore} from '.'; import {normalizeOptions} from '../components/Select'; @@ -50,7 +50,10 @@ export const FormItemStore = types options: types.optional(types.array(types.frozen()), []), expressionsInOptions: false, selectedOptions: types.optional(types.frozen(), []), - filteredOptions: types.optional(types.frozen(), []) + filteredOptions: types.optional(types.frozen(), []), + dialogSchema: types.frozen(), + dialogOpen: false, + dialogData: types.frozen(), }) .views(self => { function getForm(): any { @@ -532,6 +535,26 @@ export const FormItemStore = types clearError(); } + function openDialog(schema: any, ctx: any, additonal?: object) { + let proto = ctx.__super ? ctx.__super : self.form.data; + + if (additonal) { + proto = createObject(proto, additonal); + } + + const data = createObject(proto, { + ...ctx + }); + + self.dialogSchema = schema; + self.dialogData = data; + self.dialogOpen = true; + } + + function closeDialog() { + self.dialogOpen = false; + } + return { config, changeValue, @@ -544,7 +567,9 @@ export const FormItemStore = types syncOptions, setLoading, setSubStore, - reset + reset, + openDialog, + closeDialog }; });