Finally, I was able to get Sinatra + Warden & Rails + Devise all working together.
Here were the main aspects:
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.herokuapp.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
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.
]]>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:
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.
Click the screen shot to view the demo (requires an iPhone/iPad and Safari or Chrome) http://bit.ly/ecpzJx
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:
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.
There are a few limitations with this approach:
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:
]]>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):
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:
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.
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.
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.
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.
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'".
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.
iPad support can be found on BeeDesk's fork: http://bit.ly/gyraLr
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:
]]>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.
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.
The layout generally contains three parts: toolbar (fixed), tabbar (fixed), and content (flexible).
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:
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;
}
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.
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)
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>
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.
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.
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.
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.
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:
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)
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.
The gist (at the end of the post) solved all these timing pitfalls, so do use it.
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>
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 =====
]]>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;
}; // End touch handler
Find the full code listing here: http://github.com/beedesk/jQTouch/blob/master/jqtouch/jqtouch.js
]]>