2
0
mirror of https://github.com/tenrok/vue-form-wizard.git synced 2026-06-25 01:20:33 +03:00

Merge pull request #2 from cristijora/1-async-validation

1 async validation
This commit is contained in:
Cristi Jora
2017-04-24 22:40:02 +03:00
committed by GitHub
29 changed files with 311 additions and 6442 deletions
-14
View File
@@ -1,14 +0,0 @@
# Auto detect text files and perform LF normalization
*.scss linguist-language=Vue
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
+2 -25
View File
@@ -2,10 +2,7 @@
A dynamic form wizard to split your forms easier A dynamic form wizard to split your forms easier
Vue-form-wizard is a vue based component with **no external depenendcies** which simplifies tab wizard management and allows you to focus on the functional part of your app rather than Vue-form-wizard is a vue based component with **no external depenendcies** which simplifies tab wizard management and allows you to focus on the functional part of your app rather than
wasting time on details. Just forget about id's, external scripts and jQuery dependencies. wasting time on details. Just forget about id's, external scripts and jQuery dependencies
Vue-form-wizard is inspired by [creative-tim wizards](https://www.creative-tim.com/bootstrap-themes/wizard) but simplified and
more customizable
# Demos # Demos
Basic [demo](https://jsfiddle.net/bt5dhqtf/97/) Basic [demo](https://jsfiddle.net/bt5dhqtf/97/)
@@ -18,8 +15,6 @@ Other demos:
* [Custom title slot](https://jsfiddle.net/bt5dhqtf/102/) * [Custom title slot](https://jsfiddle.net/bt5dhqtf/102/)
* [Customized buttons with slots](https://jsfiddle.net/bt5dhqtf/103/) Replace stuff you don't like * [Customized buttons with slots](https://jsfiddle.net/bt5dhqtf/103/) Replace stuff you don't like
* [Call a function before tab switch](https://jsfiddle.net/bt5dhqtf/105/) * [Call a function before tab switch](https://jsfiddle.net/bt5dhqtf/105/)
* [Complete form example](https://jsfiddle.net/bt5dhqtf/150/) integrated with [vue-form-generator](https://github.com/icebob/vue-form-generator)
* [Vue router integration](https://jsfiddle.net/CristiJ/bt5dhqtf/252/) You can place a `router-view` inside the wizard and have a separate page per tab. A `route` prop must be passed to the tabs you want to handle certain tabs
# Usage # Usage
@@ -37,7 +32,7 @@ Download the css and js files from `dist` folder or reference them directly from
//global registration //global registration
import 'vue-form-wizard' import 'vue-form-wizard'
import 'vue-form-wizard/dist/vue-form-wizard.min.css' import 'vue-form-wizard/dist/vue-form-wizard.min.css'
Vue.use(VueFormWizard) Vue.use(VueTabWizard)
//local registration //local registration
import {FormWizard, TabContent} from 'vue-form-wizard' import {FormWizard, TabContent} from 'vue-form-wizard'
@@ -140,12 +135,6 @@ props: {
*/ */
beforeChange: { beforeChange: {
type: Function type: Function
},
/***
* Used to handle routing with vue-router. Refer to [router.push](https://router.vuejs.org/en/essentials/navigation.html) for a valid prop in this case
*/
route: {
type: [String, Object]
} }
} }
``` ```
@@ -156,15 +145,3 @@ props: {
* **prev** - Previous button content (no need to worry about handling the button functionality) * **prev** - Previous button content (no need to worry about handling the button functionality)
* **next** - Next button content * **next** - Next button content
* **finish** - Finish button content * **finish** - Finish button content
## Contribution
Open an issue or send a Pull request if you feel that something is missing or doesn't work.
## License
vue-form-wizard is available under the [MIT license](https://tldrlegal.com/license/mit-license).
## Contact
Copyright (C) 2017 Cristi Jora
[![@cristijora](https://img.shields.io/badge/github-cristijora-green.svg)](https://github.com/cristijora)
+1 -1
View File
@@ -9,7 +9,7 @@ function resolve (dir) {
module.exports = { module.exports = {
entry: { entry: {
app: './dev/main.js' app: './dev-example/main.js'
}, },
output: { output: {
path: config.build.assetsRoot, path: config.build.assetsRoot,
+86
View File
@@ -0,0 +1,86 @@
<template>
<div>
<form-wizard @on-complete="onComplete"
shape="circle"
color="#e74c3c"
@on-loading="setLoading"
class="card">
<tab-content title="Personal details"
:before-change="validateAsync"
icon="ti-user">
My first tab content
</tab-content>
<tab-content title="Additional Info"
:before-change="validate"
icon="ti-settings">
My second tab content
</tab-content>
<tab-content title="Last step"
:before-change="validateAsync"
icon="ti-check">
Yuhuuu! This seems pretty damn simple
</tab-content>
<div class="loader" v-if="loadingWizard"></div>
</form-wizard>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
loadingWizard: false
}
},
methods: {
onComplete () {
alert('Yay!')
},
setLoading (value) {
this.loadingWizard = value
},
validateAsync () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true)
}, 1000)
})
},
validate () {
return true
}
}
}
</script>
<style>
@import "loader.css";
</style>
<style lang="scss">
$border-radius-extreme: 6px !default;
$white-color: white;
$gray-input-bg: #F3F2EE !default;
$card-black-color: #252422 !default;
body {
margin-top: 20px;
background-color: #ecf0f1;
}
.card-footer {
padding: 0px 20px;
}
.card {
border-radius: $border-radius-extreme;
box-shadow: 0 2px 2px rgba(204, 197, 185, 0.5);
background-color: $white-color;
color: $card-black-color;
padding: 10px 0;
margin-bottom: 20px;
position: relative;
z-index: 1;
}
</style>
+41
View File
@@ -0,0 +1,41 @@
.loader,
.loader:after {
border-radius: 50%;
width: 10em;
height: 10em;
}
.loader {
margin: 60px auto;
font-size: 10px;
position: relative;
text-indent: -9999em;
border-top: 1.1em solid rgba(255, 255, 255, 0.2);
border-right: 1.1em solid rgba(255, 255, 255, 0.2);
border-bottom: 1.1em solid rgba(255, 255, 255, 0.2);
border-left: 1.1em solid #e74c3c;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation: load8 1.1s infinite linear;
animation: load8 1.1s infinite linear;
}
@-webkit-keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
+15
View File
@@ -0,0 +1,15 @@
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App.vue'
import FormWizard from '../src/index'
Vue.use(FormWizard)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: {App}
})
-61
View File
@@ -1,61 +0,0 @@
<template>
<div>
<form-wizard @on-complete="onComplete"
shape="circle"
color="#e74c3c"
class="card">
<tab-content title="Personal details"
route="first"
icon="ti-user">
</tab-content>
<tab-content title="Additional Info"
route="second"
icon="ti-settings">
</tab-content>
<tab-content title="Last step"
route="third"
icon="ti-check">
</tab-content>
<router-view></router-view>
</form-wizard>
</div>
</template>
<script>
export default {
name: 'app',
methods: {
onComplete () {
alert('Yay!')
}
}
}
</script>
<style lang="scss">
$border-radius-extreme: 6px !default;
$white-color: white;
$gray-input-bg: #F3F2EE !default;
$card-black-color: #252422 !default;
body {
margin-top:20px;
background-color:#ecf0f1;
}
.card-footer{
padding:0px 20px;
}
.card{
border-radius: $border-radius-extreme;
box-shadow: 0 2px 2px rgba(204, 197, 185, 0.5);
background-color: $white-color;
color: $card-black-color;
padding: 10px 0;
margin-bottom: 20px;
position: relative;
z-index: 1;
}
</style>
-28
View File
@@ -1,28 +0,0 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
import FormWizard from './../src/index'
const First = { template: '<div>First</div>' }
const Second = { template: '<div>Second</div>' }
const Third = { template: '<div>Third</div>' }
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/first', component: First },
{ path: '/second', component: Second },
{ path: '/third', component: Third }
]
})
Vue.use(VueRouter)
Vue.use(FormWizard)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
router,
el: '#app',
template: '<App/>',
components: {App}
})
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -4
View File
@@ -1,8 +1,8 @@
{ {
"name": "vue-form-wizard", "name": "vue-form-wizard",
"version": "0.1.11", "version": "0.1.6",
"description": "A vue based tab/form wizard", "description": "A vue based tab/form wizard",
"main": "dist/vue-form-wizard.js", "main": "dist/vue-tab-wizard.js",
"homepage": "https://github.com/cristijora/vue-form-wizard", "homepage": "https://github.com/cristijora/vue-form-wizard",
"scripts": { "scripts": {
"dev": "node build/dev-server.js", "dev": "node build/dev-server.js",
@@ -25,6 +25,7 @@
"url": "https://github.com/cristijora/vue-form-wizard" "url": "https://github.com/cristijora/vue-form-wizard"
}, },
"devDependencies": { "devDependencies": {
"vue": "^2.2.2",
"autoprefixer": "^6.7.2", "autoprefixer": "^6.7.2",
"babel-core": "^6.22.1", "babel-core": "^6.22.1",
"babel-eslint": "^7.1.1", "babel-eslint": "^7.1.1",
@@ -83,9 +84,7 @@
"sinon-chai": "^2.8.0", "sinon-chai": "^2.8.0",
"stats-webpack-plugin": "^0.6.0", "stats-webpack-plugin": "^0.6.0",
"url-loader": "^0.5.8", "url-loader": "^0.5.8",
"vue": "^2.2.2",
"vue-loader": "^11.1.4", "vue-loader": "^11.1.4",
"vue-router": "^2.4.0",
"vue-style-loader": "^2.0.0", "vue-style-loader": "^2.0.0",
"vue-template-compiler": "^2.2.4", "vue-template-compiler": "^2.2.4",
"webpack": "^2.2.1", "webpack": "^2.2.1",
-39
View File
@@ -1,39 +0,0 @@
.btn,
.navbar .navbar-nav > li > a.btn{
box-sizing: border-box;
border-width: $border-thick;
background-color: $transparent-bg;
font-size: $font-size-base;
font-weight: $font-weight-bold;
padding: $padding-base-vertical $padding-base-horizontal;
&:hover,
&:focus{
outline: 0 !important;
}
&:active,
&.active,
.open > &.dropdown-toggle {
@include box-shadow(none);
outline: 0 !important;
}
&.btn-icon{
border-radius: 25px;
padding: 7px 10px;
i{
margin-right: 0px;
}
}
}
.btn-disabled{
cursor: default;
}
.navbar .navbar-nav > li > a.btn.btn-wd,
.btn-wd{
min-width: 140px;
}
+85
View File
@@ -0,0 +1,85 @@
.btn,
.navbar .navbar-nav > li > a.btn{
box-sizing: border-box;
border-width: $border-thick;
background-color: $transparent-bg;
font-size: $font-size-base;
font-weight: $font-weight-bold;
padding: $padding-base-vertical $padding-base-horizontal;
@include btn-styles($default-color, $default-states-color);
@include transition($fast-transition-time, linear);
&:hover,
&:focus{
outline: 0 !important;
}
&:active,
&.active,
.open > &.dropdown-toggle {
@include box-shadow(none);
outline: 0 !important;
}
&.btn-icon{
border-radius: 25px;
padding: 7px 10px;
i{
margin-right: 0px;
}
}
[class*="ti-"]{
vertical-align: middle;
}
}
.btn-group .btn + .btn,
.btn-group .btn + .btn-group,
.btn-group .btn-group + .btn,
.btn-group .btn-group + .btn-group{
margin-left: -2px;
}
// Apply the mixin to the buttons
//.btn-default { @include btn-styles($default-color, $default-states-color); }
.navbar .navbar-nav > li > a.btn.btn-primary, .btn-primary { @include btn-styles($primary-color, $primary-states-color); }
.navbar .navbar-nav > li > a.btn.btn-info, .btn-info { @include btn-styles($info-color, $info-states-color); }
.btn{
&:disabled,
&[disabled],
&.disabled,
&.btn-disabled{
@include opacity(.5);
}
}
.btn-disabled{
cursor: default;
}
.btn-simple{
border: $none;
padding: $padding-base-vertical $padding-base-horizontal;
}
.navbar .navbar-nav > li > a.btn.btn-wd,
.btn-wd{
min-width: 140px;
}
.btn-group.select{
width: 100%;
}
.btn-group.select .btn{
text-align: left;
}
.btn-group.select .caret{
position: absolute;
top: 50%;
margin-top: -1px;
right: 8px;
}
@@ -13,7 +13,7 @@
border: 1px solid transparent; border: 1px solid transparent;
white-space: nowrap; white-space: nowrap;
@include button-size($padding-base-vertical, $padding-base-horizontal, $font-size-base, $line-height-base, $btn-border-radius-base); @include button-size($padding-base-vertical, $padding-base-horizontal, $font-size-base, $line-height-base, $btn-border-radius-base);
//@include user-select(none); @include user-select(none);
&, &,
&:active, &:active,
+8 -8
View File
@@ -1,8 +1,8 @@
/*@import "form-wizard/bs_button"; //@import "tab-wizard/bs_button";
@import "form-wizard/bs_nav_pills"; //@import "tab-wizard/bs_nav_pills";
@import "form-wizard/bs_progress_bar";*/ //@import "tab-wizard/bs_progress_bar";
@import "form-wizard/variables"; @import "tab-wizard/variables";
@import "form-wizard/mixins"; @import "tab-wizard/mixins";
@import "form-wizard/buttons"; @import "tab-wizard/buttons";
@import "form-wizard/navs-pagination"; @import "tab-wizard/navs-pagination";
@import "form-wizard/wizard-card"; @import "tab-wizard/wizard-card";
+66 -43
View File
@@ -43,7 +43,7 @@
<template> <template>
<span @click="prevTab" v-if="displayPrevButton"> <span @click="prevTab" v-if="displayPrevButton">
<slot name="prev"> <slot name="prev">
<button type="button" class="btn btn-default btn-wd" :style="fillButtonStyle"> <button type="button" class="btn btn-default btn-wd" :style="fillButtonStyle" :disabled="loading">
{{backButtonText}} {{backButtonText}}
</button> </button>
</slot> </slot>
@@ -63,7 +63,7 @@
<template> <template>
<span @click="nextTab" class="pull-right" v-if="!isLastStep"> <span @click="nextTab" class="pull-right" v-if="!isLastStep">
<slot name="next"> <slot name="next">
<button type="button" class="btn btn-fill btn-wd btn-next" :style="fillButtonStyle"> <button type="button" class="btn btn-fill btn-wd btn-next" :style="fillButtonStyle" :disabled="loading">
{{nextButtonText}} {{nextButtonText}}
</button> </button>
</slot> </slot>
@@ -130,6 +130,7 @@
isLastStep: false, isLastStep: false,
currentPercentage: 0, currentPercentage: 0,
maxStep: 0, maxStep: 0,
loading: false,
tabs: [] tabs: []
} }
}, },
@@ -195,38 +196,65 @@
return index <= this.maxStep return index <= this.maxStep
}, },
navigateToTab (index) { navigateToTab (index) {
if (index <= this.maxStep && this.beforeTabChange(this.activeTabIndex)) { if (index <= this.maxStep) {
this.changeTab(this.activeTabIndex, index) let cb = () => {
this.changeTab(this.activeTabIndex, index)
}
this.beforeTabChange(this.activeTabIndex, cb)
} }
}, },
beforeTabChange (index) { setLoading (value) {
this.loading = value
this.$emit('on-loading', value)
},
validateBeforeChange (promiseFn, callback) {
// we have a promise
if (promiseFn.then && typeof promiseFn.then === 'function') {
this.setLoading(true)
promiseFn.then((res) => {
this.setLoading(false)
let validationResult = res === true
this.executeBeforeChange(validationResult, callback)
}).catch(() => {
this.setLoading(false)
})
// we have a simple function
} else {
let validationResult = promiseFn === true
this.executeBeforeChange(validationResult, callback)
}
},
executeBeforeChange (validationResult, callback) {
this.$emit('on-validate', validationResult, this.activeTabIndex)
if (validationResult) {
callback()
}
},
beforeTabChange (index, callback) {
if (this.loading) {
return
}
let oldTab = this.tabs[index] let oldTab = this.tabs[index]
if (oldTab && oldTab.beforeChange !== undefined) { if (oldTab && oldTab.beforeChange !== undefined) {
return oldTab.beforeChange() let tabChangeRes = oldTab.beforeChange()
this.validateBeforeChange(tabChangeRes, callback)
} else {
callback()
} }
return true
}, },
changeTab (oldIndex, newIndex) { changeTab (oldIndex, newIndex) {
let oldTab = this.tabs[oldIndex] let oldTab = this.tabs[oldIndex]
let newTab = this.tabs[newIndex] let newTab = this.tabs[newIndex]
if (oldTab) { if (oldTab) {
oldTab.show = false
oldTab.active = false oldTab.active = false
} }
if (newTab) { if (newTab) {
newTab.show = true
newTab.active = true newTab.active = true
} }
this.activeTabIndex = newIndex this.activeTabIndex = newIndex
this.checkStep() this.checkStep()
this.tryChangeRoute(newTab)
return true return true
}, },
tryChangeRoute (tab) {
if (this.$router && tab.route) {
this.$router.push(tab.route)
}
},
checkStep () { checkStep () {
if (this.activeTabIndex === this.tabCount - 1) { if (this.activeTabIndex === this.tabCount - 1) {
this.isLastStep = true this.isLastStep = true
@@ -239,58 +267,53 @@
this.maxStep = this.activeTabIndex this.maxStep = this.activeTabIndex
} }
}, },
nextTab () { nextTab () {
if (!this.beforeTabChange(this.activeTabIndex)) return let cb = () => {
if (this.activeTabIndex < this.tabCount - 1) {
if (this.activeTabIndex < this.tabCount - 1) { this.changeTab(this.activeTabIndex, this.activeTabIndex + 1)
this.activeTabIndex++ this.increaseMaxStep()
this.increaseMaxStep() } else {
this.checkStep() this.isLastStep = true
} else { this.$emit('finished')
this.isLastStep = true }
this.$emit('finished')
} }
this.beforeTabChange(this.activeTabIndex, cb)
}, },
prevTab () { prevTab () {
if (!this.beforeTabChange(this.activeTabIndex)) return let cb = () => {
if (this.activeTabIndex > 0) {
if (this.activeTabIndex > 0) { this.changeTab(this.activeTabIndex, this.activeTabIndex - 1)
this.activeTabIndex-- this.isLastStep = false
this.isLastStep = false }
} }
this.beforeTabChange(this.activeTabIndex, cb)
}, },
finish () { finish () {
this.$emit('on-complete') let cb = () => {
this.$emit('on-complete')
}
this.beforeTabChange(this.activeTabIndex, cb)
} }
}, },
mounted () { mounted () {
this.tabs = this.$children.filter((comp) => comp.$options.name === 'tab-content') this.tabs = this.$children.filter((comp) => comp.$options.name === 'tab-content')
if (this.tabs.length > 0) { if (this.tabs.length > 0 && this.startIndex === 0) {
let firstTab = this.tabs[this.activeTabIndex] let firstTab = this.tabs[this.activeTabIndex]
firstTab.show = true
firstTab.active = true firstTab.active = true
this.tryChangeRoute(firstTab)
} }
if (this.startIndex < this.tabs.length) { if (this.startIndex < this.tabs.length) {
let tabToActivate = this.tabs[this.startIndex]
this.activeTabIndex = this.startIndex this.activeTabIndex = this.startIndex
tabToActivate.active = true
this.maxStep = this.startIndex this.maxStep = this.startIndex
this.tryChangeRoute(this.tabs[this.startIndex])
} else { } else {
console.warn(`Prop startIndex set to ${this.startIndex} is greater than the number of tabs - ${this.tabs.length}. Make sure that the starting index is less than the number of tabs registered`) console.warn(`Prop startIndex set to ${this.startIndex} is greater than the number of tabs - ${this.tabs.length}. Make sure that the starting index is less than the number of tabs registered`)
} }
},
watch: {
activeTabIndex: function (newVal, oldVal) {
if (this.beforeTabChange(oldVal)) {
this.changeTab(oldVal, newVal)
}
}
} }
} }
</script> </script>
<style> <style>
@import "../assets/form-wizard/bootstrap.min.css"; @import "./../assets/tab-wizard/bootstrap.min.css";
</style> </style>
<style lang="scss"> <style lang="scss">
@import "./../assets/wizard"; @import "./../assets/wizard";
+1 -5
View File
@@ -1,5 +1,5 @@
<template> <template>
<div v-show="show" class="tab-container"> <div v-if="active" class="tab-container">
<slot> <slot>
</slot> </slot>
</div> </div>
@@ -26,14 +26,10 @@
*/ */
beforeChange: { beforeChange: {
type: Function type: Function
},
route: {
type: [String, Object]
} }
}, },
data () { data () {
return { return {
show: false,
active: false active: false
} }
} }
-6211
View File
File diff suppressed because it is too large Load Diff