2
0
mirror of https://github.com/tenrok/vue-form-wizard.git synced 2026-06-24 22:50:33 +03:00
# Conflicts:
#	package.json
This commit is contained in:
cristijora
2017-04-26 20:52:11 +03:00
14 changed files with 280 additions and 139 deletions
+17 -23
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/)
@@ -19,7 +16,8 @@ Other demos:
* [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) * [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 * [Vue router integration](https://jsfiddle.net/bt5dhqtf/267/) 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
* [Async validation](https://jsfiddle.net/bt5dhqtf/272/) `before-change` prop can accept a promise that is resolved with `true` which will execute the promise before switching to another step/tab (NOTE: This feature is not present in the npm package yet)
# Usage # Usage
@@ -37,7 +35,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,17 +138,16 @@ 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]
} }
} }
``` ```
## Events
Vue-form-wizard emits certain events when certain actions happen inside the component. The events can be noticed in some of the demos and especially in the [async validation demo](https://jsfiddle.net/bt5dhqtf/272/)
* **on-complete** Called when the finish button is clicked and the `before-change` for the last step (if present) was executed. No params are sent together with this event. `this.$emit('on-complete')`
* **on-loading** Called whenever an async `before-change` is executed. This event is emitted before executing `before-change` and after finishing execution of `before-change` method. `on-loading` is emitted together with a Boolean value. `this.$emit('on-loading', value)`
* **on-validate** Called whenever the execution of a `before-change` method is completed. The event sends along a Boolean which represents the validation result as well as an int with te tab index. `this.$emit('on-validate', validationResult, this.activeTabIndex)`
# Slots ## Slots
* **Default** - Used for tab-contents * **Default** - Used for tab-contents
* **title** - Upper title section including sub-title * **title** - Upper title section including sub-title
* **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)
@@ -158,13 +155,10 @@ props: {
* **finish** - Finish button content * **finish** - Finish button content
## Contribution ## Contribution
Open an issue or send a Pull request if you feel that something is missing or doesn't work. 1. Fork the repo
2. run `npm install`
## License 3. `npm run dev` for launching the dev example
vue-form-wizard is available under the [MIT license](https://tldrlegal.com/license/mit-license). 4. After making your changes make sure to pull the changes from the source repo to avoid conflicts
5. `npm run build` to generate the new js and css bundles
## Contact 6. Commit your changes + the js and css bundles so it's easy to test them right away in fiddles, codepen etc
7. Open a Pull Request. For more information refer to [github forking workflow](https://gist.github.com/Chaser324/ce0505fbed06b947d962)
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,
+89
View File
@@ -0,0 +1,89 @@
<template>
<div>
<form-wizard @on-complete="onComplete"
shape="circle"
color="#e74c3c"
@on-loading="setLoading"
class="card">
<tab-content title="Personal details"
:before-change="validateAsync"
route="/first"
icon="ti-user">
</tab-content>
<tab-content title="Additional Info"
:before-change="validate"
route="/second"
icon="ti-settings">
</tab-content>
<tab-content title="Last step"
:before-change="validateAsync"
route="/third"
icon="ti-check">
</tab-content>
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
<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);
}
}
+6 -4
View File
@@ -1,10 +1,12 @@
// 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 Vue from 'vue'
import VueRouter from 'vue-router' import VueRouter from 'vue-router'
import App from './App.vue' import App from './App.vue'
import FormWizard from './../src/index' import FormWizard from '../src/index'
const First = { template: '<div>First</div>' } const First = { template: '<div>First page</div>' }
const Second = { template: '<div>Second</div>' } const Second = { template: '<div>Second page</div>' }
const Third = { template: '<div>Third</div>' } const Third = { template: '<div>Third page</div>' }
const router = new VueRouter({ const router = new VueRouter({
mode: 'history', mode: 'history',
-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>
+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
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "vue-form-wizard", "name": "vue-form-wizard",
"version": "0.1.12", "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-form-wizard.js",
"homepage": "https://github.com/cristijora/vue-form-wizard", "homepage": "https://github.com/cristijora/vue-form-wizard",
@@ -85,7 +85,7 @@
"url-loader": "^0.5.8", "url-loader": "^0.5.8",
"vue": "^2.2.2", "vue": "^2.2.2",
"vue-loader": "^11.1.4", "vue-loader": "^11.1.4",
"vue-router": "^2.4.0", "vue-router": "^2.5.1",
"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",
+46
View File
@@ -8,6 +8,9 @@
padding: $padding-base-vertical $padding-base-horizontal; padding: $padding-base-vertical $padding-base-horizontal;
@include btn-styles($default-color, $default-states-color);
@include transition($fast-transition-time, linear);
&:hover, &:hover,
&:focus{ &:focus{
outline: 0 !important; outline: 0 !important;
@@ -26,14 +29,57 @@
margin-right: 0px; 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{ .btn-disabled{
cursor: default; cursor: default;
} }
.btn-simple{
border: $none;
padding: $padding-base-vertical $padding-base-horizontal;
}
.navbar .navbar-nav > li > a.btn.btn-wd, .navbar .navbar-nav > li > a.btn.btn-wd,
.btn-wd{ .btn-wd{
min-width: 140px; 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;
}
+1 -1
View File
@@ -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 -35
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,26 +196,59 @@
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
@@ -239,58 +273,55 @@
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) 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]) 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 -2
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>
@@ -33,7 +33,6 @@
}, },
data () { data () {
return { return {
show: false,
active: false active: false
} }
} }