Client-side considerations

Amazingly enough, there isn't much work that we need to perform to get the contact form going on the client side. Let's examine the contact.go source file found in the client/handlers folder, section by section:

func ContactHandler(env *common.Env) isokit.Handler {
return isokit.HandlerFunc(func(ctx context.Context) {
contactForm := forms.NewContactForm(nil)
DisplayContactForm(env, contactForm)
})
}

This is our ContactHandler function, which will service the needs of the /contact route on the client side. We start off by declaring and initializing the contactForm variable, assigning it to the ContactForm instance that is returned by calling the NewContactForm constructor function.

Note that we pass nil to the constructor function, when we should normally be passing a FormParams struct. On the client side, we would populate the FormElement field of the FormParams struct to associate the form element on the web page to the contactForm object. However, prior to rendering the web page, we run into a did the chicken come before the egg scenario. We can't populate the FormElement field of the FormParams struct because a form element doesn't exist on the web page yet. So, our first order of business is to render the contact form, and for the time being, we will set the contact form's FormParams struct to nil in order to do so. Later on, we will set the contactForm object's FormParams struct using the contactForm object's SetFormParams method.

To display the contact form on the web page, we call the DisplayContactForm function passing in the env object and the contactForm object, contactForm. This function is instrumental in our first objective to preserve the seamless single-page application user experience. Here's what the DisplayContactForm function looks like:

func DisplayContactForm(env *common.Env, contactForm *forms.ContactForm) {
templateData := &templatedata.Contact{PageTitle: "Contact", Form: contactForm}
env.TemplateSet.Render("contact_content", &isokit.RenderParams{Data: templateData, Disposition: isokit.PlacementReplaceInnerContents, Element: env.PrimaryContent, PageTitle: templateData.PageTitle})
InitializeContactPage(env, contactForm)
}

We declare and initialize the templateData variable, which will be the data object that we pass to the template. The templateData variable is assigned with a newly created Contact instance from the templatedata package, having a PageTitle property set to "Contact" and the Form property set to the contactForm object.

We call the Render method of the env.TemplateSet object and specify that we wish to render the "contact_content" template. We also supply the isomorphic render parameters (RenderParams) to the Render method, setting the Data field equal to the templateData variable, and we set the Disposition field to isokit.PlacementReplaceInnerContents, which declares how we will render the template content relative to an associated element. By setting the Element field to env.PrimaryContent, we specify that the primary content div container will be the associated element that the template will be rendering to. Finally, we set the PageTitle property to dynamically change the web page's title as the user lands on the /contact route from the client side.

We call the InitializeContactPage function, supplying the env object and the contactForm object. Recall that the InitializeContactPage function is responsible for setting up the user interactivity-related code (event handlers) for the Contact page. Let's examine the InitializeContactPage function:

func InitializeContactPage(env *common.Env, contactForm *forms.ContactForm) {

formElement := env.Document.GetElementByID("contactForm").(*dom.HTMLFormElement)
contactForm.SetFormParams(&isokit.FormParams{FormElement: formElement})
contactButton := env.Document.GetElementByID("contactButton").(*dom.HTMLInputElement)
contactButton.AddEventListener("click", false, func(event dom.Event) {
handleContactButtonClickEvent(env, event, contactForm)
})
}

We call the GetElementByID method on the env.Document object to fetch the contact form element and assign it to the variable formElement. We call the SetFormParams method, supplying a FormParams struct and populating its FormElement field with the formElement variable. At this point, we have set the form parameters for the contactForm object. We obtain the contact form's button element by calling the GetElementByID method on the env.Document object and supplying an id of "contactButton".

We add an event listener, on the click event of the contact button, which will call the handleContactButtonClickEvent function and pass the env object, the event object, and the contactForm object. The handleContactButtonClickEvent function is significant because it will run the form validation on the client-side, and if the validation is successful, it will initiate an XHR call to a Rest API endpoint on the server-side. Here's the code for the handleContactButtonClickEvent function:

