Git Product home page Git Product logo

vurian-wizard's Introduction

@vurian/wizard - A wizard machine component for Vue 3

Netlify Status npm version npm download

Vurian logo

The more organized and out-of-the-box for Wizard Component from the Vurian project, written in TypeScript and using XState.

๐Ÿ“š Documentation: https://vurianjs.netlify.app

๐ŸŽฎ Playground: TBD

๐Ÿ’ป Demo: TBD

Example

An example of how to define and use the component as shown below:

1. Define the data context for the Wizard

In the script we will need to define the wizard context, with any data related to the general flow of the wizard per need. However, you need to initialize at least 2 fields: completedSteps and currentView

In the example below we define a wizard with shipping, paymentMethod, cart, shippingMethod, billing and a flag for agreeToTerms as the common data needed for a checkout wizard.

<script setup>

const context = {
  completedSteps: [],
  cart: [],
  shipping: {
    address: '',
    phone: '',
    email: '',
    id: ''
  },
  shippingMethod: '',
  billing: {
    address: '',
  },
  agreeToTerms: false,
  paymentMethod: ''
}

</script>

2. Define the steps (or states) of the Wizard

Now let's pass the configuration of the steps (or states) for the wizard to handle. For each step, it's essential to provide the following:

  • id - id of the state.
  • title - title of the state to display on the progress flow in the wizard header.
  • stepView - this is the component to render for the relevant state.
  • order - order of appearance of the state in the flow. This will be used to initialize which state to go next/prev.

The states is an object contains all the steps needed, following the above format, as seen below:

const states = {
  review: {
    title: 'Review',
      id: 'review',
      stepView: ReviewCart,
      order: 0,
      meta: {
        description: 'Review your cart',
      },
  }
}

If you wish to add internal events such as updating shipping address, payment method, etc, you can create your own events by using the property on. However, to handle updating any context data of the wizard will require using assign function from xstate.

Below is the example of the configuration for step/state shipping in states

const states = {
  /*...*/
  shipping: {
    title: 'Shipping',
    id: 'shipping',
    stepView: Shipping,
    order: 1,
    on: {
      //event to update address to be triggered inside Shipping component
      UPDATE_ADDRESS: {
        actions: assign({
          shipping: (_, event) =>  event.address
        }),
        meta: {
          description: 'Provide shipping address'
        }
      },
      //event to update shipping method to be triggered inside Shipping component
      SELECT_METHOD: {
        actions: assign({
          shippingMethod: (_, event) => event.method 
        }),
        meta: {
          description: 'Select shipping method'
        }
      },
    },
  },
}

If it is the final step, and you don't wish to enable the prev button, you can set state.type to final.

By default next/prev is handled automatically without being defined in the state configuration. However, in certain case you wish to add condition to enable/disable the next/prev functionality (as a step guard), such as in payment it is a must to complete the payment method and agreeing to the T&C.

In this scenario you can use on.NEXT.cond and define the method to trigger for validation.

const states = {
  /*...*/
  payment: {
    title: 'Payment',
    id: 'payment',
    stepView: Payment,
    order: 3,
    on: {
      /*...*/
      NEXT: {
        cond: 'isAgreeToTerm'
      }
    }
  },
}

The actual method implementation should be provide in step 3 below - define options.

3. Define additional options (validation per step, etc)

Options are the external configurations to setup for the wizard, based on Xstate options. It can receive guards, actions, delays, activities, and services.

In our example we will set the guard validation for NEXT event in Payment Step.

const options= {
  guards: {
    isAgreeToTerm: (ctx) => ctx.agreeToTerms && !!ctx.paymentMethod,
  }
}

Upon user reaches to Payment step, the Next button will be disabled until user fulfills all the inputs required (agreedToTerms and paymentMethod). Out of the box!

4. Add onComplete event listener

We can also pass an event handler to the wizard to trigger when all the steps is completed.

const onComplete = async() => {
  //redirecting to home page
}

5. Define the wizard's id and initial step

