@form-create/core
Version:
FormCreate低代码表单渲染引擎,可以通过 JSON 生成具有动态渲染、数据收集、验证和提交功能的低代码表单。支持6个UI框架,适配移动端,并且支持生成任何 Vue 组件。
358 lines (328 loc) • 13.3 kB
JavaScript
import extend from '@form-create/utils/lib/extend';
import {byCtx, copyRule, enumerable, getRule, invoke, parseFn, condition} from '../frame/util';
import is, {hasProperty} from '@form-create/utils/lib/type';
import {baseRule} from '../factory/creator';
import RuleContext from '../factory/context';
import mergeProps from '@form-create/utils/lib/mergeprops';
import {$set} from '@form-create/utils';
export default function useLoader(Handler) {
extend(Handler.prototype, {
nextRefresh(fn) {
const id = this.loadedId;
this.vm.$nextTick(() => {
id === this.loadedId && (fn ? fn() : this.refresh());
});
},
parseRule(_rule) {
const rule = getRule(_rule);
Object.defineProperties(rule, {
__origin__: enumerable(_rule, true)
});
fullRule(rule);
this.appendValue(rule);
rule.options = Array.isArray(rule.options) ? rule.options : [];
[rule, rule['prefix'], rule['suffix']].forEach(item => {
if (!item) {
return;
}
this.loadFn(item, rule);
});
this.loadCtrl(rule);
if (rule.update) {
rule.update = parseFn(rule.update);
}
return rule;
},
loadFn(item, rule) {
['on', 'props', 'nativeOn', 'deep'].forEach(k => {
item[k] && this.parseInjectEvent(rule, item[k]);
});
},
loadCtrl(rule) {
rule.control && rule.control.forEach(ctrl => {
if (ctrl.handle) {
ctrl.handle = parseFn(ctrl.handle)
}
})
},
syncProp(ctx) {
const rule = ctx.rule;
is.trueArray(rule.sync) && mergeProps([{
on: rule.sync.reduce((pre, prop) => {
pre[`update:${prop}`] = (val) => {
rule.props[prop] = val;
this.vm.$emit('sync', prop, val, rule, this.fapi);
}
return pre
}, {})
}], ctx.computed)
},
loadRule() {
// console.warn('%c load', 'color:blue');
this.cycleLoad = false;
this.loading = true;
if (this.pageEnd) {
this.bus.$emit('load-start');
}
this.deferSyncValue(() => {
this._loadRule(this.rules);
this.loading = false;
if (this.cycleLoad && this.pageEnd) {
return this.loadRule();
}
if (this.pageEnd) {
this.bus.$emit('load-end');
}
this.vm._renderRule();
this.$render.initOrgChildren();
this.syncForm();
});
},
loadChildren(children, parent) {
this.cycleLoad = false;
this.loading = true;
this.bus.$emit('load-start');
this._loadRule(children, parent);
this.loading = false;
if (this.cycleLoad) {
return this.loadRule();
} else {
this.bus.$emit('load-end');
this.syncForm();
}
this.$render.clearCache(parent);
},
_loadRule(rules, parent) {
const preIndex = (i) => {
let pre = rules[i - 1];
if (!pre || !pre.__fc__) {
return i > 0 ? preIndex(i - 1) : -1;
}
let index = this.sort.indexOf(pre.__fc__.id);
return index > -1 ? index : preIndex(i - 1);
}
const loadChildren = (children, parent) => {
if (is.trueArray(children)) {
this._loadRule(children, parent);
}
};
rules.map((_rule, index) => {
if (parent && !is.Object(_rule)) return;
if (!this.pageEnd && !parent && index >= this.first) return;
if (_rule.__fc__ && _rule.__fc__.root === rules && this.ctxs[_rule.__fc__.id]) {
loadChildren(_rule.__fc__.rule.children, _rule.__fc__);
return _rule.__fc__;
}
let rule = getRule(_rule);
const isRepeat = () => {
return !!(rule.field && this.fieldCtx[rule.field] && this.fieldCtx[rule.field][0] !== _rule.__fc__)
}
this.ruleEffect(rule, 'init', {repeat: isRepeat()});
if (isRepeat()) {
this.vm.$emit('repeat-field', _rule, this.api);
}
let ctx;
let isCopy = false;
let isInit = !!_rule.__fc__;
let defaultValue = rule.value;
if (isInit) {
ctx = _rule.__fc__;
defaultValue = ctx.defaultValue;
const check = !ctx.check(this);
if (ctx.deleted) {
if (check) {
if (isCtrl(ctx)) {
return;
}
ctx.update(this);
}
} else {
if (check) {
if (isCtrl(ctx)) {
return;
}
rules[index] = _rule = _rule._clone ? _rule._clone() : copyRule(_rule);
ctx = null;
isCopy = true;
}
}
}
if (!ctx) {
const rule = this.parseRule(_rule);
ctx = new RuleContext(this, rule, defaultValue);
this.bindParser(ctx);
} else {
if (ctx.originType !== ctx.rule.type) {
ctx.updateType();
}
this.bindParser(ctx);
this.appendValue(ctx.rule);
}
[false, true].forEach(b => this.parseEmit(ctx, b));
this.syncProp(ctx);
ctx.parent = parent || null;
ctx.root = rules;
this.setCtx(ctx);
!isCopy && !isInit && this.effect(ctx, 'load');
this.effect(ctx, 'created');
ctx.parser.loadChildren === false || loadChildren(ctx.rule.children, ctx);
if (!parent) {
const _preIndex = preIndex(index);
if (_preIndex > -1 || !index) {
this.sort.splice(_preIndex + 1, 0, ctx.id);
} else {
this.sort.push(ctx.id);
}
}
const r = ctx.rule;
if (!ctx.updated) {
ctx.updated = true;
if (is.Function(r.update)) {
this.bus.$once('load-end', () => {
this.refreshUpdate(ctx, r.value, 'init');
});
}
this.effect(ctx, 'loaded');
}
if (ctx.input)
Object.defineProperty(r, 'value', this.valueHandle(ctx));
if (this.refreshControl(ctx)) this.cycleLoad = true;
return ctx;
});
},
refreshControl(ctx) {
return ctx.input && ctx.rule.control && this.useCtrl(ctx);
},
useCtrl(ctx) {
const controls = getCtrl(ctx), validate = [], api = this.api;
if (!controls.length) return false;
for (let i = 0; i < controls.length; i++) {
const control = controls[i], handleFn = control.handle || function (val) {
return ((condition[control.condition || '=='] || condition['=='])(val, control.value));
};
if (!is.trueArray(control.rule)) continue;
const data = {
...control,
valid: invoke(() => handleFn(ctx.rule.value, api)),
ctrl: findCtrl(ctx, control.rule),
isHidden: is.String(control.rule[0]),
};
if ((data.valid && data.ctrl) || (!data.valid && !data.ctrl && !data.isHidden)) continue;
validate.push(data);
}
if (!validate.length) return false;
const hideLst = [];
let flag = false;
this.deferSyncValue(() => {
validate.reverse().forEach(({isHidden, valid, rule, prepend, append, child, ctrl, method}) => {
if (isHidden) {
valid ? ctx.ctrlRule.push({
__ctrl: true,
children: rule,
valid
})
: (ctrl && ctx.ctrlRule.splice(ctx.ctrlRule.indexOf(ctrl) >>> 0, 1));
hideLst[valid ? 'push' : 'unshift'](() => {
if (method === 'disabled' || method === 'enabled') {
this.api.disabled(!valid, rule);
} else if (method === 'display') {
this.api.display(valid, rule);
} else if (method === 'required') {
rule.forEach(item => {
this.api.setEffect(item, 'required', valid);
})
if(!valid){
this.api.clearValidateState(rule);
}
} else {
this.api.hidden(!valid, rule);
}
});
return;
}
if (valid) {
flag = true;
const ruleCon = {
type: 'fcFragment',
native: true,
__ctrl: true,
children: rule,
}
ctx.ctrlRule.push(ruleCon);
this.bus.$once('load-start', () => {
// this.cycleLoad = true;
if (prepend) {
api.prepend(ruleCon, prepend, child)
} else if (append || child) {
api.append(ruleCon, append || ctx.id, child)
} else {
ctx.root.splice(ctx.root.indexOf(ctx.origin) + 1, 0, ruleCon);
}
});
} else {
ctx.ctrlRule.splice(ctx.ctrlRule.indexOf(ctrl), 1);
const ctrlCtx = byCtx(ctrl);
ctrlCtx && ctrlCtx.rm();
}
});
});
hideLst.length && this.vm.$nextTick(() => {
hideLst.forEach(v => v());
});
this.vm.$emit('control', ctx.origin, this.api);
this.effect(ctx, 'control');
return flag;
},
reloadRule(rules) {
return this._reloadRule(rules);
},
_reloadRule(rules) {
// console.warn('%c reload', 'color:red');
if (!rules) rules = this.rules;
const ctxs = {...this.ctxs};
this.clearNextTick();
this.$render.clearOrgChildren();
this.initData(rules);
this.fc.rules = rules;
this.deferSyncValue(() => {
this.bus.$once('load-end', () => {
Object.keys(ctxs).filter(id => this.ctxs[id] === undefined)
.forEach(id => this.rmCtx(ctxs[id]));
this.$render.clearCacheAll();
});
this.reloading = true;
this.loadRule();
this.reloading = false;
this.refresh();
this.vm.$emit('reloading', this.api);
});
this.vm.$emit('update', this.api);
},
//todo 组件生成全部通过 alias
refresh() {
this.vm._refresh();
},
});
}
function fullRule(rule) {
const def = baseRule();
Object.keys(def).forEach(k => {
if (!hasProperty(rule, k)) $set(rule, k, def[k]);
});
return rule;
}
function getCtrl(ctx) {
const control = ctx.rule.control || [];
if (is.Object(control)) return [control];
else return control;
}
function findCtrl(ctx, rule) {
for (let i = 0; i < ctx.ctrlRule.length; i++) {
const ctrl = ctx.ctrlRule[i];
if (ctrl.children === rule)
return ctrl;
}
}
function isCtrl(ctx) {
return !!ctx.rule.__ctrl;
}