Posting forms with Knockout to dynamic Sammy routes
Single-page applications (SPAs) are the standard these days, and two aspects are typically considered for their development:
- Data binding the view (HTML) with the model (Javascript)
- Navigating between logical “pages” without reloading.
There are many libraries that handle both needs, and I went with Knockout for (1) and Sammy for (2) – mostly because I was already familiar with them (they are both excellent).
The way Sammy works is by defining “routes” (typically hash tag routes for SPAs), which are basically URLs that trigger JS methods. For example, you might define the following route:
get('#/:name', function() {
alert(this.params['name']);
});
When the URL changes to mysite.com/#/Ohad, an alert will pop up saying “Ohad”. Now, suppose we have a form we want to “submit” to the route above Sammy route:
<form
data-bind="submit:function(){window.location='#/'+name();}">
<input type="text" data-bind="value: name"></input>
<button type="submit">Submit</button>
</form>
The input’s data-bind _attribute binds the input’s value to our view model’s _name observable. The form’s data-bind attribute binds the form submission event to a URL redirection for the desired Sammy route. For more information about Knockout bindings, visit https://learn.knockoutjs.com.
The code above works, but not exactly as expected. See, when no action attribute is specified on the form element, the browser assumes the current URL without hashtags is the submission URL, and so right after the URL is redirected to mysite.com/#/MyName it is reverted to just mysite.com by the browser.
This messed up my hash tag navigation model, and nothing I tried prevented the browser from reverting the address (canceling the form submission event, returning false from the handler method, etc). And then it hit me – why fight the browser?
<form
data-bind="attr: { action: '#/'+name() }">
<input type="text" data-bind="value: name"></input>
<button type="submit">Submit</button>
</form>
See what we did there? Instead of using knockout to circumvent the normal form submission flow, I go with it. Whereas Knockout’s submit binding tries to cancel the browser’s submission event and replace it with the supplied method, here the submission flow is completely standard. We just need to make sure the action attribute points to the right place, and voila – hash tag history is preserved.
BTW, you may wonder why I insisted to go with a form, where in truth no “real” submission takes place here. Indeed, a regular div with a regular button would not have had this issue to begin with!
Well, the answer is a bit silly. I could not find any reliable cross-browser method to intercept the [enter] key on the input in order to trigger submission (everything is either deprecated or not globally supported). And frankly, even if I did, it would be a hack – the browser should handle such logic. Plus, I enjoyed the challenge 🙂
Leave a Comment