Skip to content

Commit

Permalink
BOOMR_mq method queue (#141)
Browse files Browse the repository at this point in the history
  • Loading branch information
cvazac authored and nicjansma committed May 16, 2017
1 parent fb7489b commit 8f29b05
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 1 deletion.
31 changes: 31 additions & 0 deletions README.md
Expand Up @@ -152,6 +152,37 @@ Opera (including Android, but not Opera Mini), Safari (including iOS), IE 6+ (bu

Boomerang also fires the `onBeforeBoomerangBeacon` and `onBoomerangBeacon` events just before and during beaconing.

#### 3.4. Method queue pattern

If you want to call a public method that lives on `BOOMR`, but either don't know if Boomerang has loaded or don't want to wait, you can use the method queue pattern!

Instead of:
```javascript
BOOMR.addVar('myVarName', 'myVarValue')
```

... you can write:
```javascript
BOOMR_mq = window.BOOMR_mq || [];
BOOMR_mq.push(['addVar', 'myVarName', 'myVarValue']);
```

Or, if you care about the return value, instead of:
```javascript
var hasMyVar = BOOMR.hasVar('myVarName');
```
... you can write:
```javascript
var hasMyVar;
BOOMR_mq = window.BOOMR_mq || [];
BOOMR_mq.push({
arguments: ['hasVar', 'myVarName'],
callback: function(returnValue) {
hasMyVar = returnValue;
}
});
```

docs
---
Documentation is in the docs/ sub directory, and is written in HTML. Your best bet is to check it out and view it locally, though it works best through a web server (you'll need cookies).
Expand Down
3 changes: 2 additions & 1 deletion plugins.json
Expand Up @@ -18,6 +18,7 @@
"plugins/errors.js",
"plugins/third-party-analytics.js",
"node_modules/usertiming-compression/src/usertiming-compression.js",
"plugins/usertiming.js"
"plugins/usertiming.js",
"plugins/mq.js"
]
}
66 changes: 66 additions & 0 deletions plugins/mq.js
@@ -0,0 +1,66 @@
/**
\file mq.js
Plugin to implement the method queue pattern
http://www.lognormal.com/blog/2012/12/12/the-script-loader-pattern/#the_method_queue_pattern
*/

(function() {
function processEntry(args, callback, thisArg) {
var methodName = args.shift();
if (typeof methodName !== "string") {
return;
}

var split = methodName.split("."), method = BOOMR, _this = BOOMR;
if (split[0] === "BOOMR") {
// the BOOMR namespace is inferred, remove it if it was specified
split.shift();
}

// loop through all of `split`, stepping into only objects and functions
while (split.length &&
method && // `null` is an object, skip it
(typeof method === "object" || typeof method === "function")) {
var word = split.shift();
method = method[word];
if (split.length) {
_this = _this[word]; // the `this` is everything up until the method name
}
}

// if we've used all of `split`, and have resolved to a function, call it
if (!split.length && typeof method === "function") {
var returnValue = method.apply(_this, args);

// pass the return value of the resolved function as the only argument to the
// optional `callback`
if (typeof callback === "function") {
callback.call(thisArg, returnValue);
}
}
}
function processEntries(entries) {
for (var i = 0; i < entries.length; i++) {
var params = entries[i];
if (!params) {
continue;
}
if (BOOMR.utils.isArray(params)) {
processEntry(params);
}
else if (typeof params === "object" && BOOMR.utils.isArray(params.arguments)) {
processEntry(params.arguments, params.callback, params.thisArg);
}
}
}

var mq = BOOMR.window.BOOMR_mq;
if (BOOMR.utils.isArray(mq)) {
processEntries(mq);
}
BOOMR.window.BOOMR_mq = {
push: function() {
processEntries(arguments);
}
};
})();
21 changes: 21 additions & 0 deletions tests/page-templates/00-basic/10-method-queue.html
@@ -0,0 +1,21 @@
<%= header %>
<%= boomerangSnippet %>
<script src="10-method-queue.js" type="text/javascript"></script>
<script>
window.BOOMR_mq = [];
window.BOOMR_mq.push(
["addVar", "var1", "value1"],
["addVar", "var2", "value2"]
);

BOOMR_test.init({
testAfterOnBeacon: true,
onBoomerangLoaded: function() {
window.BOOMR_mq.push(
["addVar", "var3", "value3"],
["addVar", "var4", "value4"]
);
}
});
</script>
<%= footer %>
22 changes: 22 additions & 0 deletions tests/page-templates/00-basic/10-method-queue.js
@@ -0,0 +1,22 @@
/*eslint-env mocha*/
/*global BOOMR_test,assert*/

describe("e2e/00-basic/10-method-queue", function() {
var tf = BOOMR.plugins.TestFramework;

it("Should have sent a beacon", function() {
assert.isTrue(tf.fired_onbeacon);
});

it("Should support entries queued up before boomerang loaded", function() {
var b = tf.lastBeacon();
assert.equal(b.var1, "value1");
assert.equal(b.var2, "value2");
});

it("Should support calls to `push()` after boomerang loaded", function() {
var b = tf.lastBeacon();
assert.equal(b.var3, "value3");
assert.equal(b.var4, "value4");
});
});
160 changes: 160 additions & 0 deletions tests/unit/04-plugins-mq.js
@@ -0,0 +1,160 @@
/*eslint-env mocha*/
/*global chai*/

describe("BOOMR.plugins.mq", function() {
var assert = chai.assert;

describe("exports", function() {
it("Should have a BOOMR_mq object", function() {
assert.isObject(BOOMR.window.BOOMR_mq);
});

it("Should have a push() function", function() {
assert.isFunction(BOOMR.window.BOOMR_mq.push);
});
});

describe("push()", function() {
it("Should handle degenerate cases", function() {
assert.doesNotThrow(function() {
BOOMR_mq.push(
null,
undefined,
false,
true,
"null",
"undefined",
"false",
"true",
"",
0,
1,
27,
[],
{},
["foo"],
["foo.bar"],
["foo.bar.baz"]
);
});
});

describe("array pattern", function() {
it("Should call methods on BOOMR", function(done) {
BOOMR.method = function() {
done();
};
BOOMR_mq.push(["method"]);
});

it("Should call namespaced methods on BOOMR", function(done) {
BOOMR.method = function() {
done();
};
BOOMR_mq.push(["BOOMR.method"]);
});

it("Should pass all arguments", function(done) {
BOOMR.method = function() {
assert.lengthOf(arguments, 3);
assert.equal(arguments[0], 0);
assert.equal(arguments[1], 1);
assert.equal(arguments[2], 2);
done();
};
BOOMR_mq.push(["method", 0, 1, 2]);
});

it("Should support `push` with multiple arguments", function(done) {
var results = [];
BOOMR.method1 = function() {
results.push("method1");
};
BOOMR.method2 = function() {
results.push("method2");
};
BOOMR.method3 = function() {
assert.lengthOf(results, 2);
assert.equal(results[0], "method1");
assert.equal(results[1], "method2");
done();
};
BOOMR_mq.push(
["method1"],
["method2"],
["method3"]
);
});

it("Should step into objects on BOOMR", function(done) {
BOOMR.obj = {
method: function() {
done();
}
};
BOOMR_mq.push(["obj.method"]);
});

it("Should step into functions on BOOMR", function(done) {
BOOMR.func = function() {};
BOOMR.func.method = function() {
done();
};
BOOMR_mq.push(["func.method"]);
});

it("Should use appropriate context", function(done) {
BOOMR.obj = {
method1: function() {
this.method2();
},
method2: function() {
done();
}
};
BOOMR_mq.push(["obj.method1"]);
});
});

describe("object pattern", function() {
it("Should support `arguments`", function(done) {
BOOMR.method = function() {
done();
};
BOOMR_mq.push({
arguments: ["method"]
});
});
it("Should support `callback`", function(done) {
BOOMR.method = function() {
return 123;
};
BOOMR_mq.push({
arguments: ["method"],
callback: function() {
assert.lengthOf(arguments, 1);
assert.equal(arguments[0], 123);
done();
}
});
});
it("Should support `thisArg`", function(done) {
function Item(value) {
this.value = value;
}
Item.prototype.callback = function() {
assert.equal(this.value, 123);
done();
};

BOOMR.method = function() {};
BOOMR_mq.push({
arguments: ["method"],
callback: Item.prototype.callback,
thisArg: new Item(123)
});
});
});
});

});
1 change: 1 addition & 0 deletions tests/unit/index.html
Expand Up @@ -41,6 +41,7 @@
<script src="04-plugins-restiming.js"></script>
<script src="04-plugins-errors.js"></script>
<script src="04-plugins-compression.js"></script>
<script src="04-plugins-mq.js"></script>
<script src="../boomerang-test-framework.js" type="text/javascript"></script>
<script src="06-mutationhandler.js"></script>
<script>
Expand Down

0 comments on commit 8f29b05

Please sign in to comment.