Created
July 13, 2017 16:52
-
-
Save cristijora/e03c1786e0d5afb5fe7798e5640efc3c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | |
typeof define === 'function' && define.amd ? define(['exports'], factory) : | |
(factory((global.vueFormWizard = global.vueFormWizard || {}))); | |
}(this, (function (exports) { 'use strict'; | |
var FormWizard = {render: function(){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"vue-form-wizard"},[_c('div',{staticClass:"wizard-header"},[_vm._t("title",[_c('h4',{staticClass:"wizard-title"},[_vm._v(_vm._s(_vm.title))]),_c('p',{staticClass:"category"},[_vm._v(_vm._s(_vm.subtitle))])])],2),_c('div',{staticClass:"wizard-navigation"},[_c('div',{staticClass:"wizard-progress-with-circle"},[_c('div',{staticClass:"wizard-progress-bar",style:(_vm.progressBarStyle)})]),_c('ul',{staticClass:"wizard-nav wizard-nav-pills"},_vm._l((_vm.tabs),function(tab,index){return _c('li',{class:{active:tab.active}},[_c('a',{attrs:{"href":""},on:{"click":function($event){$event.preventDefault();_vm.navigateToTab(index);}}},[_c('div',{staticClass:"wizard-icon-circle",class:{checked:_vm.isChecked(index),square_shape:_vm.isStepSquare, tab_shape:_vm.isTabShape},style:([_vm.isChecked(index)? _vm.stepCheckedStyle : {}, tab.validationError ? _vm.errorStyle : {}])},[_c('transition',{attrs:{"name":_vm.transition,"mode":"out-in"}},[(tab.active)?_c('div',{staticClass:"wizard-icon-container",class:{square_shape:_vm.isStepSquare, tab_shape:_vm.isTabShape},style:([tab.active ? _vm.iconActiveStyle: {}, tab.validationError ? _vm.errorStyle : {}])},[(tab.icon)?_c('i',{staticClass:"wizard-icon",class:tab.icon}):_c('i',{staticClass:"wizard-icon"},[_vm._v(_vm._s(index + 1))])]):_vm._e(),(!tab.active && tab.icon)?_c('i',{staticClass:"wizard-icon",class:tab.icon}):_vm._e(),_vm._v(" "),(!tab.active && !tab.icon)?_c('i',{staticClass:"wizard-icon"},[_vm._v(_vm._s(index + 1))]):_vm._e()])],1),_c('span',{staticClass:"stepTitle",class:{active:tab.active, has_error:tab.validationError},style:(tab.active ? _vm.stepTitleStyle : {})},[_vm._v(_vm._s(tab.title))])])])})),_c('div',{staticClass:"wizard-tab-content"},[_vm._t("default")],2)]),(!_vm.hideButtons)?_c('div',{staticClass:"wizard-card-footer clearfix"},[[(_vm.displayPrevButton)?_c('span',{staticClass:"wizard-footer-left",on:{"click":_vm.prevTab}},[_vm._t("prev",[_c('button',{staticClass:"wizard-btn btn-default wizard-btn-wd",style:(_vm.fillButtonStyle),attrs:{"type":"button","disabled":_vm.loading}},[_vm._v(_vm._s(_vm.backButtonText))])])],2):_vm._e()],[(_vm.isLastStep)?_c('span',{staticClass:"wizard-footer-right",on:{"click":_vm.finish}},[_vm._t("finish",[_c('button',{staticClass:"wizard-btn btn-fill wizard-btn-wd btn-next",style:(_vm.fillButtonStyle),attrs:{"type":"button"}},[_vm._v(_vm._s(_vm.finishButtonText))])])],2):_vm._e()],[(!_vm.isLastStep)?_c('span',{staticClass:"wizard-footer-right",on:{"click":_vm.nextTab}},[_vm._t("next",[_c('button',{staticClass:"wizard-btn btn-fill wizard-btn-wd btn-next",style:(_vm.fillButtonStyle),attrs:{"type":"button","disabled":_vm.loading}},[_vm._v(_vm._s(_vm.nextButtonText))])])],2):_vm._e()]],2):_vm._e()])},staticRenderFns: [], | |
name: 'form-wizard', | |
props: { | |
title: { | |
type: String, | |
default: 'Awesome Wizard' | |
}, | |
subtitle: { | |
type: String, | |
default: 'Split a complicated flow in multiple steps' | |
}, | |
nextButtonText: { | |
type: String, | |
default: 'Next' | |
}, | |
backButtonText: { | |
type: String, | |
default: 'Back' | |
}, | |
finishButtonText: { | |
type: String, | |
default: 'Finish' | |
}, | |
hideButtons: { | |
type: Boolean, | |
default: false | |
}, | |
validateOnBack: Boolean, | |
/*** | |
* Applies to text, border and circle | |
*/ | |
color: { | |
type: String, | |
default: '#e74c3c' | |
}, | |
errorColor: { | |
type: String, | |
default: '#8b0000' | |
}, | |
shape: { | |
type: String, | |
default: 'circle' | |
}, | |
/** | |
* Name of the transition when transition between steps | |
* */ | |
transition: { | |
type: String, | |
default: '' | |
}, | |
/*** | |
* | |
* Index of the initial tab to display | |
*/ | |
startIndex: { | |
type: Number, | |
default: 0, | |
validator: (value) => { | |
return value >= 0 | |
} | |
} | |
}, | |
data () { | |
return { | |
activeTabIndex: 0, | |
isLastStep: false, | |
currentPercentage: 0, | |
maxStep: 0, | |
loading: false, | |
tabs: [] | |
} | |
}, | |
computed: { | |
tabCount () { | |
return this.tabs.length | |
}, | |
displayPrevButton () { | |
return this.activeTabIndex !== 0 | |
}, | |
stepPercentage () { | |
return 1 / (this.tabCount * 2) * 100 | |
}, | |
progressBarStyle () { | |
return { | |
backgroundColor: this.color, | |
width: `${this.progress}%`, | |
color: this.color | |
} | |
}, | |
iconActiveStyle () { | |
return { | |
backgroundColor: this.color | |
} | |
}, | |
stepCheckedStyle () { | |
return { | |
borderColor: this.color | |
} | |
}, | |
errorStyle () { | |
return { | |
borderColor: this.errorColor, | |
backgroundColor: this.errorColor | |
} | |
}, | |
stepTitleStyle () { | |
var isError = this.tabs[this.activeTabIndex].validationError; | |
return { | |
color: isError ? this.errorColor : this.color | |
} | |
}, | |
isStepSquare () { | |
return this.shape === 'square' | |
}, | |
isTabShape () { | |
return this.shape === 'tab' | |
}, | |
fillButtonStyle () { | |
return { | |
backgroundColor: this.color, | |
borderColor: this.color, | |
color: 'white' | |
} | |
}, | |
progress () { | |
let percentage = 0; | |
if (this.activeTabIndex > 0) { | |
let stepsToAdd = 1; | |
let stepMultiplier = 2; | |
percentage = this.stepPercentage * ((this.activeTabIndex * stepMultiplier) + stepsToAdd); | |
} else { | |
percentage = this.stepPercentage; | |
} | |
return percentage | |
} | |
}, | |
methods: { | |
addTab (item) { | |
const index = this.$slots.default.indexOf(item.$vnode); | |
this.tabs.splice(index, 0, item); | |
// if a step is added before the current one, go to it | |
if (index < this.activeTabIndex + 1) { | |
console.log('Changing tabs', index, this.activeTabIndex); | |
this.maxStep = index; | |
this.changeTab(this.activeTabIndex + 1, index); | |
} | |
}, | |
removeTab (item) { | |
const tabs = this.tabs; | |
const index = tabs.indexOf(item); | |
if (index > -1) { | |
// Go one step back if the current step is removed | |
if (index === this.activeTabIndex) { | |
this.maxStep = this.activeTabIndex - 1; | |
this.changeTab(this.activeTabIndex, this.activeTabIndex - 1); | |
} | |
if (index < this.activeTabIndex) { | |
this.maxStep = this.activeTabIndex - 1; | |
this.activeTabIndex = this.activeTabIndex - 1; | |
} | |
tabs.splice(index, 1); | |
} | |
}, | |
isChecked (index) { | |
return index <= this.maxStep | |
}, | |
navigateToTab (index) { | |
this.$emit('on-change', this.activeTabIndex, index); | |
let validate = index > this.activeTabIndex; | |
if (index <= this.maxStep) { | |
let cb = () => { | |
this.changeTab(this.activeTabIndex, index); | |
}; | |
if (validate) { | |
this.beforeTabChange(this.activeTabIndex, cb); | |
} else { | |
this.setValidationError(null); | |
cb(); | |
} | |
} | |
}, | |
nextTab () { | |
this.$emit('on-change', this.activeTabIndex, this.activeTabIndex + 1); | |
let cb = () => { | |
if (this.activeTabIndex < this.tabCount - 1) { | |
this.changeTab(this.activeTabIndex, this.activeTabIndex + 1); | |
} else { | |
this.isLastStep = true; | |
this.$emit('finished'); | |
} | |
}; | |
this.beforeTabChange(this.activeTabIndex, cb); | |
}, | |
prevTab () { | |
this.$emit('on-change', this.activeTabIndex, this.activeTabIndex - 1); | |
let cb = () => { | |
if (this.activeTabIndex > 0) { | |
this.setValidationError(null); | |
this.changeTab(this.activeTabIndex, this.activeTabIndex - 1); | |
this.isLastStep = false; | |
} | |
}; | |
if (this.validateOnBack) { | |
this.beforeTabChange(this.activeTabIndex, cb); | |
} else { | |
cb(); | |
} | |
}, | |
finish () { | |
let cb = () => { | |
this.$emit('on-complete'); | |
}; | |
this.beforeTabChange(this.activeTabIndex, cb); | |
}, | |
setLoading (value) { | |
this.loading = value; | |
this.$emit('on-loading', value); | |
}, | |
setValidationError (error) { | |
this.tabs[this.activeTabIndex].validationError = error; | |
this.$emit('on-error', error); | |
}, | |
validateBeforeChange (promiseFn, callback) { | |
this.setValidationError(null); | |
// 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((error) => { | |
this.setLoading(false); | |
this.setValidationError(error); | |
}); | |
// 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(); | |
} else { | |
this.tabs[this.activeTabIndex].validationError = 'error'; | |
} | |
}, | |
beforeTabChange (index, callback) { | |
if (this.loading) { | |
return | |
} | |
let oldTab = this.tabs[index]; | |
if (oldTab && oldTab.beforeChange !== undefined) { | |
let tabChangeRes = oldTab.beforeChange(); | |
this.validateBeforeChange(tabChangeRes, callback); | |
} else { | |
callback(); | |
} | |
}, | |
changeTab (oldIndex, newIndex) { | |
let oldTab = this.tabs[oldIndex]; | |
let newTab = this.tabs[newIndex]; | |
if (oldTab) { | |
oldTab.active = false; | |
} | |
if (newTab) { | |
newTab.active = true; | |
} | |
this.activeTabIndex = newIndex; | |
this.checkStep(); | |
this.tryChangeRoute(newTab); | |
this.increaseMaxStep(); | |
return true | |
}, | |
tryChangeRoute (tab) { | |
if (this.$router && tab.route) { | |
this.$router.push(tab.route); | |
} | |
}, | |
checkStep () { | |
if (this.activeTabIndex === this.tabCount - 1) { | |
this.isLastStep = true; | |
} else { | |
this.isLastStep = false; | |
} | |
}, | |
increaseMaxStep () { | |
if (this.activeTabIndex > this.maxStep) { | |
this.maxStep = this.activeTabIndex; | |
} | |
}, | |
checkRouteChange (route) { | |
let matchingTabIndex = -1; | |
let matchingTab = this.tabs.find((tab, index) => { | |
let match = tab.route === route; | |
if (match) { | |
matchingTabIndex = index; | |
} | |
return match | |
}); | |
if (matchingTab && !matchingTab.active) { | |
const shouldValidate = matchingTabIndex > this.activeTabIndex; | |
this.navigateToTab(matchingTabIndex, shouldValidate); | |
} | |
}, | |
deactivateTabs () { | |
this.tabs.forEach(tab => { | |
tab.active = false; | |
}); | |
}, | |
activateTab (index) { | |
this.deactivateTabs(); | |
let tab = this.tabs[index]; | |
tab.active = true; | |
this.tryChangeRoute(tab); | |
}, | |
activateTabAndCheckStep (index) { | |
this.activateTab(index); | |
this.checkStep(); | |
this.maxStep = this.startIndex; | |
this.activeTabIndex = this.startIndex; | |
}, | |
initializeTabs () { | |
if (this.tabs.length > 0 && this.startIndex === 0) { | |
this.activateTab(this.activeTabIndex); | |
} | |
if (this.startIndex < this.tabs.length) { | |
this.activateTabAndCheckStep(this.startIndex); | |
} else { | |
window.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`); | |
} | |
} | |
}, | |
mounted () { | |
this.initializeTabs(); | |
}, | |
watch: { | |
'$route.path': function (newRoute) { | |
this.checkRouteChange(newRoute); | |
} | |
} | |
}; | |
var TabContent = {render: function(){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{directives:[{name:"show",rawName:"v-show",value:(_vm.active),expression:"active"}],staticClass:"wizard-tab-container"},[_vm._t("default")],2)},staticRenderFns: [], | |
name: 'tab-content', | |
props: { | |
title: { | |
type: String, | |
default: '' | |
}, | |
/*** | |
* Icon name for the upper circle corresponding to the tab | |
* Supports themify icons only for now. | |
*/ | |
icon: { | |
type: String, | |
default: '' | |
}, | |
/*** | |
* Function to execute before tab switch. Return value must be boolean | |
* If the return result is false, tab switch is restricted | |
*/ | |
beforeChange: { | |
type: Function | |
}, | |
route: { | |
type: [String, Object] | |
} | |
}, | |
data () { | |
return { | |
active: false, | |
validationError: null | |
} | |
}, | |
mounted () { | |
this.$parent.addTab(this); | |
}, | |
destroyed () { | |
if (this.$el && this.$el.parentNode) { | |
this.$el.parentNode.removeChild(this.$el); | |
} | |
this.$parent.removeTab(this); | |
} | |
}; | |
var VueFormWizard = { | |
install: function install (Vue) { | |
Vue.component('form-wizard', FormWizard); | |
Vue.component('tab-content', TabContent); | |
} | |
}; | |
// Automatic installation if Vue has been added to the global scope. | |
if (typeof window !== 'undefined' && window.Vue) { | |
window.Vue.use(VueFormWizard); | |
} | |
exports['default'] = VueFormWizard; | |
exports.FormWizard = FormWizard; | |
exports.TabContent = TabContent; | |
Object.defineProperty(exports, '__esModule', { value: true }); | |
}))); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment