Commit 9f5e2c7e4db975933ff6e481a183d51db396cdfc

Authored by 梁灏
1 parent 103cd353

update Form

update Form
src/components/form/form-item.vue
1 1 <template>
2   -
  2 + <div :class="classes">
  3 + <label :class="[prefixCls + '-label']" :style="labelStyles" v-if="label">{{ label }}</label>
  4 + <div :style="contentStyles">
  5 + <slot></slot>
  6 + <div transition="fade" :class="[prefixCls + '-error']" v-if="validateState === 'error'">{{ validateMessage }}</div>
  7 + </div>
  8 + </div>
3 9 </template>
4 10 <script>
  11 + // https://github.com/ElemeFE/element/blob/dev/packages/form/src/form-item.vue
  12 +
  13 + import AsyncValidator from 'async-validator';
  14 +
  15 + const prefixCls = 'ivu-form-item';
  16 +
  17 + function getPropByPath(obj, path) {
  18 + let tempObj = obj;
  19 + path = path.replace(/\[(\w+)\]/g, '.$1');
  20 + path = path.replace(/^\./, '');
  21 +
  22 + let keyArr = path.split('.');
  23 + let i = 0;
  24 +
  25 + for (let len = keyArr.length; i < len - 1; ++i) {
  26 + let key = keyArr[i];
  27 + if (key in tempObj) {
  28 + tempObj = tempObj[key];
  29 + } else {
  30 + throw new Error('[iView warn]: please transfer a valid prop path to form item!');
  31 + }
  32 + }
  33 + return {
  34 + o: tempObj,
  35 + k: keyArr[i],
  36 + v: tempObj[keyArr[i]]
  37 + };
  38 + }
  39 +
5 40 export default {
6   - props: {},
  41 + props: {
  42 + label: {
  43 + type: String,
  44 + default: ''
  45 + },
  46 + labelWidth: {
  47 + type: Number
  48 + },
  49 + prop: {
  50 + type: String
  51 + },
  52 + required: {
  53 + type: Boolean,
  54 + default: false
  55 + },
  56 + rules: {
  57 + type: [Object, Array]
  58 + },
  59 + error: {
  60 + type: Boolean
  61 + },
  62 + validateStatus: {
  63 + type: Boolean
  64 + }
  65 + },
7 66 data () {
8   - return {};
  67 + return {
  68 + prefixCls: prefixCls,
  69 + isRequired: false,
  70 + validateState: '',
  71 + validateMessage: '',
  72 + validateDisabled: false,
  73 + validator: {}
  74 + };
  75 + },
  76 + watch: {
  77 + error (val) {
  78 + this.validateMessage = val;
  79 + this.validateState = 'error';
  80 + },
  81 + validateStatus (val) {
  82 + this.validateState = val;
  83 + }
  84 + },
  85 + computed: {
  86 + classes () {
  87 + return [
  88 + `${prefixCls}`,
  89 + {
  90 + [`${prefixCls}-required`]: this.required || this.isRequired,
  91 + [`${prefixCls}-error`]: this.validateState === 'error',
  92 + [`${prefixCls}-validating`]: this.validateState === 'validating'
  93 + }
  94 + ];
  95 + },
  96 + form() {
  97 + let parent = this.$parent;
  98 + while (parent.$options.name !== 'iForm') {
  99 + parent = parent.$parent;
  100 + }
  101 + return parent;
  102 + },
  103 + fieldValue: {
  104 + cache: false,
  105 + get() {
  106 + const model = this.form.model;
  107 + if (!model || !this.prop) { return; }
  108 +
  109 + let path = this.prop;
  110 + if (path.indexOf(':') !== -1) {
  111 + path = path.replace(/:/, '.');
  112 + }
  113 +
  114 + return getPropByPath(model, path).v;
  115 + }
  116 + },
  117 + labelStyles () {
  118 + let style = {};
  119 + const labelWidth = this.labelWidth || this.form.labelWidth;
  120 + if (labelWidth) {
  121 + style.width = `${labelWidth}px`;
  122 + }
  123 + return style;
  124 + },
  125 + contentStyles () {
  126 + let style = {};
  127 + const labelWidth = this.labelWidth || this.form.labelWidth;
  128 + if (labelWidth) {
  129 + style.marginLeft = `${labelWidth}px`;
  130 + }
  131 + return style;
  132 + }
  133 + },
  134 + methods: {
  135 + getRules () {
  136 + let formRules = this.form.rules;
  137 + const selfRules = this.rules;
  138 +
  139 + formRules = formRules ? formRules[this.prop] : [];
  140 +
  141 + return [].concat(selfRules || formRules || []);
  142 + },
  143 + getFilteredRule (trigger) {
  144 + const rules = this.getRules();
  145 +
  146 + return rules.filter(rule => !rule.trigger || rule.trigger.indexOf(trigger) !== -1);
  147 + },
  148 + validate(trigger, callback = function () {}) {
  149 + const rules = this.getFilteredRule(trigger);
  150 + if (!rules || rules.length === 0) {
  151 + callback();
  152 + return true;
  153 + }
  154 +
  155 + this.validateState = 'validating';
  156 +
  157 + let descriptor = {};
  158 + descriptor[this.prop] = rules;
  159 +
  160 + const validator = new AsyncValidator(descriptor);
  161 + let model = {};
  162 +
  163 + model[this.prop] = this.fieldValue;
  164 +
  165 + validator.validate(model, { firstFields: true }, (errors, fields) => {
  166 + this.validateState = !errors ? 'success' : 'error';
  167 + this.validateMessage = errors ? errors[0].message : '';
  168 +
  169 + callback(this.validateMessage);
  170 + });
  171 + },
  172 + resetField () {
  173 + this.validateState = '';
  174 + this.validateMessage = '';
  175 +
  176 + let model = this.form.model;
  177 + let value = this.fieldValue;
  178 + let path = this.prop;
  179 + if (path.indexOf(':') !== -1) {
  180 + path = path.replace(/:/, '.');
  181 + }
  182 +
  183 + let prop = getPropByPath(model, path);
  184 +
  185 + if (Array.isArray(value) && value.length > 0) {
  186 + this.validateDisabled = true;
  187 + prop.o[prop.k] = [];
  188 + } else if (value) {
  189 + this.validateDisabled = true;
  190 + prop.o[prop.k] = this.initialValue;
  191 + }
  192 + },
  193 + onFieldBlur() {
  194 + this.validate('blur');
  195 + },
  196 + onFieldChange() {
  197 + if (this.validateDisabled) {
  198 + this.validateDisabled = false;
  199 + return;
  200 + }
  201 +
  202 + this.validate('change');
  203 + }
  204 + },
  205 + ready () {
  206 + if (this.prop) {
  207 + this.$dispatch('on-form-item-add', this);
  208 +
  209 + Object.defineProperty(this, 'initialValue', {
  210 + value: this.fieldValue
  211 + });
  212 +
  213 + let rules = this.getRules();
  214 +
  215 + if (rules.length) {
  216 + rules.every(rule => {
  217 + if (rule.required) {
  218 + this.isRequired = true;
  219 + return false;
  220 + }
  221 + });
  222 + // todo
  223 +// this.$on('el.form.blur', this.onFieldBlur);
  224 +// this.$on('el.form.change', this.onFieldChange);
  225 + }
  226 + }
9 227 },
10   - computed: {},
11   - methods: {}
  228 + beforeDestroy () {
  229 + this.$dispatch('on-form-item-remove', this);
  230 + }
12 231 };
13 232 </script>
14 233 \ No newline at end of file
... ...
src/components/form/form.vue
1 1 <template>
2   -
  2 + <form :class="classes"><slot></slot></form>
3 3 </template>
4 4 <script>
  5 + // https://github.com/ElemeFE/element/blob/dev/packages/form/src/form.vue
  6 +
  7 + const prefixCls = 'ivu-form';
  8 +
5 9 export default {
6   - props: {},
  10 + name: 'iForm',
  11 + props: {
  12 + model: {
  13 + type: Object
  14 + },
  15 + rules: {
  16 + type: Object
  17 + },
  18 + labelWidth: {
  19 + type: Number
  20 + },
  21 + inline: {
  22 + type: Boolean,
  23 + default: false
  24 + }
  25 + },
7 26 data () {
8   - return {};
  27 + return {
  28 + fields: []
  29 + };
  30 + },
  31 + computed: {
  32 + classes () {
  33 + return [
  34 + `${prefixCls}`,
  35 + {
  36 + [`${prefixCls}-inline`]: this.inline
  37 + }
  38 + ];
  39 + }
  40 + },
  41 + methods: {
  42 + resetFields() {
  43 + this.fields.forEach(field => {
  44 + field.resetField();
  45 + });
  46 + },
  47 + validate(callback) {
  48 + let valid = true;
  49 + let count = 0;
  50 + this.fields.forEach(field => {
  51 + field.validate('', errors => {
  52 + if (errors) {
  53 + valid = false;
  54 + }
  55 + if (typeof callback === 'function' && ++count === this.fields.length) {
  56 + callback(valid);
  57 + }
  58 + });
  59 + });
  60 + },
  61 + validateField(prop, cb) {
  62 + const field = this.fields.filter(field => field.prop === prop)[0];
  63 + if (!field) { throw new Error('[iView warn]: must call validateField with valid prop string!'); }
  64 +
  65 + field.validate('', cb);
  66 + }
  67 + },
  68 + watch: {
  69 + rules() {
  70 + this.validate();
  71 + }
9 72 },
10   - computed: {},
11   - methods: {}
  73 + events: {
  74 + 'on-form-item-add' (field) {
  75 + if (field) this.fields.push(field);
  76 + return false;
  77 + },
  78 + 'on-form-item-remove' (field) {
  79 + if (field.prop) this.fields.splice(this.fields.indexOf(field), 1);
  80 + return false;
  81 + }
  82 + }
12 83 };
13 84 </script>
14 85 \ No newline at end of file
... ...
test/app.vue
... ... @@ -46,6 +46,7 @@ li + li {
46 46 <li><a v-link="'/tabs'">Tabs</a></li>
47 47 <li><a v-link="'/menu'">Menu</a></li>
48 48 <li><a v-link="'/date'">Date</a></li>
  49 + <li><a v-link="'/form'">Form</a></li>
49 50 </ul>
50 51 </nav>
51 52 <router-view></router-view>
... ...
test/main.js
... ... @@ -127,7 +127,12 @@ router.map({
127 127 component: function (resolve) {
128 128 require(['./routers/date.vue'], resolve);
129 129 }
130   - }
  130 + },
  131 + '/form': {
  132 + component: function (resolve) {
  133 + require(['./routers/form.vue'], resolve);
  134 + }
  135 + },
131 136 });
132 137  
133 138 router.beforeEach(function () {
... ...
test/routers/form.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <i-form>
  4 +
  5 + </i-form>
  6 + </div>
  7 +</template>
  8 +<script>
  9 + export default {
  10 + props: {},
  11 + data () {
  12 + return {}
  13 + },
  14 + computed: {},
  15 + methods: {}
  16 + };
  17 +</script>
0 18 \ No newline at end of file
... ...