# UDWizardry Powershell Universal Component Module 🧙‍♂️

By
,
Powershell
,
Modules
Published 2022-09-18

This module does not contain any spells
This module does not contain any spells

I just want to clarify that the naming of these modules is influenced by the actual name of the original react component I am rebuilding to make work in Powershell Universal. So I did not think Wizardry is maybe the best noun to use but this component is based upon react-wizardry hence the name UDWizardry.

Original React Component
https://www.npmjs.com/package/react-wizardry

By no means am I a react master. But as I understand as this product is constantly being updated, you can now use hooks within the main component JSX file to allow it to communicate with the dashboard you are using. This does make the whole process a lot slicker, as I do not know a lot on JS coding for react.

Link To Download UDWizardry Module
https://www.powershellgallery.com/packages/UDWizardry/1.0.1

GitHub Link to this UDWizardry Repository
https://github.com/psDevUK/UDWizardry

How this looks in action
How this looks in action

# Demo Dashboard 😁

When building this component which I will detail in a moment, but I needed a more efficient way of passing the actual stepper page with the inputs you wanted on it. Therefore I created a sub-function. I have limited this to a maximum of 7 inputs per-step-page. Just feel if you are using more than 7 inputs then maybe you should consider breaking it down into more steps. You can extend the amount of inputs but you will need to manually modify the function within the PSM1 file.

New-UDDashboard -Title 'PowerShell Universal' -Content {
        New-UDWizardry -Strict $true -BodyHeight 340 -Content {
          New-UDWizardryPage -Pages @("Title1",@("first","fname","text",$false),@("Something","some","text",$true,"You need to type something in here")),@("Title2",@("last","sname","text",$true)),@("Title3",@("Information","info","text",$true))
        } -OnFinish {Show-UDToast ($EventData | ConvertTo-Json) -Duration 30000 }
}

# Reason behind component 💡

Although I have built stepper bars in the past, I had not built a true full wizard stepper component. So now that I am using the software again I thought I would have a look on the forum to see what was happening:-

Ironman Software Forum
https://forums.ironmansoftware.com/

I saw on here that a few users were requesting additional features on the stepper component that is built into the software. I do not have any official ties to the company but I can only assume that using a particular react themed engine like Material-UI you really need to stick to using all their components to keep the consistency through-out the dashboard components. This does not apply to me though, as I just like building components for this product to hopefully make the world a better place. Seriously though I do enjoy building components, and if I think I can build something that others will find useful, or be that missing piece in the puzzle, then I will try and build it. From the posts I was reading this particular component react-wizardry seemed to tick all those boxes. I was also amazed by the few dependencies this component required.

# Making It Possible 🧠

It really does seem anything is possible with Powershell Universal but yeah you might have to learn some new-tricks to make your exact vision of the perfect idea become a reality. Two new tricks needed to be learnt in order to put the finishing touches to this component. After getting the required dependencies I used the demo of this component to help me construct on how I was going to build this component:-

CodeSandBox Basic Demo
https://codesandbox.io/s/react-wizardy-simple-form-vu3y6b

So this proved that the component could indeed be built using the template Mr Adam Driscoll has kindly provided. It also proved that this would also eliminate the current issues with the built-in stepper component. But...The big but was how am I going to pass the stepper pages and input? And how was I going to get the actual data back that had been entered? as the example just showed this going to the console within the browser, which did work, but how could I bind that to Powershell Universal?

I know I had done components which had done this sort of thing before, but that was when the product was Universal Dashboard which was using an older version of react and a totally different themed react engine Materialize I believe with this theme.

Old theme
https://materializecss.com/
but everything has now changed as no longer using this, or the template used to build those components. Plus I mentioned before I am not a react developer, but I do look at some issues I have had building recent components, and it is complaining of the old style syntax in some of the node files which I have had to manually change.

# What did I do? 🙏

Well what else do you do when you have a complete pickle of a problem to solve with Powershell Universal? Ask the man himself Adam Driscoll. Although I never met the geezer in-person, he has always 100% been super helpful and kind enough to answer fan mail (I mean serious technical mails). I posed the question about being stuck on these two issues, I was trying to solve issue one, which was displaying the pages but for problem two I did not have a Scooby-doo how I was going to make that bit work.

As I had a repo setup for this project I shared the link and Adam kindly blessed me with his knowledge of how this could be accomplished by pushing a merge to the github repo with the required changes. To my complete shock there was not a lot of code that needed to be added or altered to get the data back. I will comment this in the code I share below.

# The Project 🏗️

This is the final version of the component JSX file:-

