Een iPhone WebApp ‘in a snap’

In need of an English translation? Please drop me a note!

Het bouwen van een app voor Apple devices hoeft niet moeilijk te zijn. Als je een native App wilt bouwen voor de iPhone/iPad/iPod dan komt daar al snel ‘kosten’ bij om de hoek kijken:

          Apple hardware om op te bouwen ($$$ + $$$)

          Keuze voor Apple development tools of Mono Touch ($$$)

          Licence om op de apple store te mogen uitrollen ($$$)

          Eventuel een nieuwe programmeertaal + Api leren ($$$)

Maar het kan ook eenvoudiger. Apple devices laten het toe om webpagina’s direct te openen vanaf het hoofdscherm, fullscreen dus zonder zoekbalk. Als zo’n pagina een Apple skin voert (via CSS) en leuke overgangen naar andere pagina’s (transities) simuleert, dan lijkt het een native app is. Pas als je echte integratie met het device wilt (zoals toegang tot de map met plaatjes) dan ga je nat J

En het mooie is dat het gewoon kan met wat HTML, Javascript en een onderliggende server laag zoals PHP, Java of .Net. En dat voor het prijsje van 0$.

Dus je wilt een webapp bouwen. Er zijn tientallen gelijkwaardige bibliotheken (ik heb ook nog eens naar IUI gekeken) maar hier toon ik hoe het kan met jQTouch en Asp.Net MVC3.

jQTouch is vooral voor de (Apple) webkit browsers en dan nog voor de iphone. Voor de ipad is jqMobile wellicht een betere keuze maar daar is (nog) weinig documentatie voor. Heb je geen iPhone om je creaties op te testen? Gebruik dan een lokale Safari browser of gebruik een andere browser die html 5 aankan. Het zou overigens ook op Android kunnen werken met uitzondering van enkele transities die de grafische processor aanspreken.

Leuk, maar waar moet je beginnen als je echt een applicatie wil bouwen? In onderstaande POC wordt een MVC3 site tot een web app omgetoverd met een homescherm, schermovergangen (transities) enkele invoervelden, en een ajax call om eea. uit te voeren.

We beginnen met een standaard, lege, MVC 3 webproject:

 

Voeg vervolgens een HomeController toe, die een eenvoudige Index actie heeft.

Voeg ook voor die Index methode een view toe. Zie dat we een masterpage aanroepen.

Voordat we naar de code kijken, moeten we eerst jQTouch toevoegen aan het project. Download deze vanaf https://github.com/senchalabs/jQTouch/zipball/b3.1 en pak het even uit. Plaats vervolgens:

In de Context map Vul deze aan met de jQTouch themes mappen (hele structuurtje even overnemen) en jqtouch.css .Verwijder de aanwezige .css
In de Scripts map Vul deze aan met jquery-1.4.2 (standaard meegeleverd bij jQTouch, de huidige 1.6.2 versie werkte bij mij niet correct) en de jqtouch.js

Ok, alles staat klaar om aangeroepen te gaan worden. Dus pas daarna de _Layout.cshtml in de View|Shared map aan:

<!DOCTYPE html>
<html>
  <head>
    <link href="@Url.Content("~/Content/jqtouch.css")"
       rel="stylesheet" type="text/css" media="screen" />
    <link href="@Url.Content("~/Content/themes/apple/theme.css")"
       rel="stylesheet" type="text/css" media="screen" />
    <script src="@Url.Content("~/Scripts/jquery-1.4.2.min.js")"
       language="JavaScript" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jqtouch.js")"
       language="JavaScript" type="text/javascript"></script>
    <script type="text/javascript" charset='utf-8'>
      jqt = $.jQTouch({
        icon: '@Url.Content("~/Content/themes/icon.png")',
        fullScreen: true,
        preloadImages: [
    '@ Url.Content("~/Content/themes/apple/img/chevron.png")',
    '@ Url.Content("~/Content/themes/apple/img/whiteButton.png")',
    '@ Url.Content("~/Content/themes/apple/img/backButton.png")',
    '@ Url.Content("~/Content/themes/apple/img/activeButton.png")',
    '@ Url.Content("~/Content/themes/apple/img/loading.gif")'
        ]
      });
      // Determine if iPhone, Android or Desktop OS and
      // setup the right click-event ("tap" vs "click").
      var userAgent = navigator.userAgent.toLowerCase();
      var isiPhone = (userAgent.indexOf('iphone') != -1 ||
        userAgent.indexOf('ipod') != -1) ? true : false;
      clickEvent = isiPhone ? 'tap' : 'click';
    </script>
  </head>
  <body>
    <div id="jqt">
      @RenderBody()
    </div>
  </body>