Two last configuration props we need to initialize is id and initial as id is required to create wizard state machine, and initial defines the first step for the wizard to start from.

initial can be the key of the state defined in states, or the id of that state.

const id = "checkout"
const initial = "review", //start with the review step

6. Bind to VrWizard component

And finally, we need to pass those variables to the VrWizard component.

<template>
  <VrWizard 
    :options="options" 
    :id="id" 
    :context="context" 
    :states="states" 
    :initial="initial"
    :onComplete="onComplete"
    title="My Vurian Checkout Wizard"
    description="Just checking out"
  />
</template>

And the full code will look something similar to the below code:

<script setup>
import VrWizard from '@vurian/wizard';
import ReviewCart from './components/Steps/ReviewCart.vue';
import Payment from './components/Steps/Payment.vue';
import Confirmation from './components/Steps/Confirmation.vue';
import Shipping from './components/Steps/Shipping.vue';
import Billing from './components/Steps/Billing.vue';
import { assign } from "xstate";

const config = {
  id: "checkout",
  initial: "review",
  context: {
    completedSteps: [],
    cart: [],
    shipping: {
      address: '',
      phone: '',
      email: '',
      id: ''
    },
    shippingMethod: '',
    billing: {
      address: '',
    },
    agreeToTerms: false,
    paymentMethod: ''
  },
  states: {
    review: {
      title: 'Review',
      id: 'review',
      meta: {
        description: 'Review your cart',
      },
      stepView: ReviewCart,
      order: 0,
    },
    shipping: {
      title: 'Shipping',
      id: 'shipping',
      stepView: Shipping,
      order: 1,
      on: {
        UPDATE_ADDRESS: {
          actions: assign({
            shipping: (_, event) =>  event.address
          }),
          meta: {
            description: 'Provide shipping address'
          }
        },
        SELECT_METHOD: {
          actions: assign({
            shippingMethod: (_, event) => event.method 
          }),
          meta: {
            description: 'Select shipping method'
          }
        },
      },
      meta: {
        description: 'Shipping address',
      }
    },
    billing: {
      title: 'Billing',
      id: 'billing',
      stepView: Billing,
      order: 2,
      on: {
        ADD_BILLING_ADDRESS: {
          meta: {
            description: 'Enter billing address'
          },
          actions: assign({
            billing: (context, event) =>  ({
              ...context.billing,
              address: event.address
            })
          }),
        },
      },
      meta: {
        description: 'Billing address',
      }
    },
    payment: {
      title: 'Payment',
      id: 'payment',
      stepView: Payment,
      order: 3,
      on: {
        SELECT_METHOD: {
          meta: {
            description: 'Select a payment method'
          },
          actions: assign({
            paymentMethod: (_, event) => event.paymentMethod
          })
        },
        AGREE_TO_TERM: {
          meta: {
            description: 'Confirm agree to terms & conditions'
          },
          actions: assign({
            agreeToTerms: (_, event) => event.agreeToTerms
          })
        },
        NEXT: {
          cond: 'isAgreeToTerm'
        }
      },
      meta: {
        description: 'Payment',
      },
    },
    success: {
      title: 'Confirmation',
      id: 'success',
      stepView: Confirmation,
      order: 4,
      meta: {
        description: 'Order confirmed',
      },
    },
  },
} 

const options = {
  guards: {
    isAgreeToTerm: (ctx) => ctx.agreeToTerms && !!ctx.paymentMethod,
  }
}

const onComplete = () => {
    /* do something */
}
</script>
<template>
  <VrWizard 
    :options="options" 
    :id="config.id" 
    :context="config.context" 
    :states="config.states" 
    :initial="config.initial"
    :onComplete="onComplete"
    title="My Vurian Checkout Wizard"
    description="Just checking out"
  />
</template>

Result

How the wizard of checkout process looks

A video will come soon.

Visualize the wizard's flows

You can also visualize your steps by using the visualizer from Stately (just need to define the next/prev yourself). There is plan for component visualizer to be coming soon :)!

Our example wizard's flows above can be visualized as below:

This is the example how the flows of the wizard's steps