import React from 'react';
import { withComponentFeatures } from 'universal-dashboard'
import { Wizard } from "react-wizardry";
import "react-wizardry/dist/react-wizardry.css";
//Got help on these below lines then on the onFinish below
const onFinish = (value) => {
  props.onFinish(value);
}
const UDWizardry = props => {
  return (
    <div className="App">
      <Wizard
        key={props.id}
        bodyHeight={props.bodyHeight}
        noPageTitle={props.noPageTitle}
        showStepperTitles={props.showStepperTitles}
        stepperItemWidth={props.stepperItemWidth}
        highlightFieldsOnValidation={props.highlightFieldsOnValidation}
        onFinish={props.onFinish}
        strict={props.strict}
        pages={props.pages}
        finishMessage={props.finishMessage}
        />
  </div>
  );
}

export default withComponentFeatures(UDWizardry)

To make the actual cmdlet behind this component you need to edit the pre-defined function in the .PSM1 file so this is my finished .PSM1 before running the invoke-build

$IndexJs = Get-ChildItem "$PSScriptRoot\index.*.bundle.js"
$AssetId = [UniversalDashboard.Services.AssetService]::Instance.RegisterAsset($IndexJs.FullName)

function New-UDWizardry {
    <#
    .SYNOPSIS
    Creates a new stepper component using react-wizardry
    
    .DESCRIPTION
    New stepper component based on react-wizardry. Lots of nice features, sub-function to include the actual form data. Keeps data when pressing back button
    
    .PARAMETER Id
    The ID of this editor

    .PARAMETER BodyHeight
    Sets the height of the form body
    
    .PARAMETER NoPageTitle
    Decides if page title is shown
    
    .PARAMETER ShowStepperTitle
    Use this parameter to display page titles under the stepper points
    
    .PARAMETER StepperWidth
    Sets the width of each stepper item
    
    .PARAMETER HighlightValidation
    Highlights the fields when the validation fails or succeeds
    
    .PARAMETER Strict
    Validation rules are applied to all inputs that have been marked for validation or that are required.
    The component prevents the user from moving on until the current step's errors have been fixed
    
    .PARAMETER Content
    Script block to display collection of the page object

    .PARAMETER FinishMessage
    String to display the finish message to the end-user once they have completed the wizard form

    .EXAMPLE
    New-UDWizardryPage -Pages @("Title1",@("first","fname","text",$true,"You need to enter your first name"),@("email","eadd","email",$false)),@("Title2",@("last","sname","text",$true)),@("Title3",@("summary","details","text",$true))
    #>
    
    param(
        [Parameter()]
        [string]$Id = (New-Guid).ToString(),
        #Sets the height for the stepper
        [Parameter()]
        [int]$BodyHeight = 500,
        #Boolean to display title or not default false
        [Parameter()]
        [bool]$NoPageTitle = $false,
        #Boolean to display stepper titles default true
        [Parameter()]
        [bool]$ShowStepperTitle = $true,
        #String to decide stepper length default 200px
        [Parameter()]
        [string]$StepperWidth = "200px",
        #Allows you to get back the data entered in the stepper
        [Parameter()]
        [Endpoint]$OnFinish,  #Help given on this
        #Highlights the fields when the validation fails or succeeds boolean default true
        [Parameter()]
        [bool]$HighlightValidation = $true,
        #Validation rules are applied to all inputs that have been marked for validation or that are required.The component prevents the user from moving on until the current step's errors have been fixed boolean default false
        [Parameter()]
        [bool]$Strict = $false,
        #Script block to display contents of the wizardry pages
        [Parameter(Mandatory)]
        [scriptblock]$Content,
        #Display a custom finish message
        [Parameter()]
        [string]$FinishMessage = "Thank you for finishing the form"
    )

    End {
        $OnFinish.Register($Id, $PSCmdlet) #This was the missing code
        @{
            assetId                     = $AssetId 
            isPlugin                    = $true 
            type                        = "udwizardry"
            id                          = $Id

            bodyHeight                  = $BodyHeight
            noPageTitle                 = $NoPageTitle
            showStepperTitles           = $ShowStepperTitle
            stepperItemWidth            = $StepperWidth
            highlightFieldsOnValidation = $HighlightValidation
            strict                      = $Strict
            pages                       = [array]$Content.Invoke()
            onFinish                    = $OnFinish #Help was given
            finishMessage               = $FinishMessage
        }
    }
}

<#
.Synopsis
   Helps creating pages steps for UDWizardry
.DESCRIPTION
   Enables you to add data to the UDWizardry Content script block
.EXAMPLE
   Example of how to use this cmdlet
.EXAMPLE
   New-UDWizardryPage -Pages @("Title1",@("first","fname","text",$false),@("email","eadd","email",$false)),@("Title2",@("last","sname","text",$true)),@("Title3",@("mast","mname","text",$true))
#>

function New-UDWizardryPage {
    [CmdletBinding()]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory = $true, Position = 0)]
        [array[]]$Pages
    )

    Begin {
        $number = $Pages.Count #find the amount of pages/titles
        $Data = @() #Empty array to add to
    }
    Process {
        for ($i = 0; $i -lt $number; $i++) { 
            switch ($Pages.Item($i).count) {
                2 {
                    $Data += [PSCustomObject]@{
                        title  = $Pages.Item($i)[0]
                        fields = @([ordered]@{
                                label             = @($Pages.Item($i)[1])[0]
                                name              = @($Pages.Item($i)[1])[1]
                                type              = @($Pages.Item($i)[1])[2]
                                isRequired        = @($Pages.Item($i)[1])[3]
                                validationMessage = @($Pages.Item($i)[1])[4]
                            })
                    }
                }
                3 {
                    $Data += [PSCustomObject]@{
                        title  = $Pages.Item($i)[0]
                        fields = @([ordered]@{
                                label             = @($Pages.Item($i)[1])[0]
                                name              = @($Pages.Item($i)[1])[1]
                                type              = @($Pages.Item($i)[1])[2]
                                isRequired        = @($Pages.Item($i)[1])[3]
                                validationMessage = @($Pages.Item($i)[1])[4]
                            }
                            , [ordered]@{
                                label             = @($Pages.Item($i)[2])[0]
                                name              = @($Pages.Item($i)[2])[1]
                                type              = @($Pages.Item($i)[2])[2]
                                isRequired        = @($Pages.Item($i)[2])[3]
                                validationMessage = @($Pages.Item($i)[2])[4]
                            })
                    } 
                }
                4 {
                    $Data += [PSCustomObject]@{
                        title  = $Pages.Item($i)[0]
                        fields = @([ordered]@{
                                label             = @($Pages.Item($i)[1])[0]
                                name              = @($Pages.Item($i)[1])[1]
                                type              = @($Pages.Item($i)[1])[2]
                                isRequired        = @($Pages.Item($i)[1])[3]
                                validationMessage = @($Pages.Item($i)[1])[4]
                            }
                            , [ordered]@{
                                label             = @($Pages.Item($i)[2])[0]
                                name              = @($Pages.Item($i)[2])[1]
                                type              = @($Pages.Item($i)[2])[2]
                                isRequired        = @($Pages.Item($i)[2])[3]
                                validationMessage = @($Pages.Item($i)[2])[4]
                            }
                            , [ordered]@{
                                label             = @($Pages.Item($i)[3])[0]
                                name              = @($Pages.Item($i)[3])[1]
                                type              = @($Pages.Item($i)[3])[2]
                                isRequired        = @($Pages.Item($i)[3])[3]
                                validationMessage = @($Pages.Item($i)[3])[4]
                            })
                    }
                }
                5 {
                    $Data += [PSCustomObject]@{ 
                        title  = $Pages.Item($i)[0]
                        fields = @([ordered]@{
                                label             = @($Pages.Item($i)[1])[0]
                                name              = @($Pages.Item($i)[1])[1]
                                type              = @($Pages.Item($i)[1])[2]
                                isRequired        = @($Pages.Item($i)[1])[3]
                                validationMessage = @($Pages.Item($i)[1])[4]
                            }
                            , [ordered]@{
                                label             = @($Pages.Item($i)[2])[0]
                                name              = @($Pages.Item($i)[2])[1]
                                type              = @($Pages.Item($i)[2])[2]
                                isRequired        = @($Pages.Item($i)[2])[3]
                                validationMessage = @($Pages.Item($i)[2])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[3])[0]
                                name              = @($Pages.Item($i)[3])[1]
                                type              = @($Pages.Item($i)[3])[2]
                                isRequired        = @($Pages.Item($i)[3])[3]
                                validationMessage = @($Pages.Item($i)[3])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[4])[0]
                                name              = @($Pages.Item($i)[4])[1]
                                type              = @($Pages.Item($i)[4])[2]
                                isRequired        = @($Pages.Item($i)[4])[3]
                                validationMessage = @($Pages.Item($i)[4])[4]
                            })
                    }
                }
                6 {
                    $Data += [PSCustomObject]@{
                        title  = $Pages.Item($i)[0]
                        fields = @([ordered]@{
                                label             = @($Pages.Item($i)[1])[0]
                                name              = @($Pages.Item($i)[1])[1]
                                type              = @($Pages.Item($i)[1])[2]
                                isRequired        = @($Pages.Item($i)[1])[3]
                                validationMessage = @($Pages.Item($i)[1])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[2])[0]
                                name              = @($Pages.Item($i)[2])[1]
                                type              = @($Pages.Item($i)[2])[2]
                                isRequired        = @($Pages.Item($i)[2])[3]
                                validationMessage = @($Pages.Item($i)[2])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[3])[0]
                                name              = @($Pages.Item($i)[3])[1]
                                type              = @($Pages.Item($i)[3])[2]
                                isRequired        = @($Pages.Item($i)[3])[3]
                                validationMessage = @($Pages.Item($i)[3])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[4])[0]
                                name              = @($Pages.Item($i)[4])[1]
                                type              = @($Pages.Item($i)[4])[2]
                                isRequired        = @($Pages.Item($i)[4])[3]
                                validationMessage = @($Pages.Item($i)[4])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[5])[0]
                                name              = @($Pages.Item($i)[5])[1]
                                type              = @($Pages.Item($i)[5])[2]
                                isRequired        = @($Pages.Item($i)[5])[3]
                                validationMessage = @($Pages.Item($i)[5])[4]
                            })
                    }
                }
                7 {
                    $Data += [PSCustomObject]@{    
                        title  = $Pages.Item($i)[0]
                        fields = @([ordered]@{
                                label             = @($Pages.Item($i)[1])[0]
                                name              = @($Pages.Item($i)[1])[1]
                                type              = @($Pages.Item($i)[1])[2]
                                isRequired        = @($Pages.Item($i)[1])[3]
                                validationMessage = @($Pages.Item($i)[1])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[2])[0]
                                name              = @($Pages.Item($i)[2])[1]
                                type              = @($Pages.Item($i)[2])[2]
                                isRequired        = @($Pages.Item($i)[2])[3]
                                validationMessage = @($Pages.Item($i)[2])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[3])[0]
                                name              = @($Pages.Item($i)[3])[1]
                                type              = @($Pages.Item($i)[3])[2]
                                isRequired        = @($Pages.Item($i)[3])[3]
                                validationMessage = @($Pages.Item($i)[3])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[4])[0]
                                name              = @($Pages.Item($i)[4])[1]
                                type              = @($Pages.Item($i)[4])[2]
                                isRequired        = @($Pages.Item($i)[4])[3]
                                validationMessage = @($Pages.Item($i)[4])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[5])[0]
                                name              = @($Pages.Item($i)[5])[1]
                                type              = @($Pages.Item($i)[5])[2]
                                isRequired        = @($Pages.Item($i)[5])[3]
                                validationMessage = @($Pages.Item($i)[5])[4]
                            },
                            [ordered]@{
                                label             = @($Pages.Item($i)[6])[0]
                                name              = @($Pages.Item($i)[6])[1]
                                type              = @($Pages.Item($i)[6])[2]
                                isRequired        = @($Pages.Item($i)[6])[3]
                                validationMessage = @($Pages.Item($i)[6])[4]
                            })
                    }
                }
                Default { Write-Warning "Something went wrong please check your syntax" }
            }
    
        }

    }
    End {
        $Data | ConvertTo-Json -AsArray -Depth 5 | ConvertFrom-Json
    }
}

# Bit more explaining 🥱

I had the challenge of how to construct the wizardry pages and wizardry input fields and how many input fields. So the solution was not as elegant as I had hoped, and in the function I wrote for this you are limited to a maximum of six input fields you could modify the function to over-come this limit.

You need to pass the design in an array format. The first item is the TITLE of that wizardry page. Then you need to nest another array to now include the fields. In a specific order, first item will be the LABEL displayed on the input field, the second item in the nested array will be the NAME of that field. Third item defines the TYPE of input that the field should be (please see below for a complete list of all the different types of input this component supports). Forth item is a boolean value to declare if this is a required input or not. Lastly you can type a custom validation message

Code below is showing a three step wizard that has three inputs on the first page, one input on the second step, and two inputs on the final third step.

New-UDWizardryPage -Pages @("Title1",@("Enter First Name","firstname","text",$false),@("Enter Surname","surname","text",$false),@("Enter Email","eaddress","email",$false)),@("Title2",@("Information","info","text",$true)),@("Title3",@("Phone number","phonen","phone",$true),@("Enter Password","passw","password",$true))

# Fun Challenge 💯

I can honestly say this was a super-cool project to build as it gave a fun challenge to complete.

So there is a lot you can do with this stepper, totally open to ideas to make the data easier to pass, I did it this way as it worked and I got the results I wanted. Always happy for you to ask me on twitter if none of this makes sense on how to use this component.

Link to this module on marketplace
https://marketplace.universaldashboard.io/Dashboard/UDWizardry

# 💥 That is how this Powershell module was done. Till next time, take care