The date picker cog

Let's consider a scenario that warrants the implementation of a hybrid cog. Molly, the de-facto product manager of IGWEB, came up with a killer idea to provide better customer support. Her feature request to the tech team was to allow the website user to provide an optional priority date on the contact form, by which a user should hear back from a gopher on the IGWEB team. 

Molly found a self-contained, date picker widget, implemented in vanilla JavaScript (no framework/library dependencies) called Pikaday: https://github.com/dbushell/Pikaday.

The Pikaday, JavaScript date picker widget, highlights the fact that was presented in the beginning of this section. JavaScript is not going away, and there are many useful solutions that have already been made using it. That means, we must have the capability to leverage existing JavaScript solutions when it makes sense to do so. The Pikaday date picker is a particular use-case where it is more beneficial to leverage this existing JavaScript date picker widget, rather than implement one as a pure cog:

Figure 9.9: A wireframe design depicting the time sensitivity date input field and the calendar date picker widget

Figure 9.9 is a wireframe design depicting the contact form with a time sensitivity input field, that when clicked, will reveal a calendar date picker. Let's see what it takes to fulfill Molly's request by implementing the date picker cog, a hybrid cog, made with Go and JavaScript. 

We start out by placing the JavaScript and CSS source files required by the Pikaday, date picker widget, inside the js and css folders (respectively), inside the cog's static folder.

Inside the shared/templates/partials/contactform_partial.tmpl source file, we declare the mount point for the date picker cog (shown in bold):

    <fieldset class="pure-control-group">
<div data-component="cog" id="sensitivityDate"></div>
</fieldset>

The div container fulfills two basic requirements of all cog mounting points: we have set the attribute, "data-component", with the value "cog", and we have specified an id of "sensitivityDate" for the cog container.

Let's examine the implementation of the date picker cog, defined in the shared/cogs/datepicker/datepicker.go source file, section by section. First, we start out by declaring the package name:

package datepicker

Here's the cog's import grouping:

import (
"errors"
"reflect"
"time"

"github.com/gopherjs/gopherjs/js"
"github.com/uxtoolkit/cog"
)

Notice that we include the gopherjs package in the import grouping (shown in bold). We will need functionality from gopherjs to query the DOM.

Right after we declare the cogType, we initialize the JS variable to js.Global:

var cogType reflect.Type
var JS = js.Global

As you may recall, this saves us a little bit of typing. We can directly refer to js.Global as JS.

From the Pikaday project web page, https://github.com/dbushell/Pikaday, we can learn all of the input parameters the date picker widget accepts. The input parameters are provided as a single JavaScript object. The date picker cog will expose a subset of these input parameters, just enough to fulfill Molly's feature request. We create a struct called DatePickerParams that serves as the input parameters to the date picker widget:

type DatePickerParams struct {
*js.Object
Field *js.Object `js:"field"`
FirstDay int `js:"firstDay"`
MinDate *js.Object `js:"minDate"`
MaxDate *js.Object `js:"maxDate"`
YearRange []int `js:"yearRange"`
}

We embed the *js.Object to indicate that this is a JavaScript object. We then declare the respective Go fields for the struct for the respective properties of the JavaScript input object. For example, the field named Field is for the field property. The "js" struct tags that we have provided for each field allows GopherJS to convert the struct and its field, from its designated Go name to its equivalent JavaScript name. Just as we declared the field named Field, we have also declared fields for FirstDay (firstDay), MinDate (minDate), MaxDate (maxDate), and YearRange (yearRange).

Reading the Pikaday documentation, https://github.com/dbushell/Pikaday, we can learn what purpose each of these input parameters serve:

  • Field - Used to bind the date picker to a form field.
  • FirstDay - Used to specify the first day of the week. (0 for Sunday, 1 for Monday, etc).
  • MinDate - The earliest date that can be selected in the date picker widget.
  • MaxDate - The latest date that can be selected in the date picker widget.
  • YearRange - The range of years to display.

Now that we've defined the date picker's input parameters struct, DatePickerParams, it's time to implement the date picker cog. We start out by declaring the DatePicker struct:

type DatePicker struct {
cog.UXCog
picker *js.Object
}

As usual, we embed the cog.UXCog to bring along all the UXCog functionality we need. We also declare a field, picker, that's a pointer to a js.Object. The picker property will be used to refer to the Pikaday date picker JavaScript object.

We then implement a constructor function for the date picker cog called NewDatePicker:

func NewDatePicker() *DatePicker {
d := &DatePicker{}
d.SetCogType(cogType)
return d
}

By now, the cog constructor should look familiar to you. Its duties are to return a new instance of a DatePicker and to set the cog's cogType.

Now that our constructor function is in place, it's time to examine the date picker cog's Start method:

func (d *DatePicker) Start() error {

if d.Props["datepickerInputID"] == nil {
return errors.New("Warning: The datePickerInputID prop need to be set!")
}

err := d.Render()
if err != nil {
return err
}

We start out by checking to see if the "datepickerInputID" prop has been set. This is the id of the input field element, which will be used as the Field value in the DatePickerParams struct. It is a hard requirement, that this prop must be set by the caller, before starting the cog. Failure to set this prop will result in an error.

If the "datepickerInputID" prop has been set, we call the cog's Render method to render the cog. This will render the HTML markup for the input field that the date picker JavaScript widget object will rely on.

We then go on to declare and instantiate, params, the input parameters JavaScript object that will be shipped to the date picker JavaScript widget:

params := &DatePickerParams{Object: js.Global.Get("Object").New()}

The date picker input parameters object, params, is a JavaScript object. The Pikaday JavaScript object will use the params object for initial configuration.

We use the cog's Props property to range through the cog's properties. For each iteration we fetch the property's name (propName) and the property's value (propValue):

 for propName, propValue := range d.Props {

The switch block we have declared is important for readability:

    switch propName {

case "datepickerInputID":
inputFieldID := propValue.(string)
dateInputField := JS.Get("document").Call("getElementById", inputFieldID)
params.Field = dateInputField

case "datepickerLabel":
// Do nothing

case "datepickerMinDate":
datepickerMinDate := propValue.(time.Time)
minDateUnix := datepickerMinDate.Unix()
params.MinDate = JS.Get("Date").New(minDateUnix * 1000)

case "datepickerMaxDate":
datepickerMaxDate := propValue.(time.Time)
maxDateUnix := datepickerMaxDate.Unix()
params.MaxDate = JS.Get("Date").New(maxDateUnix * 1000)

case "datepickerYearRange":
yearRange := propValue.([]int)
params.YearRange = yearRange

default:
println("Warning: Unknown prop name provided: ", propName)
}
}

Each case statement inside the switch block, tells us all the properties the date picker cog accepts as input parameters that will be ferried over to the Pikaday JavaScript widget. If a prop name is not recognized, we print out a warning in the web console that the prop is unknown.

The first case, handles the "datepickerInputID" prop. It will be used to specify the id of the input element that activates the Pikaday widget. Inside this case, we get the input element field by calling the getElementById method on the document object and passing the inputFieldID to the method. We set the input params property, Field to the input field element that was obtained from the getElementById method call.

The second case handles the "datepickerLabel" prop. The value for the "datepickerLabel" prop will be used in the cog's template source file. Therefore there's no work needed to handle this particular case.

The third case handles the "datepickerMinDate" prop. It will be used to get the minimum date that should be shown by the Pikaday widget. We convert the "datepickerMinDate" value of type time.Time provided by the caller to its Unix timestamp representation. We then create a new JavaScript date object using the Unix timestamp, which is suitable for use for the minDate input parameter.

The fourth case handles the "datepickerMaxDate" prop. It will be used to get the maximum date that should be shown by the date picker widget. We follow the same strategy here, that we did for the minDate argument.

The fifth case handles the "datepickerYearRange" prop. It will be used to specify the range of years that the displayed calendar will cover. The year range is a slice, and we populate the YearRange property of the input parameters object using the prop's value.

As stated earlier, the default case handles the scenario, where the caller provides a prop name that is not known. If we reach the default case, we print out a warning message in the web console.

Now we can instantiate the Pikaday widget and provide the input parameters obect, params, to it like so:

d.picker = JS.Get("Pikaday").New(params)

Finally, we indicate that there were no errors starting up the cog, by returning a nil value:

return nil

Now that we have implemented the date picker cog, let's take a look at what the cog's primary template, defined in the shared/cogs/datepicker/templates/datepicker.tmpl source file, looks like this:

 <label class="datepickerLabel" for="datepicker">{{.datepickerLabel}}</label>
<input class="datepickerInput" type="text" id="{{.datepickerInputID}}" name="{{.datepickerInputID}}">

We declare a label element to display the label of the date picker cog using the prop "datepickerLabel". We declare an input element that will serve as the input element field that will be used in conjunction with the Pikaday widget. We specify the id attribute of the input element field using the "datepickerInputID" prop.

Now that we have implemented the date picker cog, it's time to start using it. We instantiate the cog inside the InitializeContactPage function, found in the client/handlers/contact.go source file:

  byDate := datepicker.NewDatePicker()
byDate.CogInit(env.TemplateSet)
byDate.SetID("sensitivityDate")
byDate.SetProp("datepickerLabel", "Time Sensitivity Date:")
byDate.SetProp("datepickerInputID", "byDateInput")
byDate.SetProp("datepickerMinDate", time.Now())
byDate.SetProp("datepickerMaxDate", time.Date(2027, 12, 31, 23, 59, 0, 0, time.UTC))
err := byDate.Start()
if err != nil {
println("Encountered the following error when attempting to start the datepicker cog: ", err)
}

First, we create a new instance of the DatePicker cog. We then call the cog's CogInit method, to register the application's template set. We call the SetID method to set the cog's mount point. We make calls to the cog's SetProp method to set the datePickerLabel, datepickerInputID, datepickerMinDate, and datepickerMaxDate props. We call the cog's Start method to activate it. If there were any errors starting the cog, we print the error message out to the web console.

And that's all there is to it! We could leverage the functionality we needed from the Pikaday widget using the date picker hybrid cog. The advantage of this approach is that Go developers utilizing the date picker cog will not need to have knowledge of the inner workings (JavaScript) of the Pikaday widget in order to use it. Instead, they can use the functionality that the date picker cog exposes to them from within the confines of Go.

Figure 9.10 shows a screenshot of the date picker cog in action:

Figure 9.10: The calendar date picker widget in action

Even if the cog user didn't provide any props, other than the required datepickerInputID, to custom configure the date picker cog, the Pikaday widget starts up just fine. However, what if we needed to supply a default set of parameters for the cog? In the next example, we are going to build another hybrid cog, a carousel (an image slider) cog, in which we will define default parameters.

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

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