-
-
Save jwx/1d3f7842b3bf40657c21f429aac89f20 to your computer and use it in GitHub Desktop.
Custom Element with Slots
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dumber Gist</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"> | |
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> | |
<base href="/"> | |
</head> | |
<!-- | |
Dumber Gist uses dumber bundler, the default bundle file | |
is /dist/entry-bundle.js. | |
The starting module is pointed to "main" (data-main attribute on script) | |
which is your src/main.js. | |
--> | |
<body> | |
<my-app></my-app> | |
<script src="/dist/entry-bundle.js" data-main="main"></script> | |
</body> | |
</html> |
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
{ | |
"dependencies": { | |
"aurelia": "dev" | |
} | |
} |
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
<use-shadow-dom></use-shadow-dom> | |
<template display.style="selectedStepIndex === stepIndex && isVisible ? 'block' : 'none !important'" class="m-1 p-1 d-block border border-primary"> | |
<slot></slot> | |
</template> |
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
import { inject, customElement, bindable, BindingMode, ISignaler, EventAggregator, shadowCSS } from 'aurelia'; | |
import style from './form-wizard.css'; | |
let counter = 0; | |
@inject(ISignaler, EventAggregator) | |
export class FormWizardItem { | |
static get dependencies() { return [shadowCSS(style)]; } | |
@bindable title = ''; | |
@bindable stepIndex = 0; | |
@bindable selectedStepIndex = 0; | |
@bindable isComplete = false; | |
@bindable disabled = false; | |
@bindable navClick; | |
@bindable isVisible = true; | |
constructor(signaler, messageBus) { | |
this.signaler = signaler; | |
this.messageBus = messageBus; | |
this.id = `form-wizard-item-${counter++}`; | |
} | |
} |
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
:host .pointer-events-none, | |
.pointer-events-none { | |
pointer-events: none; | |
} | |
.grid {display: grid;} | |
.grid-cols-1 {grid-template-columns: auto;} | |
.grid-cols-2 {grid-template-columns: auto auto;} | |
.grid-cols-3 {grid-template-columns: auto auto auto;} | |
.grid-rows-1 {grid-template-rows: auto;} | |
.grid-rows-2 {grid-template-rows: auto auto;} | |
.grid-rows-3 {grid-template-rows: auto auto auto;} | |
:host form-wizard, | |
form-wizard | |
{ | |
position: relative; | |
height: calc(100% - 10px); | |
} | |
:host form-wizard #wizard-container, | |
form-wizard #wizard-container | |
{ | |
height: 100%; | |
} | |
:host form-wizard #wizard-header, | |
:host form-wizard #wizard-content, | |
form-wizard #wizard-header, | |
form-wizard #wizard-content | |
{ | |
position: relative; | |
} | |
:host form-wizard #wizard-header, | |
form-wizard #wizard-header | |
{ | |
line-height: 2.2em; | |
} | |
:host form-wizard #wizard-header.header-top, | |
form-wizard #wizard-header.header-top | |
{ | |
margin-top: .5em; | |
} | |
:host form-wizard #wizard-header>span, | |
form-wizard #wizard-header>span | |
{ | |
position: relative; | |
padding: 1.5em; | |
cursor: pointer; | |
user-select: none; | |
} | |
:host form-wizard #wizard-container.orientation-left, | |
form-wizard #wizard-container.orientation-left | |
{ | |
display: grid; | |
grid-template-columns: auto 1fr; | |
grid-template-rows: auto; | |
grid-template-areas: "header" "main"; | |
} | |
:host form-wizard #wizard-content.flex-row-1, | |
form-wizard #wizard-content.flex-row-1 | |
{ | |
margin: 0; | |
} | |
:host form-wizard #wizard-content.flex-column-1, | |
form-wizard #wizard-content.flex-column-1 | |
{ | |
margin: 0; | |
} | |
:host form-wizard #wizard-content, | |
form-wizard #wizard-content | |
{ | |
padding: 15px; | |
} | |
:host #wizard-header .step, | |
#wizard-header .step | |
{ | |
border-left: 4px solid transparent; | |
} | |
:host #wizard-header .step.active, | |
#wizard-header .step.active | |
{ | |
border-left: 4px solid var(--primary-color); | |
} | |
:host .wizard-title-container.visible, | |
.wizard-title-container.visible | |
{ | |
margin-top: 15px; | |
margin-bottom: 15px; | |
padding-bottom: 15px; | |
/*border-color: #e7eaec;*/ | |
/*border-style: solid solid none;*/ | |
border-bottom: solid 1px #e7eaec; | |
} | |
:host form-wizard .wizard-title, | |
form-wizard .wizard-title | |
{ | |
margin-left: 15px; | |
margin-right: 15px; | |
font-size: 14px; | |
font-weight: 600; | |
} | |
:host form-wizard #wizard-header > span .badge-lg, | |
form-wizard #wizard-header > span .badge-lg | |
{ | |
color: var(--primary-text-color); | |
background-color: var(--primary-color); | |
} | |
:host form-wizard #wizard-header > span .title, | |
form-wizard #wizard-header > span .title { | |
font-size: 16px; | |
font-weight: 300; | |
} | |
:host form-wizard #wizard-header > span.is-complete .badge-lg, | |
form-wizard #wizard-header > span.is-complete .badge-lg | |
{ | |
color: var(--btn-success-color); | |
background-color: var(--btn-success-bg-color); | |
} | |
:host form-wizard #wizard-header > span.active .badge-lg, | |
form-wizard #wizard-header > span.active .badge-lg | |
{ | |
color: var(--primary-text-color); | |
background-color: var(--primary-color); | |
} | |
:host form-wizard #wizard-header > span.active .title, | |
form-wizard #wizard-header > span.active .title | |
{ | |
/* color: var(--wizard-header-active-color); */ | |
} | |
:host form-wizard #wizard-header > span.step:hover:not(.step-disabled), | |
form-wizard #wizard-header > span.step:hover:not(.step-disabled) | |
{ | |
/* background: #f3f3f4; */ | |
} | |
:host form-wizard #wizard-header > span.step:hover:not(.step-disabled) .title, | |
form-wizard #wizard-header > span.step:hover:not(.step-disabled) .title | |
{ | |
/* color: var(--wizard-header-active-color); */ | |
} | |
:host form-wizard #wizard-header > span.step:hover:not(.step-disabled):not(.is-complete) .badge-lg, | |
form-wizard #wizard-header > span.step:hover:not(.step-disabled):not(.is-complete) .badge-lg | |
{ | |
color: var(--btn-primary-color-hover); | |
background-color: var(--btn-primary-bg-color-hover); | |
} | |
:host form-wizard #wizard-header > span.step.is-complete:hover:not(.active) .badge-lg, | |
form-wizard #wizard-header > span.step.is-complete:hover:not(.active) .badge-lg | |
{ | |
color: var(--btn-success-color-hover); | |
background-color: var(--btn-success-bg-color-hover); | |
} | |
:host form-wizard #wizard-header > span.step.step-disabled .badge-lg, | |
form-wizard #wizard-header > span.step.step-disabled .badge-lg | |
{ | |
background-color: var(--btn-primary-bg-color-disabled); | |
border: var(--btn-primary-border-disabled); | |
} | |
:host form-wizard #wizard-header > span.step.step-disabled:hover, | |
form-wizard #wizard-header > span.step.step-disabled:hover | |
{ | |
cursor: initial; | |
} | |
:host form-wizard .badge-lg, | |
form-wizard .badge-lg | |
{ | |
height: 45px; | |
width: 45px; | |
padding: 15px 20px; | |
border-radius: 50px !important; | |
font-size: 16px; | |
} | |
:host form-wizard progress::-webkit-progress-value, | |
form-wizard progress::-webkit-progress-value | |
{ | |
transition: width .6s ease; | |
} | |
:host form-wizard progress[value], | |
form-wizard progress[value] | |
{ | |
-webkit-appearance: none; | |
appearance: none; | |
background-color: #f5f5f5; | |
border-radius: 3px; | |
/* width: calc(100% - 40px); */ | |
width: calc(100%); | |
height: 20px; | |
} | |
:host form-wizard progress[value]::-webkit-progress-bar, | |
form-wizard progress[value]::-webkit-progress-bar | |
{ | |
background-color: #f5f5f5; | |
border-radius: 3px; | |
} | |
:host form-wizard progress[value]::-webkit-progress-value, | |
form-wizard progress[value]::-webkit-progress-value | |
{ | |
/*background-size: 35px 35px, 100% 100%, 100% 100%;*/ | |
border-radius:3px; | |
/* background-color: #0369B1; */ | |
background-color: var(--primary-color); | |
} | |
:host form-wizard form-wizard-item, | |
form-wizard form-wizard-item | |
{ | |
min-height: 225px; | |
} |
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
<use-shadow-dom></use-shadow-dom> | |
<div id="wizard-title" | |
class="flex-column-none" | |
display.style="titleVisibility === 'visible' ? 'block' : 'none !important'"> | |
<slot name="title"></slot> | |
</div> | |
<div id="wizard-container" class="${containerClass}"> | |
<div id="wizard-header" class="${headerClass}"> | |
<span repeat.for="step of steps & signal:'name-signal'" | |
click.trigger="navClick($event)" | |
class="${selectedStepIndex === step.stepIndex ? 'active' : ''} ${step.isComplete ? 'is-complete' : ''} ${step.disabled ? 'step-disabled' : ''} ${step.isVisible ? '' : 'hidden'} step" | |
disabled.bind="step.disabled" | |
data-index="${step.stepIndex & signal:'name-signal'}"> | |
<span class="badge-lg pointer-events-none">${step.stepIndex + 1 & signal:'name-signal'}</span> | |
<span class="margin-left-10 title pointer-events-none"> ${step.title} </span> | |
</span> | |
</div> | |
<div id="wizard-content" class="${contentClass} ${contentCustomClass}" style="min-height: 0;"> | |
<progress if.bind="progressOrientation === 'top'" id="wizard-progress" class="${progressClass} ${progressVisibility === 'visible' ? '' : 'hidden'}" max="${numberSteps & signal:'name-signal'}" | |
value="${progressValue}"> | |
</progress> | |
<slot></slot> | |
<progress if.bind="progressOrientation === 'bottom'" id="wizard-progress" class="${progressClass} ${progressVisibility === 'visible' ? '' : 'hidden'}" max="${numberSteps & signal:'name-signal'}" | |
value="${progressValue}"> | |
</progress> | |
</div> | |
</div> |
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
import { inject, customElement, bindable, BindingMode, children, ISignaler, EventAggregator, shadowCSS } from 'aurelia'; | |
import {FormWizardItem} from './form-wizard-item'; | |
@inject(Element, ISignaler, EventAggregator) | |
export class FormWizard { | |
@children({ filter: el => el && el.nodeType === 1 && el.matches('form-wizard-item') }) steps = []; | |
// @children('form-wizard-item') steps = []; | |
@bindable titleVisibility = 'hidden'; | |
@bindable orientation = 'top'; | |
@bindable containerClass = ''; | |
@bindable headerClass = ''; | |
@bindable contentClass = ''; | |
@bindable contentCustomClass = ''; | |
@bindable progressVisibility = 'hidden'; | |
@bindable progressOrientation = 'top'; | |
@bindable progressClass = ''; | |
@bindable numberSteps = 1; | |
@bindable progressValue = 0; | |
@bindable selectedStepIndex = 0; | |
@bindable computeStepsTrigger; | |
@bindable credential; | |
constructor(element, signaler, messageBus) { | |
this.element = element; | |
this.signaler = signaler; | |
this.messageBus = messageBus; | |
} | |
/** | |
* This function fires whenever the selectedStep property changes. | |
* It then determines the correct step and calls the firesSelectionChange | |
* function. | |
*/ | |
selectedStepIndexChanged(newValue, oldValue) { | |
this.progressValue = this.selectedStepIndex + 1; | |
this.fireSelectionChange(); | |
} | |
/** | |
* This function fires whenever the orientation property changes. | |
* It then determines the layout of the wizard. | |
*/ | |
orientationChanged(newValue, oldValue) { | |
// console.log('orientationChanged', newValue); | |
if (newValue === 'top') { | |
this.containerClass = 'flex-column-1' | |
this.headerClass = 'flex-row-none order-0 header-top' | |
this.contentClass = 'flex-row-1 order-1' | |
} else if (newValue === 'bottom') { | |
this.containerClass = 'flex-column-1' | |
this.headerClass = 'flex-row-none order-1' | |
this.contentClass = 'flex-row-1 order-0' | |
} else if (newValue === 'left') { | |
debugger; | |
this.containerClass = 'grid-cols-2'; | |
// this.containerClass = 'flex-row-1' | |
// this.headerClass = 'flex-column-none order-0' | |
// this.contentClass = 'flex-column-1 order-1' | |
} else if (newValue === 'right') { | |
this.containerClass = 'flex-row-1' | |
this.headerClass = 'flex-column-none order-1' | |
this.contentClass = 'flex-column-1 order-0' | |
} | |
} | |
/** | |
* This function fires whenever the computeStepsTrigger expression | |
* changes. It will then filter and compute the steps based on the | |
* expression. | |
* @param {*} newValue | |
*/ | |
async computeStepsTriggerChanged(newValue) { | |
await wait(150); | |
this.steps.forEach((step, index) => { | |
if (!step.isVisible) { | |
step.stepIndex = -step.stepIndex; | |
} | |
}); | |
const visibleSteps = this.steps.filter(s => s.isVisible); | |
visibleSteps.forEach((step, index) => { | |
step.stepIndex = index; | |
}); | |
this.numberSteps = visibleSteps.length; | |
this.signaler.signal('name-signal'); | |
} | |
/** | |
* This function is called when the element is attached to the DOM. | |
*/ | |
afterAttach() { | |
this.firstStepSub = this.messageBus.subscribe('wizard:firststep', this.firstStep.bind(this)); | |
this.nextStepSub = this.messageBus.subscribe('wizard:nextstep', this.nextStep.bind(this)); | |
this.gotoStepSub = this.messageBus.subscribe('wizard:gotostep', this.gotoStep.bind(this)); | |
this.prevStepSub = this.messageBus.subscribe('wizard:prevstep', this.prevStep.bind(this)); | |
this.lastStepSub = this.messageBus.subscribe('wizard:laststep', this.lastStep.bind(this)); | |
this.completeStepSub = this.messageBus.subscribe('wizard:complete-step', (payload) => { | |
const {index} = payload; | |
this.completeStep(index); | |
}); | |
this.completeCurrentStepSub = this.messageBus.subscribe('wizard:complete-current-step', () => { | |
this.completeCurrentStep(); | |
}); | |
this.completeStepAndGoToLastStepSub = this.messageBus.subscribe('wizard:complete-current-step-and-go-to-last', () => { | |
this.completeStepAndGoToLastStep(); | |
}); | |
this.numberSteps = this.steps.length; | |
this.steps.forEach((item, index) => { | |
item.stepIndex = index; | |
}); | |
this.progressValue = this.selectedStepIndex + 1; | |
} | |
/** | |
* This function is called when the element detached from the DOM. | |
*/ | |
afterDetach() { | |
this.firstStepSub.dispose(); | |
this.nextStepSub.dispose(); | |
this.gotoStepSub.dispose(); | |
this.prevStepSub.dispose(); | |
this.lastStepSub.dispose(); | |
this.completeStepSub.dispose(); | |
this.completeCurrentStepSub.dispose(); | |
this.completeStepAndGoToLastStepSub.dispose(); | |
} | |
/** | |
* This function is fired when a user clicks on individual | |
* steps. It then navigates the user to the corresponding | |
* step. | |
*/ | |
async navClick(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
debugger; | |
let canContinue = false; | |
const index = Number(e.currentTarget.attributes['data-index'].value); | |
const isMovingForward = index > this.selectedStepIndex; | |
const currentStep = this.steps[this.selectedStepIndex]; | |
const nextStep = this.steps[index]; | |
if (nextStep.disabled) return; | |
if (!isMovingForward) { | |
canContinue = true; | |
} else if (nextStep.navClick) { | |
canContinue = await nextStep.navClick(); | |
} | |
if (canContinue) { | |
if (isMovingForward) { | |
currentStep.isComplete = true; | |
} | |
this.selectedStepIndex = index; | |
} | |
} | |
/** | |
* This function fires whenever the seletectStepChange event fires. | |
* It dispatches events for both changing and changed events. | |
*/ | |
async fireSelectionChange() { | |
const selectionChangingEvent = new CustomEvent('wizard-selection-changing', {bubbles: true, detail: this.selectedStepIndex}); | |
this.element.dispatchEvent(selectionChangingEvent); | |
await wait(25); | |
this.steps.forEach((item, index) => { | |
item.selectedStepIndex = this.selectedStepIndex; | |
}); | |
const selectionChangedEvent = new CustomEvent('wizard-selection-changed', {bubbles: true, detail: this.selectedStepIndex}); | |
this.element.dispatchEvent(selectionChangedEvent); | |
} | |
/** | |
* This function navigates the wizard to the first step. | |
*/ | |
firstStep() { | |
this.selectedStepIndex = 0; | |
} | |
/** | |
* This function navigates the wizard to the next step. | |
*/ | |
nextStep() { | |
let count = this.steps.length; | |
if (this.selectedStepIndex < count - 1) { | |
this.selectedStepIndex++; | |
} | |
} | |
/** | |
* This function navigates the wizard to the index provided. | |
*/ | |
gotoStep(payload) { | |
let count = this.steps.length; | |
if (payload.index > 0 && payload.index < count) { | |
this.selectedStepIndex = payload.index; | |
} | |
} | |
/** | |
* This function navigates the wizard to the previous step. | |
*/ | |
prevStep() { | |
let count = this.steps.length; | |
if (this.selectedStepIndex > 0) { | |
this.selectedStepIndex--; | |
} | |
} | |
/** | |
* This function navigates the wizard to the last step. | |
*/ | |
lastStep() { | |
this.selectedStepIndex = this.steps.length - 1; | |
} | |
completeStep(index) { | |
this.steps[index].isComplete = true; | |
} | |
completeStepAndGoToLastStep() { | |
this.steps[this.selectedStepIndex].isComplete = true; | |
this.selectedStepIndex = this.steps.length - 1; | |
} | |
completeCurrentStep() { | |
this.steps[this.selectedStepIndex].isComplete = true; | |
this.nextStep(); | |
} | |
} | |
function wait(t) { | |
return new Promise(r => setTimeout(r, t)); | |
} |
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
import Aurelia from 'aurelia'; | |
import { MyApp } from './my-app'; | |
import {FormWizard} from './form-wizard'; | |
import {FormWizardItem} from './form-wizard-item'; | |
import {Wizard} from './wizard'; | |
Aurelia | |
.register(FormWizard) | |
.register(FormWizardItem) | |
.register(Wizard) | |
.app(MyApp).start(); |
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
<!-- | |
Try to create a paired css/scss/sass/less file like my-app.scss. | |
It will be automatically imported based on convention. | |
--> | |
<!-- | |
There is no bundler config you can change in Dumber Gist to | |
turn on shadow DOM. | |
But you can turn shadow DOM on by adding a meta tag in every | |
html template: | |
<use-shadow-dom> | |
--> | |
<h1>${message}</h1> | |
<form-wizard view-model.ref="wizard" | |
title-visibility="visible" | |
title="Create Ticket" | |
progress-class="margin-bottom-15" | |
progress-visibility="visible" | |
progress-orientation="top" | |
orientation="left" | |
content-custom-class="margin-20" | |
compute-steps-trigger.bind="project.type & throttle:500"> | |
<div slot="title" class="wizard-title-container"> | |
<span class="wizard-title">New Project Wizard</span> | |
</div> | |
</form-wizard> | |
<wizard steps.bind="[ | |
{ title: 'Project name', component: 'project-name' }, | |
{ title: 'Project description', component: 'project-description' }, | |
]"> | |
<div slot="title" class="wizard-title-container"> | |
<span class="wizard-title">New New Project Wizard</span> | |
</div> | |
</wizard> |
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
export class MyApp { | |
message = 'Hello Aurelia 2!'; | |
wizard = null; | |
} |
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
<use-shadow-dom></use-shadow-dom> | |
Wizard: | |
<div id="wizard-title" | |
class="flex-column-none" | |
display.style="titleVisibility === 'visible' ? 'block' : 'none !important'"> | |
<slot name="title"></slot> | |
</div> | |
<div id="wizard-container" class="${containerClass}"> | |
<div id="wizard-header" class="${headerClass}"> | |
<a repeat.for="step of steps" | |
load.bind="step.component" | |
class="${step.isComplete ? 'is-complete' : ''} ${step.disabled ? 'step-disabled' : ''} ${step.isVisible ? '' : 'hidden'} step" | |
disabled.bind="step.disabled"> | |
<span class="badge-lg pointer-events-none">${step.stepIndex + 1}</span> | |
<span class="margin-left-10 title pointer-events-none"> ${step.title} </span> | |
</a> | |
</div> | |
<div id="wizard-content" class="${contentClass} ${contentCustomClass}" style="min-height: 0;"> | |
<progress if.bind="progressOrientation === 'top'" id="wizard-progress" class="${progressClass} ${progressVisibility === 'visible' ? '' : 'hidden'}" max="${numberSteps & signal:'name-signal'}" | |
value="${progressValue}"> | |
</progress> | |
<au-viewport name="wizard-viewport"></au-viewport> | |
<progress if.bind="progressOrientation === 'bottom'" id="wizard-progress" class="${progressClass} ${progressVisibility === 'visible' ? '' : 'hidden'}" max="${numberSteps & signal:'name-signal'}" | |
value="${progressValue}"> | |
</progress> | |
</div> | |
</div> |
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
import { inject, customElement, bindable, BindingMode, children, ISignaler, EventAggregator, shadowCSS } from 'aurelia'; | |
import {FormWizardItem} from './form-wizard-item'; | |
@inject(Element, ISignaler, EventAggregator) | |
export class Wizard { | |
@bindable steps = []; | |
@bindable titleVisibility = 'hidden'; | |
@bindable orientation = 'top'; | |
@bindable containerClass = ''; | |
@bindable headerClass = ''; | |
@bindable contentClass = ''; | |
@bindable contentCustomClass = ''; | |
@bindable progressVisibility = 'hidden'; | |
@bindable progressOrientation = 'top'; | |
@bindable progressClass = ''; | |
@bindable numberSteps = 1; | |
@bindable progressValue = 0; | |
@bindable selectedStepIndex = 0; | |
@bindable computeStepsTrigger; | |
@bindable credential; | |
constructor(element, signaler, messageBus) { | |
this.element = element; | |
this.signaler = signaler; | |
this.messageBus = messageBus; | |
} | |
/** | |
* This function fires whenever the selectedStep property changes. | |
* It then determines the correct step and calls the firesSelectionChange | |
* function. | |
*/ | |
selectedStepIndexChanged(newValue, oldValue) { | |
this.progressValue = this.selectedStepIndex + 1; | |
this.fireSelectionChange(); | |
} | |
/** | |
* This function fires whenever the orientation property changes. | |
* It then determines the layout of the wizard. | |
*/ | |
orientationChanged(newValue, oldValue) { | |
// console.log('orientationChanged', newValue); | |
if (newValue === 'top') { | |
this.containerClass = 'flex-column-1' | |
this.headerClass = 'flex-row-none order-0 header-top' | |
this.contentClass = 'flex-row-1 order-1' | |
} else if (newValue === 'bottom') { | |
this.containerClass = 'flex-column-1' | |
this.headerClass = 'flex-row-none order-1' | |
this.contentClass = 'flex-row-1 order-0' | |
} else if (newValue === 'left') { | |
debugger; | |
this.containerClass = 'grid-cols-2'; | |
// this.containerClass = 'flex-row-1' | |
// this.headerClass = 'flex-column-none order-0' | |
// this.contentClass = 'flex-column-1 order-1' | |
} else if (newValue === 'right') { | |
this.containerClass = 'flex-row-1' | |
this.headerClass = 'flex-column-none order-1' | |
this.contentClass = 'flex-column-1 order-0' | |
} | |
} | |
/** | |
* This function fires whenever the computeStepsTrigger expression | |
* changes. It will then filter and compute the steps based on the | |
* expression. | |
* @param {*} newValue | |
*/ | |
async computeStepsTriggerChanged(newValue) { | |
await wait(150); | |
this.steps.forEach((step, index) => { | |
if (!step.isVisible) { | |
step.stepIndex = -step.stepIndex; | |
} | |
}); | |
const visibleSteps = this.steps.filter(s => s.isVisible); | |
visibleSteps.forEach((step, index) => { | |
step.stepIndex = index; | |
}); | |
this.numberSteps = visibleSteps.length; | |
this.signaler.signal('name-signal'); | |
} | |
/** | |
* This function is called when the element is attached to the DOM. | |
*/ | |
afterAttach() { | |
this.firstStepSub = this.messageBus.subscribe('wizard:firststep', this.firstStep.bind(this)); | |
this.nextStepSub = this.messageBus.subscribe('wizard:nextstep', this.nextStep.bind(this)); | |
this.gotoStepSub = this.messageBus.subscribe('wizard:gotostep', this.gotoStep.bind(this)); | |
this.prevStepSub = this.messageBus.subscribe('wizard:prevstep', this.prevStep.bind(this)); | |
this.lastStepSub = this.messageBus.subscribe('wizard:laststep', this.lastStep.bind(this)); | |
this.completeStepSub = this.messageBus.subscribe('wizard:complete-step', (payload) => { | |
const {index} = payload; | |
this.completeStep(index); | |
}); | |
this.completeCurrentStepSub = this.messageBus.subscribe('wizard:complete-current-step', () => { | |
this.completeCurrentStep(); | |
}); | |
this.completeStepAndGoToLastStepSub = this.messageBus.subscribe('wizard:complete-current-step-and-go-to-last', () => { | |
this.completeStepAndGoToLastStep(); | |
}); | |
this.numberSteps = this.steps.length; | |
this.steps.forEach((item, index) => { | |
item.stepIndex = index; | |
}); | |
this.progressValue = this.selectedStepIndex + 1; | |
} | |
/** | |
* This function is called when the element detached from the DOM. | |
*/ | |
afterDetach() { | |
this.firstStepSub.dispose(); | |
this.nextStepSub.dispose(); | |
this.gotoStepSub.dispose(); | |
this.prevStepSub.dispose(); | |
this.lastStepSub.dispose(); | |
this.completeStepSub.dispose(); | |
this.completeCurrentStepSub.dispose(); | |
this.completeStepAndGoToLastStepSub.dispose(); | |
} | |
/** | |
* This function is fired when a user clicks on individual | |
* steps. It then navigates the user to the corresponding | |
* step. | |
*/ | |
async navClick(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
debugger; | |
let canContinue = false; | |
const index = Number(e.currentTarget.attributes['data-index'].value); | |
const isMovingForward = index > this.selectedStepIndex; | |
const currentStep = this.steps[this.selectedStepIndex]; | |
const nextStep = this.steps[index]; | |
if (nextStep.disabled) return; | |
if (!isMovingForward) { | |
canContinue = true; | |
} else if (nextStep.navClick) { | |
canContinue = await nextStep.navClick(); | |
} | |
if (canContinue) { | |
if (isMovingForward) { | |
currentStep.isComplete = true; | |
} | |
this.selectedStepIndex = index; | |
} | |
} | |
/** | |
* This function fires whenever the seletectStepChange event fires. | |
* It dispatches events for both changing and changed events. | |
*/ | |
async fireSelectionChange() { | |
const selectionChangingEvent = new CustomEvent('wizard-selection-changing', {bubbles: true, detail: this.selectedStepIndex}); | |
this.element.dispatchEvent(selectionChangingEvent); | |
await wait(25); | |
this.steps.forEach((item, index) => { | |
item.selectedStepIndex = this.selectedStepIndex; | |
}); | |
const selectionChangedEvent = new CustomEvent('wizard-selection-changed', {bubbles: true, detail: this.selectedStepIndex}); | |
this.element.dispatchEvent(selectionChangedEvent); | |
} | |
/** | |
* This function navigates the wizard to the first step. | |
*/ | |
firstStep() { | |
this.selectedStepIndex = 0; | |
} | |
/** | |
* This function navigates the wizard to the next step. | |
*/ | |
nextStep() { | |
let count = this.steps.length; | |
if (this.selectedStepIndex < count - 1) { | |
this.selectedStepIndex++; | |
} | |
} | |
/** | |
* This function navigates the wizard to the index provided. | |
*/ | |
gotoStep(payload) { | |
let count = this.steps.length; | |
if (payload.index > 0 && payload.index < count) { | |
this.selectedStepIndex = payload.index; | |
} | |
} | |
/** | |
* This function navigates the wizard to the previous step. | |
*/ | |
prevStep() { | |
let count = this.steps.length; | |
if (this.selectedStepIndex > 0) { | |
this.selectedStepIndex--; | |
} | |
} | |
/** | |
* This function navigates the wizard to the last step. | |
*/ | |
lastStep() { | |
this.selectedStepIndex = this.steps.length - 1; | |
} | |
completeStep(index) { | |
this.steps[index].isComplete = true; | |
} | |
completeStepAndGoToLastStep() { | |
this.steps[this.selectedStepIndex].isComplete = true; | |
this.selectedStepIndex = this.steps.length - 1; | |
} | |
completeCurrentStep() { | |
this.steps[this.selectedStepIndex].isComplete = true; | |
this.nextStep(); | |
} | |
} | |
function wait(t) { | |
return new Promise(r => setTimeout(r, t)); | |
} |
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
<div title="Project Description" class="flex-row-1"> | |
<div class="flex-column-1"> | |
<div class="flex-column-1"> | |
<div class="form-group"> | |
<label>Project Description</label> | |
<input class="form-control" value.bind="description"> | |
</div> | |
<p>Enter a description for your project.</p> | |
</div> | |
<div class="flex-row-none justify-content-end"> | |
<div class="flex-row-1 justify-content-start"> | |
</div> | |
<div class="flex-row-1 justify-content-center"> | |
</div> | |
<div class="flex-row-1 justify-content-end"> | |
<button class="btn btn-primary" | |
click.trigger="wizard.prevStep($event)"> | |
Previous | |
</button> | |
<button class="btn btn-primary" | |
click.trigger="wizard.nextStep($event)"> | |
Next | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> |
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
<div title="Project Name" class="flex-row-1"> | |
<div class="flex-column-1"> | |
<div class="flex-column-1"> | |
<div class="form-group"> | |
<label>Project Name</label> | |
<input class="form-control" value.bind="name"> | |
</div> | |
<p>Enter a unique name for your project.</p> | |
</div> | |
<div class="flex-row-none justify-content-end"> | |
<div class="flex-row-1 justify-content-start"> | |
</div> | |
<div class="flex-row-1 justify-content-center"> | |
</div> | |
<div class="flex-row-1 justify-content-end"> | |
<button class="btn btn-primary" | |
click.trigger="wizard.nextStep($event)"> | |
Next | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment