From febc77032b9f0be067900a42540edd9e2b773e25 Mon Sep 17 00:00:00 2001 From: efim Date: Fri, 14 Jul 2023 20:17:15 +0000 Subject: [PATCH] feat(15): steps submit form and set next --- .../src/main/resources/templates/step1.html | 1 + .../src/main/resources/templates/step2.html | 10 +++ .../src/main/resources/templates/step3.html | 39 +++++++--- .../src/main/resources/templates/step4.html | 10 +++ .../src/main/scala/multistepform/Models.scala | 75 ++++++++++++++++++- .../src/main/scala/multistepform/Routes.scala | 70 ++++++++++------- 6 files changed, 168 insertions(+), 37 deletions(-) diff --git a/15-multi-step-form/src/main/resources/templates/step1.html b/15-multi-step-form/src/main/resources/templates/step1.html index f331457..ac326d8 100644 --- a/15-multi-step-form/src/main/resources/templates/step1.html +++ b/15-multi-step-form/src/main/resources/templates/step1.html @@ -40,6 +40,7 @@ id="form-step" th:fragment="formFragment(formData)" hx-post="/submit-step/1" + hx-swap="outerHTML" action="/submit-step/1" method="post" > diff --git a/15-multi-step-form/src/main/resources/templates/step2.html b/15-multi-step-form/src/main/resources/templates/step2.html index affffbb..75f0d7e 100644 --- a/15-multi-step-form/src/main/resources/templates/step2.html +++ b/15-multi-step-form/src/main/resources/templates/step2.html @@ -39,6 +39,10 @@
Go Back
+ Next Step Add-ons help enhance your gaming experience.

-
+
@@ -136,10 +142,14 @@
-
+

Larger storage

Extra 1TB of cloud save

-

+$2/mo

+

+ +$2/mo +

@@ -154,10 +164,14 @@
-
+

Customizable Profile

Custom theme on your profile

-

+$2/mo

+

+ +$2/mo +

@@ -176,7 +190,14 @@ >Go Back
+ Next Step Go Back
+ Confirm this.copy( + step1 = this.step1.fromFormData(rawData), + currentStep = stepNum + 1 + ) + case 2 => this.copy( + step2 = this.step2.fromFormData(rawData), + currentStep = stepNum + 1 + ) + case 3 => this.copy( + step3 = this.step3.fromFormData(rawData), + currentStep = stepNum + 1 + ) + case 4 => this.copy( + currentStep = stepNum + 1 + ) + case _ => this + } + } + } + + enum PlanType: + case Arcade, Advanced, Pro + + enum Addons: + case OnlineService, LargerStorage, CustomProfile + + sealed trait StepAnswers { + def fromFormData(rawData: String): StepAnswers + } + object StepAnswers { + final case class Step1( + name: String = "", + email: String = "", + phone: String = "" + ) extends StepAnswers { + override def fromFormData(rawData: String): Step1 = { + val fieldValues = rawData.split("&").map { field => + val Array(name, value) = field.split("=") + name -> value + }.toMap + + val name = fieldValues.getOrElse("name", "") + val email = fieldValues.getOrElse("email", "") + val phone = fieldValues.getOrElse("phone", "") + + Step1(name, email, phone) + } + } + final case class Step2( + planType: PlanType = PlanType.Arcade, + isYearly: Boolean = false + ) extends StepAnswers { + override def fromFormData(rawData: String): Step2 = { + val a = 1 + Step2() + } + } + final case class Step3(addons: Set[Addons] = Set.empty) + extends StepAnswers { + override def fromFormData(rawData: String): Step3 = { + val a = 1 + Step3() + } + } } } diff --git a/15-multi-step-form/src/main/scala/multistepform/Routes.scala b/15-multi-step-form/src/main/scala/multistepform/Routes.scala index be6143f..61d7f33 100644 --- a/15-multi-step-form/src/main/scala/multistepform/Routes.scala +++ b/15-multi-step-form/src/main/scala/multistepform/Routes.scala @@ -19,16 +19,23 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger) templateEngine.setTemplateResolver(templateResolver) val sessoinCookieName = "sessionId" - /** - * This route works with and without 'sessionId' cookie present - * set's this cookie if not present, and returns initial 'index.html' - * where the form is not yet initialized, and will be requested for the session - */ + + /** This route works with and without 'sessionId' cookie present set's this + * cookie if not present, and returns initial 'index.html' where the form is + * not yet initialized, and will be requested for the session + */ @cask.get("/") def getIndex(ctx: cask.Request) = { val sessionCookie = ctx.cookies.get(sessoinCookieName) lazy val newSessionCookies = sessionCookie match { - case None => Seq(cask.Cookie(sessoinCookieName, UUID.randomUUID().toString(), path = "/")) + case None => + Seq( + cask.Cookie( + sessoinCookieName, + UUID.randomUUID().toString(), + path = "/" + ) + ) case Some(_) => Seq.empty // don't set new cookies } @@ -45,7 +52,8 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger) @cask.get("/force-new-session") def forceNewSession() = { - val newSessionCookie = cask.Cookie(sessoinCookieName, UUID.randomUUID().toString(), path = "/") + val newSessionCookie = + cask.Cookie(sessoinCookieName, UUID.randomUUID().toString(), path = "/") println(s"setting new session ${newSessionCookie.value}") cask.Response( s"New session forced. Force new session", @@ -54,18 +62,21 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger) ) } - /** - * This method only works when cookie 'sessionId' is present - * will get or init Form State for the session, - * and return last unsubmitted form step fragment - */ + val formDataContextVarName = "formData" + + /** This method only works when cookie 'sessionId' is present will get or init + * Form State for the session, and return last unsubmitted form step fragment + */ @cask.get("/get-form") def getForm(sessionId: cask.Cookie) = { val state = Models.Answers(currentStep = 1) - cask.Response("yoyo") val context = new Context() - context.setVariable("formData", state) - val formFragment = templateEngine.process(state.fragmentName, Set("formFragment").asJava, context) + context.setVariable(formDataContextVarName, state) + val formFragment = templateEngine.process( + state.fragmentName, + Set("formFragment").asJava, + context + ) cask.Response( formFragment, headers = Seq("Content-Type" -> "text/html;charset=UTF-8") @@ -74,20 +85,29 @@ case class Routes()(implicit cc: castor.Context, log: cask.Logger) // i guess let's make step a hidden input? @cask.post("/submit-step/:stepNum") - def submitStep(sessionId: cask.Cookie, stepNum: Int, request: cask.Request) = { + def submitStep( + sessionId: cask.Cookie, + stepNum: Int, + request: cask.Request + ) = { val id = sessionId.value - println(s"got $request for $id") + println(s"got $request for $id. it's data is ${request.text()}") val userAnswers = Sessions.sessionReplies.getOrElse(id, Answers(id)) - // now i want to do what? - // select 'answerStep' form userAnswers - // delegate parsing and saving of the request.text to it - // set current step to next - // and return next rendered form step - // with the 'form data' caclulated from the updated answers - - cask.Response(s"what i got is $request and ${request.text()}") + val updatedAnswers = userAnswers.updateStep(stepNum, request.text()) + val context = new Context() + context.setVariable(formDataContextVarName, updatedAnswers) + val nextFormFragment = templateEngine.process( + updatedAnswers.fragmentName, + Set("formFragment").asJava, + context + ) + println(s"the state now is $updatedAnswers") + cask.Response( + nextFormFragment, + headers = Seq("Content-Type" -> "text/html;charset=UTF-8") + ) } @cask.staticResources("/public")