vurian-wizard's People

Contributors

mayashavin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

vurian-wizard's Issues

Machine service

I wanted to add an onTransition callback to the machine service while defining my component.
Unfortunately I have no access to the machine except in the child views. Do you think exposing the machine service defineExpose( { machineService } ); could be of any interest ?

By the way. Could you remove the annoying console log in :

console.log(newState);

Thanks for your great package
Cheers,
Dante

Async Transition for NEXT

Hi @mayashavin !

First of all: This is an really awesome wizard implementation for Vue!

I built a wizard and was wondering if there is the possibility to execute some asnyc action at the NEXT event.

The requirement in my case is to save the state of each wizard step, so that a user can continue the whole wizard at anytime without loosing data.

I tried something like this to have an "intermediate" step this kind of async operation.
The problem was that this intermediate state is considered as a wizard step and therefore it tells me that I have to define the stepView.

  saveFirstStep: {
    invoke: {
      id: 'saveFirstStep',
      src: saveData, // is a Promise that returns the saved id
      onDone: {
        target: 'firstStep',
        actions: [assign({
          id: (context, event: EventObject) => event.data,
        }), raise('NEXT')],
      },
      onError: {
        target: 'firstStep',
      },
    },
    order: 0,
  },

Is there already a possibilty with the current solution to make this kind of async transition work?

In the meantime I implemented my own buttons on each step, that do the work and then send the "NEXT" event.

Thanks in advance!
Christoph

stepView error

vue-router.mjs:3472 TypeError: Cannot read properties of undefined (reading 'stepView')
at setup (vr-wizard.esm.js:4524:1)
at callWithErrorHandling (runtime-core.esm-bundler.js:158:1)
at setupStatefulComponent (runtime-core.esm-bundler.js:7236:1)
at setupComponent (runtime-core.esm-bundler.js:7197:1)
at mountComponent (runtime-core.esm-bundler.js:5599:1)
at processComponent (runtime-core.esm-bundler.js:5565:1)
at patch (runtime-core.esm-bundler.js:5040:1)
at mountChildren (runtime-core.esm-bundler.js:5284:1)
at mountElement (runtime-core.esm-bundler.js:5191:1)
at processElement (runtime-core.esm-bundler.js:5156:1)

<VrWizard :id="wizConfig.id"
		  :options="wizOptions"
		  :context="wizConfig.context"
		  :states="wizConfig.states"
		  :initial="wizConfig.initial"
		  :onComplete="onComplete"
		  :showStepsProgress="true"
		  title="Registration Wizard"
		  description=""
		  nextText="Forward"
		  prevText="Back" 
		  doneText="Complete"/>

const wizConfig = {
		id: 'registration',
		initial: 'basic',
		context: {
			completedSteps: [],
			user: {
				username: '',
				password: '',
				confirmUser: ''
			},
			agreeToTerms: false,
			subscriptionChoice: ''
		},
		states: {
			Basic: {
				id: 'basic',
				title: 'Basic',
				meta: { description: 'Sign Up' },
				stepView: RegisterBasic,
				order: 0,
				on: {
					NEXT: { cond: 'isAgreeToTerm' }
				}
			},
			Personal: {
				id: 'personal',
				title: 'Personal',
				meta: { description: 'Personal' },
				stepView: RegisterPersonal,
				order: 1
			},
			Choice: {
				id: 'choice',
				title: 'Choice',
				meta: { description: 'Choice' },
				stepView: RegisterChoice,
				order: 2,
				on: {
					SELECT_METHOD: {
						actions: assign({ subscriptionMethod: (_, event) => event.method })
					},
					NEXT: {
						cond: ''
					}
				}
			},
			Business: {
				id: 'business',
				title: 'Business',
				meta: { description: 'Business' },
				stepView: RegisterBusiness,
				order: 3
			},
			Subscription: {
				id: 'basic',
				title: '',
				meta: { description: 'Subscription' },
				stepView: RegisterSubscription,
				order: 4,
				type: 'final'
			}
		}
	};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.