Have you ever written a function that looks like this?
[code language="javascript"]
function requestProductDetails(id, k) {
var value = gProductDetailsCache[id];
if (value)
k(value)
else
ajax.get('/product/'+id, function(data) {
gProductDetailsCache[id] = data;
k(data);
});
}
[/code]
requestProductDetails calls its callback with the product details, which are stored in a cache. Since it might need to request this information from the server, it has to “return” it by passing it to a callback; in order to present a uniform API whether or not the product is cached, it “returns” the data this way whether it came from the cache or not.
Here’s a potential JSSpec spec for Sequentially.trickle.map:
[code language="javascript"]
describe('Sequentially.trickle.map', {
'should apply to all the elements': function() {
Sequentially.trickle.map(
['a', 'b', 'c'],
function(x) { return x + 1 },
1,
function(result) {
value_of(result.join(',')).should_be('a1,b1,c1');
});
});
}
});
[/code]
This doesn’t work. The problem is that Sequentially.trickle.map is asynchronous (it defers most of its computation — including the invocation of the callback — via setTimeout). This means that should_be isn’t called until after the spec has returned. If it succeeds, this isn’t a problem, but if it fails, JSSpec can’t associate it with the failing spec — worse, JSSpec will have already have marked it successful.
What’s wrong with this function? (Hint: it’s meant to execute periodically on a JavaScript page.)
[code language="javascript"]
function updateExpirationText() {
var now = new Date;
products.forEach(function(item) {
var expiresDate = item.expiresDate || Date.parse(item.expires),
remaining = expiresDate - now,
text = remaining < 0 ? 'expired' : msToDuration(remaining);
$('item-' + item.id + ' .time-remaining').text(remaining);
});
}
[/code]
It’s a trick question. Maybe nothing’s wrong. But if products can get very long, or if the msToDuration is very slow, you’ve locked up the UI for a long time. At best, this makes for sluggish response; at worst, the page that contains this will trigger a “script running slowly” error, and the user will likely abort all the JavaScript on the page.
The DB Content Rails plugin adds tasks to save and restore database content.
Usage
[code]
-- dump the development database to db/archive/development-content.sql.gz
rake db:content:dump
-- load the dumped database, and apply any necessary migrations
$ rake db:content:load
-- dump the production database to db/archive/production-content.sql.gz
$ RAILS_ENV=production rake db:content:dump
-- save the development database to db/archive/{timestamp}.sql.gz
$ rake db:content:save
-- save the (compressed) database to my-data.sql.gz
$ rake db:content:save FILE=my-data.sql.gz
JCON (the JavaScript Conformance gem) tests JSON values against ECMAScript 4.0-style type definitions
(PDF) such as string?, (int, boolean), or [string, (int, boolean), {x:double, y:double}?].
Usage
[code language="ruby"]
type = JCON::parse "[string, int]"
type.contains?(['a', 1]) # => true
type.contains?(['a', 'b']) # => false
type.contains?(['a', 1, 2]) # => true
[/code]
JCON also defines an RSpec matcher, conforms_to_js:
Three small libraries, that I carry with me from project to project:
Fluently — Construction Kit for Chainable Methods
With Fluently, you can do this:
[code language="javascript"]
var o = Fluently.make(function(define) {
define('fn1', function() {console.info('called fn1')});
define('fn2', function() {console.info('called fn2')});
define('fn3', function() {return 3});
});
[/code]
to define an object with chained methods, that can be invoked thus:
[code language="javascript"]
o.fn1().fn2() // calls fn1 and then fn2
o.fn2().fn1() // calls fn2 and then fn1
JavaScript Fu extends Rails with a few facilities to better integrate JavaScript into Rails development:
1. The notes and statistics rake tasks compass JavaScript files in the public/javascript directory:
[code language="bash"]
$ rake notes
public/javascripts/controls.js:
* [782] [TODO] improve sanity check
[/code]
[code language="bash"]
$ rake stats
| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |
[...]
| JavaScript | 7287 | 6322 | 0 | 0 | 0 | 0 |
[...]
[/code]
2. The call_js RSpec matcher asserts that a string or response contains a script tag, that contains JavaScript that calls the named function or method:
I’ve updated my OpenLaszlo utility grab-bag to make browser <-> applet communication even easier. How easy?
Proxies
Put this in your browser JavaScript:
[code language="javascript"]
var gObject = {
f: function() { console.info('gObject.f', arguments) },
g: function() { console.info('gObject.g', arguments) }
};
[/code]
And this in an OpenLaszlo applet:
[code language="javascript"]
var gObject = FlashBridge.createRemoteProxy('gObject', ['f', 'g']);
gObject.f(1, 2);
gObject.g(3);
[/code]
When you run the applet code, it prints this to the browser console: