2
0
mirror of https://github.com/tenrok/vue-form-wizard.git synced 2026-06-16 06:10:32 +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
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.
Vue-form-wizard is inspired by [creative-tim wizards](https://www.creative-tim.com/bootstrap-themes/wizard) but simplified and
more customizable
wasting time on details. Just forget about id's, external scripts and jQuery dependencies
# Demos
Basic [demo](https://jsfiddle.net/bt5dhqtf/97/)
@@ -18,8 +15,6 @@ Other demos:
* [Custom title slot](https://jsfiddle.net/bt5dhqtf/102/)
* [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/)
* [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
@@ -37,7 +32,7 @@ Download the css and js files from `dist` folder or reference them directly from
//global registration
import 'vue-form-wizard'
import 'vue-form-wizard/dist/vue-form-wizard.min.css'
Vue.use(VueFormWizard)
Vue.use(VueTabWizard)
//local registration
import {FormWizard, TabContent} from 'vue-form-wizard'
@@ -140,12 +135,6 @@ props: {
*/
beforeChange: {
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)
* **next** - Next 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 = {
entry: {
app: './dev/main.js'
app: './dev-example/main.js'
},
output: {
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",
"version": "0.1.11",
"version": "0.1.6",
"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",
"scripts": {
"dev": "node build/dev-server.js",
@@ -25,6 +25,7 @@
"url": "https://github.com/cristijora/vue-form-wizard"
},
"devDependencies": {
"vue": "^2.2.2",
"autoprefixer": "^6.7.2",
"babel-core": "^6.22.1",
"babel-eslint": "^7.1.1",
@@ -83,9 +84,7 @@
"sinon-chai": "^2.8.0",
"stats-webpack-plugin": "^0.6.0",
"url-loader": "^0.5.8",
"vue": "^2.2.2",
"vue-loader": "^11.1.4",
"vue-router": "^2.4.0",
"vue-style-loader": "^2.0.0",
"vue-template-compiler": "^2.2.4",
"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;
white-space: nowrap;
@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,
+8 -8
View File
@@ -1,8 +1,8 @@
/*@import "form-wizard/bs_button";
@import "form-wizard/bs_nav_pills";
@import "form-wizard/bs_progress_bar";*/
@import "form-wizard/variables";
@import "form-wizard/mixins";
@import "form-wizard/buttons";
@import "form-wizard/navs-pagination";
@import "form-wizard/wizard-card";
//@import "tab-wizard/bs_button";
//@import "tab-wizard/bs_nav_pills";
//@import "tab-wizard/bs_progress_bar";
@import "tab-wizard/variables";
@import "tab-wizard/mixins";
@import "tab-wizard/buttons";
@import "tab-wizard/navs-pagination";
@import "tab-wizard/wizard-card";
+66 -43
View File
@@ -43,7 +43,7 @@
<template>
<span @click="prevTab" v-if="displayPrevButton">
<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}}
</button>
</slot>
@@ -63,7 +63,7 @@
<template>
<span @click="nextTab" class="pull-right" v-if="!isLastStep">
<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}}
</button>
</slot>
@@ -130,6 +130,7 @@
isLastStep: false,
currentPercentage: 0,
maxStep: 0,
loading: false,
tabs: []
}
},
@@ -195,38 +196,65 @@
return index <= this.maxStep
},
navigateToTab (index) {
if (index <= this.maxStep && this.beforeTabChange(this.activeTabIndex)) {
this.changeTab(this.activeTabIndex, index)
if (index <= this.maxStep) {
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]
if (oldTab && oldTab.beforeChange !== undefined) {
return oldTab.beforeChange()
let tabChangeRes = oldTab.beforeChange()
this.validateBeforeChange(tabChangeRes, callback)
} else {
callback()
}
return true
},
changeTab (oldIndex, newIndex) {
let oldTab = this.tabs[oldIndex]
let newTab = this.tabs[newIndex]
if (oldTab) {
oldTab.show = false
oldTab.active = false
}
if (newTab) {
newTab.show = true
newTab.active = true
}
this.activeTabIndex = newIndex
this.checkStep()
this.tryChangeRoute(newTab)
return true
},
tryChangeRoute (tab) {
if (this.$router && tab.route) {
this.$router.push(tab.route)
}
},
checkStep () {
if (this.activeTabIndex === this.tabCount - 1) {
this.isLastStep = true
@@ -239,58 +267,53 @@
this.maxStep = this.activeTabIndex
}
},
nextTab () {
if (!this.beforeTabChange(this.activeTabIndex)) return
if (this.activeTabIndex < this.tabCount - 1) {
this.activeTabIndex++
this.increaseMaxStep()
this.checkStep()
} else {
this.isLastStep = true
this.$emit('finished')
let cb = () => {
if (this.activeTabIndex < this.tabCount - 1) {
this.changeTab(this.activeTabIndex, this.activeTabIndex + 1)
this.increaseMaxStep()
} else {
this.isLastStep = true
this.$emit('finished')
}
}
this.beforeTabChange(this.activeTabIndex, cb)
},
prevTab () {
if (!this.beforeTabChange(this.activeTabIndex)) return
if (this.activeTabIndex > 0) {
this.activeTabIndex--
this.isLastStep = false
let cb = () => {
if (this.activeTabIndex > 0) {
this.changeTab(this.activeTabIndex, this.activeTabIndex - 1)
this.isLastStep = false
}
}
this.beforeTabChange(this.activeTabIndex, cb)
},
finish () {
this.$emit('on-complete')
let cb = () => {
this.$emit('on-complete')
}
this.beforeTabChange(this.activeTabIndex, cb)
}
},
mounted () {
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]
firstTab.show = true
firstTab.active = true
this.tryChangeRoute(firstTab)
}
if (this.startIndex < this.tabs.length) {
let tabToActivate = this.tabs[this.startIndex]
this.activeTabIndex = this.startIndex
tabToActivate.active = true
this.maxStep = this.startIndex
this.tryChangeRoute(this.tabs[this.startIndex])
} 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`)
}
},
watch: {
activeTabIndex: function (newVal, oldVal) {
if (this.beforeTabChange(oldVal)) {
this.changeTab(oldVal, newVal)
}
}
}
}
</script>
<style>
@import "../assets/form-wizard/bootstrap.min.css";
@import "./../assets/tab-wizard/bootstrap.min.css";
</style>
<style lang="scss">
@import "./../assets/wizard";
+1 -5
View File
@@ -1,5 +1,5 @@
<template>
<div v-show="show" class="tab-container">
<div v-if="active" class="tab-container">
<slot>
</slot>
</div>
@@ -26,14 +26,10 @@
*/
beforeChange: {
type: Function
},
route: {
type: [String, Object]
}
},
data () {
return {
show: false,
active: false
}
}
-6211
View File
File diff suppressed because it is too large Load Diff