mirror of
https://github.com/tenrok/vue-form-wizard.git
synced 2026-06-22 07:00:32 +03:00
#4 Add support for error messages when doing async before change
Add color styles when before-change fails
This commit is contained in:
@@ -93,6 +93,13 @@ props: {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '#e74c3c' //circle, border and text color
|
default: '#e74c3c' //circle, border and text color
|
||||||
},
|
},
|
||||||
|
/***
|
||||||
|
* Is set to current step and text when beforeChange function fails
|
||||||
|
*/
|
||||||
|
errorColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#8b0000'
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Can take one of the following values: 'circle|square|tab`
|
* Can take one of the following values: 'circle|square|tab`
|
||||||
*/
|
*/
|
||||||
|
|||||||
+21
-7
@@ -1,13 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<form-wizard @on-complete="onComplete"
|
<form-wizard @on-complete="onComplete"
|
||||||
shape="circle"
|
shape="square"
|
||||||
color="#e74c3c"
|
color="gray"
|
||||||
@on-loading="setLoading"
|
@on-loading="setLoading"
|
||||||
class="card">
|
@on-error="setError"
|
||||||
|
class="card" ref="wizard">
|
||||||
<tab-content title="Personal details"
|
<tab-content title="Personal details"
|
||||||
:before-change="validateAsync"
|
|
||||||
route="/first"
|
route="/first"
|
||||||
|
:before-change="validateAsync"
|
||||||
icon="ti-user">
|
icon="ti-user">
|
||||||
</tab-content>
|
</tab-content>
|
||||||
<tab-content title="Additional Info"
|
<tab-content title="Additional Info"
|
||||||
@@ -16,7 +17,6 @@
|
|||||||
icon="ti-settings">
|
icon="ti-settings">
|
||||||
</tab-content>
|
</tab-content>
|
||||||
<tab-content title="Last step"
|
<tab-content title="Last step"
|
||||||
:before-change="validateAsync"
|
|
||||||
route="/third"
|
route="/third"
|
||||||
icon="ti-check">
|
icon="ti-check">
|
||||||
</tab-content>
|
</tab-content>
|
||||||
@@ -24,6 +24,9 @@
|
|||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</transition>
|
</transition>
|
||||||
<div class="loader" v-if="loadingWizard"></div>
|
<div class="loader" v-if="loadingWizard"></div>
|
||||||
|
<div v-if="error">
|
||||||
|
{{error}}
|
||||||
|
</div>
|
||||||
</form-wizard>
|
</form-wizard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -34,7 +37,9 @@
|
|||||||
name: 'app',
|
name: 'app',
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
loadingWizard: false
|
loadingWizard: false,
|
||||||
|
error: null,
|
||||||
|
count: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -44,10 +49,19 @@
|
|||||||
setLoading (value) {
|
setLoading (value) {
|
||||||
this.loadingWizard = value
|
this.loadingWizard = value
|
||||||
},
|
},
|
||||||
|
setError (error) {
|
||||||
|
this.error = error
|
||||||
|
},
|
||||||
validateAsync () {
|
validateAsync () {
|
||||||
|
//simulating an error for the first time and a success for the second time
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve(true)
|
if (this.count < 1) {
|
||||||
|
this.count ++
|
||||||
|
reject('Some custom error')
|
||||||
|
} else {
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,89 +1,95 @@
|
|||||||
.vue-tab-wizard{
|
.vue-tab-wizard, .vue-form-wizard {
|
||||||
padding-bottom:20px;
|
padding-bottom: 20px;
|
||||||
.icon-circle{
|
.is_error {
|
||||||
font-size: 18px;
|
border-color: $danger-states-color !important;
|
||||||
border: 3px solid $gray-input-bg;
|
.icon-container {
|
||||||
border-radius: 50%;
|
background: $danger-states-color !important;
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
width: 70px;
|
|
||||||
height: 70px;
|
|
||||||
background-color: $white-color;
|
|
||||||
position: relative;
|
|
||||||
display:flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-content: center;
|
|
||||||
&.square_shape{
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
&.tab_shape{
|
|
||||||
width:100%;
|
|
||||||
min-width:100px;
|
|
||||||
height:40px;
|
|
||||||
border:none;
|
|
||||||
background-color: $gray-input-bg;
|
|
||||||
border-radius:0;
|
|
||||||
}
|
|
||||||
.icon-container{
|
|
||||||
display:flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex:1;
|
|
||||||
border-radius: 40%;
|
|
||||||
&.square_shape,&.tab_shape{
|
|
||||||
border-radius:0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.icon{
|
|
||||||
display:flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.tab-content{
|
.icon-circle {
|
||||||
min-height: 100px;
|
font-size: 18px;
|
||||||
padding: 30px 20px 10px;
|
border: 3px solid $gray-input-bg;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
background-color: $white-color;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
&.square_shape {
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
&.tab_shape {
|
||||||
.card-footer{
|
width: 100%;
|
||||||
padding: 0 20px;
|
min-width: 100px;
|
||||||
|
height: 40px;
|
||||||
|
border: none;
|
||||||
|
background-color: $gray-input-bg;
|
||||||
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
.icon-container {
|
||||||
.wizard-header{
|
display: flex;
|
||||||
padding: 15px 15px 15px 15px;
|
justify-content: center;
|
||||||
position: relative;
|
flex: 1;
|
||||||
border-radius: 3px 3px 0 0;
|
border-radius: 40%;
|
||||||
z-index: 3;
|
&.square_shape, &.tab_shape {
|
||||||
text-align: center;
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.icon {
|
||||||
.wizard-title{
|
display: flex;
|
||||||
color: $card-black-color;
|
align-items: center;
|
||||||
font-weight: $font-weight-light;
|
justify-content: center;
|
||||||
margin: 0;
|
|
||||||
text-align:center;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.category{
|
.tab-content {
|
||||||
font-size: 14px;
|
min-height: 100px;
|
||||||
font-weight: 400;
|
padding: 30px 20px 10px;
|
||||||
color: #9A9A9A;
|
}
|
||||||
margin-bottom: 0px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wizard-navigation{
|
.card-footer {
|
||||||
.progress-with-circle{
|
padding: 0 20px;
|
||||||
position: relative;
|
}
|
||||||
top: 40px;
|
|
||||||
z-index: 50;
|
|
||||||
height: 4px;
|
|
||||||
|
|
||||||
.progress-bar{
|
.wizard-header {
|
||||||
box-shadow: none;
|
padding: 15px 15px 15px 15px;
|
||||||
-webkit-transition: width .3s ease;
|
position: relative;
|
||||||
-o-transition: width .3s ease;
|
border-radius: 3px 3px 0 0;
|
||||||
transition: width .3s ease;
|
z-index: 3;
|
||||||
}
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wizard-title {
|
||||||
|
color: $card-black-color;
|
||||||
|
font-weight: $font-weight-light;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #9A9A9A;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wizard-navigation {
|
||||||
|
.progress-with-circle {
|
||||||
|
position: relative;
|
||||||
|
top: 40px;
|
||||||
|
z-index: 50;
|
||||||
|
height: 4px;
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
box-shadow: none;
|
||||||
|
-webkit-transition: width .3s ease;
|
||||||
|
-o-transition: width .3s ease;
|
||||||
|
transition: width .3s ease;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,19 +14,25 @@
|
|||||||
<ul class="nav nav-pills">
|
<ul class="nav nav-pills">
|
||||||
<li v-for="(tab, index) in tabs" :class="{active:tab.active}">
|
<li v-for="(tab, index) in tabs" :class="{active:tab.active}">
|
||||||
<a href="" @click.prevent="navigateToTab(index)">
|
<a href="" @click.prevent="navigateToTab(index)">
|
||||||
<div class="icon-circle" :class="{checked:isChecked(index),square_shape:isStepSquare, tab_shape:isTabShape}"
|
<div class="icon-circle"
|
||||||
:style="isChecked(index)? stepCheckedStyle : {}">
|
:class="{checked:isChecked(index),square_shape:isStepSquare, tab_shape:isTabShape}"
|
||||||
|
:style="[isChecked(index)? stepCheckedStyle : {}, tab.validationError ? errorStyle : {}]">
|
||||||
|
|
||||||
<transition :name="transition" mode="out-in">
|
<transition :name="transition" mode="out-in">
|
||||||
<div v-if="tab.active" class="icon-container" :class="{square_shape:isStepSquare, tab_shape:isTabShape}"
|
<div v-if="tab.active" class="icon-container"
|
||||||
:style="tab.active ? iconActiveStyle: {}">
|
:class="{square_shape:isStepSquare, tab_shape:isTabShape}"
|
||||||
|
:style="[tab.active ? iconActiveStyle: {}, tab.validationError ? errorStyle : {}]">
|
||||||
<i v-if="tab.icon" :class="tab.icon" class="icon"></i>
|
<i v-if="tab.icon" :class="tab.icon" class="icon"></i>
|
||||||
<i v-else class="icon">{{index + 1}}</i>
|
<i v-else class="icon">{{index + 1}}</i>
|
||||||
</div>
|
</div>
|
||||||
<i v-if="!tab.active && tab.icon" :class="tab.icon" class="icon"></i>
|
<i v-if="!tab.active && tab.icon" :class="tab.icon" class="icon"></i>
|
||||||
<i v-if="!tab.active && !tab.icon" class="icon">{{index + 1}}</i>
|
<i v-if="!tab.active && !tab.icon" class="icon">{{index + 1}}</i>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<span class="stepTitle" :style="tab.active ? stepTitleStyle : {}">
|
<span class="stepTitle"
|
||||||
|
:class="{active:tab.active, has_error:tab.validationError}"
|
||||||
|
:style="tab.active ? stepTitleStyle : {}">
|
||||||
{{tab.title}}
|
{{tab.title}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -104,6 +110,10 @@
|
|||||||
type: String,
|
type: String,
|
||||||
default: '#e74c3c'
|
default: '#e74c3c'
|
||||||
},
|
},
|
||||||
|
errorColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#8b0000'
|
||||||
|
},
|
||||||
shape: {
|
shape: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'circle'
|
default: 'circle'
|
||||||
@@ -161,9 +171,16 @@
|
|||||||
borderColor: this.color
|
borderColor: this.color
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
stepTitleStyle () {
|
errorStyle () {
|
||||||
return {
|
return {
|
||||||
color: this.color
|
borderColor: this.errorColor,
|
||||||
|
backgroundColor: this.errorColor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stepTitleStyle () {
|
||||||
|
var isError = this.tabs[this.activeTabIndex].validationError
|
||||||
|
return {
|
||||||
|
color: isError ? this.errorColor : this.color
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isStepSquare () {
|
isStepSquare () {
|
||||||
@@ -207,7 +224,12 @@
|
|||||||
this.loading = value
|
this.loading = value
|
||||||
this.$emit('on-loading', value)
|
this.$emit('on-loading', value)
|
||||||
},
|
},
|
||||||
|
setValidationError (error) {
|
||||||
|
this.tabs[this.activeTabIndex].validationError = error
|
||||||
|
this.$emit('on-error', error)
|
||||||
|
},
|
||||||
validateBeforeChange (promiseFn, callback) {
|
validateBeforeChange (promiseFn, callback) {
|
||||||
|
this.setValidationError(null)
|
||||||
// we have a promise
|
// we have a promise
|
||||||
if (promiseFn.then && typeof promiseFn.then === 'function') {
|
if (promiseFn.then && typeof promiseFn.then === 'function') {
|
||||||
this.setLoading(true)
|
this.setLoading(true)
|
||||||
@@ -215,8 +237,9 @@
|
|||||||
this.setLoading(false)
|
this.setLoading(false)
|
||||||
let validationResult = res === true
|
let validationResult = res === true
|
||||||
this.executeBeforeChange(validationResult, callback)
|
this.executeBeforeChange(validationResult, callback)
|
||||||
}).catch(() => {
|
}).catch((error) => {
|
||||||
this.setLoading(false)
|
this.setLoading(false)
|
||||||
|
this.setValidationError(error)
|
||||||
})
|
})
|
||||||
// we have a simple function
|
// we have a simple function
|
||||||
} else {
|
} else {
|
||||||
@@ -228,6 +251,8 @@
|
|||||||
this.$emit('on-validate', validationResult, this.activeTabIndex)
|
this.$emit('on-validate', validationResult, this.activeTabIndex)
|
||||||
if (validationResult) {
|
if (validationResult) {
|
||||||
callback()
|
callback()
|
||||||
|
} else {
|
||||||
|
this.tabs[this.activeTabIndex].validationError = 'error'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeTabChange (index, callback) {
|
beforeTabChange (index, callback) {
|
||||||
@@ -254,6 +279,7 @@
|
|||||||
this.activeTabIndex = newIndex
|
this.activeTabIndex = newIndex
|
||||||
this.checkStep()
|
this.checkStep()
|
||||||
this.tryChangeRoute(newTab)
|
this.tryChangeRoute(newTab)
|
||||||
|
this.increaseMaxStep()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
tryChangeRoute (tab) {
|
tryChangeRoute (tab) {
|
||||||
@@ -277,7 +303,6 @@
|
|||||||
let cb = () => {
|
let cb = () => {
|
||||||
if (this.activeTabIndex < this.tabCount - 1) {
|
if (this.activeTabIndex < this.tabCount - 1) {
|
||||||
this.changeTab(this.activeTabIndex, this.activeTabIndex + 1)
|
this.changeTab(this.activeTabIndex, this.activeTabIndex + 1)
|
||||||
this.increaseMaxStep()
|
|
||||||
} else {
|
} else {
|
||||||
this.isLastStep = true
|
this.isLastStep = true
|
||||||
this.$emit('finished')
|
this.$emit('finished')
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
active: false
|
active: false,
|
||||||
|
validationError: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user