Created
May 14, 2021 13:16
-
-
Save mmpataki/d0b37ee4442d88a51ae1e8508de7581c to your computer and use it in GitHub Desktop.
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
<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> | |
function render(name, spec, elemCreated, container) { | |
let e; | |
if (!spec.preBuilt) { | |
e = document.createElement(spec.ele); | |
spec.iden && elemCreated(spec.iden, e) | |
if (spec.text) e.innerHTML = spec.text; | |
if (spec.classList) { | |
e.classList = `${name}-` + spec.classList.split(/\s+/).join(` ${name}-`) | |
} | |
spec.attribs && Object.keys(spec.attribs).forEach(key => { | |
e[key] = spec.attribs[key] | |
}) | |
spec.styles && Object.keys(spec.styles).forEach(key => { | |
e.style[key] = spec.styles[key] | |
}) | |
spec.evnts && Object.keys(spec.evnts).forEach(key => { | |
e.addEventListener(key, spec.evnts[key]) | |
}) | |
if (spec.children) { | |
if (spec.children instanceof Function) { | |
spec.children().map(x => e.appendChild(x)) | |
} | |
else spec.children.forEach(child => render(name, child, elemCreated, e)) | |
} | |
} else { | |
e = spec.ele; | |
} | |
if (container) { | |
let lbl; | |
if (spec.label || spec.postlabel) { | |
let rgid = "id_" + Math.random(); | |
e.id = rgid | |
lbl = document.createElement('label') | |
lbl.innerHTML = spec.label || spec.postlabel | |
lbl.setAttribute('for', rgid) | |
} | |
if (spec.label) container.appendChild(lbl) | |
container.appendChild(e) | |
if (spec.postlabel) container.appendChild(lbl) | |
return container; | |
} | |
return e; | |
} | |
class StoryTeller { | |
constructor(storyBoardElement, firstStoryClass, args) { | |
this.stack = []; | |
render('storyboard', { | |
ele: 'div', | |
children: [{ | |
ele: 'div', classList: 'titlebar', children: [ | |
{ ele: 'button', iden: 'backbtn', attribs: { innerHTML: '←' }, 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) 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 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 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) | |
} | |
} | |
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 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 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 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, ' ') } | |
}) | |
} | |
} | |
let storyTeller = new StoryTeller(document.getElementById('storyBoard'), PinCodeStory, {}) | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment