Button按钮--inject与provide
inject 和 provider 是vue中的组合选项,需要一起使用。目的是允许一个祖先组件向其所有子孙后代注入依赖(简单地说就是祖先组件向子孙后代传值的一种方法,祖先组件通过provider提供变量,子孙后代通过inject注入接收变量)
provider: Object || () => Object
inject: Array || Object
Eg.
button.vue:
1 <template> 2 <button 3 class="el-button" 4 @click="handleClick" 5 :disabled="buttonDisabled || loading" 6 :autofocus="autofocus" 7 :type="nativeType" 8 :class="[ 9 type ? 'el-button--' + type : '', 10 buttonSize ? 'el-button--' + buttonSize : '', 11 { 12 'is-disabled': buttonDisabled, 13 'is-loading': loading, 14 'is-plain': plain, 15 'is-round': round, 16 'is-circle': circle 17 } 18 ]" 19 > 20 <i class="el-icon-loading" v-if="loading"></i> 21 <i :class="icon" v-if="icon && !loading"></i> 22 <span v-if="$slots.default"><slot></slot></span> 23 </button> 24 </template> 25 <script> 26 export default { 27 name: 'ElButton', 28 29 // 通过inject向button中注入变量 30 inject: { 31 elForm: { 32 default: '' 33 }, 34 elFormItem: { 35 default: '' 36 } 37 }, 38 39 props: { 40 type: { 41 type: String, 42 default: 'default' 43 }, 44 size: String, 45 icon: { 46 type: String, 47 default: '' 48 }, 49 nativeType: { 50 type: String, 51 default: 'button' 52 }, 53 loading: Boolean, 54 disabled: Boolean, 55 plain: Boolean, 56 autofocus: Boolean, 57 round: Boolean, 58 circle: Boolean 59 }, 60 61 computed: { 62 _elFormItemSize() { 63 return (this.elFormItem || {}).elFormItemSize; 64 }, 65 buttonSize() { 66 return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size; 67 }, 68 buttonDisabled() { 69 return this.disabled || (this.elForm || {}).disabled; // 通过inject注入的form变量,获得祖先节点form的disabled属性 70 } 71 }, 72 73 methods: { 74 handleClick(evt) { 75 this.$emit('click', evt); 76 } 77 } 78 }; 79 </script>
form.vue:
1 <template> 2 <form class="el-form" :class="[ 3 labelPosition ? 'el-form--label-' + labelPosition : '', 4 { 'el-form--inline': inline } 5 ]"> 6 <slot></slot> 7 </form> 8 </template> 9 <script> 10 import objectAssign from 'element-ui/src/utils/merge'; 11 12 export default { 13 name: 'ElForm', 14 15 componentName: 'ElForm', 16 17 // 通过provider向子孙后代注入变量elform,讲this(即form)注入给子孙后代,后代通过获取此变量获取form中的各种配置,如disabled属性等 18 provide() { 19 return { 20 elForm: this 21 }; 22 }, 23 24 props: { 25 model: Object, 26 rules: Object, 27 labelPosition: String, 28 labelWidth: String, 29 labelSuffix: { 30 type: String, 31 default: '' 32 }, 33 inline: Boolean, 34 inlineMessage: Boolean, 35 statusIcon: Boolean, 36 showMessage: { 37 type: Boolean, 38 default: true 39 }, 40 size: String, 41 disabled: Boolean, 42 validateOnRuleChange: { 43 type: Boolean, 44 default: true 45 }, 46 hideRequiredAsterisk: { 47 type: Boolean, 48 default: false 49 } 50 }, 51 watch: { 52 rules() { 53 if (this.validateOnRuleChange) { 54 this.validate(() => {}); 55 } 56 } 57 }, 58 data() { 59 return { 60 fields: [] 61 }; 62 }, 63 created() { 64 this.$on('el.form.addField', (field) => { 65 if (field) { 66 this.fields.push(field); 67 } 68 }); 69 /* istanbul ignore next */ 70 this.$on('el.form.removeField', (field) => { 71 if (field.prop) { 72 this.fields.splice(this.fields.indexOf(field), 1); 73 } 74 }); 75 }, 76 methods: { 77 resetFields() { 78 if (!this.model) { 79 process.env.NODE_ENV !== 'production' && 80 console.warn('[Element Warn][Form]model is required for resetFields to work.'); 81 return; 82 } 83 this.fields.forEach(field => { 84 field.resetField(); 85 }); 86 }, 87 clearValidate(props = []) { 88 const fields = props.length 89 ? this.fields.filter(field => props.indexOf(field.prop) > -1) 90 : this.fields; 91 fields.forEach(field => { 92 field.clearValidate(); 93 }); 94 }, 95 validate(callback) { 96 if (!this.model) { 97 console.warn('[Element Warn][Form]model is required for validate to work!'); 98 return; 99 } 100 101 let promise; 102 // if no callback, return promise 103 if (typeof callback !== 'function' && window.Promise) { 104 promise = new window.Promise((resolve, reject) => { 105 callback = function(valid) { 106 valid ? resolve(valid) : reject(valid); 107 }; 108 }); 109 } 110 111 let valid = true; 112 let count = 0; 113 // 如果需要验证的fields为空,调用验证时立刻返回callback 114 if (this.fields.length === 0 && callback) { 115 callback(true); 116 } 117 let invalidFields = {}; 118 this.fields.forEach(field => { 119 field.validate('', (message, field) => { 120 if (message) { 121 valid = false; 122 } 123 invalidFields = objectAssign({}, invalidFields, field); 124 if (typeof callback === 'function' && ++count === this.fields.length) { 125 callback(valid, invalidFields); 126 } 127 }); 128 }); 129 130 if (promise) { 131 return promise; 132 } 133 }, 134 validateField(prop, cb) { 135 let field = this.fields.filter(field => field.prop === prop)[0]; 136 if (!field) { throw new Error('must call validateField with valid prop string!'); } 137 138 field.validate('', cb); 139 } 140 } 141 }; 142 </script>
form-item.vue
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 :for="labelFor" class="el-form-item__label" :style="labelStyle" v-if="label || $slots.label"> 13 <slot name="label">{{label + form.labelSuffix}}</slot> 14 </label> 15 <div class="el-form-item__content" :style="contentStyle"> 16 <slot></slot> 17 <transition name="el-zoom-in-top"> 18 <slot 19 v-if="validateState === 'error' && showMessage && form.showMessage" 20 name="error" 21 :error="validateMessage"> 22 <div 23 class="el-form-item__error" 24 :class="{ 25 'el-form-item__error--inline': typeof inlineMessage === 'boolean' 26 ? inlineMessage 27 : (elForm && elForm.inlineMessage || false) 28 }" 29 > 30 {{validateMessage}} 31 </div> 32 </slot> 33 </transition> 34 </div> 35 </div> 36 </template> 37 <script> 38 import AsyncValidator from 'async-validator'; 39 import emitter from 'element-ui/src/mixins/emitter'; 40 import objectAssign from 'element-ui/src/utils/merge'; 41 import { noop, getPropByPath } from 'element-ui/src/utils/util'; 42 43 export default { 44 name: 'ElFormItem', 45 46 componentName: 'ElFormItem', 47 48 mixins: [emitter], 49 50 // 通过provider向子孙后代注入变量elform,讲this(即form-item)注入给子孙后代,后代通过获取此变量获取form中的各种配置,如size属性等 51 provide() { 52 return { 53 elFormItem: this 54 }; 55 }, 56 57 inject: ['elForm'], 58 59 props: { 60 label: String, 61 labelWidth: String, 62 prop: String, 63 required: { 64 type: Boolean, 65 default: undefined 66 }, 67 rules: [Object, Array], 68 error: String, 69 validateStatus: String, 70 for: String, 71 inlineMessage: { 72 type: [String, Boolean], 73 default: '' 74 }, 75 showMessage: { 76 type: Boolean, 77 default: true 78 }, 79 size: String 80 }, 81 watch: { 82 error: { 83 immediate: true, 84 handler(value) { 85 this.validateMessage = value; 86 this.validateState = value ? 'error' : ''; 87 } 88 }, 89 validateStatus(value) { 90 this.validateState = value; 91 } 92 }, 93 computed: { 94 labelFor() { 95 return this.for || this.prop; 96 }, 97 labelStyle() { 98 const ret = {}; 99 if (this.form.labelPosition === 'top') return ret; 100 const labelWidth = this.labelWidth || this.form.labelWidth; 101 if (labelWidth) { 102 ret.width = labelWidth; 103 } 104 return ret; 105 }, 106 contentStyle() { 107 const ret = {}; 108 const label = this.label; 109 if (this.form.labelPosition === 'top' || this.form.inline) return ret; 110 if (!label && !this.labelWidth && this.isNested) return ret; 111 const labelWidth = this.labelWidth || this.form.labelWidth; 112 if (labelWidth) { 113 ret.marginLeft = labelWidth; 114 } 115 return ret; 116 }, 117 form() { 118 let parent = this.$parent; 119 let parentName = parent.$options.componentName; 120 while (parentName !== 'ElForm') { 121 if (parentName === 'ElFormItem') { 122 this.isNested = true; 123 } 124 parent = parent.$parent; 125 parentName = parent.$options.componentName; 126 } 127 return parent; 128 }, 129 fieldValue() { 130 const model = this.form.model; 131 if (!model || !this.prop) { return; } 132 133 let path = this.prop; 134 if (path.indexOf(':') !== -1) { 135 path = path.replace(/:/, '.'); 136 } 137 138 return getPropByPath(model, path, true).v; 139 }, 140 isRequired() { 141 let rules = this.getRules(); 142 let isRequired = false; 143 144 if (rules && rules.length) { 145 rules.every(rule => { 146 if (rule.required) { 147 isRequired = true; 148 return false; 149 } 150 return true; 151 }); 152 } 153 return isRequired; 154 }, 155 _formSize() { 156 return this.elForm.size; 157 }, 158 elFormItemSize() { 159 return this.size || this._formSize; 160 }, 161 sizeClass() { 162 return this.elFormItemSize || (this.$ELEMENT || {}).size; 163 } 164 }, 165 data() { 166 return { 167 validateState: '', 168 validateMessage: '', 169 validateDisabled: false, 170 validator: {}, 171 isNested: false 172 }; 173 }, 174 methods: { 175 validate(trigger, callback = noop) { 176 this.validateDisabled = false; 177 const rules = this.getFilteredRule(trigger); 178 if ((!rules || rules.length === 0) && this.required === undefined) { 179 callback(); 180 return true; 181 } 182 183 this.validateState = 'validating'; 184 185 const descriptor = {}; 186 if (rules && rules.length > 0) { 187 rules.forEach(rule => { 188 delete rule.trigger; 189 }); 190 } 191 descriptor[this.prop] = rules; 192 193 const validator = new AsyncValidator(descriptor); 194 const model = {}; 195 196 model[this.prop] = this.fieldValue; 197 198 validator.validate(model, { firstFields: true }, (errors, invalidFields) => { 199 this.validateState = !errors ? 'success' : 'error'; 200 this.validateMessage = errors ? errors[0].message : ''; 201 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 215 let model = this.form.model; 216 let value = this.fieldValue; 217 let path = this.prop; 218 if (path.indexOf(':') !== -1) { 219 path = path.replace(/:/, '.'); 220 } 221 222 let prop = getPropByPath(model, path, true); 223 224 this.validateDisabled = true; 225 if (Array.isArray(value)) { 226 prop.o[prop.k] = [].concat(this.initialValue); 227 } else { 228 prop.o[prop.k] = this.initialValue; 229 } 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 238 const prop = getPropByPath(formRules, this.prop || ''); 239 formRules = formRules ? (prop.o[this.prop || ''] || prop.v) : []; 240 241 return [].concat(selfRules || formRules || []).concat(requiredRule); 242 }, 243 getFilteredRule(trigger) { 244 const rules = this.getRules(); 245 246 return rules.filter(rule => { 247 if (!rule.trigger || trigger === '') return true; 248 if (Array.isArray(rule.trigger)) { 249 return rule.trigger.indexOf(trigger) > -1; 250 } else { 251 return rule.trigger === trigger; 252 } 253 }).map(rule => objectAssign({}, rule)); 254 }, 255 onFieldBlur() { 256 this.validate('blur'); 257 }, 258 onFieldChange() { 259 if (this.validateDisabled) { 260 this.validateDisabled = false; 261 return; 262 } 263 264 this.validate('change'); 265 } 266 }, 267 mounted() { 268 if (this.prop) { 269 this.dispatch('ElForm', 'el.form.addField', [this]); 270 271 let initialValue = this.fieldValue; 272 if (Array.isArray(initialValue)) { 273 initialValue = [].concat(initialValue); 274 } 275 Object.defineProperty(this, 'initialValue', { 276 value: initialValue 277 }); 278 279 let rules = this.getRules(); 280 281 if (rules.length || this.required !== undefined) { 282 this.$on('el.form.blur', this.onFieldBlur); 283 this.$on('el.form.change', this.onFieldChange); 284 } 285 } 286 }, 287 beforeDestroy() { 288 this.dispatch('ElForm', 'el.form.removeField', [this]); 289 } 290 }; 291 </script>
原创博客,欢迎讨论,转载请注明出处、链接