From 62f8610f37ea3cea2503d32801d9d003e1bdea30 Mon Sep 17 00:00:00 2001 From: cristijora Date: Wed, 18 Oct 2017 23:10:17 +0300 Subject: [PATCH] Initial jsx support --- dev-example/main.js | 1 + src/components/FWizard.js | 453 ++++++++++++++++++++++++++++++++++ src/components/TabContent.vue | 33 +-- src/index.js | 3 +- 4 files changed, 473 insertions(+), 17 deletions(-) create mode 100644 src/components/FWizard.js diff --git a/dev-example/main.js b/dev-example/main.js index 8b3b96f..362f49b 100644 --- a/dev-example/main.js +++ b/dev-example/main.js @@ -7,6 +7,7 @@ import FormWizard from '../src/index' import WizardRoute from './WizardRoute.vue' import FormGeneratorRoute from './FormGeneratorRoute.vue' import TestRoute from './TestRoute.vue' +import '../dist/vue-form-wizard.min.css' const First = {template: '
First page
'} const Second = {template: '
Second page
'} diff --git a/src/components/FWizard.js b/src/components/FWizard.js new file mode 100644 index 0000000..07029a7 --- /dev/null +++ b/src/components/FWizard.js @@ -0,0 +1,453 @@ +import WizardButton from './WizardButton.vue' +import WizardStep from './WizardStep.vue' +import {isPromise, findElementAndFocus, getFocusedTabIndex} from './helpers' + +export default { + name: 'form-wizard', + components: { + WizardButton, + WizardStep + }, + provide () { + return { + addTab: this.addTab, + removeTab: this.removeTab, + shape: this.shape, + color: this.color, + errorColor: this.errorColor, + reRegisterTabs: this.reRegisterTabs + } + }, + 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' + }, + layout: { + type: String, + default: 'horizontal' + }, + stepsClasses: { + type: [String, Array], + default: '' + }, + stepSize: { + type: String, + default: 'md', + validator: (value) => { + let acceptedValues = ['xs', 'sm', 'md', 'lg'] + return acceptedValues.indexOf(value) !== -1 + } + }, + /** + * 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, + currentPercentage: 0, + maxStep: 0, + loading: false, + tabs: [] + } + }, + computed: { + slotProps () { + return { + nextTab: this.nextTab, + prevTab: this.prevTab, + activeTabIndex: this.activeTabIndex, + isLastStep: this.isLastStep, + fillButtonStyle: this.fillButtonStyle + } + }, + tabCount () { + return this.tabs.length + }, + isLastStep () { + return this.activeTabIndex === this.tabCount - 1 + }, + isVertical () { + return this.layout === 'vertical' + }, + displayPrevButton () { + return this.activeTabIndex !== 0 + }, + stepPercentage () { + return 1 / (this.tabCount * 2) * 100 + }, + progressBarStyle () { + return { + backgroundColor: this.color, + width: `${this.progress}%`, + color: this.color + } + }, + 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: { + emitTabChange (prevIndex, nextIndex) { + // this.$emit('on-change', prevIndex, nextIndex) + // this.$emit('update:startIndex', nextIndex) + }, + addTab (item) { + const index = this.$slots.default.indexOf(item.$vnode) + item.tabId = `t-${item.title.replace(/ /g, '')}${index}` + this.tabs.splice(index, 0, item) + // if a step is added before the current one, go to it + if (index < this.activeTabIndex + 1) { + 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 + this.emitTabChange(this.activeTabIndex + 1, this.activeTabIndex) + } + tabs.splice(index, 1) + } + }, + navigateToTab (index) { + let validate = index > this.activeTabIndex + if (index <= this.maxStep) { + let cb = () => { + if (validate && index - this.activeTabIndex > 1) { + // validate all steps recursively until destination index + this.changeTab(this.activeTabIndex, this.activeTabIndex + 1) + this.beforeTabChange(this.activeTabIndex, cb) + } else { + this.changeTab(this.activeTabIndex, index) + } + } + if (validate) { + this.beforeTabChange(this.activeTabIndex, cb) + } else { + this.setValidationError(null) + cb() + } + } + return index <= this.maxStep + }, + nextTab () { + let cb = () => { + if (this.activeTabIndex < this.tabCount - 1) { + this.changeTab(this.activeTabIndex, this.activeTabIndex + 1) + } else { + this.$emit('on-complete') + } + } + this.beforeTabChange(this.activeTabIndex, cb) + }, + prevTab () { + let cb = () => { + if (this.activeTabIndex > 0) { + this.setValidationError(null) + this.changeTab(this.activeTabIndex, this.activeTabIndex - 1) + } + } + if (this.validateOnBack) { + this.beforeTabChange(this.activeTabIndex, cb) + } else { + cb() + } + }, + focusNextTab () { + let tabIndex = getFocusedTabIndex(this.tabs) + if (tabIndex !== -1 && tabIndex < this.tabs.length - 1) { + let tabToFocus = this.tabs[tabIndex + 1] + if (tabToFocus.checked) { + findElementAndFocus(tabToFocus.tabId) + } + } + }, + focusPrevTab () { + let tabIndex = getFocusedTabIndex(this.tabs) + if (tabIndex !== -1 && tabIndex > 0) { + let toFocusId = this.tabs[tabIndex - 1].tabId + findElementAndFocus(toFocusId) + } + }, + 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 (isPromise(promiseFn)) { + 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, emitChangeEvent = true) { + let oldTab = this.tabs[oldIndex] + let newTab = this.tabs[newIndex] + if (oldTab) { + oldTab.active = false + } + if (newTab) { + newTab.active = true + } + if (emitChangeEvent && this.activeTabIndex !== newIndex) { + this.emitTabChange(oldIndex, newIndex) + } + this.activeTabIndex = newIndex + this.activateTabAndCheckStep(this.activeTabIndex) + return true + }, + tryChangeRoute (tab) { + if (this.$router && tab.route) { + this.$router.push(tab.route) + } + }, + 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] + if (tab) { + tab.active = true + tab.checked = true + this.tryChangeRoute(tab) + } + }, + activateTabAndCheckStep (index) { + this.activateTab(index) + if (index > this.maxStep) { + this.maxStep = index + } + this.activeTabIndex = index + }, + 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' (newRoute) { + this.checkRouteChange(newRoute) + } + }, + render () { + const tabs = this.$slots.default.filter( + component => component.componentOptions + ) + if (tabs.length !== this.tabs.length) { + this.tabs = tabs + } + const steps = this.tabs.map((tab, index) => { + return this.navigateToTab(index)} + transition={this.transition} + index={this.index}> + + }) + return (
+
+ +

{this.title}

+

{this.subtitle}

+
+
+
+
+
+
+
+
    + {steps} +
+
+ + {this.tabs[this.activeTabIndex]} + +
+ + +
+
) + } +} diff --git a/src/components/TabContent.vue b/src/components/TabContent.vue index 8a7bced..aa9ab68 100644 --- a/src/components/TabContent.vue +++ b/src/components/TabContent.vue @@ -1,17 +1,17 @@ + diff --git a/src/index.js b/src/index.js index bb1b74d..d0a974f 100644 --- a/src/index.js +++ b/src/index.js @@ -2,9 +2,10 @@ import FormWizard from './components/FormWizard.vue' import TabContent from './components/TabContent.vue' import WizardButton from './components/WizardButton.vue' import WizardStep from './components/WizardStep.vue' +import FWizard from './components/FWizard' const VueFormWizard = { install (Vue) { - Vue.component('form-wizard', FormWizard) + Vue.component('form-wizard', FWizard) Vue.component('tab-content', TabContent) Vue.component('wizard-button', WizardButton) Vue.component('wizard-step', WizardStep)