func handleContactButtonClickEvent(env *common.Env, event dom.Event, contactForm *forms.ContactForm) {

event.PreventDefault()
clientSideValidationResult := contactForm.Validate()

if clientSideValidationResult == true {

contactFormErrorsChannel := make(chan map[string]string)
go ContactFormSubmissionRequest(contactFormErrorsChannel, contactForm)

The first thing that we do is suppress the default behavior of clicking the Contact button, which will submit the entire web form. This default behavior stems from the fact that the contact button element is an input element of type submit, whose default behavior when clicked is to submit the web form.

We then declare and initialize clientSideValidationResult, a Boolean variable, assigned to the result of calling the Validate method on the contactForm object. If the value of clientSideValidationResult is false, we reach the else block where we call the DisplayErrors method on the contactForm object. The DisplayErrors method is provided to us from the BasicForm type in the isokit package.

If the value of the clientSideValidationResult is true, that means the form validated properly on the client side. At this point, the contact form submission has cleared the first round of validation on the client-side.

To commence the second (and final) round of validation, we need to call the Rest API endpoint on the server-side, which is responsible for validating the contents of the form and rerun the same set of validations. We create a channel named contactFormErrorsChannel, which is channel that we'll send map[string]string values over. We call the ContactFormSubmissionRequest function as a goroutine, passing in the channel contactFormErrorsChannel and the contactForm object. The ContactFormSubmissionRequest function will initiate an XHR call to the server-side Rest API endpoint, to validate the contact form on the server-side. A map of errors will be sent over contactFormErrorsChannel.

Let's take a quick look at the ContactFormSubmissionRequest function before we return to the handleContactButtonClickEvent function:

func ContactFormSubmissionRequest(contactFormErrorsChannel chan map[string]string, contactForm *forms.ContactForm) {

jsonData, err := json.Marshal(contactForm.Fields())
if err != nil {
println("Encountered error: ", err)
return
}

data, err := xhr.Send("POST", "/restapi/contact-form", jsonData)
if err != nil {
println("Encountered error: ", err)
return
}

var contactFormErrors map[string]string
json.NewDecoder(strings.NewReader(string(data))).Decode(&contactFormErrors)

contactFormErrorsChannel <- contactFormErrors
}

In the ContactFormSubmissionRequest function, we JSON marshal the fields of the contactForm object and fire an XHR call to the web server by calling the Send function from the xhr package. We specify that the XHR call will be using the POST HTTP Method and will be posting to the /restapi/contact-form endpoint. We pass in the JSON-encoded data of the contact form fields as the final argument to the Send function.

If there were no errors in either the JSON marshaling process, or while making the XHR call, we obtain the data retrieved from the server and attempt to decode it from JSON format into the contactFormErrors variable. We then send the contactFormErrors variable over the channel, contactFormErrorsChannel.

Now, let's return back to the handleContactButtonClickEvent function:

    go func() {

serverContactFormErrors := <-contactFormErrorsChannel
serverSideValidationResult := len(serverContactFormErrors) == 0

if serverSideValidationResult == true {
env.TemplateSet.Render("contact_confirmation_content", &isokit.RenderParams{Data: nil, Disposition: isokit.PlacementReplaceInnerContents, Element: env.PrimaryContent})
} else {
contactForm.SetErrors(serverContactFormErrors)
contactForm.DisplayErrors()
}

}()

} else {
contactForm.DisplayErrors()
}
}

To prevent blocking within the event handler, we create and run an anonymous goroutine function. We receive the map of errors into the serverContactFormErrors variable, from contactFormErrorsChannel. The serverSideValidationResult Boolean variable is responsible for determining if there were errors in the contact form by examining the length of the errors map. If the length of the errors is zero that indicates there were no errors in the contact form submission. If the length is greater than zero that indicates that errors are present in the contact form submission.

If the severSideValidationResult Boolean variable has a value of true, we call the Render method on the isomorphic template set to render the contact_confirmation_content template and we pass in the isomorphic template render parameters. In the RenderParams object, we set the Data field to nil because we won't be passing in any data object to the template. We specify the value isokit.PlacementReplaceInnerContents for the Disposition field to indicate that we will be performing a replace inner HTML operation on the associated element. We set the Element field to the associated element, the primary content div container, since this is where the template will render to.

If the serverSideValidationResult Boolean variable has a value of false, that means the form still contains errors that need to be corrected. We call the SetErrors method on the contactForm object passing in the serverContactFormErrors variable. We then call the DisplayErrors method on the contactForm object to display the errors to the user.

We're just about done, the only item we have left to realizing the contact form on the client-side is implementing the server-side, Rest API endpoint that performs the second round of validation on the contact form submission.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.173.199