form-item.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <template>
  2. <div class="el-form-item" :class="[{
  3. 'el-form-item--feedback': elForm && elForm.statusIcon,
  4. 'is-error': validateState === 'error',
  5. 'is-validating': validateState === 'validating',
  6. 'is-success': validateState === 'success',
  7. 'is-required': isRequired || required,
  8. 'is-no-asterisk': elForm && elForm.hideRequiredAsterisk
  9. },
  10. sizeClass ? 'el-form-item--' + sizeClass : ''
  11. ]">
  12. <label-wrap
  13. :is-auto-width="labelStyle && labelStyle.width === 'auto'"
  14. :update-all="form.labelWidth === 'auto'">
  15. <label :for="labelFor" class="el-form-item__label" :style="labelStyle" v-if="label || $slots.label">
  16. <slot name="label">{{label + form.labelSuffix}}</slot>
  17. </label>
  18. </label-wrap>
  19. <div class="el-form-item__content" :style="contentStyle">
  20. <slot></slot>
  21. <transition name="el-zoom-in-top">
  22. <slot
  23. v-if="validateState === 'error' && showMessage && form.showMessage"
  24. name="error"
  25. :error="validateMessage">
  26. <div
  27. class="el-form-item__error"
  28. :class="{
  29. 'el-form-item__error--inline': typeof inlineMessage === 'boolean'
  30. ? inlineMessage
  31. : (elForm && elForm.inlineMessage || false)
  32. }"
  33. >
  34. {{validateMessage}}
  35. </div>
  36. </slot>
  37. </transition>
  38. </div>
  39. </div>
  40. </template>
  41. <script>
  42. import AsyncValidator from 'async-validator';
  43. import emitter from 'element-ui/src/mixins/emitter';
  44. import objectAssign from 'element-ui/src/utils/merge';
  45. import { noop, getPropByPath } from 'element-ui/src/utils/util';
  46. import LabelWrap from './label-wrap';
  47. export default {
  48. name: 'ElFormItem',
  49. componentName: 'ElFormItem',
  50. mixins: [emitter],
  51. provide() {
  52. return {
  53. elFormItem: this
  54. };
  55. },
  56. inject: ['elForm'],
  57. props: {
  58. label: String,
  59. labelWidth: String,
  60. prop: String,
  61. required: {
  62. type: Boolean,
  63. default: undefined
  64. },
  65. rules: [Object, Array],
  66. error: String,
  67. validateStatus: String,
  68. for: String,
  69. inlineMessage: {
  70. type: [String, Boolean],
  71. default: ''
  72. },
  73. showMessage: {
  74. type: Boolean,
  75. default: true
  76. },
  77. size: String
  78. },
  79. components: {
  80. // use this component to calculate auto width
  81. LabelWrap
  82. },
  83. watch: {
  84. error: {
  85. immediate: true,
  86. handler(value) {
  87. this.validateMessage = value;
  88. this.validateState = value ? 'error' : '';
  89. }
  90. },
  91. validateStatus(value) {
  92. this.validateState = value;
  93. }
  94. },
  95. computed: {
  96. labelFor() {
  97. return this.for || this.prop;
  98. },
  99. labelStyle() {
  100. const ret = {};
  101. if (this.form.labelPosition === 'top') return ret;
  102. const labelWidth = this.labelWidth || this.form.labelWidth;
  103. if (labelWidth) {
  104. ret.width = labelWidth;
  105. }
  106. return ret;
  107. },
  108. contentStyle() {
  109. const ret = {};
  110. const label = this.label;
  111. if (this.form.labelPosition === 'top' || this.form.inline) return ret;
  112. if (!label && !this.labelWidth && this.isNested) return ret;
  113. const labelWidth = this.labelWidth || this.form.labelWidth;
  114. if (labelWidth === 'auto') {
  115. if (this.labelWidth === 'auto') {
  116. ret.marginLeft = this.computedLabelWidth;
  117. } else if (this.form.labelWidth === 'auto') {
  118. ret.marginLeft = this.elForm.autoLabelWidth;
  119. }
  120. } else {
  121. ret.marginLeft = labelWidth;
  122. }
  123. return ret;
  124. },
  125. form() {
  126. let parent = this.$parent;
  127. let parentName = parent.$options.componentName;
  128. while (parentName !== 'ElForm') {
  129. if (parentName === 'ElFormItem') {
  130. this.isNested = true;
  131. }
  132. parent = parent.$parent;
  133. parentName = parent.$options.componentName;
  134. }
  135. return parent;
  136. },
  137. fieldValue() {
  138. const model = this.form.model;
  139. if (!model || !this.prop) { return; }
  140. let path = this.prop;
  141. if (path.indexOf(':') !== -1) {
  142. path = path.replace(/:/, '.');
  143. }
  144. return getPropByPath(model, path, true).v;
  145. },
  146. isRequired() {
  147. let rules = this.getRules();
  148. let isRequired = false;
  149. if (rules && rules.length) {
  150. rules.every(rule => {
  151. if (rule.required) {
  152. isRequired = true;
  153. return false;
  154. }
  155. return true;
  156. });
  157. }
  158. return isRequired;
  159. },
  160. _formSize() {
  161. return this.elForm.size;
  162. },
  163. elFormItemSize() {
  164. return this.size || this._formSize;
  165. },
  166. sizeClass() {
  167. return this.elFormItemSize || (this.$ELEMENT || {}).size;
  168. }
  169. },
  170. data() {
  171. return {
  172. validateState: '',
  173. validateMessage: '',
  174. validateDisabled: false,
  175. validator: {},
  176. isNested: false,
  177. computedLabelWidth: ''
  178. };
  179. },
  180. methods: {
  181. validate(trigger, callback = noop) {
  182. this.validateDisabled = false;
  183. const rules = this.getFilteredRule(trigger);
  184. if ((!rules || rules.length === 0) && this.required === undefined) {
  185. callback();
  186. return true;
  187. }
  188. this.validateState = 'validating';
  189. const descriptor = {};
  190. if (rules && rules.length > 0) {
  191. rules.forEach(rule => {
  192. delete rule.trigger;
  193. });
  194. }
  195. descriptor[this.prop] = rules;
  196. const validator = new AsyncValidator(descriptor);
  197. const model = {};
  198. model[this.prop] = this.fieldValue;
  199. validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
  200. this.validateState = !errors ? 'success' : 'error';
  201. this.validateMessage = errors ? errors[0].message : '';
  202. callback(this.validateMessage, invalidFields);
  203. this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
  204. });
  205. },
  206. clearValidate() {
  207. this.validateState = '';
  208. this.validateMessage = '';
  209. this.validateDisabled = false;
  210. },
  211. resetField() {
  212. this.validateState = '';
  213. this.validateMessage = '';
  214. let model = this.form.model;
  215. let value = this.fieldValue;
  216. let path = this.prop;
  217. if (path.indexOf(':') !== -1) {
  218. path = path.replace(/:/, '.');
  219. }
  220. let prop = getPropByPath(model, path, true);
  221. this.validateDisabled = true;
  222. if (Array.isArray(value)) {
  223. prop.o[prop.k] = [].concat(this.initialValue);
  224. } else {
  225. prop.o[prop.k] = this.initialValue;
  226. }
  227. // reset validateDisabled after onFieldChange triggered
  228. this.$nextTick(() => {
  229. this.validateDisabled = false;
  230. });
  231. this.broadcast('ElTimeSelect', 'fieldReset', this.initialValue);
  232. },
  233. getRules() {
  234. let formRules = this.form.rules;
  235. const selfRules = this.rules;
  236. const requiredRule = this.required !== undefined ? { required: !!this.required } : [];
  237. const prop = getPropByPath(formRules, this.prop || '');
  238. formRules = formRules ? (prop.o[this.prop || ''] || prop.v) : [];
  239. return [].concat(selfRules || formRules || []).concat(requiredRule);
  240. },
  241. getFilteredRule(trigger) {
  242. const rules = this.getRules();
  243. return rules.filter(rule => {
  244. if (!rule.trigger || trigger === '') return true;
  245. if (Array.isArray(rule.trigger)) {
  246. return rule.trigger.indexOf(trigger) > -1;
  247. } else {
  248. return rule.trigger === trigger;
  249. }
  250. }).map(rule => objectAssign({}, rule));
  251. },
  252. onFieldBlur() {
  253. this.validate('blur');
  254. },
  255. onFieldChange() {
  256. if (this.validateDisabled) {
  257. this.validateDisabled = false;
  258. return;
  259. }
  260. this.validate('change');
  261. },
  262. updateComputedLabelWidth(width) {
  263. this.computedLabelWidth = width ? `${width}px` : '';
  264. },
  265. addValidateEvents() {
  266. const rules = this.getRules();
  267. if (rules.length || this.required !== undefined) {
  268. this.$on('el.form.blur', this.onFieldBlur);
  269. this.$on('el.form.change', this.onFieldChange);
  270. }
  271. },
  272. removeValidateEvents() {
  273. this.$off();
  274. }
  275. },
  276. mounted() {
  277. if (this.prop) {
  278. this.dispatch('ElForm', 'el.form.addField', [this]);
  279. let initialValue = this.fieldValue;
  280. if (Array.isArray(initialValue)) {
  281. initialValue = [].concat(initialValue);
  282. }
  283. Object.defineProperty(this, 'initialValue', {
  284. value: initialValue
  285. });
  286. this.addValidateEvents();
  287. }
  288. },
  289. beforeDestroy() {
  290. this.dispatch('ElForm', 'el.form.removeField', [this]);
  291. }
  292. };
  293. </script>