Sinatra + Warden & Rails + Devise

Finally, I was able to get Sinatra + Warden & Rails + Devise all working together.

Here were the main aspects:

  • Get Devise working with Rails (Devise is a Rails app, won't work without it)
  • Setup the mapping (route) on Rack level to support both Rails and Sinatra
  • Share the sessions between Rails and Sinatra
  • Setup Warden and make it available to Sinatra

Obviously, many had attempted it, as observed from questions on stackoverflow.com. But, there was not any full solutions published. So, here you're, the first one. I hope this post will help the community, or lead other to post a better solution.

First, let's clarify what I want to get out of it.

I am building a new html5 app. In which, MVC is running on the browser. In many cases, I do not need Rails and the performance hit associated with it.

For my html5 app, the server perform two functions: a) serves static html, javascript, and css files. b) All data is served as JSON thru a RESTFul manner. The model code (similar to backbone.js) in the html5 app connect to the server for the JSON data.

With Rack, static files are served. So, we are set for function (a). For RESTFul data, Sinatra just fits the task very well. So, it is the weapons of choice for function (b).

However, in the Sinatra world, authenication implementations are not as well built as those in Rails'. I had actually completed a project with Sinatra on Warden with the help of dm-is-authenicate. I knew I was violating "Don't Repeat Yourself (DRY)" principle, and the task took days, and I was still lacking typical features like "Remember Me"; not to mention that I would have to implement all the feature I might need myself, such as OAuth.

 I went with that long route anyway, because the schedule was tight and I couldn't find any sucessful proclaimation (although, all experts I talked to believe it could be done). The risk of couldn't complete authenication in-time outweight some missing features. With the long route, at least I knew I could have the very very minimal feature in a few days. And, I would able to throw in some long days to put in some feature if needed.

With this setup, the session is shared between the Rails app and the Sinatra app. The only way to authenicate is thru Rails and Devise. Once it is authenicated, both app are authenicated. Same for logout.

You can try a demo here:

   http://sinatra-with-devise.heroku.com/

I also carefully crafted a commit tree on github to show you the exact steps you need to setup the simplest authenication, starting from 'rails new MyApp'.

   https://github.com/beedesk/sinatra-with-devise/commits/master

If you have a sucessful Devise and Rail setup, you might want to import the following changes. The only change you might also need is updating Gemfile and do a 'bundle update'.

 

Along the way, you might have seen some of these errors. Hopefully, by listing them, Google will direct a few of you here and save you some hairs:
- no marshal_dump is defined for class Proc
- rack-1.0.1/lib/rack/session/cookie.rb:64:in 'dump'
- undefined method `unauthenticated?' for nil:NilClass
- if env['warden'].unauthenticated?
- file: whiny_nil.rb location: method_missing line: 48
- manager.default_scope = Devise.default_scope

Rails meets Sinatra on Heroku

I have a Sinatra app. But, I need Devise for authenication. So, I searched around and found Pratik post:

    http://m.onkey.org/rails-meets-sinatra

I started with it, but without real understanding of what "thin" and "rack" is. I stumbled a bit before I got it right.

Heroku has already run with "thin" middleware (it is true at least for pre-Cedar), so some lines from Pratik's post is not necessary.

Fortunately, after all figured out, it is even simpler.

Consider that the rails app is generated with the following line:


    rails create MyApp

Here are various files you needed:

To start it locally:


    thin start

In the hindsight, once I understood what rack really is, it is pretty simple. The Ruby Rack architecture already support this natively.

iPhone Navibar / Toolbar / ButtonBar Examples using Pure Css3

Introduction

Buttons would appear to be a small task, but they aren't. To get a user interface right, a wide variety of options are needed:

  1. Color
  2. Text-shadow
  3. Border-shadow
  4. Active, checked, and disabled states
  5. Placement options
  6. Spanning and padding options
  7. Grouping
  8. Landscape/portrait mode
  9. Radio button behavior
  10. Mask image icon

The permutation gets pretty big pretty quickly. It is all available to BeeDesk's jQTouch fork.

Here are some (hopefully) helpful examples for these options.

Screenshot for Buttons Example

Click the screen shot to view the demo (requires an iPhone/iPad and Safari or Chrome)  http://bit.ly/ecpzJx

Screenshot for Buttons Example (Landscape)

HTML5 iPhone Choice Pane (Button Pane) and Progress Pane

Today I added two frequently used but absent panes from iPhone to BeeDesk's fork of jQTouch: choice pane and progress pane. I also added features that enable similar panes to be made easily.


I considered two approaches:

  1. Introduce a new concept of modal elements.
  2. Reuse the jQTouch page.

In the end I discovered that reusing the jQTouch page yielded a clearer solution and enabled me to reuse existing transition effects.


Those new pages, however, are different from the existing ones. They allow users to see the old page in the background; thus, new code and settings are needed.
The new option name is "animationModifier". Two options—"smokedglass" and "clearglass"—are now available. These options can be specified along with regular animation markup, for example:

[a class="smokedglass slideup" href="#choicepane"]Choice Pane[/a]

To avoid having to modify HTML, a creative use of ::before pseudo selectors was employed to "smoke" the background page.

Following are some screen shots.

Choice Pane

 

Progress Pane

Limitations

There are a few limitations with this approach:

 

  • It’s not pixel-perfect yet.
  • It doesn't work with iPad mode.
  • It could use some better fading transition for the background.
  • It doesn't cover the status bar.
  • You must programatically call goBack() before goTo() page.

I believe this is still better than existing HTML5 CSS at time of writing, whch is why I am posting it here.

Free feel to send me your suggestions for improvement!

Code

See BeeDesk's fork for the most up-to-date version.

    https://github.com/beedesk/jQTouch

For our BeeDesk Labnotes audience members who probably find the CSS the most interesting, so here you are:

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

Click, Scroll and pseudo :active on Mobile Webkit

In the mobile application world, you still sometimes need to build plumbing for things to work correctly. One area I put effort into recently was getting the right visual feedback on a Touch device (namely, iPhone).

With a standard mouse device, the feedback is straightforward, the:active CSS pseudo class makes it easy to give instant visual feedback. For example, a web designer might want to darken the background when a user presses a mouse button on a widget. The web browser simply activates the pseudo class when the button is pressed and deactivates it when the mouse is released. This can be achieved with a couple of lines of CSS:

    .mybutton:active {
       background-color: 0;
    }

Touch interfaces such as iPhone require more dedicated treatment in some situations. In an iPhone, a touch-and-move gesture will initiate scrolling, which should also deactivate the pseudo class. But this behavior is not built in. As such it needs to be reinvented.
Based on jQTouch, I re-implemented this behavior. The original code set a timeout handler to activate the class (.active, instead of :active) after a brief delay to ensure a scroll does not trigger .active. However, this means a quick touch and release will not generate the active.

 

style.css:

    .mybutton.active {
       background-color: 0;
    }

script.js:

$(tapSelectors.join(', ')).live(START_EVENT, touchstart);

function touchstart(e) {
    var $el = null;
    var startX, startY, startTime;
    var deltaX, deltaY, deltaT;
    var endX, endY, endTime;
    var swipped = false, tapped = false, moved = false, inprogress = false;

    function bindEvents($el) {
        $el.bind(MOVE_EVENT, handlemove).bind(END_EVENT, handleend);
        if ($.support.touch) {
            $el.bind(CANCEL_EVENT, handlecancel);
        } else {
            $(document).bind('mouseout', handleend);
        }
    }

    function unbindEvents($el) {
        $el.unbind(MOVE_EVENT, handlemove).unbind(END_EVENT, handleend);
        if ($.support.touch) {
            $el.unbind(CANCEL_EVENT, handlecancel);
        } else {
            $(document).unbind('mouseout', handlecancel);
        }
    }

    function updateChanges() {
        var first = $.support.touch? event.changedTouches[0]: event;
        deltaX = first.pageX - startX;
        deltaY = first.pageY - startY;
        deltaT = (new Date).getTime() - startTime;
    }
    function handlestart(e) {
        inprogress = true, swipped = false, tapped = false, moved = false, timed = false;
        startX = $.support.touch? event.changedTouches[0].clientX: event.clientX;
        startY = $.support.touch? event.changedTouches[0].clientY: event.clientY;
        startTime = (new Date).getTime();
        endX = null, endY = null, endTime = null;
        deltaX = 0;
        deltaY = 0;
        deltaT = 0;

        if (!!$el) {
            $el.removeClass('active');
        }
        $el = $(e.currentTarget);

        // Let's bind these after the fact, so we can keep some internal values
        bindEvents($el);

        setTimeout(function() {
            handlehover(e);
        }, 50);
    };

    function handlemove(e) {
        updateChanges();
        var absX = Math.abs(deltaX);
        var absY = Math.abs(deltaY);

        if (absX >= 1 || absY >= 1) {
            moved = true;
        }
        if (absY <= 5) {
            if (absX > absY && (absX > 35) && deltaT < 1000) {
                inprogress = false;
                $el.removeClass('active');
                unbindEvents($el);

                swipped = true;
                $el.trigger('swipe', {direction: (deltaX < 0) ? 'left' : 'right', deltaX: deltaX, deltaY: deltaY });
            }
        } else {
            // moved too much, can't swipe anymore
            inprogress = false;
            $el.removeClass('active');
            unbindEvents($el);
        }
    };

    function handleend(e) {
        updateChanges();
        var absX = Math.abs(deltaX);
        var absY = Math.abs(deltaY);

        inprogress = false;
        unbindEvents($el);
        if (!tapped && (absX <= 1 && absY <= 1)) {
            tapped = true;
            setTimeout(function() {
              $el.trigger('tap');
            }, 10); /* give a chance other touch to end */
            setTimeout(function() {
              $el.removeClass('active');
          }, 1000);
        } else {
            $el.removeClass('active');
            e.preventDefault();
        }
    };

    function handlecancel(e) {
        inprogress = false;
        $el.removeClass('active');
        unbindEvents();
    };

    function handlehover(e) {
        timed = true;
        if (tapped) {
            // flash the selection
            $el.addClass('active');
            setTimeout(function() {
                $el.removeClass('active');
            }, 1000);
        } else if (inprogress && !moved) {
            $el.addClass('active');
        }
    };

    handlestart(e);

}; // End touch handler 

Find the full code listing here: http://github.com/beedesk/jQTouch/blob/master/jqtouch/jqtouch.js