Element UI Common Issues and Solutions
This article documents practical solutions for common problems encountered when working with Element UI in Vue.js applications.
1. el-cascader Cascade Selector
a. Clear Value Programmatically
<el-cascader
ref="regionCascader"
:options="regionOptions"
v-model="selectedRegion"
:show-all-levels="false"
expand-trigger="click"
clearable
change-on-select>
</el-cascader>
To clear the cascader value programmatically:
let eventProxy = {};
eventProxy.stopPropagation = () => {};
this.$refs.regionCascader.clearValue(eventProxy);
b. Enable Selection at Every Level
Add change-on-select atrtibute. Remove it to allow only leaf node selection.
c. Set Specific Value
let targetValue = [];
targetValue.push(someValue);
this.$refs.regionCascader.handlePick(targetValue);
d. Show/Hide Menu
this.$refs.regionCascader.showMenu();
this.$refs.regionCascader.hideMenu();
e. Searchable Cascader with Custom Input
<template>
<el-cascader
filterable
ref="searchableCascader"
:options="locationData"
v-model="locationValue"
:props="{ checkStrictly: true }"
:show-all-levels="showLevels"
:size="inputSize"
:debounce="300"
style="width: 100%"
:before-filter="beforeFilterLocation"
@blur="handleBlur"
@focus="handleFocus"
@change="handleChange"
@visible-change="handleVisibilityChange"
:placeholder="placeholderText"
clearable
></el-cascader>
</template>
<script>
import {cloneDeep} from '@/utils/objectUtils';
import {fetchLocationData} from '@/api/locations';
export default {
name: "searchable-location-cascader",
props: {
modelValue: {},
showLevels: {
type: Boolean,
default: false
},
inputSize: {
type: String,
default: 'medium'
},
placeholderText: {
type: String,
default: ''
}
},
data() {
return {
locationList: [],
locationData: [],
locationDataBackup: [],
locationDataLength: undefined,
locationValue: [],
userInputValue: undefined,
incrementCounter: 1
}
},
created() {
this.loadLocationData();
},
mounted() {
this.initializeValue();
},
methods: {
loadLocationData() {
fetchLocationData().then(response => {
this.locationList = response.data.attachData;
this.locationData = response.data.cascaderData;
this.locationDataLength = this.locationData.length;
this.locationDataBackup = cloneDeep(this.locationData);
this.initializeValue();
})
},
initializeValue() {
if (!this.modelValue) return;
let value = [];
let list = cloneDeep(this.locationDataBackup);
let location = this.findLocationById(this.modelValue);
if (!location) {
list.push(this.createItem(this.modelValue));
value.push(this.modelValue);
} else {
if (location.treePosition) {
value = location.treePosition.substr(1).split('&');
}
value.push(this.modelValue);
}
this.locationData = list;
this.locationValue = value;
},
findLocationById(id) {
let location = undefined;
if (this.locationList && id) {
for (let item of this.locationList) {
if (id === item.id) {
location = cloneDeep(item);
break;
}
}
}
return location;
},
createItem(name) {
return {
value: name,
label: name,
children: undefined,
disabled: false
}
},
beforeFilterLocation: function(inputValue) {
this.userInputValue = inputValue;
return false;
},
handleBlur() {
if (!this.userInputValue) return;
this.userInputValue = this.userInputValue.trim();
if (this.userInputValue === "") return;
this.$set(this.locationData, this.locationDataLength, this.createItem(this.userInputValue));
this.locationValue = [this.userInputValue];
this.userInputValue = undefined;
this.handleChange(this.locationValue);
},
handleFocus() {
this.$refs.searchableCascader.$refs.input.$refs.input.select();
},
handleChange(v) {
if (v && v.length > 0) {
this.$emit('update:modelValue', v[v.length - 1]);
} else {
this.$emit('update:modelValue', '');
}
},
handleVisibilityChange(visible) {
if (visible) return;
if (this.locationValue && this.locationValue.length > 0) {
let locationId = this.locationValue[this.locationValue.length - 1];
let locationName = '';
let location = this.findLocationById(locationId);
if (location) {
locationName = location.name;
} else {
locationName = locationId;
}
this.$emit('locationChanged', locationId, locationName);
} else {
this.$emit('locationChanged', '', '');
}
},
setLocationValue(v) {
this.locationValue = v;
}
}
}
</script>
f. Get Current Label
<el-cascader ref="myCascader" :options="dataList" v-model="selectedData" @change="handleChange" size="small"></el-cascader>
fetchDataList(){
this.dataList = [];
fetchFromDatabase().then(response => {
this.dataList = response.data;
if(this.dataList.length > 0){
let v = [];
this.getFirstPath(v, this.dataList);
this.selectedData = v;
if (v.length > 0) {
let selectedValue = v[v.length - 1];
let selectedLabel = this.getCurrentLabels(v);
}
}
})
},
getFirstPath(result, list) {
if (list) {
result.push(list[0].value);
this.getFirstPath(result, list[0].children);
}
},
getCurrentLabels(v) {
let options = this.dataList;
let labels = [];
v.forEach(value => {
const targetOption = options && options.filter(option => option['value'] === value)[0];
if (targetOption) {
labels.push(targetOption['label']);
options = targetOption['children'];
}
});
let label = this.$refs.myCascader.showAllLevels ? labels.join('/') : labels[labels.length - 1];
return label;
},
handleChange(v){
if (v.length > 0) {
let selectedValue = v[v.length - 1];
let selectedLabel = this.getCurrentLabels(v);
}
}
2. Reset Form Fields or Clear Validation
validate - Validate entire form
this.$refs['myForm'].validate((valid, obj) => {});
validateField - Validate specific field
this.$refs['myForm'].validateField('fieldName', errorMsg => {});
resetFields - Reset all fields to initial values
this.$refs['myForm'].resetFields();
clearValidate - Remove validation results
this.$refs['myForm'].clearValidate();
this.$refs['myForm'].clearValidate(["field1","field2"]);
3. Form Validation
Complete Validation Example
// Reset form
this.$refs['myForm'].resetFields();
// Clear validation messages
this.$refs['myForm'].clearValidate();
// Validate entire form
this.$refs['myForm'].validate(valid => {
if(valid) {
// Validation passed
} else {
// Validation failed
}
});
// Validate specific field
this.$refs['myForm'].validateField('username', errorMsg => {
if (!errorMsg) {
// Field is valid
} else {
// Field has error
}
});
Conditional Validation
const validateChineseName = (rule, value, callback) => {
if (/^[\u4e00-\u9fa5]{2,4}$/.test(value)) {
callback();
} else {
callback(new Error('Please enter Chinese characters'));
}
};
validationRules: {
username: [
{required: true, message: 'Please enter username'},
{validator: validateChineseName, trigger: ['blur','change']}
],
}
<el-form-item label="Username" prop="username" :rules="isEnabled ? validationRules.username : []">
<el-input v-model="formData.username" maxlength="4"></el-input>
</el-form-item>
4. Force Component Re-render
this.$forceUpdate();
5. Set Cursor Focus
In Vue, DOM update are batched. Use nextTick to access updated DOM:
this.$nextTick(() => {
this.$refs['inputField'].focus();
});
6. Custom Validation Rules
validationRules: {
username: [
{required: true, message: 'Please enter username'}
{validator: validateUsername, trigger: ['blur','change']}
]
}
const validateUsername = (rule, value, callback) => {
checkUsernameExists(value).then(response => {
if (response.status == 200 && response.data) {
callback(new Error(response.message));
return;
}
});
callback();
};
Check for non-numeric characters: /[^\d]/g.test(value) returns true if non-numeric characters exist.
7. Element Default Border Colors
Normal: border-color: #dcdfe6;
Focus: border-color: #409EFF;
Error: border-color: #f56c6c;
8. Custom Input Styles
input::-webkit-input-placeholder {
color: #C0C4CC;
font-size: 14px;
}
input:-moz-placeholder {
color: #C0C4CC;
font-size: 14px;
}
input:-ms-input-placeholder {
color: #C0C4CC;
font-size: 14px;
}
.customInput {
margin-top: 2px;
-webkit-appearance: none;
background-color: #fff;
background-image: none;
border-radius: 4px;
border: 1px solid #dcdfe6;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #606266;
display: inline-block;
color: #303133;
font-size: 14px;
height: 40px;
line-height: 40px;
outline: 0;
padding: 0 15px;
-webkit-transition: border-color .2s cubic-bezier(.645,.045,.355,1);
transition: border-color .2s cubic-bezier(.645,.045,.355,1);
width: 160px;
}
.customInput input[type=text]:focus{
outline: 1px solid #409EFF;
}
9. Form Array Element Validation
dataModel: {
images: undefined,
imageArray: []
}
imageArray structure: {name: undefined, url: undefined, del: false}
<el-form-item label="Images" prop="images">
<el-input v-model="dataModel.images"></el-input>
<div v-for="(item, index) in dataModel.imageArray" class="image-uploader">
<el-form-item :prop="`imageArray[${index}].name`" :rules="{ required: true, message: 'Image name required', trigger: 'blur' }">
<img :src="item.url" class="image-add">
<el-input v-model="item.name" maxlength="20"></el-input>
</el-form-item>
</div>
</el-form-item>
10. Video Auto-fit el-col Width
video {
width: 100%;
height: 100%;
object-fit: fill;
}
11. el-table Operations
Select Row
this.$refs.dataTable.toggleRowSelection(this.dataList[0]);
Column Content Overflow
el-table-column show-overflow-tooltip="true"
el-table-column tooltip-effect="light"
Lazy Loading
Set lazy property on el-table and use load function to load children for rows where hasChildren = true.
12. el-tree Operations
a. Set Selected Node Color
.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
background-color: #a6d1ff;
}
b. Select Tree Node
// By key
this.$refs.treeRef.setCurrentKey(nodeKey);
// By node object
this.$refs.treeRef.setCurrentNode(nodeObject);
// Select single node
this.$refs.treeRef.setChecked(nodeId, true, false);
// Clear selection
this.$refs.treeRef.setCheckedKeys([])
13. Chrome Browser Input Autofill Color
Change yellow background to white:
input:-webkit-autofill {
box-shadow: 0 0 0px 1000px white inset !important;
}
input:-webkit-autofill:focus {
box-shadow: 0 0 0px 1000px white inset !important;
}
Set transparent:
input:-internal-autofill-previewed,
input:-internal-autofill-selected {
-webkit-text-fill-color: #FFFFFF !important;
transition: background-color 5000s ease-in-out 0s !important;
}
14. Searchable Cascader Component
See section 1e for complete implementation example.
15. Expand Specific el-tree Node
this.$refs['treeRef'].store.currentNode.expanded = true
this.$refs['treeRef'].store.nodesMap["nodeId"].expanded = true
16. Date, Time, Datetime Pickers
<!-- Date -->
<el-date-picker v-model.trim="currentDate"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
type="date"
placeholder="Select date"></el-date-picker>
<!-- Time -->
<el-time-picker v-model.trim="currentTime"
value-format="HH:mm:ss"
format="HH:mm:ss"
placeholder="Select time"></el-time-picker>
<!-- Datetime -->
<el-date-picker v-model.trim="currentDateTime"
value-format="yyyy-MM-dd HH:mm:ss"
format="yyyy-MM-dd HH:mm:ss"
type="datetime"
placeholder="Select datetime"></el-date-picker>
value-format controls the value format, format controls the display format.
17. el-table-tree-column Issues
<el-table-tree-column
fixed
:expand-all="false"
:indent-size="20"
child-key="children"
levelKey="level"
treeKey="value"
parentKey="parentId"
prop="label"
label="Name"
>
Note: If treeKey is not specified, the object's ID property is used by default. Without levelKey, there will be no indentation between parent and child rows.
18. el-dialog Body Height Setting
Problem: Setting height: 100% on iframe doesn't expand el-dialog__body
Solution: Dynamicaly calculate and set height
data() {
return {
windowWidth: document.documentElement.clientWidth,
windowHeight: document.documentElement.clientHeight,
iframeHeight: '100%'
};
},
watch:{
'windowWidth':function(val){},
'windowHeight':function(val){
this.calculateIframeHeight();
}
},
mounted() {
var _this = this;
window.onresize = function(){
_this.windowWidth = document.documentElement.clientWidth;
_this.windowHeight = document.documentElement.clientHeight;
};
},
methods: {
calculateIframeHeight() {
this.$nextTick(()=>{
this.iframeHeight = this.$refs['dialogRef'].$el.offsetHeight - 130 + 'px';
})
},
}
19. el-cascader TypeError Fix
When encountering TypeError: Cannot read property 'level' of null, add a key to force re-render:
<el-cascader
:key="cascaderKey"
:options="cascaderOptions"
></el-cascader>
// In data
cascaderKey: 0,
cascaderOptions: []
// When updating data
++this.cascaderKey;
this.cascaderOptions = newOptions;
20. Password Field Auto-fill Issues
<el-input type="password" v-model.trim="passwordValue" maxlength="20" show-password auto-complete="new-password" placeholder="Password"></el-input>
Note: Use auto-complete="new-password" instead of autocomplete="off"
21. Table Single Selection
<el-table ref="dataTableRef" :data="tableData" stripe border highlight-current-row @selection-change="handleSelectionChange" @select="handleSelect">
</el-table>
handleSelect(selection, row) {
this.selectedRows = [];
let isChecked = false;
for (let item of selection) {
if (item.id === row.id) {
isChecked = true;
break;
}
}
if (isChecked) {
this.selectedRows.push(row);
}
if (this.tableData) {
for (let rowItem of this.tableData) {
if (rowItem.id === row.id) {
this.$refs['dataTableRef'].toggleRowSelection(rowItem, isChecked);
} else {
this.$refs['dataTableRef'].toggleRowSelection(rowItem, false);
}
}
}
},
handleSelectionChange(selection) {
if (this.tableData) {
for (let rowItem of this.tableData) {
let isChecked = false;
for (let selectedItem of this.selectedRows) {
if (rowItem.id === selectedItem.id) {
isChecked = true;
break;
}
}
this.$refs['dataTableRef'].toggleRowSelection(rowItem, isChecked);
}
}
},
// Clear selection
this.$refs['dataTableRef'].clearSelection();
Hide header checkbox:
<style scoped>
/deep/.el-table__header-wrapper .el-checkbox {
display:none
}
</style>
22. el-checkbox Binding Value Issue
When changing binding value to false but checkbox appears checked:
<el-checkbox v-model="checkboxValue" @change="handleCheckboxChange(value, event)"></el-checkbox>
handleCheckboxChange(v, ev) {
// Force uncheck
this.checkboxValue = false;
ev.target.checked = false;
}
23. Prevent el-dialog Close on Mask/ESC
<el-dialog v-model="dialogVisible" title="Confirmation" width="400" :close-on-click-modal="false" :close-on-press-escape="false">
<div style="text-align: center;">{{message}}</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="confirmAction">Confirm</el-button>
<el-button @click="cancelAction">Cancel</el-button>
</div>
</template>
</el-dialog>
24. Hide el-dialog Header
.no-header-dialog .el-dialog__header {
display: none;
}
<el-dialog class="no-header-dialog" v-model="progressVisible" :show-close="false" :close-on-click-modal="false" :close-on-press-escape="false">
<div style="text-align: right; width: 100%"><el-button type="danger" @click="closeDialog">Close</el-button></div>
Content here
</el-dialog>
25. Style Overrides
::v-deep(.el-collapse-item__title) {
width: 95%;
}