form-item.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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. rules(value) {
  95. if ((!value || value.length === 0) && this.required === undefined) {
  96. this.clearValidate();
  97. }
  98. }
  99. },
  100. computed: {
  101. labelFor() {
  102. return this.for || this.prop;
  103. },
  104. labelStyle() {
  105. const ret = {};
  106. if (this.form.labelPosition === 'top') return ret;
  107. const labelWidth = this.labelWidth || this.form.labelWidth;
  108. if (labelWidth) {
  109. ret.width = labelWidth;
  110. }
  111. return ret;
  112. },
  113. contentStyle() {
  114. const ret = {};
  115. const label = this.label;
  116. if (this.form.labelPosition === 'top' || this.form.inline) return ret;
  117. if (!label && !this.labelWidth && this.isNested) return ret;
  118. const labelWidth = this.labelWidth || this.form.labelWidth;
  119. if (labelWidth === 'auto') {
  120. if (this.labelWidth === 'auto') {
  121. ret.marginLeft = this.computedLabelWidth;
  122. } else if (this.form.labelWidth === 'auto') {
  123. ret.marginLeft = this.elForm.autoLabelWidth;
  124. }
  125. } else {
  126. ret.marginLeft = labelWidth;
  127. }
  128. return ret;
  129. },
  130. form() {
  131. let parent = this.$parent;
  132. let parentName = parent.$options.componentName;
  133. while (parentName !== 'ElForm') {
  134. if (parentName === 'ElFormItem') {
  135. this.isNested = true;
  136. }
  137. parent = parent.$parent;
  138. parentName = parent.$options.componentName;
  139. }
  140. return parent;
  141. },
  142. fieldValue() {
  143. const model = this.form.model;
  144. if (!model || !this.prop) { return; }
  145. let path = this.prop;
  146. if (path.indexOf(':') !== -1) {
  147. path = path.replace(/:/, '.');
  148. }
  149. return getPropByPath(model, path, true).v;
  150. },
  151. isRequired() {
  152. let rules = this.getRules();
  153. let isRequired = false;
  154. if (rules && rules.length) {
  155. rules.every(rule => {
  156. if (rule.required) {
  157. isRequired = true;
  158. return false;
  159. }
  160. return true;
  161. });
  162. }
  163. return isRequired;
  164. },
  165. _formSize() {
  166. return this.elForm.size;
  167. },
  168. elFormItemSize() {
  169. return this.size || this._formSize;
  170. },
  171. sizeClass() {
  172. return this.elFormItemSize || (this.$ELEMENT || {}).size;
  173. }
  174. },
  175. data() {
  176. return {
  177. validateState: '',
  178. validateMessage: '',
  179. validateDisabled: false,
  180. validator: {},
  181. isNested: false,
  182. computedLabelWidth: ''
  183. };
  184. },
  185. methods: {
  186. validate(trigger, callback = noop) {
  187. this.validateDisabled = false;
  188. const rules = this.getFilteredRule(trigger);
  189. if ((!rules || rules.length === 0) && this.required === undefined) {
  190. callback();
  191. return true;
  192. }
  193. this.validateState = 'validating';
  194. const descriptor = {};
  195. if (rules && rules.length > 0) {
  196. rules.forEach(rule => {
  197. delete rule.trigger;
  198. });
  199. }
  200. descriptor[this.prop] = rules;
  201. const validator = new AsyncValidator(descriptor);
  202. const model = {};
  203. model[this.prop] = this.fieldValue;
  204. validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
  205. this.validateState = !errors ? 'success' : 'error';
  206. this.validateMessage = errors ? errors[0].message : '';
  207. callback(this.validateMessage, invalidFields);
  208. this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
  209. });
  210. },
  211. clearValidate() {
  212. this.validateState = '';
  213. this.validateMessage = '';
  214. this.validateDisabled = false;
  215. },
  216. resetField() {
  217. this.validateState = '';
  218. this.validateMessage = '';
  219. let model = this.form.model;
  220. let value = this.fieldValue;
  221. let path = this.prop;
  222. if (path.indexOf(':') !== -1) {
  223. path = path.replace(/:/, '.');
  224. }
  225. let prop = getPropByPath(model, path, true);
  226. this.validateDisabled = true;
  227. if (Array.isArray(value)) {
  228. prop.o[prop.k] = [].concat(this.initialValue);
  229. } else {
  230. prop.o[prop.k] = this.initialValue;
  231. }
  232. // reset validateDisabled after onFieldChange triggered
  233. this.$nextTick(() => {
  234. this.validateDisabled = false;
  235. });
  236. this.broadcast('ElTimeSelect', 'fieldReset', this.initialValue);
  237. },
  238. getRules() {
  239. let formRules = this.form.rules;
  240. const selfRules = this.rules;
  241. const requiredRule = this.required !== undefined ? { required: !!this.required } : [];
  242. const prop = getPropByPath(formRules, this.prop || '');
  243. formRules = formRules ? (prop.o[this.prop || ''] || prop.v) : [];
  244. return [].concat(selfRules || formRules || []).concat(requiredRule);
  245. },
  246. getFilteredRule(trigger) {
  247. const rules = this.getRules();
  248. return rules.filter(rule => {
  249. if (!rule.trigger || trigger === '') return true;
  250. if (Array.isArray(rule.trigger)) {
  251. return rule.trigger.indexOf(trigger) > -1;
  252. } else {
  253. return rule.trigger === trigger;
  254. }
  255. }).map(rule => objectAssign({}, rule));
  256. },
  257. onFieldBlur() {
  258. this.validate('blur');
  259. },
  260. onFieldChange() {
  261. if (this.validateDisabled) {
  262. this.validateDisabled = false;
  263. return;
  264. }
  265. this.validate('change');
  266. },
  267. updateComputedLabelWidth(width) {
  268. this.computedLabelWidth = width ? `${width}px` : '';
  269. },
  270. addValidateEvents() {
  271. const rules = this.getRules();
  272. if (rules.length || this.required !== undefined) {
  273. this.$on('el.form.blur', this.onFieldBlur);
  274. this.$on('el.form.change', this.onFieldChange);
  275. }
  276. },
  277. removeValidateEvents() {
  278. this.$off();
  279. }
  280. },
  281. mounted() {
  282. if (this.prop) {
  283. this.dispatch('ElForm', 'el.form.addField', [this]);
  284. let initialValue = this.fieldValue;
  285. if (Array.isArray(initialValue)) {
  286. initialValue = [].concat(initialValue);
  287. }
  288. Object.defineProperty(this, 'initialValue', {
  289. value: initialValue
  290. });
  291. this.addValidateEvents();
  292. }
  293. },
  294. beforeDestroy() {
  295. this.dispatch('ElForm', 'el.form.removeField', [this]);
  296. }
  297. };
  298. </script>