iPad Split-view supports on a jqtouch's fork

Background

The original jQTouch does not support iPad other than expanding every widget to fill the new space. However, it is still one of the best starting points for an HTML5 mobile application.


BeeDesk is about productivity. Making better use of the extra iPad screen space is important, so we put much effort into making that happen.


Here is a screen shot of the modified main jQTouch example.


Link to demo here (supports iOS devices, Safari or Chrome):

    http://bit.ly/beedesk-jqt

Scope of work

We spent three very long days on the initial work and roughly the same amount of time spread out over the following three weeks fixing bugs such as an animation laying problem that is now fixed.

Changes include:

  • CSS
  • Adding initialization code to support two "current" panes.
  • Rewriting the page history management.
  • Adding an HTML marker for "section" to let jQTouch know which section of a pane to display.
  • Adding a conditional widget (a widget that shows or hides depending on whether it is in split-view or singular-view mode).
  • Adding a new “Toggle” button type (in addition to <a> and <a class="back">).

Initially, we thought we’d only need some CSS tricks and initialization code. However, more effort was required to create a meaningful application that would be compatible with both split view and singular view.

Author’s Note: After this work we also discovered that URL-search (i.e, http://example.com/mobileapp?search=abc) passing is important for split-view navigation. We’ve since added that support and will present it in a later blog post.

Goal

We strive to maintain the original jQTouch’s ease of use. We also want the same HTML markup to run on both screens, and minimal time investment in decorating existing jQTouch applications to support split view.

Mini User Guide

Main switch

To activate split-view support, a user should add ".splitscreen" to "#jqt", as shown here:

    <body id="jqt" class="splitscreen">
        <div id="homepane" class="current">
           <!-- ... -->
        </div>
        <div id="uipane">
           <!-- ... -->
        </div>
        <!-- ... -->
    </body>

If jQTouch detects the screen is smaller than iPad, it automatically falls back to the original behaviour.

Assign a section

By default, all panes appear on the main side (right, larger side) of the split view. Specify "<div id="xyz-pan" section="aside"> for each pane that should appear on the "aside" (left side) of the split view.

Also, remember to mark one of each side as ".current". For example:

    <body id="jqt" class="splitscreen">
        <div id="homepane" class="current" section="aside">
           <!-- ... -->
        </div>
        <div id="uipane" class="current">
           <!-- ... -->
        </div>
        <!-- ... -->
    </body>

TIP! Sometimes, you might want to create a new blank pane and assign current to it, so that it has a page to start with.

Condition widget

If you are converting the main example or your application, you may notice that in many cases, the Back button is no longer useful. The new code should handle it gracefully if you click it, but it doesn't do anything meaningful.

To switch it off you can use the new "section='aside | [ main | full ]'" markup. For example:

    <div id="ui" class="current">
        <div class="toolbar">
            <h1>UI Demos</h1>
            <a class="back" href="#" section="full">Home</a>
        </div>
        <!-- ... -->
    </div>

This markup lets jQTouch know that the widget should be displayed only if the application is run in full screen mode and hide it otherwise. If a widget should appear in more than one section, you may use a comma separated list such as "section='main, full'".

Toggle Button

 

A new type of button is added to support the case where a button on the aside section controls the display of the main section.

      <div id="uinavibar" class="navibar">
        <a href="#settingpane" class="tog left nopad slideup">Toggle</a>  
        </div>
        <!-- ... -->
      </div>

Because the class ".toggle" conflicts with existing jQTouch CSS, we simply use ".tog", which toggles settingpane. You can mix and match it with a conditional widget.

Where to get it

iPad support can be found on BeeDesk's fork: http://bit.ly/gyraLr

Porting Split-view to the official fork

Jan 2013

I has become the maintainer of the official jQTouch. I would like spend some time porting splitview to the official branch. If you like to see it happens much sooner, please consider making a donation:

PayPal - The safer, easier way to pay online!

iPhone App Layout with CSS3 box-flex on mobile webkit (jqtouch)

Introduction

CSS3 provides a much needed improvement on the box model: flex-box: it fills a void left by CSS2 by providing a better way to do elastic layout, in which some part of the HTML expands to fill leftover space.

The best tutorial of flex-box can be found at Mozilla's site: http://hacks.mozilla.org/2010/04/the-css-3-flexible-box-model/.

>

Behaviors are similar in Mozilla and WebKit implementations. Although we are online concerned with mobile WebKit here, it’s a good start. It should be relatively easy to port for your own purpose.

iPhone App Layout

To support navbar (available to BeeDesk jQTouch's forks), landscape layout, and iPad split screen, reworking jQTouch's theme using flex-box makes sense. It doesn't hurt that it works better on a desktop screen.

Before the change we used javascript to calculate height for the flex <div> triggered by orientation change and page events. It is bug-prone, not always reliable, and noticeably slower than CSS.

Layout

The layout generally contains three parts: toolbar (fixed), tabbar (fixed), and content (flexible).

screenshot of iphone

Achieving backward compatibility was tricky, and it supports content wrapper, which is used to support scrolling (not available in the official fork).

That jQTouch doesn't have a class name for the flex part also adds some work.

To get boxflex starts, three markups need to be added:

  1. on container: { display: -webkit-flex-box; }
  2. on each flex children of the container: { display: -webkit-flex-box; }
  3. non-zero value on each flex children of the container: { -webkit-box-flex: 10; }

For our vertical layout, we add these lines to the container:

{
    -webkit-box-sizing: border-box;
    -webkit-box-orient: vertical;
    -webkit-box-pack: start;
    -webkit-box-align: stretch;
}

Using { display: block; } as children of { display: -webkit-flex-box }

With WebKit the default layout of the "children of the {display: webkit-flex-box;}" behaves differently than "children of {display: block;}". Content of the children does not span the width of the flex box. To compensate we need to add these lines to the children:

{
    display: block;
    top:0;
    left
:0;
    right:0;
}

The { left:0; right:0; } markup causes the content to span the entire width.

On the other hand, using { width: 100%; } would not work, because width does not compensate for the margin, and causes content to overflow to the right.

Markup { width: ?px; } won't work either, because it is not flexible.

No name for content

Because jqt didn't give a name for the flex part, we have to resolve to a series of :not() to avoid affecting the toolbar, navbar, and tabbar. This is is how it looks:

#jqt > * > div:not(.contentwrap):not(.navibar):not(.toolbar):not(.bar):not(.info)

Support content wrapper

The rest of the complexity comes from the need to support a variety of content. For example:

<body id="jqt">
  <div id="var1"><!-- simple case -->
     <div class="toolbar"></div>
     <div class="">Content</div>
     <div class="navibar"></div>
  </div>

  <div id="var2"><!-- simple case with .info -->
     <div class="toolbar"></div>
     <div class="info">Warning: this is a warning to warn you that you're warned.</div>
     <div class="">Content</div>
     <div class="navibar"></div>
  </div>

  <div id="var3"><!-- scroll pane case -->
     <div class="toolbar"></div>
     <div class="contentwrap">
       <div>Content</div>
     </div>
     <div class="navibar"></div>
  </div>

  <div id="var4"><!-- modified scroll pane case -->
     <div class="toolbar"></div>
     <div class="contentwrap">
       <div class="afloat_header"></div>
       <!-- must specify .content if it is not the first element -->
       <div class="content">Content</div>
     </div>
     <div class="navibar"></div>
  </div>
</body>

The pitfalls of Html5 applicationCache and how to avoid it

There are only a handful of methods for applicationCache, but getting it to work the first time isn’t always easy. Some well-written tutorials are available such as this one:

   http://www.thecssninja.com/javascript/how-to-create-offline-webapps-on-the-ip...

So, I am going to focus on the pitfalls you might encounter and need help with. Code to avoid most of the pitfalls can be found at the end of this article.

Pitfall 1: Didn't know you need to call update() method

14.00 Normal 0 false false false EN-US X-NONE X-NONE

After the application loads the first time, you need to call update() every time. Otherwise the web browser might (or might not) check the cache. If the implementation doesn't, you will be stuck with the old application forever.

Pitfall 2: Misunderstood update() method will reload the app

The update() method only updates the cache—not the web application or anything that is loaded. This is obvious when you get it working for the first time. The easiest way to update the page when the cache is ready is by reloading it:

location.reload(); 

14.00 Normal 0 false false false EN-US X-NONE X-NONE

It is also desirable to let users decide whether they want to reload the application when the cache is first updated. If you are not careful you might lose user data in the session.

Pitfall 3: Poll and event, you need both

At first, applicationCache may appear to be unreliable because updating the cache is a process that takes time, and happens in the background. You can either poll it repeatedly on an interval, or you need to poll the status AND register a listener. If you register for the event after CACHEREADY, you will not be called.

Pitfall 4: The cake is ready in-between the poll and event registering, so you missed it

You might poll the status and if it is not ready, listen to the CACHEREADY event. But, in between the poll and registering of the event you might miss the CACHEREADY event. To avoid it you need to double check this:

    if (appCache.status === appCache.UPDATEREADY) {
         confirmAndUpdate();
    } else {
        appCache.addEventListener('updateready', confirmAndUpdate, false);
        if (appCache.status === appCache.UPDATEREADY) {
            confirmAndUpdate();
        }
    }

Pitfall 5: Update() was called before

This is another pitfall that may make you believe applicationCache is unreliable. If update() is called before the application is reloaded (either programmatically or by the user), the cache might be updated and become CACHEREADY, even if your application has not yet called update() this time.
Therefore, you should not assume registering for the event before calling update() is sufficient. You must still check this:

     (appCache.status === appCache.UPDATEREADY)

Pitfall 6: Reload before CACHEREADY

Again, Cache updates take time. If you reload the application after calling update(), but before CACHEREADY, your application might run on the old cache. If you continue doing that, your device will not have time to update the cache and you may still run on the old cache even after many reloads.

Taking a break

The gist (at the end of the post) solved all these timing pitfalls, so do use it.

Pitfall 7: Manifest type (config on the web server)

This is covered by all tutorials, but still easy to miss—and very easy to forget on the production server! With apache, a one-liner in .htaccess will do it.

    AddType text/cache-manifest .manifest

With tomcat it must be the server's web.xml (webapp's web.xml didn't work).

    <mime-mapping>
        <extension>manifest</extension>
        <mime-type>text/cache-manifest</mime-type>
    </mime-mapping>

Pitfall 8: Missing files in Manifest

In Webkit implementations at least, even when you are connected, a file that is not included in the manifest will not be loaded. Each file you use (either statically linked or loaded by ajax) must be included. Missing files will not be cached, nor loaded.

I like this failfast implementation of WebKit (I didn't' test this negative case in other major browsers). It creates fewer surprises during deployment.

===== the code =====

    github gist