-
-
Save bigopon/71126b10461fc2a5bff4559ea85c8377 to your computer and use it in GitHub Desktop.
Custom Element with Slots
This file contains hidden or 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 hidden or 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 hidden or 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
<template show.bind="selectedStepIndex === stepIndex && isVisible" class="m-1 p-1 d-block border border-primary"> | |
<slot></slot> | |
</template> |
This file contains hidden or 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, bindable, BindingMode, ISignaler, EventAggregator } from 'aurelia'; | |
let counter = 0; | |
@inject(ISignaler, EventAggregator) | |
export class FormWizardItem { | |
@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 hidden or 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
form-wizard { | |
position: relative; | |
height: calc(100% - 10px); | |
} | |
form-wizard #wizard-container { | |
height: 100%; | |
} | |
form-wizard #wizard-header, | |
form-wizard #wizard-content { | |
position: relative; | |
} | |
form-wizard #wizard-header { | |
line-height: 2.2em; | |
} | |
form-wizard #wizard-header.header-top { | |
margin-top: .5em; | |
} | |
form-wizard #wizard-header>span { | |
position: relative; | |
padding: 1.5em; | |
cursor: pointer; | |
user-select: none; | |
} | |
form-wizard #wizard-content.flex-row-1 { | |
margin: 0; | |
} | |
form-wizard #wizard-content.flex-column-1 { | |
margin: 0; | |
} | |
form-wizard #wizard-content { | |
padding: 15px; | |
} | |
#wizard-header .step { | |
border-left: 4px solid transparent; | |
} | |
#wizard-header .step.active { | |
border-left: 4px solid var(--primary-color); | |
} | |
.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; | |
} | |
form-wizard .wizard-title { | |
margin-left: 15px; | |
margin-right: 15px; | |
font-size: 14px; | |
font-weight: 600; | |
} | |
form-wizard #wizard-header > span .badge-lg { | |
color: var(--primary-text-color); | |
background-color: var(--primary-color); | |
} | |
form-wizard #wizard-header > span .title { | |
font-size: 16px; | |
font-weight: 300; | |
} | |
form-wizard #wizard-header > span.is-complete .badge-lg { | |
color: var(--btn-success-color); | |
background-color: var(--btn-success-bg-color); | |
} | |
form-wizard #wizard-header > span.active .badge-lg { | |
color: var(--primary-text-color); | |
background-color: var(--primary-color); | |
} | |
form-wizard #wizard-header > span.active .title { | |
/* color: var(--wizard-header-active-color); */ | |
} | |
form-wizard #wizard-header > span.step:hover:not(.step-disabled) { | |
/* background: #f3f3f4; */ | |
} | |
form-wizard #wizard-header > span.step:hover:not(.step-disabled) .title { | |
/* color: var(--wizard-header-active-color); */ | |
} | |
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); | |
} | |
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); | |
} | |
form-wizard #wizard-header > span.step.step-disabled .badge-lg { | |
background-color: var(--btn-primary-bg-color-disabled); | |
border: var(--btn-primary-border-disabled); | |
} | |
form-wizard #wizard-header > span.step.step-disabled:hover { | |
cursor: initial; | |
} | |
form-wizard .badge-lg { | |
height: 45px; | |
width: 45px; | |
padding: 15px 20px; | |
border-radius: 50px !important; | |
font-size: 16px; | |
} | |
form-wizard progress::-webkit-progress-value { | |
transition: width .6s ease; | |
} | |
form-wizard progress[value] { | |
-webkit-appearance: none; | |
appearance: none; | |
background-color: #f5f5f5; | |
border-radius: 3px; | |
/* width: calc(100% - 40px); */ | |
width: calc(100%); | |
height: 20px; | |
} | |
form-wizard progress[value]::-webkit-progress-bar { | |
background-color: #f5f5f5; | |
border-radius: 3px; | |
} | |
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); | |
} | |
form-wizard form-wizard-item { | |
min-height: 225px; | |
} |
This file contains hidden or 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 from="./form-wizard.css"></import> | |
<div id="wizard-title" | |
class="flex-column-none ${titleVisibility == 'visible' ? '' : 'hidden'}"> | |
<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 hidden or 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, bindable, BindingMode, children, ISignaler, EventAggregator } 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') { | |
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) { | |
let canContinue = false; | |
const index = Number(e.target.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 hidden or 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'; | |
Aurelia | |
.register(FormWizard) | |
.register(FormWizardItem) | |
.app(MyApp).start(); |
This file contains hidden or 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-item 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> | |
</form-wizard-item> | |
<form-wizard-item 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> | |
</form-wizard-item> | |
</form-wizard> |
This file contains hidden or 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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment