Every Nth Item in Handlebars

You’re all jazzed and using your favourite CSS grid but you’ve run into a wall in your Handlebars template. You’ve got a template that outputs a list of books. The problem is after every third book you need to start a new row. You’re stuck. Well… look no further I’ve got just the helper for you, grouped_each. It’s part helper, part convention. Stronger than mere mortal men.

Here’s how it works. It has two parameters. The first is the number items in each group and the second is an array of items. That’s the helper part. Now here’s the convention. Inside the grouped_each block you use the built-in each helper to iterate over this. For example look at the following Handlebars template.

<div class="container">
{{#grouped_each 3 books}}
<div class="row">
{{#each this }}
<div class="span4 book" data-isbn="{{ isbn }}">
<strong>{{ name }}</strong>
</div>
{{/each}}
</div>
{{/grouped_each}}
</div>
view raw 412.handlebars hosted with ❤ by GitHub

And finally here’s the magic sauce that is the helper.

Handlebars.registerHelper('grouped_each', function(every, context, options) {
var out = "", subcontext = [], i;
if (context && context.length > 0) {
for (i = 0; i < context.length; i++) {
if (i > 0 && i % every === 0) {
out += options.fn(subcontext);
subcontext = [];
}
subcontext.push(context[i]);
}
out += options.fn(subcontext);
}
return out;
});
view raw 412.js hosted with ❤ by GitHub

Integrate Handlebars with Marionette

If you need to use Handlebars with Marionette then today is your luck day! I’ve got just what your looking for. This first attempts to load pre-compiled templates, then by selector, and finally via XHR.

To customize the file extension and location of your templates when loading via XHR just override Marionette.Handlebars

/**
Usage: Just include this script after Marionette and Handlebars loading
IF you use require.js add script to shim and describe it in the requirements
*/
(function(Handlebars, Marionette) {
Marionette.Handlebars = {
path: 'templates/',
extension: '.handlebars'
};
Marionette.TemplateCache.prototype.load = function() {
if (this.compiledTemplate) {
return this.compiledTemplate;
}
if (Handlebars.templates && Handlebars.templates[this.templateId]) {
this.compiledTemplate = Handlebars.templates[this.templateId];
}
else {
var template = this.loadTemplate(this.templateId);
this.compiledTemplate = this.compileTemplate(template);
}
return this.compiledTemplate;
};
Marionette.TemplateCache.prototype.loadTemplate = function(templateId) {
var template, templateUrl;
try {
template = Marionette.$(templateId).html();
}
catch (e) {}
if (!template || template.length === 0) {
templateUrl = Marionette.Handlebars.path + templateId + Marionette.Handlebars.extension;
Marionette.$.ajax({
url: templateUrl,
success: function(data) {
template = data;
},
async: false
});
if (!template || template.length === 0){
throw "NoTemplateError - Could not find template: '" + templateUrl + "'";
}
}
return template;
};
Marionette.TemplateCache.prototype.compileTemplate = function(rawTemplate) {
return Handlebars.compile(rawTemplate);
};
}(Handlebars, Marionette));

Search using Geolocation data in MySQL

One of the many things brought to the forefront by HTML5 is Geolocation. It’s easy enough to get someones geolocation data using HTML5 but what do you do with it once you have it? Ideally you’d like to search through some sort of database be it store locations, real estate listings or what have your. This is where most people start to get tripped up.

There’s lots of different techniques for achieving this unfortunately they don’t seem to ever be explained very well as they relate to MySQL. Until now… dun dun dun dahhh.

The two most common technique are the Haversine formula and the Spherical Law of Cosines. The method I’ve chosen to use in my example is the Spherical Law of Cosines. The formula is a bit short and uses fewer mathematical functions.

The example is comprised of two parts. The first is the Spherical Law of Cosines. The second part is defining a search radius. Defining this search radius will significantly narrow the scope of our search. This is a good thing.

Alright enough chitchat. Here’s the sample MySQL query. It’s built in PHP below but can easily be converted to your language of preference.

Note: I’m Canadian so the example below is done using kilometers. If you want miles you’ll need to multiple $earths_radius and $surface_distance_coeffient by 0.6214 to convert them to miles.

<?php
// Search parameters
$lat = 45.411572;
$lng = -75.698194;
$radius = 25;
// Constants related to the surface of the Earth
$earths_radius = 6371;
$surface_distance_coeffient = 111.320;
// Spherical Law of Cosines
$distance_formula = "$earths_radius * ACOS( SIN(RADIANS(latitude)) * SIN(RADIANS($lat)) + COS(RADIANS(longitude - $lng)) * COS(RADIANS(latitude)) * COS(RADIANS($lat)) )";
// Create a bounding box to reduce the scope of our search
$lng_b1 = $lng - $radius / abs(cos(deg2rad($lat)) * $surface_distance_coeffient);
$lng_b2 = $lng + $radius / abs(cos(deg2rad($lat)) * $surface_distance_coeffient);
$lat_b1 = $lat - $radius / $surface_distance_coeffient;
$lat_b2 = $lat + $radius / $surface_distance_coeffient;
// Construct our sql statement
$sql = <<<SQL
SELECT *, ($distance_formula) AS distance
FROM listings
WHERE (latitude BETWEEN $lat_b1 AND $lat_b2) AND (longitude BETWEEN $lng_b1 AND $lng_b2)
HAVING distance < $radius
ORDER BY distance ASC
SQL;
view raw 308.php hosted with ❤ by GitHub

If your interested in digging deeper into the theory and math behind the Spherical Law of Cosines or the Haversine formula checkout the Movable Type Scrips site they have tons of background information.

Passpack + Chrome = Frustration

Everyday I log into Passpack and leave it open all day. So here’s the problem after having Passpack open for a while I get the following message (it is extremely annoying as it interupts whatever else your up to on the net). This only happens for me in Chrome. It works like butter in Firefox and I can’t speak for IE.

Passpack History Problem

The initial trigger for the message seems to be random. But after it initial message is seems to come back around every 3 minutes. So I did some digging. After poking around in the source I discovered that they’re using an older version of Tako Sano’s jQuery history plugin.

So I added some code to the historyCallback do some logging so I could get a better feel for what was going on.

errorOccurred = lastErrorOccurred = new Date().getTime();
_back = function(h) {
if (_backOk) {
if (_backCallback) {
_backCallback();
_backCallback = null;
}
var hh = parseInt(h);
var m = $.browser.netscape ? 2 : $.browser.msie ? 3 : 7;
if (hh > m) {
errorOccurred = new Date().getTime();
console.log("hh: "+ hh +", m: "+ m +", delta: "+ (errorOccurred - lastErrorOccurred));
lastErrorOccurred = errorOccurred;
alert(PP.msg.man.bac);
}
}
};
view raw 217-1.js hosted with ❤ by GitHub

After letting this run for a while I started to see a pattern. Here’s an excerpt of the results.

  • hh: 8, m: 7, delta: 209901 (~3.4 minutes)
  • hh: 8, m: 7, delta: 200888 (~3.3 minutes)
  • hh: 8, m: 7, delta: 190944 (~3.2 minutes)
  • hh: 8, m: 7, delta: 183514 (~3.0 minutes)
  • hh: 8, m: 7, delta: 197517 (~3.3 minutes)

For webkit (Chrome/Safari) based browsers the jQuery history plugin manually creates a stack to keep track of the history. So this is where I believe we are encountering the problem. If you look at line 4426 you can see that we create a history with a stack length of 9.

_back = function(h) {
if (_backOk) {
if (_backCallback) {
_backCallback();
_backCallback = null;
}
var hh = parseInt(h);
var m = $.browser.netscape ? 2 : $.browser.msie ? 3 : 7;
if (hh > m) alert(PP.msg.man.bac);
}
};
runHistory = function() {
$.historyInit(_back);
z = $.browser.netscape ? 2 : $.browser.msie ? 4 : 8;
for (var j = z; j >= 0; j--) $.historyLoad(j);
_SetTimeout(function() {
_backOk = 1
},
3000);
};
view raw 217-2.js hosted with ❤ by GitHub

But on line 4419 we are working with a stack with a length of 8.

var m = $.browser.netscape ? 2 : $.browser.msie ? 3 : 7;

Shouldn’t this read as follows?

var m = $.browser.netscape ? 2 : $.browser.msie ? 4 : 8;