@p9k nope: `bundle install –path vendor –binstubs` and add ./bin to PATH. Gemsets are a terrible idea.
— Chris Moore (@cdmwebs) November 12, 2012
Recently, I tweeted:
Going to start inlining Zepto into all my bookmarklet and widget Javascript. All the benefits of jquery without the big download.
— gerard sychay (@hellogerard) September 29, 2012
which prompted:
@hellogerard share a sample, please.
— Richard Bronosky (@RichardBronosky) October 12, 2012
Unfortunately, the code I was referring to was client code, but how about a blog post?
The Widget Problem
For whatever reason, I have been writing lots of Javascript widgets and bookmarklets. This type of 3rd-party code is usually copied and pasted into a website or initiated via a boorkmark (both work in similar fashion). The tricky party about widgets is that the code must be lean and fast, and must be robust to hostile environments. By hostile environments, I mean that you can’t make any assumptions about a particular website these days. If you’ve ever tried to scrape arbitrary webpages, you know what I’m talking about.
The problem with jQuery is that sometimes it is neither lean nor fast, and you can’t assume that the website hosting your widget has jQuery already, or has a particular version of jQuery. But we need jQuery. We need its selectors, and Ajax helpers, and CSS helpers. The solution is to load our own jQuery.
I Got Mine (jQuery)
Start with the typical function expression module that isolates your code from any other Javascript.
(function(document, undefined) {
// ... your isolated code
})(document);
Load jQuery asynchronously, and prepare a callback that runs when jQuery is loaded.
(function(document, undefined) {
// Load our own jQuery
s = document.createElement('script');
s.src='//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js';
s.onload = function() {
// ...
});
d.getElementsByTagName('head')[0].appendChild(s);
})(document);
When jQuery loads, it will assign itself to the $ variable, overwriting whatever was using it beforehand, such as the host site’s copy of jQuery. Fortunately, jQuery keeps a copy of the original value, and running jQuery.noConflict() restores the original value of $ and returns the latest value. Run $.noConflict()
to get our own, local copy of jQuery in the $ variable.
(function(document, undefined) {
// Load our own jQuery
s = document.createElement('script');
s.src='//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js';
s.onload = function() {
var $ = jQuery.noConflict();
// ... $ now safe to use
});
d.getElementsByTagName('head')[0].appendChild(s);
})(document);
Can We Do Better?
This works well. However, if the thought of a whole network request (even if cached) just to run your widget bothers you, there’s a surprisingly simple alternative: inline jQuery. That is, copy and paste the entire jQuery library into your widget code. This may seem unusual, but despite it’s size, it’s still cheaper than a network request.
(function(document, undefined) {
// Load our own jQuery
/*! jQuery v@1.8.1 jquery.com | jquery.org/license */
(function(a,b){function G(a){var b=F[a]={};return ...
var $ = jQuery.noConflict();
// ... $ now safe to use
})(document);
Can We (Still) Do Better?
If inlining all 91k (as of 1.8.2) of jQuery also bothers you: consider Zepto (23k for 1.0RC1). Zepto is billed as “a minimalist JavaScript library for modern browsers with a largely jQuery-compatible API.”
(function(document, undefined) {
// Load our own Zepto
/* Zepto v1.0rc1 - polyfill zepto event detect fx ajax form touch - zeptojs.com/license */
(function(a){String.prototype.trim===a&&(String.prototype ...
var $ = window.Zepto;
// ... $ now safe to use
})(document);
Zepto, unlike jQuery, will not assign itself to $ if it is already defined, so no issue there.
Now, don’t make the mistake I did in thinking Zepto is a drop-in replacement as a “lightweight” jQuery because it certainly is not, especially in CSS manipulations. Support for CSS effects in IE is sketchy, and its Ajax helpers are a bit different. However, Zepto gives you all the selectors and DOM manipulators you’re used to, has all the Ajax helpers you need, and offers useful utilities such as $.proxy().
So if you need visual effects in your widget, Zepto will probably not do. But if all you need is the ability to add/remove/select elements, then Zepto works great as a jQuery replacement.
If you’re like me, you have a number of offline jobs running in your application. These jobs process images, send emails, warm up caches, and sync up data with 3rd-party APIs.
Also, if you’re like me, you run into the same problems over and over:
Enter jobby, the PHP cron job manager.
Install a single crontab entry on each of your servers, and never edit crontab again. The master cron job runs a PHP file which contains all of your real jobs and executes them according to their individual schedules (using crontab syntax) along with other configs. The nice thing is that since jobby
is just PHP, you can decided what jobs to run and when programmatically. Now, I know there are more expansive server configuration tools out there, but those felt too heavy for this kind of use.
More features: jobby
will ensure only 1 copy of a job runs at a time, can email one or more recipients if a job fails, and can run as different users on select hosts.
I developed a version of jobby
years ago, and have brought it along with me on various projects, slowly tweaking it. It’s helped me immensely and if you’re interested, check out the README and composer install
away!
I had an existing site I created using Blueprint CSS a while ago. I wanted to make it mobile-friendly. There are responsive CSS grid frameworks out there now, but I didn’t feel like replacing Blueprint, and I figured all I had to do was, using media queries, make every column span a fixed width. That was mostly correct. In the end, I used 100% widths, plus a few other alterations. Here is how I ended up doing it.
Normally, you will link to your Blueprint screen.css
file like so:
The following lines tell the browser to link first to a mobile-friendly CSS file. Then, if and when the width is 600px
, link to the normal, desktop CSS file. The idea is that bandwidth is scarcer on mobile, so we don’t mind downloading two files on a desktop machine if we have to.
To create my mobile.css
, I copied screen.css
, and made the following changes.
2px
th, td, caption {padding: 2px;}
a img {border:none;max-width:90%}
input
text fields to 67% width.
input.text, input.title {width: 67%; padding: 5px;}
textarea
inputs to 80% width, and reduce height
to 150px
.
textarea {width: 80%; height: 150px; padding: 5px;}
container
to 90% width.
.container {width: 90%; margin: 0 auto;}
span
grid classes to 100% width.
.span-1 {width: 100%;}
...
.span-24 {width: 100%; margin-right: 0;}
input.span
and textarea.span
classes, though I’m not sure why now.append
grid classes to 0px
right padding.
.append-1 {padding-right: 0;}
...
.append-24 {padding-right: 0;}
prepend
grid classes to 0px
left padding.
.prepend-1 {padding-left: 0;}
...
.prepend-24 {padding-left: 0;}
pull
grid classes to 0px
left margin.
.pull-1 {margin-left: 0;}
...
.pull-24 {margin-left: 0;}
push
grid classes to 0px
margin.
.push-1 {margin: 0;}
...
.push-24 {margin: 0;}
Not pretty, but it seems to work well. Happy CSS!
NOTE: I had a version of this blog post in draft mode for months addressing the old (PHP SDK v2.1.2) Facebook PHP library. In a fit of momentum, I am publishing this post now, updated to use the new library (PHP SDK v3.1.1). Since I am not as familiar with the new one, there may be better ways to do the following, although the code below works.
Facebook authentication, much like the rest of the platform, can be maddening. It appears easy enough at 1st glance, until you realize the contrived XFBML examples in the docs will not get you very far. So then you might take a look at the advanced document. And then realize that the advance document, that giant, complicated, very detailed and thorough explanation of OAuth 2.0 as implemented by Facebook, does not actually help you code your website.
This blog post is more for my benefit than yours, and serves to summarize my current Facebook authentication strategy, so I don’t have to figure it out over and over. At least, until Facebook changes it.
My Requirements
First off, realize that there are 3 ways to do any particular thing on the Facebook platform: XFBML, JS, and PHP (server-side).
That said, I do not use the XFBML <fb:login/>
button. XFBML is great for non-developers who can copy and paste the documentation into a website and impress their friends. But XFBML is slower, ties your user’s application session to your user’s Facebook session, and you can’t make general API calls, so you may as well learn how to use the JS or PHP SDKs anyway.
Implementation
First, I need a login login link that non-JS browsers can use.
<a id="fb-login" href="<?= $loginUrl ?>">Login</a>
That $loginUrl
comes from:
$loginUrl = $facebook->getLoginUrl(array('scope' => 'offline_access'));
The login link will ask the user for offline_access
permission, as an example.
Then we need some the FB JS SDK to handle the link for those of us with modern browsers.
FB.init( { appId: 'XXXXXX', status: true, cookie: false, oauth: true } );
$('#fb-login').click(function(e) {
e.preventDefault();
FB.getLoginStatus(function(response) {
// maintain application anchor/query string, if any
q = window.location.search.substring(1);
if (window.location.hash) {
q = window.location.hash.split('?')[1];
}
// if already logged in, redirect
if (response.authResponse) {
window.location.href = '/?signed_request=' + response.authResponse.signedRequest + '&' + q;
} else {
// else present user with FB auth popup
FB.login(function(response) {
// if user logs in successfully, redirect
if (response.authResponse) {
window.location.href = '/?signed_request=' + response.authResponse.signedRequest + '&' + q;
}
}, { scope:'offline_access' } );
}
});
});
Couple interesting things to point out:
FB.init()
, the cookie
flag is set to false
. In most FB examples, this flag is set to true
. If this flag is true
, the JS will set a cookie (that begins with ‘fbsr_
’) that will keep the user connected to your Facebook app during a browser session. This can confuse your app, because if you do not clear this cookie when your user logs out, your app may automatically re-authenticate your user. If you do intend to use your user’s Facebook session as your website’s session, set this flag to true
. window.location.reload()
might appear. This only works if the JS sets the Facebook cookie, as this cookie will tell the server that the user is authenticated after the reload. Since we can’t use the cookie, we need another way to tell the server if this login attempt was successful. Server side
On the server-side, Facebook will tell us if this user is who he says he is with the getUser()
method. If this method returns a non-zero user ID, then Facebook has authenticated this user, and we can create a normal PHP login session, however you usually do that.
$id = $facebook->getUser();
if ($id)
{
// login successful
$_SESSION['user'] = $id;
}
// redirect to user dashboard or something
header('HTTP/1.1 302 Found');
header('Location: /');
exit;
getUser()
tries to authenticate a few different ways. You can read the code in the PHP SDK if you’re really curious. One way is from the ‘fbsr_
’ cookie. Another is by checking for the signed_request
in the query string. Remember when we passed that in earlier via JS? The cool thing is, that even in the non-JS case, Facebook will send that signed_request
in the query string, so this code will function the same in both cases.
Keeping the user logged in as he makes requests is the same as usual. I simply check for the flag (or, in the example above, a user ID) to verify authenticated status.
To logout, I can simply clear the PHP session like normal.
// logout
session_unset();
session_destroy();
// redirect to external home page
header('HTTP/1.1 302 Found');
header('Location: /');
exit;
Again, this does not log me out of Facebook
Conclusion
When might you want to use your user’s Facebook session for your user’s website session? I think the only case you would do that is if your web app is a Facebook-centric application. That is, if every feature of your app involves Facebook somehow. In that case, maybe it makes sense because your user cannot use your app without being connected to Facebook.
You can find a fully working example of this flow on my GitHub. This really belongs in an MVC-type framework, but for simplicity I put everything in a giant if/else
statement. Throw the files in a webroot, add your app’s ID and secret to the PHP file, and try it out. Try it with JavaScript enabled and disabled. After logging into the example, try logging out of Facebook, and then logging out of the example, and vice-versa. And let me know if you spot anything fishy.