Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
storyteller pattern
class AadharDetailsStory {
constructor(args) { this.args = args; }
title() { return "Enter your Aadhar ID" }
moral() { return { ...this.args, aadharid: this.aadharid.value } }
async isCompleted() { return this.aadharid.value && this.aadharid.value.length == 12 }
getQuestions() { return "Enter a valid aadhar id" }
nextStoryName() { return OTPVerifyStory }
preDestroy() { this.status.innerText = 'validating aadhar details...'; return new Promise(res => setTimeout(() => res(), 1000)) }
tell() {
return render('aadhar', {
ele: 'div', children: [
{ ele: 'span', iden: 'status', attribs: { style: 'display: block'} },
{ ele: 'input', iden: 'aadharid', label: 'Aadhar ID' }
]
}, (id, ele) => this[id] = ele)
}
}
<html>
<body>
<style>
.storyboard-content { padding: 10px; }
.storyboard-titlebar { padding: 10px 0px; }
.storyboard-errmsg { color: red; }
.storyboard-title { font-weight: bolder; font-size: 1.1em; }
input, select { display: block; margin: 5px 10px; padding: 5px; min-width: 200px; }
button { padding: 5px 10px; margin-right: 10px; }
</style>
<div id="storyBoard"></div>
<script>
let storyTeller = new StoryTeller(document.getElementById('storyBoard'), PinCodeStory, {})
</script>
</body>
</html>
class LocationPickerStory {
constructor(args) { this.args = args; }
title() { return "Pick a location for vaccination" }
moral() { return { ...this.args, location: this.location.value } }
async isCompleted() { return true }
getQuestions() { return "Pick a valid location" }
nextStoryName() { return SummaryStory }
tell() {
return render('loc', {
ele: 'div', children: [
{ ele: 'select', iden: 'location', label: 'Available Locations', children: Array.from({ length: 10 }, (v, i) => ({ ele: 'option', text: `loc ${i}`})) }
]
}, (id, ele) => this[id] = ele)
}
}
class OTPVerifyStory {
constructor(args) { this.args = args; }
title() { return "Enter the OTP" }
moral() { return { ...this.args } }
isCompleted() { return new Promise(res => setTimeout(() => res(true), 3000)) }
getQuestions() { return "Enter a valid PAN no" }
nextStoryName() { return LocationPickerStory }
preDestroy() { return new Promise(res => setTimeout(() => res(), 3000)) }
tell() {
let id, reset = () => {
alert('OTP (re)sent to your registerd mobile number')
let cntr = 60;
this.reset.disabled = true
setTimeout(() => { this.reset.disabled = false; this.reset.innerText = 'Resend OTP'; clearInterval(id)}, 60 * 1000)
id = setInterval(() => { this.reset.innerText = `Resend OTP in ${--cntr}s`}, 1000)
}
let x = render('pan', {
ele: 'div', children: [
{ ele: 'input', iden: 'panno', label: 'OTP (sent to your registered number)' },
{ ele: 'button', iden: 'reset', text: 'resend otp', attribs: { disabled: true}, evnts: { click: reset} }
]
}, (id, ele) => this[id] = ele)
reset()
return x
}
}
class PanDetailsStory {
constructor(args) { this.args = args; }
title() { return "Enter your PAN number" }
moral() { return { ...this.args, panno: this.panno.value } }
async isCompleted() { return this.panno.value && this.panno.value.length == 10 }
getQuestions() { return "Enter a valid PAN no" }
nextStoryName() { return OTPVerifyStory }
preDestroy() { this.status.innerText = 'validating PAN details...'; return new Promise(res => setTimeout(() => res(), 1000)) }
tell() {
return render('pan', {
ele: 'div', children: [
{ ele: 'span', iden: 'status', attribs: { style: 'display: block'} },
{ ele: 'input', iden: 'panno', label: 'PAN no' }
]
}, (id, ele) => this[id] = ele)
}
}
class PinCodeStory {
title() { return "Enter your pincode" }
moral() { return { pin: this.pincode.value, idtype: this.idtype.value } }
async isCompleted() { return this.pincode.value && this.pincode.value.length == 6 }
getQuestions() { return "Enter a valid pincode" }
nextStoryName() { return { 'aadhar': AadharDetailsStory, 'pan': PanDetailsStory }[this.idtype.value] }
tell() {
return render('pincode', {
ele: 'div', children: [
{ ele: 'input', iden: 'pincode', label: 'PIN code' },
{
ele: 'select', iden: 'idtype', label: 'Pick a government id proof type',
children: [
{ ele: 'option', text: 'aadhar' },
{ ele: 'option', text: 'pan' }
]
}
]
}, (id, ele) => this[id] = ele)
}
}
class StoryTeller {
constructor(storyBoardElement, firstStoryClass, args) {
this.stack = [];
render('storyboard', {
ele: 'div',
children: [{
ele: 'div', classList: 'titlebar', children: [
{ ele: 'button', iden: 'backbtn', attribs: { innerHTML: '&larr;' }, evnts: { click: () => this.back() } },
{ ele: 'span', classList: 'title', text: '', iden: 'title' }
]
},
{ ele: 'div', classList: 'errmsg', iden: 'errmsg' },
{ ele: 'div', classList: 'content', iden: 'storyBoard' },
{ ele: 'button', text: 'Next', iden: 'nextbtn', evnts: { click: () => this.next() } }
]
}, (id, ele) => this[id] = ele, storyBoardElement)
this.tell(new firstStoryClass(args))
}
back() {
this.stack.pop()
this.tell()
}
next() {
let story = this.stack[this.stack.length - 1]
this.nextbtn.disabled = true
story.isCompleted().then(val => {
if(!val) {
this.nextbtn.disabled = false
return (this.errmsg.innerHTML = story.getQuestions())
}
let func = story.preDestroy ? story.preDestroy() : Promise.resolve()
func.then(() => {
if (story.nextStoryName())
this.tell(new (story.nextStoryName())(story.moral()))
})
})
}
tell(story) {
if (story) this.stack.push(story)
story = story || this.stack[this.stack.length - 1]
story.ele = story.ele || story.tell();
this.nextbtn.disabled = false
this.storyBoard.innerHTML = this.errmsg.innerHTML = ''
this.nextbtn.style.display = (!story.nextStoryName) ? "none" : 'block';
this.backbtn.style.display = this.stack.length < 2 ? 'none' : 'inline-block';
this.title.innerHTML = story.title();
this.storyBoard.appendChild(story.ele)
}
}
class SummaryStory {
constructor(args) { this.args = args }
title() { return "Your ticket for vaccination" }
tell() {
return render('pan', {
ele: 'pre',
attribs: { innerText: JSON.stringify(this.args, undefined, ' ') }
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment