Skip to content

Instantly share code, notes, and snippets.

@cristijora
Created July 13, 2017 16:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cristijora/8a40d977599f2ade494edb0f2a07c7ce to your computer and use it in GitHub Desktop.
Save cristijora/8a40d977599f2ade494edb0f2a07c7ce to your computer and use it in GitHub Desktop.
(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