</html>

Hier worden de links naar CSS en Javascript gezet. Zie ook dat er een div met als naam jqt in staat. Deze is later nodig als de invulling van de body (in index.cshtml) geplaatst wordt. Dit is een standaard dingetje voor jqTouch, vergeet deze div niet!

De tap vs. Click code is overgenomen uit http://www.timo-ernst.net/2010/08/tutorial-a-simple-twitter-client-with-jqtouch/. Handig als je ook Android wilt ondersteunen.

Vervolgens gaan we de index.cshtml, de View aanpassen:

@ {Layout = "~/Views/Shared/_Layout.cshtml";}
<div id="home">
  <div>
    <h1>Welcome</h1>
  </div>
  <h2>Welcome here</h2>
  Please go to the next page to enter some information.<br />
  <ul>
    <li><a href="#input">Start entering some info</a></li>
  </ul>
</div>
<div id="input" >
  <div>
    <a id="backFromInput" href="#home">back</a>
    <h1>Start</h1>
  </div>
  <h2>Start entering</h2>
  Please enter the requested information:
  <br />
  <ul>
    <li>Name:<br /><input type="text" name="name"
          placeholder="name" id="name" /></li>
    <li>Phone number:<br /><input type="tel" name="phone"
          placeholder="Phone" id="phone" /></li>
    <li>Zipcode:<br /><input type="number" name="zip"
          placeholder="Numbers" id="zip" /></li>
    <li>Email address:<br /><input type="email" name="Email"
      placeholder="Please enter your email address" id="email" /></li>
    <li><input type="checkbox" name="Agree" title="I accept" id="agree" /></li>
  </ul>
  <a href="#" style="margin: 10px;" id="btnStartCallback">Start callback</a>
</div>
<div id="waiting" >
  <div>
    <h1>Waiting</h1>
  </div>
  <h2><center>Please wait while we do something...</center></h2>
  <center>
    <img src="@ Url.Content("~/Content/themes/apple/img/loading.gif")"
         alt="wait" />
  </center>
</div>
<div id="success" >
  <div>
    <h1>Succeeded</h1>
  </div>
  <h2><center>Entering succeeded<center/></h2>
  <center>Thank you for entering the info.</center>
  <center>You can now close this app.</center>
</div>
<div id="error" >
  <div>
    <h1>Oops</h1>
  </div>
  <h2><center>Oops, an error...</center></h2>
  <center>An error occurred. Please contact the administrator...</center>
  <center><label id="errorInfo"></label></center>
</div>
<script type="text/javascript" charset='utf-8'>
  // first hide the submit button
  $("#btnStartCallback").hide();
  // enable or disable the callback button
  $('#agree').click(function () {
    if ($('#agree').attr("checked")) {
      $("#btnStartCallback").show();
    }
    else {
      $("#btnStartCallback").hide();
    }
  });
  // Attach an event when starting screen
  $('#btnStartCallback').bind(clickEvent, function (e) {
    var name = $('#name').val() + "";
    var phone = $('#phone').val() + "";
    var zip = $('#zip').val() + 0;
    var email = $('#email').val() + "";
    var dataToSend = { 'name': name, 'phone': phone,
                            'zip': zip, 'email': email };
    var url = "@Url.Action("DoSomething", "Home")";
    $.ajax({
      url: url,
      data: dataToSend,
      dataType: 'json',
      type: 'POST',
      beforeSend: function () {
        // before callback, show the waiting animation
        jqt.goTo('#waiting', 'dissolve');
      },
      success: function (result) {
        if (result.Succeeded) {
          // after a successfull callback
          jqt.goTo('#success', 'slideleft');
        }
        else {
          // after an unsuccessful callback
          $('#errorInfo').text("Invalid input");
          jqt.goTo('#error', 'slideleft');
        }
      },
      error: function (xhr, err) {
        // after an error
        $('#errorInfo').text("Status: " + xhr.status);
        jqt.goTo('#error', 'slideleft');
      }
    });
  });
</script>

Hier zien we de standaard opzetje die veel van dit soort webapp bibliotheken gemeen hebben. Dit zie je ook terug in b.v. jQMobile en IUI. Alles schermen die straks getoond moeten gaan worden, de verschillende pagina’s, worden in één index.cshtml opgenomen als verschillende divs:

We hebben hier vijf pagina’s:

1.       Home; De openingspagina; Bevat alleen een lijstje met navigatie buttons (ééntje)

2.       Input; Invoer van gegevens en een verstuurknop

Dit is overigens geen form oplossing en er is dus ook geen echte submit-button maar we gebruiken een url gestyled als een button. Deze traps dan een ajax call af. Eventueel kan men terugnavigeren naar de Home pagina. De invoer controls zijn ook zo ingesteld dat ze hun eigen specifieke toetsenbord opvragen. De button is pas zichtbaar als de gebruiker de ‘voorwaarden’ geaccepteerd heeft.

3.       Waiting; Dit scherm wordt getoond tijdens de callback van de ajax call.

Een animated gif doet het altijd leuk hier. jQTouch levert er al eentje bij.

4.       Success; De actie is geslaagd. De gebruiker mag de applicatie sluiten.

5.       Error; Deze pagina wordt getoond als de ajax call fout gaat.

De overgangen zijn via jqtouch functies opgelost. Ik mag zelfs de transities aangeven J.

Blijft nog over de controller. Deze is erg kaal:

public class HomeController : Controller
{
  public ActionResult Index()
  {
    return View();
  }
  [AcceptVerbs(HttpVerbs.Post)]
  public ActionResult DoSomething(string name, string phone,
                                    int zip, string email)
  {
    var myJsonResult = new MyJsonResult();
    try
    {
      // Do something
      Thread.Sleep(2000);
      // throw new ArgumentException("boo");
      myJsonResult.Succeeded = true;
    }
    catch (Exception)
    {
      myJsonResult.Succeeded = false;
    }

    return new JsonResult { Data = myJsonResult };
  }
}
public class MyJsonResult
{
  public bool Succeeded { get; set; }
}

Hier zien we de aanroep van het Index scherm. Er is geen tweede
Index action gedefineerd voor een eventuele submit van het Index scherm, want
die is er niet. Als de ‘submit’ knop ingedrukt wordt dan wordt de ajax call
uitgevoerd die vervolgens de DoSomething aanroept. Hieracher kan code
uitgevoerd worden en eventueel informatie teruggeven aan de callback. Hier geef
ik alleen een seintje terug of de denkbeeldige functionaliteit werkt. Dit wordt
dan vertaald in de juiste transitie in door de javascript code achter de callback.

Conclusie

Voordat ik er uit was hoe ik deze structuur moest opzetten was ik enkele uurtjes verder. Doe er dus je voordeel mee. Diezelfde blog die me op de ‘tab’ wees vertelde dat een transitie via code niet mogelijk was. Dus wel. Kijk maar in de jqtouch.js.

Natuurlijk kan dit nog veel fraaier gebouwd worden. Het is met jQTouch mogelijk om de ‘GPS’ locatie op te vragen die de browser beschikbaar kan stellen. jQTouch kan ook reageren op het kantelen van toestellen. Het is geen volledige integratie met het toestel maar het is een goede start.

Dus hoe ziet het resultaat er uiteindelijk uit? Hieronder toon ik hoe het er in de Safari browser uit ziet (links) en daarnaast de printscreen van een iPad met het uiteindelijke resultaat (rechts).

Safari
Browser
Ipad
 
 

Voor jQMobile zou de code er bijna hetzelfde uit moeten zien. Heb je dit uitgeprobeerd? Dan zie ik jou bevindingen graag terug in de comments hieronder.

Advertenties