Hybrid mobile apps: Forms and AJAX

This is part 3 of a series of posts about the conversion of OneSchool's fully native app to a hybrid HTML/native app. Read part 2 or part 1.

We've set up some basic links between the native and HTML parts of our app. But just links aren't enough for a whole app.

GET forms

Let's set up OneSchool's directory search in HTML. The search page has a text entry box for the search term and a submit button to start the search. Our first instinct might be to simply set up with form with a "os://" URL as the action:

<form action="os://directory/results" method="get">
    <input type="entry" name="q">
    <input type="submit" value="Search">
</form>

But this won't work. Although our custom handler will intercept "os://directory/results" and redirect it to the proper Web URL, it will discard the "q" GET parameter. One option would be to extend the WebController custom handler to extract the GET parameters and forward them to the next view. But then we would need to find a way to pass the arguments to the next WebController in native code. There's a better way, one that involves no changes to the native code (uses jQuery):

<script>
    $(document).ready(function(){
        $('#search-button').click(function(){
            window.location = 'os://directory/search/' + $('#search-term').val();
        })
    })
</script>
<input type="entry" name="term" id="search-term">
<input type="button" value="Search" id="search-button">

We've abandoned the HTML form. Instead, we write a Javascript click handler that gets the search term, and constructs and navigates to an "os://" URL that includes the search term. All that's needed now is for the Web server to be able to respond properly to a URL of the form "directory/search/<term>" with the search term embedded directly into it, and we're good to go.

POST forms

Embedding parameters into the URL can only get us so far. Eventually we need to make a page that needs to send a larger amount of information more securely, like a login form. In this case, the easiest way is to make a POST form with an empty action:

<form method="post" action="">
    <input type="entry" name="username" />
    <input type="password" name="password" />
    <input type="submit" value="Login" />
</form>

The empty action effectively makes the target of the POST the real Web URL of the current page. So when the user submits the form, the UIWebView will reload without a native transition to show the results of the POST. As with all "http://" links in this hybrid model, you need to make sure that the page returned is fundamentally the same as the page before. In our login form, we will re-display the same form with an error message on a failed login, so this criteria is met.

On a successful login, we will want to redirect the user to a new page. We can use our custom schemes here, so if we wanted to redirect the user to the launcher we can use (Django-specific):

    def process(self, request):
        return HttpResponseRedirect('tt://launcher')

There's one small problem: the login page will remain on the navigation stack, when we probably want it to disappear. I'll cover handling this later.

AJAX

POST forms work fine for simple views, but what about more complex pages with multiple places actions can be performed by the user? Or what if we want the user to be able to take an action without navigating at all? AJAX was designed for these use cases, and there's just a few things to keep in mind when incorporating it into our hybrid model.

For AJAX URLs, you will want to use normal "http://" links. Previously, the fact that "http://" links don't trigger native transitions was something to be careful of, but now it's exactly what we want.

    <script>
$(document).ready(function(){
$('#submit-button').click(function(){
$.get('http://localhost:8080/iphone/post', function(){
window.location = 'os://bus';
});
});
});
</script>

In this example, we're AJAX loading the "ajaxtest" page when a submit button is pressed. When the request completes, we redirect the user to the "bus" page using a native transition.

In the next post, I will return to the native side, showing some improvements that can be made to WebController.

Unfortunately, since I am no longer part of the OneSchool team, it will not be possible for me to continue this series.