diff --git a/.npmignore b/.npmignore
index 89297a8..f83342d 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,4 +1,6 @@
# msngr.js specific
+benchRunner.html
+benchRunner.min.html
specRunner.html
specRunner.min.html
crossWindowVerifier.html
diff --git a/.travis.yml b/.travis.yml
index 3ee2e1c..24a0284 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,9 @@
language: node_js
sudo: false
node_js:
+ - "5.0"
+ - "4.2"
+ - "4.1"
- "4.0"
- "0.12"
- "0.11"
diff --git a/Gruntfile.js b/Gruntfile.js
index 7a314cd..3aff1cf 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -24,7 +24,8 @@ module.exports = (function(grunt) {
"src/module.exports.js",
"!**/*.aspec.js",
"!**/*.cspec.js",
- "!**/*.nspec.js"
+ "!**/*.nspec.js",
+ "!**/*.bench.js"
];
grunt.initConfig({
@@ -38,11 +39,7 @@ module.exports = (function(grunt) {
},
uglify: {
minify: {
- options: {
- mangle: false,
- preserveComments: false,
- compress: {}
- },
+ options: { },
files: {
"./msngr.min.js": paths
}
@@ -112,74 +109,171 @@ module.exports = (function(grunt) {
grunt.log.subhead("Unit testing with node.js");
});
+ grunt.registerTask("header:nodeBenching", function() {
+ grunt.log.subhead("Benchmarking with node.js");
+ });
+
grunt.registerTask("header:clientTesting", function() {
grunt.log.subhead("Client-side unit testing with phantom.js");
});
- /*
- The setRunner task modifies the specRuner.html file, dynamically, with the
- unit tests within the project to allow test running with phantomjs.
- */
- grunt.registerTask("setRunner", "Set the client side spec runner", function() {
- var makeScript = function(path) {
- return "";
- };
+ var jsPaths = ["./", "./src/", "./docs/"];
+ var fetchJsFiles = function(filters) {
var fs = require("fs");
var path = require("path");
+ var results = [];
- var tests = [];
- var testPaths = ["./", "./src/", "./docs/"];
-
- for (var k = 0; k < testPaths.length; ++k) {
- var dirs = fs.readdirSync(testPaths[k]);
+ for (var k = 0; k < jsPaths.length; ++k) {
+ var dirs = fs.readdirSync(jsPaths[k]);
for (var i = 0; i < dirs.length; ++i) {
- if (fs.statSync(testPaths[k] + dirs[i]).isDirectory()) {
- var files = fs.readdirSync(testPaths[k] + dirs[i]);
+ if (fs.statSync(jsPaths[k] + dirs[i]).isDirectory()) {
+ var files = fs.readdirSync(jsPaths[k] + dirs[i]);
for (var j = 0; j < files.length; ++j) {
- var p = path.join("./", testPaths[k], dirs[i], files[j]);
- if (tests.indexOf(p) === -1) {
- tests.push(p);
+ var p = path.join("./", jsPaths[k], dirs[i], files[j]);
+ if (results.indexOf(p) === -1) {
+ results.push(p);
}
}
} else {
- var p = path.join("./", testPaths[k], dirs[i]);
- if (tests.indexOf(p) === -1) {
- tests.push(p);
+ var p = path.join("./", jsPaths[k], dirs[i]);
+ if (results.indexOf(p) === -1) {
+ results.push(p);
}
}
}
}
- var scriptHtml = "";
-
- if (tests !== undefined && tests.length > 0) {
- var file = tests.shift();
- while (tests.length > 0) {
- if (file.indexOf(".cspec.js") !== -1 || file.indexOf(".aspec.js") !== -1) {
- scriptHtml += makeScript(file) + "\n";
+ var filteredResults = [];
+ for (var i = 0; i < results.length; ++i) {
+ var include = false;
+ for (var k = 0; k < filters.length; ++k) {
+ if (results[i].indexOf(filters[k]) !== -1) {
+ include = true;
+ break;
}
- file = tests.shift();
+ }
+ if (include) {
+ filteredResults.push(results[i]);
}
}
- var runnerHtml = fs.readFileSync("./specRunner.html", {
+ return filteredResults;
+ };
+
+ var setRunner = function(runner, files) {
+ var fs = require("fs");
+ var makeScript = function(path) {
+ return "";
+ };
+
+ var scriptHtml = "";
+
+ if (files !== undefined && files.length > 0) {
+ var file = files.shift();
+ while (file) {
+ scriptHtml += makeScript(file) + "\n";
+ file = files.shift();
+ }
+ }
+ var runnerFileName = "./" + runner;
+ var runnerHtml = fs.readFileSync(runnerFileName, {
encoding: "utf8"
});
- var scriptStart = runnerHtml.indexOf("");
- var scriptEnd = runnerHtml.indexOf("");
+ var scriptStart = runnerHtml.indexOf("");
+ var scriptEnd = runnerHtml.indexOf("");
var newHtml = runnerHtml.substring(0, scriptStart);
- newHtml += "";
+ newHtml += "";
newHtml += scriptHtml;
newHtml += runnerHtml.substring(scriptEnd);
- fs.writeFileSync("./specRunner.html", newHtml, {
+ fs.writeFileSync(runnerFileName, newHtml, {
encoding: "utf8"
});
- fs.writeFileSync("./specRunner.min.html", newHtml, {
+ fs.writeFileSync(runnerFileName, newHtml, {
encoding: "utf8"
});
+ };
+
+ /*
+ The setRunner task modifies the specRuner.html file, dynamically, with the
+ unit tests within the project to allow test running with phantomjs.
+ */
+ grunt.registerTask("setRunner", "Set the client side spec runner", function() {
+ var tests = fetchJsFiles([".cspec.js", ".aspec.js"]);
+ setRunner("specRunner.html", tests.concat([]));
+ setRunner("specRunner.min.html", tests.concat([]));
+ });
+
+ grunt.registerTask("run-benchmarks", "Finds all benchmarks and executes them", function() {
+ var async = require("async");
+ var done = this.async();
+ var benchmarks = fetchJsFiles([".bench.js"]);
+ setRunner("benchRunner.html", benchmarks.concat([]));
+ setRunner("benchRunner.min.html", benchmarks.concat([]));
+ var meths = [];
+ for (var i = 0; i < benchmarks.length; ++i) {
+ meths.push(function(p) {
+ return require(p);
+ }("./" + benchmarks[i]));
+ }
+ async.series(meths, function (err, results) {
+ done();
+ });
+ });
+
+ grunt.registerTask("start-reflective-server", "Creates a test service with some dummy endpoints for testing", function() {
+ var http = require("http");
+ var server = http.createServer(function(request, response) {
+ var body = "";
+ request.on("data", function(chunk) {
+ body = body + chunk;
+ });
+
+ request.on("end", function() {
+ var result = {
+ method: request.method,
+ headers: request.headers,
+ path: request.url,
+ body: body
+ };
+
+ var headers = {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Headers": "origin, content-type, accept",
+ "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS,HEAD"
+ };
+
+ try {
+ var objBody = JSON.parse(body);
+ if (objBody.headers != undefined && Object.keys(objBody.headers).length > 0) {
+ for (var key in objBody.headers) {
+ headers[key] = objBody.headers[key];
+ }
+ }
+
+ if (objBody.body != undefined) {
+ result.body = objBody.body;
+ }
+ } catch (ex) {
+ // Couldn't care less as opposed to the commonly misused "could care less"
+ // in which you actually do care a little. No, I couldn't care less because
+ // this error just means there are no commands to reflect :)
+ }
+
+ if (headers["content-type"] === undefined) {
+ headers["content-type"] = "application/json";
+ }
+
+ response.writeHead(200, headers);
+ response.end(JSON.stringify(result, null, 2));
+ });
+ });
+
+ server.listen("8009", "127.0.0.1", function(e) {
+ console.log("Reflective http server started");
+ });
});
/*
@@ -190,6 +284,8 @@ module.exports = (function(grunt) {
grunt.registerTask("build", "Cleans, sets version and builds msngr.js", ["header:building", "clean", "verisionify", "concat", "uglify:minify", "setRunner"]);
- grunt.registerTask("test", "Cleans, sets version, builds and runs mocha unit tests through node.js and phantom.js", ["build", "header:nodeTesting", "mochaTest", "header:clientTesting", "mocha_phantomjs"]);
+ grunt.registerTask("test", "Cleans, sets version, builds and runs mocha unit tests through node.js and phantom.js", ["build", "header:nodeTesting", "start-reflective-server", "mochaTest", "header:clientTesting", "mocha_phantomjs"]);
+
+ grunt.registerTask("benchmark", "Cleans, sets version, builds and runs benchmarks through node.js", ["build", "header:nodeBenching", "run-benchmarks"]);
});
diff --git a/README.cspec.js b/README.cspec.js
deleted file mode 100644
index 37dc37b..0000000
--- a/README.cspec.js
+++ /dev/null
@@ -1,62 +0,0 @@
-if (typeof chai === "undefined" && typeof window === "undefined") {
- var chai = require("chai");
-}
-
-if (typeof expect === "undefined") {
- var expect = chai.expect;
-}
-
-if (typeof msngr === "undefined" && typeof window === "undefined") {
- var msngr = require("./msngr");
-}
-
-describe("./README.md", function () {
- "use strict";
-
- before(function () {
- msngr.debug = true;
- });
-
- beforeEach(function () {
- msngr.internal.reset();
- });
-
- after(function () {
- msngr.debug = false;
- });
-
- it("Example 1 of DOM binding", function (done) {
- var userInput = document.createElement("input");
- userInput.setAttribute("name", "Username");
- userInput.value = "Kris";
-
- var passwordInput = document.createElement("input");
- passwordInput.setAttribute("name", "Password");
- passwordInput.value = "hunter2";
-
- var button = document.createElement("button");
- button.appendChild(document.createTextNode("Submit"));
-
- document.body.appendChild(userInput);
- document.body.appendChild(passwordInput);
- document.body.appendChild(button);
-
- msngr("User", "Save")
- .bind("button", "click")
- .option("dom", ["input"])
- .on(function (payload) {
- expect(payload.Username).to.equal("Kris");
- expect(payload.Password).to.equal("hunter2");
-
- document.body.removeChild(userInput);
- document.body.removeChild(passwordInput);
- document.body.removeChild(button);
-
- done();
- });
-
- var me = document.createEvent("MouseEvents");
- me.initEvent("click", true, false);
- button.dispatchEvent(me);
- });
-});
diff --git a/README.md b/README.md
index 7d0cac0..7170dac 100644
--- a/README.md
+++ b/README.md
@@ -32,31 +32,13 @@ While msngr.js isn't very large the documentation has been split up for easy rea
[Full API](docs/api.md) - This is the full, exposed API that msngr makes available. This includes the methods that can be used (it does not cover internal methods or objects since those are subject to change) and examples for each.
-[Messaging patterns](docs/messaging patterns.md) - Explains how to use the basic messaging features of msngr.js with some typical patterns.
-
[Web browser niceties](docs/web browser niceties.md) - This covers binding msngr.js to elements and events, unbinding them, how to gather up values from various types of elements and cross-window communication.
[Extending and hacking](docs/extending and hacking.md) - Want to extend the capabilities of msngr.js? It's actually quite easy and this document covers it. Using msngr.js deep in a production system then suddenly find *something* that you need to change to avoid catastrophe? Hacking msngr.js is also covered for those times when you need *unorthodox* solutions :)
[Contributing](docs/contributing.md) - Want to contributed to msngr.js? There are a couple of things you should know before you submit that pull request to better ensure it gets accepted :)
-## Roadmap
-The current release of msngr.js works in node.js for server-side messaging as well as the web browser. The web browser has some extra features like messaging between windows and binding to DOM elements but future features will receive more focus on the core of msngr.js with more node.js fun!
-
-### What's Next?
-Below is what's being worked on for future 2.x releases.
-
-* Web Socket Messaging - Easy web socket communication with messages between a server and client.
-
-* Feature detection - Support verifying what certain features can work in the environment they're in (e.g. older web browsers). When they cannot log a warning and disable the feature (warnings will be toggle-able).
-
-* Better browser support - Currently msngr.js should work in most current web browsers but some features may not work well in older versions of Internet Explorer and really old versions of Firefox. Additional testing and tweaking needs to be conducted for older browser support and to provide a baseline for what is or isn't supported.
-
-* Benchmarking and optimization - Now that the majority of msngr.js's structure is written and fairly solidified we need to begin benchmarking along with profiling and optimization. Optimization had previously been ignored while the API was finalized and while msngr.js is really fast it needs to be *scientifically* fast.
-
-### What's Next Next?
-At this point further planning will occur once more community feedback comes through. I have a few ideas involving integration into other messaging systems, some streaming ideas and a few optional extensions that provide message based APIs for other libraries but I'm hesitant to *only* go in one direction should other needs arise.
-
+## Getting in contact
For questions, news, and whatever else that doesn't fit in GitHub issues you can follow me [@KrisSiegel](https://twitter.com/KrisSiegel)
Copyright © 2014-2015 Kris Siegel
diff --git a/benchRunner.html b/benchRunner.html
new file mode 100644
index 0000000..76ba61c
--- /dev/null
+++ b/benchRunner.html
@@ -0,0 +1,22 @@
+
+
+ Benchmarks
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/benchRunner.min.html b/benchRunner.min.html
new file mode 100644
index 0000000..685e91c
--- /dev/null
+++ b/benchRunner.min.html
@@ -0,0 +1,22 @@
+
+
+ Benchmarks
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bower.json b/bower.json
index 315b98b..748e2b9 100644
--- a/bower.json
+++ b/bower.json
@@ -9,6 +9,8 @@
],
"license": "MIT",
"ignore": [
+ "benchRunner.html",
+ "benchRunner.min.html",
"specRunner.html",
"specRunner.min.html",
"crossWindowVerifier.html",
diff --git a/docs/api.md b/docs/api.md
index 3c941d1..5288486 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -1,5 +1,5 @@
-#msngr.js API
-This document outlines the exposed msngr.js API. It does not cover anything internal that isn't expected to be used by typical developers.
+# msngr.js API
+This document outlines the exposed msngr.js API. It does not cover anything internal that isn't expected to be used by typical developers and as such all internal APIs are not bound by semantic versioning conventions (aka even minor point updates may break your code if you rely on internal, private API calls).
## ```msngr(topic, category, subcategory)```
The main msngr method takes in 3 strings to generate a message object from which all other actions are derived.
@@ -46,11 +46,11 @@ msg.persist("My data");
### ```.on(callback)```
Registers a handler to handle all emissions of the specified message. The callback can take a result either synchronously or asynchronously to supply back to an emit callback.
-```callback (required)``` - registers a method to handle the previously supplied message. The value returned is then sent to the emit callback. If a return value needs to be asynchronous then the second parameter supplied to the callback can be used.
+```callback (required)``` - registers a method to handle the previously supplied message. The value returned is then sent to the emit callback. If a return value needs to be asynchronous then the third parameter supplied to the callback can be used.
```javascript
var msg = msngr("MyTopic");
-msg.on(function (payload, async) {
+msg.on(function (payload, message, async) {
var done = async();
done("Somevalue");
});
@@ -62,11 +62,11 @@ msg.emit(function (result) {
### ```.once(callback)```
The same as the ```.on(callback)``` except the handler is automatically unregistered after one execution.
-```callback (required)``` - registers a method to handle the previously supplied message. The value returned is then sent to the emit callback. If a return value needs to be asynchronous then the second parameter supplied to the callback can be used.
+```callback (required)``` - registers a method to handle the previously supplied message. The value returned is then sent to the emit callback. If a return value needs to be asynchronous then the third parameter supplied to the callback can be used.
```javascript
var msg = msngr("MyTopic");
-msg.once(function (payload, async) {
+msg.once(function (payload, message, async) {
var done = async();
done("Somevalue");
});
@@ -151,6 +151,63 @@ var msg = msngr("MyTopic");
msg.dropAll(); // Drops all handlers even outside of 'MyTopic'!!!
```
+## ```msngr.net(protocol, host, port)```
+Provides a way to conduct consistent network communications in both the web browser and node.js. The result of this method is a msngr net object.
+
+```protocol (required)``` - Specifies the protocol to use (currently HTTP or HTTPS). Alternatively the entire URI can be passed in.
+
+```host (optional)``` - Specified the host name. It's marked as optional as protocol, host and port can be specified as a single item in the first parameter.
+
+```port (optional)``` - Specified the port. It's marked as optional as protocol, host and port can be specified as a single item in the first parameter.
+
+## msngr net object
+The net object provides handy shortcuts for conducting your network activities. Note that this is mostly useful for plaintext or JSON transport (or other non-binary related types). This DOES NOT handle multipart forms / files and likely never will as it's meant to be a basic, consistent mode of communication.
+
+### ```.get(options, callback)```
+### ```.post(options, callback)```
+### ```.put(options, callback)```
+### ```.delete(options, callback)```
+### ```.options(options, callback)```
+Conducts an HTTP / HTTPS for the specified verb operation.
+
+``` options (required)``` - Expects an object that contains the following, optional properties (all defaults are specified in this definition below and therefore can be elided if desired):
+```javascript
+{
+ path: "/", // The path to send the request to
+ autoJson: true, // Whether or not it should attempt to JSON.parse() a response IF the content type is 'application/json'
+ query: { }, // A set of properties used to generate a query string
+ queryString: undefined, // The query object is used to generate this property; only explicitly use this to provide your own querystring
+ payload: undefined // The payload to deliver in the request body
+}
+```
+
+``` callback (required)``` - The callback to execute when a request has returned a response or has failed. It follows the typical node.js pattern of passing back two parameters: first an error parameter, which should be null when successful, and second a result parameter that should contain the result of the request.
+
+```javascript
+var net = msngr.net("http://localhost:3001");
+net.post({
+ path: "/users",
+ payload: {
+ username: "kris",
+ email: "redacted@redacted.com"
+ }
+}, function(err, result) {
+ if (err) {
+ console.log("Oh noes it failed!");
+ }
+ console.log(result);
+});
+```
+
+### ```.protocol```
+This property returns the protocol of the current net object (either parsed from a single input or explicitly provided in the parameters).
+
+### ```.host```
+This property returns the host of the current net object (either parsed from a single input or explicitly provided in the parameters).
+
+### ```.port```
+This property returns the port of the current net object (either parsed from a single input or explicitly provided in the parameters).
+
## Handy DOM utilities
In implementing the binding and dom option features of msngr some handy DOM utilities had to be created. Why not expose them?
@@ -232,18 +289,42 @@ var elms = msngr.querySelectorAllWithEq("div:eq(1) > input");
## Miscellaneous utilities
There are multiple utility methods included in msngr most of which start out as internal only and eventually make their way to external exposure depending on whether Kris finds them useful or not :)
+### ```msngr.immediate(fn)```
+Executes a function, asynchronously, as quickly as possible. In node.js and IE this will use ```setImmediate()```, in Chrome / Firefox / Safari / Opera it will use ```window.postMessage()``` and all other environments that do not support the former will use ```setTimeout(fn, 0);```. This method is handy as ```setImmediate()``` and the ```window.postMessage()``` hack are the fastest ways to execute a method asynchronously. ```setTimeout(fn, 0)``` is hella slow.
+
+```fn (required)``` - the function to execute asynchronously.
+
### Executer object
-The executor object is a very simple way of specifying n number of functions that can then be executed either as is or in parallel (similar to async.parallel).
+The executor object is a very simple way of specifying n number of functions that can then be executed in parallel (similar to async.parallel).
```javascript
-var executorObj = msngr.executer(arrayOfFunctions, payload, context);
+var funcs1 = [
+ function() {
+ return 1 + 1;
+ }
+];
+var executerObj1 = msngr.executer(funcs1);
+
+// Alternatively
+var funcs2 = [
+ {
+ method: function() {
+ return 1 + 1;
+ },
+ params: [1, 2, 3, 4]
+ }
+];
+var executerObj2 = msngr.executer(funcs2);
```
-```arrayOfFunctions (required)``` - A single function or an array of functions to be handled by the executer
-
-```payload (optional)``` - An optional payload to pass into every method executed
-
-```context (optional)``` - Sets the context in which callbacks are executed with.
+```paramater 1 (required)``` - The parameter should be an array of either functions or of objects in the following format:
+```javascript
+{
+ method: function() {},
+ params: [],
+ context: this
+}
+```
#### ```executorObj.parallel(done)```
Executes all methods specified and uses an async callback to provide the sync or async result from each method executed. The callback is only called once each method executes its own callback signifying that code execution has been completed.
@@ -253,32 +334,19 @@ Executes all methods specified and uses an async callback to provide the sync or
Example:
```javascript
var funcs = [];
-funcs.push(function (payload, async) {
+funcs.push(function (async) {
var done = async();
done(42);
});
-funcs.push(function (payload, async) {
+funcs.push(function (async) {
return 15;
});
-var exec = msngr.executer(funcs, { });
+var exec = msngr.executer(funcs);
exec.parallel(function (results) {
console.log(results); // Prints [42, 15]
});
```
-#### ```executorObj.execute(done)```
-Executes the specified method and provides an async callback to provide the sync or async result from the method itself. *NOTE* this only executes a single function (the first function if an array of functions were specified).
-
-```done (optional)``` - The callback method that receives the results from the method executed.
-
-
-### ```msngr.options(key, value)```
-Sets a global set of options that apply to all messages created after globals have been set.
-
-```key (required)``` - The key pertaining to the option desired to be globally configured.
-
-```value (optional)``` - The optional configuration values that come along with globally setting options.
-
### ```msngr.extend(obj, target)```
Extends either the msngr object or a specified target object.
@@ -297,7 +365,7 @@ console.log(msngr.sayHello());
```
### ```msngr.merge(input1, input2, ..., inputN)```
-Merges an n number of inputs together. Combines objects with other objects (the merging overwrites in order should conflict arrise), functions with objects, strings and strings and arrays.
+Merges an n number of inputs together. Combines objects with other objects (the merging overwrites in order should conflict arrise), functions with objects, strings and strings and arrays. **NOTE** this will merge references together; it does not produce deep copies for objects.
```inputn (required)``` - Specify as many parameters as necessary for merging.
@@ -307,6 +375,31 @@ console.log(merged.val1); // Prints "test"
console.log(merged.val2); // Prints "no!"
```
+### ```msngr.copy(object)```
+Performs a deep copy on the supplied object. Any non-native JavaScript object is simply returned as a reference (coping those, accurately, without knowledge of the object is difficult to get right and is rarely something you'd want copied anyway).
+
+### ```msngr.config(key, object)```
+Provides a handy way to store a configuration that never completely overwrites but, instead, merges with the intent of applying a default configuration in the first call and a specific environment configuration in a subsequent call. This is also used for all internal constants for msngr so a developer can override them should they wish.
+
+```key (required)``` - The key used to fetch the configuration associated.
+
+```object (optional)``` - If the object is elided then the configuration will be returned otherwise the provided object will be merged with the existing configuration.
+
+```javascript
+msngr.config("yotest", {
+ something: true,
+ another: {
+ what: "yes"
+ }
+});
+
+msngr.config("yotest", {
+ okay: 47
+});
+
+console.log(msngr.config("yotest")); // outputs { something: true, another: { what: "yes" }, okay: 47 };
+```
+
### ```msngr.exist(obj)```
Returns false if obj is undefined or null otherwise true.
@@ -462,6 +555,9 @@ var myArray = [5, 17, 42, 56, 42, 56, 42, 97];
console.log(msngr.deDupeArray(myArray)); // Outputs [5, 17, 42, 56, 97]
```
+### ```msngr.isBrowser()```
+Returns a boolean indicating whether msngr is running in a browser context or not.
+
### ```msngr.argumentsToArray(args)```
Converts an arguments object to an array object.
diff --git a/docs/contributing.md b/docs/contributing.md
index 7343745..20cdf10 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -10,14 +10,14 @@ The checklists should serve as a guide for submitting your pull request.
When fixing a bug these apply:
- A unit test *must* be included that cover the bug to ensure proper regression testing during future updates.
-- If an API change is required to fix a bug the API change cannot break current compatibility. Should it break compatibility then a discussion should happen before submission of the fix.
+- If an API change is required to fix a bug the API change cannot break current compatibility. Should it break compatibility then a discussion should happen regarding necessary semantic versioning prior to acceptance.
### Feature changes and additions
The following should be done when changing or adding features:
- Ensure proper discussion is done within GitHub issues to ensure it will be accepted (naturally forks are not a problem; if it's not desired to put into msngr.js's main repository then feel free to keep the fork!).
- All API compatibility breaks must have a new major version number.
-- All documentation [and accompanying unit tests] must be updated to comply with the new changes. If there are additions then the documentation needs to be amended along with unit tests to cover *all* documentation examples (see existing unit tests for each current document).
+- All documentation [and accompanying unit tests] must be updated to comply with the new changes.
- All unit tests must pass when run against node, phantomjs, Internet Explorer 10+, Firefox and Chrome browsers in their expanded and minified versions.
## Have fun!
diff --git a/docs/extending and hacking.md b/docs/extending and hacking.md
index 06cfd92..87911f2 100644
--- a/docs/extending and hacking.md
+++ b/docs/extending and hacking.md
@@ -24,12 +24,12 @@ You can also supply a method which is provided the external and internal interfa
```javascript
msngr.extend(function (external, internal) {
- internal.options["my option"] = function (message, payload, options, async) {
+ internal.option("my option", function (message, payload, options, async) {
var config = options["my option"];
var done = async();
// Do something here
done("My Result");
- };
+ });
return { }; // Optionally return an object that gets mixed with the msngr object
});
diff --git a/docs/messaging patterns.md b/docs/messaging patterns.md
deleted file mode 100644
index 8dd121a..0000000
--- a/docs/messaging patterns.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# Messaging patterns
-So at this point you've hopefully come to the *right* side of thinking that messaging is the best pattern (**S**uperior **A**bstraction **L**ayer comes to mind) when developing software. No? Okay well that's fine since there is no single way of writing software that is better than all others but let's talk some messaging patterns you can use with msngr.js in your server or client code.
-
-## Separation of concerns
-Some abstraction strategies lean to the 'black boxing' approach in which messaging can be quite handy. So let's look at an example of using messaging to save a user's preferences.
-
-```javascript
-// BusinessLogic.js
-msngr("Preferences", "Save", "application/json")
- .on(function(preferences) {
- // Save these
- console.log(preferences);
- });
-```
-```javascript
-// RestLayer.js
-msngr("Preferences", "Save", "application/json")
- .emit(request.params.preferences, function () {
- console.log("Saved");
- });
-```
-
-In the example above we're saying when we receive a message with a topic of "Preferences", a category of "Save" and a subcategory of "application/json" that we should act on it and "save" them (or in this case dump to console). The restful layer sends the preferences and once the on is finished executing it will execute the emit's callback letting it know it's done.
-
-So a simple way of separating things. One can envision creating multiple versions of 'BusinessLogic.js' where one saves to a local storage mechanism (memory perhaps?), another to MySQL, etc. This allows you to swap out backends depending on environment.
-
-But this is too simple; msngr is *way* cooler than this!
-
-## A better separation
-Let's take a look at a more complex example.
-
-```javascript
-// Server.js
-// Boilerplate server code here
-msngr("Server", "Ready").persist();
-```
-```javascript
-// RestfulEndpoints.js
-msngr("Server", "Ready").on(function () {
- // Server is ready; setup restful endpoints
- router.get("/Users/", function (req, res) {
- msngr("Users", "List").emit(function (result) {
- res.json(result);
- });
- });
-});
-```
-```javascript
-// MySQLBackend.js
-msngr("Server", "Ready").on(function () {
- // Server is ready; setup backend message handlers
- msngr("Users", "List").on(function (payload, async) {
- var done = async();
- // async call to MySQL for relevant data
- done(results);
- });
-});
-```
-```javascript
-// MemoryBackend.js
-msngr("Server", "Ready").on(function () {
- var users = { };
- // Server is ready; setup backend message handlers
- msngr("Users", "List").on(function (payload) {
- return users;
- });
-});
-```
-In the above example there are a several things to take note of. First the usage of ```persist()``` can persist a specific payload or just a message itself. By using persist in this way it doesn't matter what order the scripts run because they will all get the ready event from the server to let them setup themselves.
-
-After that, using an environment configuration, you can choose to load ```MySQLBackend.js``` or ```MemoryBackend.js```. Both use the exact same messaging handlers, one works asynchronously and the other synchronously but the receiving end (the callback specified in emit) gets the data in the same way regardless.
-
-Neat, right?
diff --git a/msngr.js b/msngr.js
index e164b8f..b76a893 100644
--- a/msngr.js
+++ b/msngr.js
@@ -9,16 +9,25 @@ var msngr = msngr || (function() {
// Defaults for some internal functions
var internal = {
- globalOptions: {},
warnings: true
};
+ internal.config = { };
+
// The main method for msngr uses the message object
var external = function(topic, category, subcategory) {
return internal.objects.message(topic, category, subcategory);
};
- external.version = "2.4.1";
+ external.version = "3.0.0";
+
+ var getType = function(input) {
+ return Object.prototype.toString.call(input);
+ };
+
+ var extractFunction = function(input) {
+ return input.bind({});
+ };
// Merge two inputs into one
var twoMerge = function(input1, input2) {
@@ -131,13 +140,41 @@ var msngr = msngr || (function() {
return result;
};
- // An external options interface for global options settings
- external.options = function(key, value) {
- if (!external.exist(key)) {
- throw internal.InvalidParametersException("key");
+ external.copy = function(obj) {
+ if (obj === undefined || obj === null) {
+ return obj;
+ }
+ var objType = getType(obj);
+ if (["[object Object]", "[object Function]"].indexOf(objType) === -1) {
+ return obj;
+ }
+
+ var result;
+ if (getType(obj) === "[object Object]") {
+ result = {};
+ } else if (getType(obj) === "[object Function]") {
+ result = extractFunction(obj)
}
- internal.globalOptions[key] = value;
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ var keyType = getType(obj[key]);
+ if (["[object Object]", "[object Function]"].indexOf(keyType) !== -1) {
+ result[key] = external.copy(obj[key]);
+ } else {
+ result[key] = obj[key];
+ }
+ }
+ }
+
+ return result;
+ };
+
+ external.config = function(key, value) {
+ if (value !== undefined) {
+ internal.config[key] = external.merge((internal.config[key] || { }), external.copy(value));
+ }
+ return internal.config[key];
};
// Create a debug property to allow explicit exposure to the internal object structure.
@@ -284,12 +321,16 @@ msngr.extend((function(external, internal) {
msngr.extend((function(external, internal) {
"use strict";
- internal.InvalidParametersException = function(str) {
- return {
+ internal.InvalidParametersException = function(str, reason) {
+ var m = {
name: "InvalidParametersException",
severity: "unrecoverable",
message: ("Invalid parameters supplied to the {method} method".replace("{method}", str))
};
+ if (!external.isEmptyString(reason)) {
+ m.message = m.message + " " + reason;
+ }
+ return m;
};
internal.ReservedKeywordsException = function(keyword) {
@@ -315,6 +356,24 @@ msngr.extend((function(external, internal) {
msngr.extend((function(external, internal) {
"use strict";
+ // This chunk of code is only for the browser as a setImmediate workaround
+ if (typeof window !== "undefined" && typeof window.postMessage !== "undefined") {
+ external.config("immediate", {
+ channel: "__msngr_immediate"
+ });
+
+ var immediateQueue = [];
+
+ window.addEventListener("message", function(event) {
+ if (event.source === window && event.data === internal.config["immediate"].channel) {
+ event.stopPropagation();
+ if (immediateQueue.length > 0) {
+ immediateQueue.shift()();
+ }
+ }
+ }, true);
+ }
+
var nowPerformance = function() {
return performance.now();
};
@@ -324,12 +383,14 @@ msngr.extend((function(external, internal) {
};
var nowLegacy = function() {
- return (new Date).getTime();
+ return Date.now();
};
var nowExec = undefined;
var nowExecDebugLabel = "";
var lastNow = undefined;
+ var isBrowserCached;
+ var immediateFn;
return {
id: function() {
@@ -371,7 +432,7 @@ msngr.extend((function(external, internal) {
}
arr.pop();
},
- deDupeArray: function (arr) {
+ deDupeArray: function(arr) {
var hash = { };
var result = [];
var arrLength = arr.length;
@@ -383,6 +444,29 @@ msngr.extend((function(external, internal) {
}
return result;
+ },
+ isBrowser: function() {
+ if (isBrowserCached === undefined) {
+ isBrowserCached = (typeof XMLHttpRequest !== "undefined");
+ }
+ return isBrowserCached;
+ },
+ immediate: function(fn) {
+ if (immediateFn === undefined) {
+ if (typeof setImmediate !== "undefined") {
+ immediateFn = setImmediate;
+ } else if (typeof window !== "undefined" && typeof window.postMessage !== "undefined") {
+ immediateFn = function(f) {
+ immediateQueue.push(f);
+ window.postMessage(internal.config["immediate"].channel, "*");
+ };
+ } else {
+ immediateFn = function(f) {
+ setTimeout(f, 0);
+ };
+ }
+ }
+ immediateFn(fn);
}
};
}));
@@ -473,9 +557,6 @@ msngr.extend((function(external, internal) {
},
areEmptyStrings: function() {
return internal.reiterativeValidation(external.isEmptyString, external.argumentsToArray(arguments));
- },
- hasWildCard: function(str) {
- return (str.indexOf("*") !== -1);
}
};
}));
@@ -484,50 +565,47 @@ msngr.extend((function(external, internal) {
"use strict";
internal.objects = internal.objects || {};
- internal.objects.executer = function(methods, payload, context) {
-
- if (external.isFunction(methods)) {
- methods = [methods];
- }
+ internal.objects.executer = function(methods) {
if (!external.exist(methods) || !external.isArray(methods)) {
throw internal.InvalidParametersException("executor");
}
- var exec = function(method, pay, ctx, done) {
- setTimeout(function() {
- var async = false;
+ // Support passing in just methods
+ for (var i = 0; i < methods.length; ++i) {
+ if (external.isFunction(methods[i])) {
+ methods[i] = {
+ method: methods[i]
+ };
+ }
+ }
+
+ var exec = function(method, params, ctx, done) {
+ external.immediate(function() {
+ var asyncFlag = false;
var asyncFunc = function() {
- async = true;
+ asyncFlag = true;
return function(result) {
done.apply(ctx, [result]);
};
}
- var params = undefined;
- if (external.isArray(pay)) {
- params = pay;
- } else {
- params = [pay];
+ if (!external.isArray(params)) {
+ if (external.exist(params)) {
+ params = [params];
+ } else {
+ params = [];
+ }
}
params.push(asyncFunc);
-
var syncResult = method.apply(ctx || this, params);
- if (async !== true) {
+ if (asyncFlag !== true) {
done.apply(ctx, [syncResult]);
}
- }, 0);
+ });
};
return {
- execute: function(done) {
- if (methods.length === 0 && external.exist(done)) {
- return done.apply(context, [
- []
- ]);
- }
- return exec(methods[0], payload, context, done);
- },
parallel: function(done) {
var results = [];
var executed = 0;
@@ -539,6 +617,10 @@ msngr.extend((function(external, internal) {
}
for (var i = 0; i < methods.length; ++i) {
+ var method = methods[i].method;
+ var params = methods[i].params;
+ var context = methods[i].context;
+
(function(m, p, c) {
exec(m, p, c, function(result) {
if (external.exist(result)) {
@@ -551,7 +633,7 @@ msngr.extend((function(external, internal) {
done.apply(context, [results]);
}
});
- }(methods[i], payload, context));
+ }(method, params, context));
}
}
};
@@ -687,6 +769,9 @@ msngr.extend((function(external, internal) {
"use strict";
internal.objects = internal.objects || {};
+ internal.option = function(opt, handler) {
+ internal.option[opt] = handler;
+ };
var messageIndex = internal.objects.memory();
var payloadIndex = internal.objects.memory();
@@ -732,8 +817,11 @@ msngr.extend((function(external, internal) {
internal.processOpts = function(opts, message, payload, callback) {
var optProcessors = [];
for (var key in opts) {
- if (opts.hasOwnProperty(key) && external.exist(internal.options[key])) {
- optProcessors.push(internal.options[key]);
+ if (opts.hasOwnProperty(key) && external.exist(internal.option[key])) {
+ optProcessors.push({
+ method: internal.option[key],
+ params: [message, payload, opts]
+ });
}
}
@@ -743,7 +831,7 @@ msngr.extend((function(external, internal) {
}
// Long circuit to do stuff (du'h)
- var execs = internal.objects.executer(optProcessors, [message, payload, opts], this);
+ var execs = internal.objects.executer(optProcessors);
execs.parallel(function(results) {
var result = payload;
@@ -784,7 +872,7 @@ msngr.extend((function(external, internal) {
}
if (external.isObject(topic)) {
- msg = topic;
+ msg = external.copy(topic);
} else {
msg = {};
msg.topic = topic;
@@ -798,9 +886,7 @@ msngr.extend((function(external, internal) {
}
}
- // Copy global options
- var options = external.merge({}, internal.globalOptions);
-
+ var options = {};
var counts = {
emits: 0,
persists: 0,
@@ -812,19 +898,23 @@ msngr.extend((function(external, internal) {
var explicitEmit = function(payload, uuids, callback) {
var uuids = uuids || messageIndex.query(msg) || [];
- var methods = [];
- var toDrop = [];
- for (var i = 0; i < uuids.length; ++i) {
- var obj = handlers[uuids[i]];
- methods.push(obj.handler);
- if (obj.once === true) {
- toDrop.push(obj.handler);
+ internal.processOpts(options, msg, payload, function(result) {
+ var methods = [];
+ var toDrop = [];
+ for (var i = 0; i < uuids.length; ++i) {
+ var obj = handlers[uuids[i]];
+ methods.push({
+ method: obj.handler,
+ params: [result, msg]
+ });
+
+ if (obj.once === true) {
+ toDrop.push(obj.handler);
+ }
}
- }
- internal.processOpts(options, msg, payload, function(result) {
- var execs = internal.objects.executer(methods, result, (msg.context || this));
+ var execs = internal.objects.executer(methods);
for (var i = 0; i < toDrop.length; ++i) {
msgObj.drop(toDrop[i]);
@@ -861,7 +951,7 @@ msngr.extend((function(external, internal) {
throw internal.InvalidParametersException("option");
}
- options[key] = value;
+ options[key] = external.copy(value);
counts.options = counts.options + 1;
return msgObj;
@@ -871,7 +961,7 @@ msngr.extend((function(external, internal) {
callback = payload;
payload = undefined;
}
- explicitEmit(payload, undefined, callback);
+ explicitEmit(external.copy(payload), undefined, callback);
counts.emits = counts.emits + 1;
return msgObj;
@@ -884,11 +974,11 @@ msngr.extend((function(external, internal) {
var uuids = payloadIndex.query(msg);
if (uuids.length === 0) {
var uuid = payloadIndex.index(msg);
- payloads[uuid] = payload;
+ payloads[uuid] = external.copy(payload);
uuids = [uuid];
} else {
for (var i = 0; i < uuids.length; ++i) {
- payloads[uuids[i]] = external.extend(payload, payloads[uuids[i]]);
+ payloads[uuids[i]] = external.merge(payload, payloads[uuids[i]]);
}
}
@@ -1041,6 +1131,322 @@ msngr.extend((function(external, internal) {
return {};
}));
+msngr.extend((function(external, internal) {
+ "use strict";
+
+ // Setup constants
+ external.config("net", {
+ defaults: {
+ protocol: "http",
+ port: {
+ http: "80",
+ https: "443"
+ },
+ autoJson: true
+ }
+ });
+
+ // This method handles requests when msngr is running within a semi-modern net browser
+ var browser = function(server, options, callback) {
+ try {
+ var xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200 || xhr.status === 201) {
+ var obj;
+ if (options.autoJson === true && this.getResponseHeader("content-type") === "application/json") {
+ try {
+ obj = JSON.parse(xhr.response);
+ } catch (ex) {
+ // Don't do anything; probably wasn't JSON anyway
+ // Set obj to undefined just incase it contains something awful
+ obj = undefined;
+ }
+ }
+ callback.apply(undefined, [null, (obj || xhr.response)]);
+ } else {
+ var errObj = {
+ status: xhr.status,
+ response: xhr.response
+ };
+ callback.apply(undefined, [errObj, null]);
+ }
+ }
+ };
+
+ var url = server.protocol + "://" + server.host;
+ if (server.canOmitPort === true) {
+ url = url + options.path;
+ } else {
+ url = url + ":" + server.port + options.path;
+ }
+
+ var datum;
+ if (external.exist(options.payload)) {
+ if (external.isObject(options.payload)) {
+ try {
+ datum = JSON.stringify(options.payload);
+ } catch (ex) {
+ // Really couldn't give a shit about this exception
+ }
+ }
+
+ // undefined has no meaning in JSON but null does; so let's only
+ // and explicitly set anything if it's still undefined (so no null checks)
+ if (datum === undefined) {
+ datum = options.payload;
+ }
+ }
+
+ xhr.open(options.method, url);
+ xhr.send(datum);
+ } catch (ex) {
+ callback.apply(undefined, [ex, null]);
+ }
+ };
+
+ // This method handles requests when msngr is running within node.js
+ var node = function(server, options, callback) {
+ var http = require("http");
+ var request = http.request({
+ method: options.method,
+ host: server.host,
+ port: server.port,
+ path: options.path
+ }, function(response) {
+ response.setEncoding("utf8");
+ var body = "";
+ response.on("data", function(chunk) {
+ body = body + chunk;
+ });
+
+ response.on("end", function() {
+ var obj;
+ if (options.autoJson === true && response.headers["content-type"] === "application/json") {
+ try {
+ obj = JSON.parse(body);
+ } catch (ex) {
+ // Don't do anything; probably wasn't JSON anyway
+ // Set obj to undefined just incase it contains something awful
+ obj = undefined;
+ }
+ }
+ obj = obj || body;
+ var errObj;
+ if (request.statusCode >= 400) {
+ errObj = {
+ status: request.statusCode,
+ response: (obj || body)
+ };
+ obj = null;
+ }
+ callback.apply(undefined, [errObj, obj]);
+ });
+ });
+
+ if (external.exist(options.payload)) {
+ var datum;
+ if (external.isObject(options.payload)) {
+ try {
+ datum = JSON.stringify(options.payload);
+ } catch (ex) {
+ // Really couldn't give a shit about this exception
+ }
+ }
+
+ // undefined has no meaning in JSON but null does; so let's only
+ // and explicitly set anything if it's still undefined (so no null checks)
+ if (datum === undefined) {
+ datum = options.payload;
+ }
+
+ request.write(datum);
+ }
+
+ request.end();
+ };
+
+ var request = function(server, opts, callback) {
+ opts.path = opts.path || "/";
+ opts.autoJson = opts.autoJson || internal.config["net"].defaults.autoJson;
+
+ if (external.exist(opts.query)) {
+ if (external.isString(opts.query)) {
+ opts.queryString = opts.query;
+ }
+
+ if (external.isObject(opts.query)) {
+ opts.queryString = "?";
+ for (var key in opts.query) {
+ if (opts.query.hasOwnProperty(key)) {
+ if (opts.queryString !== "?") {
+ opts.queryString = opts.queryString + "&";
+ }
+ opts.queryString = opts.queryString + encodeURIComponent(key) + "=" + encodeURIComponent(opts.query[key]);
+ }
+ }
+ }
+ }
+
+ opts.path = opts.path + (opts.queryString || "");
+
+ if (external.isBrowser()) {
+ browser(server, opts, callback);
+ } else {
+ node(server, opts, callback);
+ }
+ };
+
+ // This method is crazy; tries to figure out what the developer sent to
+ // the net() method to allow maximum flexibility. Normalization is important here.
+ var figureOutServer = function(protocol, host, port) {
+ var server = { protocol: undefined, host: undefined, port: undefined, canOmitPort: false };
+ var handled = false;
+ var invalid = false;
+ var invalidReason;
+
+ if (external.isEmptyString(protocol)) {
+ invalid = true;
+ invalidReason = "Protocol or host not provided";
+ }
+
+ if (!invalid && !external.isEmptyString(protocol) && external.isEmptyString(host) && external.isEmptyString(port)) {
+ // Only one argument was provided; must be whole host.
+ var split = protocol.split("://");
+ if (split.length == 2) {
+ server.protocol = split[0];
+ server.host = split[1];
+ } else {
+ // Must have omitted protocol.
+ server.host = protocol;
+ server.protocol = internal.config.net.defaults.protocol;
+ }
+
+ var lastColon = server.host.lastIndexOf(":");
+ if (lastColon !== -1) {
+ // There is a port; let's grab it!
+ server.port = server.host.substring(lastColon + 1, server.host.length);
+ server.host = server.host.substring(0, lastColon);
+ } else {
+ // There ain't no port!
+ server.port = internal.config.net.defaults.port[server.protocol];
+ }
+
+ handled = true;
+ }
+
+ if (!invalid && !handled && !external.isEmptyString(protocol) && !external.isEmptyString(host) && external.isEmptyString(port)) {
+ // Okay, protocol and host are provided. Figure out port!
+ server.protocol = protocol;
+ server.host = host;
+
+ var lastColon = server.host.lastIndexOf(":");
+ if (lastColon !== -1) {
+ // There is a port; let's grab it!
+ server.port = server.host.substring(lastColon + 1, server.host.length);
+ server.host = server.host.substring(0, lastColon);
+ } else {
+ // There ain't no port!
+ server.port = internal.config.net.defaults.port[server.protocol];
+ }
+
+ handled = true;
+ }
+
+ if (!invalid && !handled && !external.isEmptyString(protocol) && !external.isEmptyString(host) && !external.isEmptyString(port)) {
+ // Everything is provided. Holy shit, does that ever happen!?
+ server.protocol = protocol;
+ server.host = host;
+ server.port = port;
+
+ handled = true;
+ }
+
+ // Port explicitness can be omitted for some protocols where the port is their default
+ // so let's mark them as can be omitted. This will make output less confusing for
+ // more inexperienced developers plus it looks prettier :).
+ if (!invalid && handled && internal.config.net.defaults.port[server.protocol] === server.port) {
+ server.canOmitPort = true;
+ }
+
+ if (!invalid && !handled) {
+ // Well we didn't handle the input but also didn't think it was invalid. Crap!
+ invalid = true;
+ invalidReason = "Unable to handle input into method. Please open a GitHub issue with your input :)";
+ }
+
+ if (invalid === true) {
+ throw internal.InvalidParametersException("net", invalidReason);
+ }
+
+ // Strip any supplied paths
+ var stripPath = function(input) {
+ var index = input.indexOf("/");
+ return input.substring(0, ((index === -1) ? input.length : index));
+ };
+
+ server.host = stripPath(server.host);
+ server.port = stripPath(server.port);
+
+ return server;
+ };
+
+ return {
+ net: function(protocol, host, port) {
+ var server = figureOutServer(protocol, host, port);
+
+ var netObj = {
+ get: function(options, callback) {
+ var opts = external.merge(options, { });
+ opts.method = "get";
+ request(server, opts, callback);
+ },
+ post: function(options, callback) {
+ var opts = external.merge(options, { });
+ opts.method = "post";
+ request(server, opts, callback);
+ },
+ put: function(options, callback) {
+ var opts = external.merge(options, { });
+ opts.method = "put";
+ request(server, opts, callback);
+ },
+ delete: function(options, callback) {
+ var opts = external.merge(options, { });
+ opts.method = "delete";
+ request(server, opts, callback);
+ },
+ options: function(options, callback) {
+ var opts = external.merge(options, { });
+ opts.method = "options";
+ request(server, opts, callback);
+ }
+ };
+
+ Object.defineProperty(netObj, "protocol", {
+ get: function() {
+ return server.protocol;
+ }
+ });
+
+ Object.defineProperty(netObj, "host", {
+ get: function() {
+ return server.host;
+ }
+ });
+
+ Object.defineProperty(netObj, "port", {
+ get: function() {
+ return server.port;
+ }
+ });
+
+ return netObj;
+ }
+ };
+}));
+
/*
./options/cross-window.js
@@ -1050,9 +1456,9 @@ msngr.extend((function(external, internal) {
msngr.extend((function(external, internal) {
"use strict";
- var CHANNEL_NAME = "__msngr_cross-window";
-
- internal.options = internal.options || {};
+ external.config("cross-window", {
+ channel: "__msngr_cross-window"
+ });
// Let's check if localstorage is even available. If it isn't we shouldn't register
if (typeof localStorage === "undefined" || typeof window === "undefined") {
@@ -1060,7 +1466,7 @@ msngr.extend((function(external, internal) {
}
window.addEventListener("storage", function(event) {
- if (event.key === CHANNEL_NAME) {
+ if (event.key === internal.config["cross-window"].channel) {
// New message data. Respond!
var obj;
try {
@@ -1075,7 +1481,7 @@ msngr.extend((function(external, internal) {
}
});
- internal.options["cross-window"] = function(message, payload, options, async) {
+ internal.option("cross-window", function(message, payload, options, async) {
// Normalize all of the inputs
options = options || {};
options = options["cross-window"] || {};
@@ -1086,13 +1492,13 @@ msngr.extend((function(external, internal) {
};
try {
- localStorage.setItem(CHANNEL_NAME, JSON.stringify(obj));
+ localStorage.setItem(internal.config["cross-window"].channel, JSON.stringify(obj));
} catch (ex) {
throw "msngr was unable to store data in its storage channel";
}
return undefined;
- };
+ });
// This is an internal extension; do not export explicitly.
return {};
@@ -1106,9 +1512,7 @@ msngr.extend((function(external, internal) {
msngr.extend((function(external, internal) {
"use strict";
- internal.options = internal.options || {};
-
- internal.options.dom = function(message, payload, options, async) {
+ internal.option("dom", function(message, payload, options, async) {
// Normalize all of the inputs
options = options || {};
options = options.dom || {};
@@ -1172,7 +1576,7 @@ msngr.extend((function(external, internal) {
return resultMap;
- };
+ });
// This is an internal extension; do not export explicitly.
return {};
diff --git a/msngr.min.js b/msngr.min.js
index 70e1949..074cba7 100644
--- a/msngr.min.js
+++ b/msngr.min.js
@@ -1 +1 @@
-var msngr=msngr||function(){"use strict";var internal={globalOptions:{},warnings:!0},external=function(topic,category,subcategory){return internal.objects.message(topic,category,subcategory)};external.version="2.4.1";var twoMerge=function(input1,input2){if(void 0===input1||null===input1)return input2;if(void 0===input2||null===input2)return input1;var result,type1=Object.prototype.toString.call(input1),type2=Object.prototype.toString.call(input2);if("[object Object]"===type1&&"[object Object]"===type2){result={};for(var key in input1)input1.hasOwnProperty(key)&&(result[key]=input1[key]);for(var key in input2)if(input2.hasOwnProperty(key))if("[object Object]"===Object.prototype.toString.call(input2[key]))void 0===result[key]&&(result[key]={}),result[key]=external.merge(input1[key],input2[key]);else if("[object Array]"===Object.prototype.toString.call(input1[key])&&"[object Array]"===Object.prototype.toString.call(input2[key])){result[key]=input1[key]||[];for(var i=0;i0)for(var i=0;i0?elms[0]:elms},findElements:function(selector,root){var elm;if(external.isHtmlElement(selector)&&(elm=selector),void 0===elm&&external.isString(selector)){var doc=root||document,result=doc.querySelectorAll(selector);null!==result&&(elm=result)}return elm},getDomPath:function(element){var node=external.isHtmlElement(element)?element:void 0;return void 0===node?void 0:(void 0===node.id&&(node.id=external.id()),"#"+node.id)},querySelectorAllWithEq:function(selector,root){if(void 0===selector)return null;for(var doc=root||document,queue=[],process=function(input){if(-1===input.indexOf(":eq("))return void 0;var eqlLoc=input.indexOf(":eq("),sel=input.substring(0,eqlLoc),ind=input.substring(eqlLoc+4,input.indexOf(")",eqlLoc));selector=input.substring(input.indexOf(")",eqlLoc)+1,input.length),">"===sel.charAt(0)&&(sel=sel.substring(1,sel.length)),">"===selector.charAt(0)&&(selector=selector.substring(1,selector.length)),queue.push({selector:sel,index:parseInt(ind,10)})};-1!==selector.indexOf(":eq");)process(selector);for(var result;queue.length>0;){var item=queue.shift();result=(result||doc).querySelectorAll(item.selector)[item.index]}return selector.trim().length>0?(result||doc).querySelectorAll(selector):[result]},querySelectorWithEq:function(selector,root){return external.querySelectorAllWithEq(selector,root)[0]}}}),msngr.extend(function(external,internal){"use strict";return internal.InvalidParametersException=function(str){return{name:"InvalidParametersException",severity:"unrecoverable",message:"Invalid parameters supplied to the {method} method".replace("{method}",str)}},internal.ReservedKeywordsException=function(keyword){return{name:"ReservedKeywordsException",severity:"unrecoverable",message:"Reserved keyword {keyword} supplied as action.".replace("{keyword}",keyword)}},internal.MangledException=function(variable,method){return{name:"MangledException",severity:"unrecoverable",message:"The {variable} was unexpectedly mangled in {method}.".replace("{variable}",variable).replace("{method}",method)}},{}}),msngr.extend(function(external,internal){"use strict";var nowPerformance=function(){return performance.now()},nowNode=function(){return process.hrtime()[1]/1e6},nowLegacy=function(){return(new Date).getTime()},nowExec=void 0,nowExecDebugLabel="",lastNow=void 0;return{id:function(){var d=external.now(),uuid="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(c){var r=(d+16*Math.random())%16|0;return d=Math.floor(d/16),("x"==c?r:3&r|8).toString(16)});return uuid},now:function(noDuplicate){void 0===nowExec&&("undefined"!=typeof performance?(nowExec=nowPerformance,nowExecDebugLabel="performance"):"undefined"!=typeof process?(nowExec=nowNode,nowExecDebugLabel="node"):(nowExec=nowLegacy,nowExecDebugLabel="legacy"));var now=nowExec();return noDuplicate===!0&&lastNow===now?external.now(noDuplicate):(lastNow=now,now)},removeFromArray:function(arr,value){var inx=arr.indexOf(value),endIndex=arr.length-1;if(inx!==endIndex){var temp=arr[endIndex];arr[endIndex]=arr[inx],arr[inx]=temp}arr.pop()},deDupeArray:function(arr){for(var hash={},result=[],arrLength=arr.length,i=0;arrLength>i;++i)void 0===hash[arr[i]]&&(hash[arr[i]]=!0,result.push(arr[i]));return result}}}),msngr.extend(function(external,internal){"use strict";return internal.reiterativeValidation=function(validationMethod,inputs){var result=!1;if(external.exist(validationMethod)&&external.exist(inputs)){external.isArray(inputs)||(inputs=[inputs]);for(var i=0;i0)for(var i=0;i0)for(var i=0;i0)for(var i=0;ii;++i){var found=external.findElements(selectors[i],doc);found.length>0&&(elements=elements.concat(Array.prototype.slice.call(found)))}if(0===elements.length)return void 0;for(var resultMap=void 0,elmLength=elements.length,unnamedTags=0,i=0;elmLength>i;++i){var key=void 0,elm=elements[i],nameAttr=elm.getAttribute("name"),idAttr=elm.id,tagName=elm.tagName.toLowerCase(),val=elm.value;external.exist(nameAttr)&&!external.isEmptyString(nameAttr)?key=nameAttr:external.exist(idAttr)&&!external.isEmptyString(idAttr)?key=idAttr:(key=tagName+unnamedTags,unnamedTags++),void 0===resultMap&&(resultMap={}),resultMap[key]=val}return resultMap},{}}),"undefined"!=typeof module&&"undefined"!=typeof module.exports&&(module.exports=msngr);
\ No newline at end of file
+var msngr=msngr||function(){"use strict";var a={warnings:!0};a.config={};var b=function(b,c,d){return a.objects.message(b,c,d)};b.version="3.0.0";var c=function(a){return Object.prototype.toString.call(a)},d=function(a){return a.bind({})},e=function(a,c){if(void 0===a||null===a)return c;if(void 0===c||null===c)return a;var d,e=Object.prototype.toString.call(a),f=Object.prototype.toString.call(c);if("[object Object]"===e&&"[object Object]"===f){d={};for(var g in a)a.hasOwnProperty(g)&&(d[g]=a[g]);for(var g in c)if(c.hasOwnProperty(g))if("[object Object]"===Object.prototype.toString.call(c[g]))void 0===d[g]&&(d[g]={}),d[g]=b.merge(a[g],c[g]);else if("[object Array]"===Object.prototype.toString.call(a[g])&&"[object Array]"===Object.prototype.toString.call(c[g])){d[g]=a[g]||[];for(var h=0;h0)for(var b=0;b0?d[0]:d},findElements:function(b,c){var d;if(a.isHtmlElement(b)&&(d=b),void 0===d&&a.isString(b)){var e=c||document,f=e.querySelectorAll(b);null!==f&&(d=f)}return d},getDomPath:function(b){var c=a.isHtmlElement(b)?b:void 0;return void 0===c?void 0:(void 0===c.id&&(c.id=a.id()),"#"+c.id)},querySelectorAllWithEq:function(a,b){if(void 0===a)return null;for(var c=b||document,d=[],e=function(b){if(-1===b.indexOf(":eq("))return void 0;var c=b.indexOf(":eq("),e=b.substring(0,c),f=b.substring(c+4,b.indexOf(")",c));a=b.substring(b.indexOf(")",c)+1,b.length),">"===e.charAt(0)&&(e=e.substring(1,e.length)),">"===a.charAt(0)&&(a=a.substring(1,a.length)),d.push({selector:e,index:parseInt(f,10)})};-1!==a.indexOf(":eq");)e(a);for(var f;d.length>0;){var g=d.shift();f=(f||c).querySelectorAll(g.selector)[g.index]}return a.trim().length>0?(f||c).querySelectorAll(a):[f]},querySelectorWithEq:function(b,c){return a.querySelectorAllWithEq(b,c)[0]}}}),msngr.extend(function(a,b){"use strict";return b.InvalidParametersException=function(b,c){var d={name:"InvalidParametersException",severity:"unrecoverable",message:"Invalid parameters supplied to the {method} method".replace("{method}",b)};return a.isEmptyString(c)||(d.message=d.message+" "+c),d},b.ReservedKeywordsException=function(a){return{name:"ReservedKeywordsException",severity:"unrecoverable",message:"Reserved keyword {keyword} supplied as action.".replace("{keyword}",a)}},b.MangledException=function(a,b){return{name:"MangledException",severity:"unrecoverable",message:"The {variable} was unexpectedly mangled in {method}.".replace("{variable}",a).replace("{method}",b)}},{}}),msngr.extend(function(a,b){"use strict";if("undefined"!=typeof window&&"undefined"!=typeof window.postMessage){a.config("immediate",{channel:"__msngr_immediate"});var c=[];window.addEventListener("message",function(a){a.source===window&&a.data===b.config.immediate.channel&&(a.stopPropagation(),c.length>0&&c.shift()())},!0)}var d,e,f=function(){return performance.now()},g=function(){return process.hrtime()[1]/1e6},h=function(){return Date.now()},i=void 0,j="",k=void 0;return{id:function(){var b=a.now(),c="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var c=(b+16*Math.random())%16|0;return b=Math.floor(b/16),("x"==a?c:3&c|8).toString(16)});return c},now:function(b){void 0===i&&("undefined"!=typeof performance?(i=f,j="performance"):"undefined"!=typeof process?(i=g,j="node"):(i=h,j="legacy"));var c=i();return b===!0&&k===c?a.now(b):(k=c,c)},removeFromArray:function(a,b){var c=a.indexOf(b),d=a.length-1;if(c!==d){var e=a[d];a[d]=a[c],a[c]=e}a.pop()},deDupeArray:function(a){for(var b={},c=[],d=a.length,e=0;d>e;++e)void 0===b[a[e]]&&(b[a[e]]=!0,c.push(a[e]));return c},isBrowser:function(){return void 0===d&&(d="undefined"!=typeof XMLHttpRequest),d},immediate:function(a){void 0===e&&(e="undefined"!=typeof setImmediate?setImmediate:"undefined"!=typeof window&&"undefined"!=typeof window.postMessage?function(a){c.push(a),window.postMessage(b.config.immediate.channel,"*")}:function(a){setTimeout(a,0)}),e(a)}}}),msngr.extend(function(a,b){"use strict";return b.reiterativeValidation=function(b,c){var d=!1;if(a.exist(b)&&a.exist(c)){a.isArray(c)||(c=[c]);for(var e=0;e0)for(var d=0;d0)for(var d=0;d0)for(var b=0;b=400&&(h={status:f.statusCode,response:e||b},e=null),d.apply(void 0,[h,e])})});if(a.exist(c.payload)){var g;if(a.isObject(c.payload))try{g=JSON.stringify(c.payload)}catch(h){}void 0===g&&(g=c.payload),f.write(g)}f.end()},e=function(e,f,g){if(f.path=f.path||"/",f.autoJson=f.autoJson||b.config.net.defaults.autoJson,a.exist(f.query)&&(a.isString(f.query)&&(f.queryString=f.query),a.isObject(f.query))){f.queryString="?";for(var h in f.query)f.query.hasOwnProperty(h)&&("?"!==f.queryString&&(f.queryString=f.queryString+"&"),f.queryString=f.queryString+encodeURIComponent(h)+"="+encodeURIComponent(f.query[h]))}f.path=f.path+(f.queryString||""),a.isBrowser()?c(e,f,g):d(e,f,g)},f=function(c,d,e){var f,g={protocol:void 0,host:void 0,port:void 0,canOmitPort:!1},h=!1,i=!1;if(a.isEmptyString(c)&&(i=!0,f="Protocol or host not provided"),!i&&!a.isEmptyString(c)&&a.isEmptyString(d)&&a.isEmptyString(e)){var j=c.split("://");2==j.length?(g.protocol=j[0],g.host=j[1]):(g.host=c,g.protocol=b.config.net.defaults.protocol);var k=g.host.lastIndexOf(":");-1!==k?(g.port=g.host.substring(k+1,g.host.length),g.host=g.host.substring(0,k)):g.port=b.config.net.defaults.port[g.protocol],h=!0}if(!i&&!h&&!a.isEmptyString(c)&&!a.isEmptyString(d)&&a.isEmptyString(e)){g.protocol=c,g.host=d;var k=g.host.lastIndexOf(":");-1!==k?(g.port=g.host.substring(k+1,g.host.length),g.host=g.host.substring(0,k)):g.port=b.config.net.defaults.port[g.protocol],h=!0}if(i||h||a.isEmptyString(c)||a.isEmptyString(d)||a.isEmptyString(e)||(g.protocol=c,g.host=d,g.port=e,h=!0),!i&&h&&b.config.net.defaults.port[g.protocol]===g.port&&(g.canOmitPort=!0),i||h||(i=!0,f="Unable to handle input into method. Please open a GitHub issue with your input :)"),i===!0)throw b.InvalidParametersException("net",f);var l=function(a){var b=a.indexOf("/");return a.substring(0,-1===b?a.length:b)};return g.host=l(g.host),g.port=l(g.port),g};return{net:function(b,c,d){var g=f(b,c,d),h={get:function(b,c){var d=a.merge(b,{});d.method="get",e(g,d,c)},post:function(b,c){var d=a.merge(b,{});d.method="post",e(g,d,c)},put:function(b,c){var d=a.merge(b,{});d.method="put",e(g,d,c)},"delete":function(b,c){var d=a.merge(b,{});d.method="delete",e(g,d,c)},options:function(b,c){var d=a.merge(b,{});d.method="options",e(g,d,c)}};return Object.defineProperty(h,"protocol",{get:function(){return g.protocol}}),Object.defineProperty(h,"host",{get:function(){return g.host}}),Object.defineProperty(h,"port",{get:function(){return g.port}}),h}}}),msngr.extend(function(a,b){"use strict";return a.config("cross-window",{channel:"__msngr_cross-window"}),"undefined"==typeof localStorage||"undefined"==typeof window?{}:(window.addEventListener("storage",function(c){if(c.key===b.config["cross-window"].channel){var d;try{d=JSON.parse(c.newValue)}catch(e){throw"msngr was unable to parse the data in its storage channel"}void 0!==d&&a.isObject(d)&&b.objects.message(d.message).emit(d.payload)}}),b.option("cross-window",function(a,c,d,e){d=d||{},d=d["cross-window"]||{};var f={message:a,payload:c};try{localStorage.setItem(b.config["cross-window"].channel,JSON.stringify(f))}catch(g){throw"msngr was unable to store data in its storage channel"}return void 0}),{})}),msngr.extend(function(a,b){"use strict";return b.option("dom",function(b,c,d,e){d=d||{},d=d.dom||{};var f=d.doc||d.document||document,g=void 0;if(a.isObject(d)&&a.exist(d.selectors)&&a.isString(d.selectors)?g=[d.selectors]:a.isString(d)?g=[d]:a.isArray(d)&&(g=d),!a.exist(f)||!a.exist(g)||0===g.length)return void 0;for(var h=[],i=g.length,j=0;i>j;++j){var k=a.findElements(g[j],f);k.length>0&&(h=h.concat(Array.prototype.slice.call(k)))}if(0===h.length)return void 0;for(var l=void 0,m=h.length,n=0,j=0;m>j;++j){var o=void 0,p=h[j],q=p.getAttribute("name"),r=p.id,s=p.tagName.toLowerCase(),t=p.value;a.exist(q)&&!a.isEmptyString(q)?o=q:a.exist(r)&&!a.isEmptyString(r)?o=r:(o=s+n,n++),void 0===l&&(l={}),l[o]=t}return l}),{}}),"undefined"!=typeof module&&"undefined"!=typeof module.exports&&(module.exports=msngr);
\ No newline at end of file
diff --git a/package.json b/package.json
index eb8ffaf..8048c72 100644
--- a/package.json
+++ b/package.json
@@ -1,32 +1,43 @@
{
- "name": "msngr",
- "main": "msngr.js",
- "description": "msngr.js is a small library used to facilitate communication through messages rather than direct binding. This loose coupling allows connecting components to each other or to UI components in an abstract way on the server or the client.",
- "version": "2.4.1",
- "keywords": ["message", "messaging", "subscription", "delegation", "eventing", "dom", "binding"],
- "repository": {
- "type": "git",
- "url": "https://github.com/KrisSiegel/msngr.js.git"
- },
- "license": "MIT",
- "homepage": "http://www.msngrjs.com/",
- "author": {
- "name": "Kris Siegel",
- "url": "http://www.KrisSiegel.com/"
- },
- "devDependencies": {
- "grunt": "0.4.5",
- "grunt-contrib-uglify": "0.9.2",
- "grunt-contrib-concat": "0.5.1",
- "grunt-contrib-clean": "0.6.0",
- "grunt-mocha-test": "0.12.7",
- "mocha": "2.3.3",
- "grunt-mocha-phantomjs": "2.0.0",
- "grunt-available-tasks": "0.6.1",
- "chai": "3.3.0"
- },
- "scripts": {
- "test": "node -e \"var g = require('grunt'); g.cli.tasks = ['test']; g.cli();\"",
- "build": "node -e \"var g = require('grunt'); g.cli.tasks = ['build']; g.cli();\""
- }
+ "name": "msngr",
+ "main": "msngr.js",
+ "description": "msngr.js is a small library used to facilitate communication through messages rather than direct binding. This loose coupling allows connecting components to each other or to UI components in an abstract way on the server or the client.",
+ "version": "3.0.0",
+ "keywords": [
+ "message",
+ "messaging",
+ "subscription",
+ "delegation",
+ "eventing",
+ "dom",
+ "binding"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/KrisSiegel/msngr.js.git"
+ },
+ "license": "MIT",
+ "homepage": "http://www.msngrjs.com/",
+ "author": {
+ "name": "Kris Siegel",
+ "url": "http://www.KrisSiegel.com/"
+ },
+ "devDependencies": {
+ "async": "1.5.0",
+ "benchmark": "1.0.0",
+ "chai": "3.4.1",
+ "grunt": "0.4.5",
+ "grunt-available-tasks": "0.6.1",
+ "grunt-contrib-clean": "0.6.0",
+ "grunt-contrib-concat": "0.5.1",
+ "grunt-contrib-uglify": "0.10.0",
+ "grunt-mocha-phantomjs": "2.0.0",
+ "grunt-mocha-test": "0.12.7",
+ "mocha": "2.3.3"
+ },
+ "scripts": {
+ "test": "node -e \"var g = require('grunt'); g.cli.tasks = ['test']; g.cli();\"",
+ "build": "node -e \"var g = require('grunt'); g.cli.tasks = ['build']; g.cli();\"",
+ "benchmark": "node -e \"var g = require('grunt'); g.cli.tasks = ['benchmark']; g.cli();\""
+ }
}
diff --git a/specRunner.html b/specRunner.html
index bff8a5e..47fccf7 100644
--- a/specRunner.html
+++ b/specRunner.html
@@ -12,8 +12,8 @@
-
-
+
+
diff --git a/specRunner.min.html b/specRunner.min.html
index cdd5ca1..ff26d5f 100644
--- a/specRunner.min.html
+++ b/specRunner.min.html
@@ -12,8 +12,8 @@
-
-
+
+
diff --git a/src/main.aspec.js b/src/main.aspec.js
index 91ce176..f4b573f 100644
--- a/src/main.aspec.js
+++ b/src/main.aspec.js
@@ -84,19 +84,6 @@ describe("./main.js", function() {
expect(merged.this.is.a.test.yup()).to.equal("yup!");
});
- it("msngr.merge(input1, input2) - merges two methods together", function() {
- var func1 = function() {
- return "test"
- };
- var func2 = function() {
- return "again"
- };
-
- var merged = msngr.merge(func1, func2);
- expect(merged).to.exist;
- expect(merged()).to.equal("testagain");
- });
-
it("msngr.merge(input1, input2) - merges a method with properties", function() {
var myFunc = function() {
return 15;
@@ -118,16 +105,14 @@ describe("./main.js", function() {
});
it("msngr.merge(input1, input2) - merging undefined value is simply ignored", function() {
- var myTest = {};
- var merged = msngr.merge(undefined, myTest);
+ var merged = msngr.merge(undefined, {});
expect(merged).to.exist;
expect(Object.keys(merged).length).to.equal(0);
});
it("msngr.merge(input1, input2) - Property extends a string with another string", function() {
- var t = "something";
- var merged = msngr.merge("whatever", t);
+ var merged = msngr.merge("whatever", "something");
expect(merged).to.exist;
expect(msngr.getType(merged)).to.equal("[object String]");
expect(merged).to.equal("whateversomething");
@@ -221,6 +206,42 @@ describe("./main.js", function() {
delete msngr.sayHello;
});
+ it("msngr.copy(obj) - copies an object", function() {
+ var obj = {
+ stuff: {
+ goes: {
+ here: {
+ value: 41,
+ str: "some",
+ fn: function () {},
+ yeah: true
+ }
+ }
+ }
+ };
+
+ var copy = msngr.copy(obj);
+
+ // Make sure the references / copy are at least good
+ expect(copy).to.exist;
+ expect(copy.stuff).to.exist;
+ expect(copy.stuff.goes).to.exist;
+ expect(copy.stuff.goes.here).to.exist;
+ expect(copy.stuff.goes.here.value).to.equal(41);
+ expect(copy.stuff.goes.here.str).to.equal("some");
+ expect(msngr.isFunction(copy.stuff.goes.here.fn)).to.equal(true);
+ expect(copy.stuff.goes.here.yeah).to.equal(true);
+
+ // Let's make sure this is a real copy and not references to the original
+ obj.stuff.goes.here.value = 999;
+ expect(obj.stuff.goes.here.value).to.equal(999);
+ expect(copy.stuff.goes.here.value).to.equal(41);
+
+ obj.stuff.goes.here.str = "whatever!";
+ expect(obj.stuff.goes.here.str).to.equal("whatever!");
+ expect(copy.stuff.goes.here.str).to.equal("some");
+ });
+
it("msngr.debug - property setting exports internal object for testing and debugging", function() {
msngr.debug = false;
expect(msngr.internal).to.not.exist;
@@ -238,38 +259,55 @@ describe("./main.js", function() {
expect(msngr.warnings).to.equal(false);
});
- it("msngr.options(key, value) - allows saving of global options", function() {
- msngr.debug = true;
- msngr.options("myoptions", true);
- msngr.options("anotheroption", {
- something: true
+ it("msngr.config() - can set key value pairs for configuration", function() {
+ msngr.config("something_goofy", {
+ crazy: true
});
- expect(msngr.internal.globalOptions["myoptions"]).to.equal(true);
- expect(msngr.internal.globalOptions["anotheroption"].something).to.equal(true);
-
- delete msngr.internal.options["myoptions"];
- delete msngr.internal.globalOptions["myoptions"];
- delete msngr.internal.options["anotheroption"];
- delete msngr.internal.globalOptions["anotheroption"];
+ msngr.debug = true;
+ expect(msngr.internal.config.something_goofy).to.exist;
+ expect(msngr.internal.config.something_goofy.crazy).to.exist;
+ expect(msngr.internal.config.something_goofy.crazy).to.equal(true);
msngr.debug = false;
});
- it("msngr.options(key, value) - global options are copied and sent to any options", function(done) {
- msngr.debug = true;
+ it("msngr.config() - configuration options should be additive", function() {
+ msngr.config("yotest", {
+ something: true,
+ another: {
+ what: "yes"
+ }
+ });
- msngr.options("my-opts", {
- chicken: "tasty"
+ msngr.config("yotest", {
+ okay: 47
});
- msngr.internal.options["my-opts"] = function(message, payload, options, async) {
- expect(options["my-opts"].chicken).to.equal("tasty");
- delete msngr.internal.options["my-opts"];
- delete msngr.internal.globalOptions["my-opts"];
- msngr.debug = false;
- done();
- };
+ msngr.debug = true;
+ expect(msngr.internal.config.yotest).to.exist;
+ expect(msngr.internal.config.yotest.something).to.exist;
+ expect(msngr.internal.config.yotest.another).to.exist;
+ expect(msngr.internal.config.yotest.okay).to.exist;
+ expect(msngr.internal.config.yotest.okay).to.equal(47);
+
+ msngr.config("yotest", {
+ okay: 999
+ });
+
+ expect(msngr.internal.config.yotest.okay).to.equal(999);
+ expect(msngr.internal.config.yotest.another.what).to.equal("yes");
+ });
+
+ it("msngr.config() - providing only the key should return the existing config", function() {
+ msngr.config("whatevers", {
+ stuff: true,
+ you: {
+ there: "yes"
+ }
+ });
- msngr("MyTopic").emit("test");
+ expect(msngr.config("whatevers")).to.exist;
+ expect(msngr.config("whatevers").stuff).to.equal(true);
+ expect(msngr.config("whatevers").you.there).to.equal("yes");
});
});
diff --git a/src/main.js b/src/main.js
index ce41e04..92df46d 100644
--- a/src/main.js
+++ b/src/main.js
@@ -9,16 +9,25 @@ var msngr = msngr || (function() {
// Defaults for some internal functions
var internal = {
- globalOptions: {},
warnings: true
};
+ internal.config = { };
+
// The main method for msngr uses the message object
var external = function(topic, category, subcategory) {
return internal.objects.message(topic, category, subcategory);
};
- external.version = "2.4.1";
+ external.version = "3.0.0";
+
+ var getType = function(input) {
+ return Object.prototype.toString.call(input);
+ };
+
+ var extractFunction = function(input) {
+ return input.bind({});
+ };
// Merge two inputs into one
var twoMerge = function(input1, input2) {
@@ -131,13 +140,41 @@ var msngr = msngr || (function() {
return result;
};
- // An external options interface for global options settings
- external.options = function(key, value) {
- if (!external.exist(key)) {
- throw internal.InvalidParametersException("key");
+ external.copy = function(obj) {
+ if (obj === undefined || obj === null) {
+ return obj;
+ }
+ var objType = getType(obj);
+ if (["[object Object]", "[object Function]"].indexOf(objType) === -1) {
+ return obj;
+ }
+
+ var result;
+ if (getType(obj) === "[object Object]") {
+ result = {};
+ } else if (getType(obj) === "[object Function]") {
+ result = extractFunction(obj)
+ }
+
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ var keyType = getType(obj[key]);
+ if (["[object Object]", "[object Function]"].indexOf(keyType) !== -1) {
+ result[key] = external.copy(obj[key]);
+ } else {
+ result[key] = obj[key];
+ }
+ }
}
- internal.globalOptions[key] = value;
+ return result;
+ };
+
+ external.config = function(key, value) {
+ if (value !== undefined) {
+ internal.config[key] = external.merge((internal.config[key] || { }), external.copy(value));
+ }
+ return internal.config[key];
};
// Create a debug property to allow explicit exposure to the internal object structure.
diff --git a/src/objects/executer.aspec.js b/src/objects/executer.aspec.js
index 5640df5..219520f 100644
--- a/src/objects/executer.aspec.js
+++ b/src/objects/executer.aspec.js
@@ -21,49 +21,15 @@ describe("./objects/executer.js", function() {
msngr.debug = false;
});
-
- it("msngr.internal.objects.executer(method, payload, context).execute(done) - executes and returns a result from a sync method", function(done) {
- var myFunc = function(payload, async) {
- return 15;
- };
-
- msngr.internal.objects.executer(myFunc, undefined, this).execute(function(result) {
- expect(result).to.exist;
- expect(result).to.equal(15);
- done();
- });
- });
-
- it("msngr.internal.objects.executer(method, payload, context).execute(done) - executes and returns a result from an async method", function(done) {
- var myFunc = function(payload, async) {
- var d = async();
- d(42);
- };
-
- msngr.internal.objects.executer(myFunc, undefined, this).execute(function(result) {
- expect(result).to.existl
- expect(result).to.equal(42);
- done();
- });
- });
-
- it("msngr.internal.objects.executer(method, payload, context).execute(done) - done is executed even with no methods", function(done) {
- msngr.internal.objects.executer([], undefined, this).execute(function(result) {
+ it("msngr.internal.objects.executer(methodsAndParams, context).parallel(done) - done is executed even with no methods", function(done) {
+ msngr.internal.objects.executer([]).parallel(function(result) {
expect(result).to.exist;
expect(result.length).to.equal(0);
done();
});
});
- it("msngr.internal.objects.executer(method, payload, context).parallel(done) - done is executed even with no methods", function(done) {
- msngr.internal.objects.executer([], undefined, this).parallel(function(result) {
- expect(result).to.exist;
- expect(result.length).to.equal(0);
- done();
- });
- });
-
- it("msngr.internal.objects.executer(methods, payload, context).parallel(done) - executes multiple methods and aggregates results", function(done) {
+ it("msngr.executer(methods, payload, context).parallel(done) - executes multiple methods and aggregates results", function(done) {
var func1 = function(payload, async) {
expect(payload.t).to.exist;
expect(payload.t).to.equal(false);
@@ -101,7 +67,14 @@ describe("./objects/executer.js", function() {
return true;
}
- var executer = msngr.internal.objects.executer([func1, func2, func3, func4, func5, func6], {
+ var makeObj = function(fn) {
+ return {
+ method: fn,
+ params: [{ t: false }]
+ };
+ }
+
+ var executer = msngr.executer([makeObj(func1), makeObj(func2), makeObj(func3), makeObj(func4), makeObj(func5), makeObj(func6)], {
t: false
}, this);
executer.parallel(function(results) {
@@ -121,8 +94,18 @@ describe("./objects/executer.js", function() {
it("msngr.executer(methods, payload, context) - executer is exposed for anyone to access", function() {
expect(msngr.executer).to.exist;
expect(msngr.executer([], {}, undefined)).to.exist;
- expect(msngr.executer([], {}, undefined).execute).to.exist;
expect(msngr.executer([], {}, undefined).parallel).to.exist;
});
+ it("msngr.executer(funcs).parallel() - With no parameters specified async is the first parameter", function(done) {
+ var executer = msngr.executer([
+ function(async) {
+ expect(async).to.exist;
+ expect(msngr.isFunction(async)).to.equal(true);
+ }
+ ]).parallel(function() {
+ done();
+ });
+ });
+
});
diff --git a/src/objects/executer.bench.js b/src/objects/executer.bench.js
new file mode 100644
index 0000000..41a5d5c
--- /dev/null
+++ b/src/objects/executer.bench.js
@@ -0,0 +1,55 @@
+if (typeof chai === "undefined" && typeof window === "undefined") {
+ var benchmark = require("benchmark");
+}
+
+if (typeof msngr === "undefined" && typeof window === "undefined") {
+ var msngr = require("../../msngr");
+}
+
+var exporter;
+if (msngr.isBrowser()) {
+ window.benchmarkers = window.benchmarkers || [];
+ exporter = function(fn) {
+ window.benchmarkers.push(fn);
+ };
+} else {
+ exporter = function(fn) {
+ module.exports = fn;
+ };
+}
+
+exporter(function(done) {
+ "use strict";
+ var bench = new benchmark.Suite;
+
+ msngr.debug = true;
+ msngr.internal.reset();
+
+ console.log("Benchmarks starting for executer object methods");
+ bench.add("1x functions", {
+ defer: true,
+ fn: function(deferred) {
+ var func = function() {
+ return 42;
+ };
+
+ msngr.executer([func]).parallel(function() {
+ deferred.resolve();
+ });
+ }
+ });
+
+ bench.on("cycle", function(event) {
+ console.log(String(event.target));
+ msngr.debug = true;
+ msngr.internal.reset();
+ });
+
+ bench.on("complete", function() {
+ console.log("Benchmarks complete for executer object methods");
+ msngr.debug = false;
+ done(null);
+ });
+
+ bench.run({ 'async': true });
+});
diff --git a/src/objects/executer.js b/src/objects/executer.js
index b594080..cac34f9 100644
--- a/src/objects/executer.js
+++ b/src/objects/executer.js
@@ -2,50 +2,47 @@ msngr.extend((function(external, internal) {
"use strict";
internal.objects = internal.objects || {};
- internal.objects.executer = function(methods, payload, context) {
-
- if (external.isFunction(methods)) {
- methods = [methods];
- }
+ internal.objects.executer = function(methods) {
if (!external.exist(methods) || !external.isArray(methods)) {
throw internal.InvalidParametersException("executor");
}
- var exec = function(method, pay, ctx, done) {
- setTimeout(function() {
- var async = false;
+ // Support passing in just methods
+ for (var i = 0; i < methods.length; ++i) {
+ if (external.isFunction(methods[i])) {
+ methods[i] = {
+ method: methods[i]
+ };
+ }
+ }
+
+ var exec = function(method, params, ctx, done) {
+ external.immediate(function() {
+ var asyncFlag = false;
var asyncFunc = function() {
- async = true;
+ asyncFlag = true;
return function(result) {
done.apply(ctx, [result]);
};
}
- var params = undefined;
- if (external.isArray(pay)) {
- params = pay;
- } else {
- params = [pay];
+ if (!external.isArray(params)) {
+ if (external.exist(params)) {
+ params = [params];
+ } else {
+ params = [];
+ }
}
params.push(asyncFunc);
-
var syncResult = method.apply(ctx || this, params);
- if (async !== true) {
+ if (asyncFlag !== true) {
done.apply(ctx, [syncResult]);
}
- }, 0);
+ });
};
return {
- execute: function(done) {
- if (methods.length === 0 && external.exist(done)) {
- return done.apply(context, [
- []
- ]);
- }
- return exec(methods[0], payload, context, done);
- },
parallel: function(done) {
var results = [];
var executed = 0;
@@ -57,6 +54,10 @@ msngr.extend((function(external, internal) {
}
for (var i = 0; i < methods.length; ++i) {
+ var method = methods[i].method;
+ var params = methods[i].params;
+ var context = methods[i].context;
+
(function(m, p, c) {
exec(m, p, c, function(result) {
if (external.exist(result)) {
@@ -69,7 +70,7 @@ msngr.extend((function(external, internal) {
done.apply(context, [results]);
}
});
- }(methods[i], payload, context));
+ }(method, params, context));
}
}
};
diff --git a/src/objects/message.aspec.js b/src/objects/message.aspec.js
index 7dadb92..b19ea22 100644
--- a/src/objects/message.aspec.js
+++ b/src/objects/message.aspec.js
@@ -118,20 +118,20 @@ describe("./objects/message.js", function() {
});
it("msngr().option() - custom option processor works as expected", function(done) {
- msngr.internal.options["testsync"] = function(message, payload, options, async) {
+ msngr.internal.option("testsync", function(message, payload, options, async) {
return "synced!";
- };
+ });
var msg = msngr("MyTopic").option("testsync").on(function(payload) {
expect(payload).to.exist;
expect(payload).to.equal("synced!");
- msngr.internal.options["testasync"] = function(message, payload, options, async) {
- var d = async();
+ msngr.internal.option("testasync", function(message, payload, options, masync) {
+ var d = masync();
d({
words: "asynced!"
});
- };
+ });
var msg2 = msngr("AnotherTopic").option("testasync").on(function(payload2) {
expect(payload2).to.exist;
@@ -141,6 +141,37 @@ describe("./objects/message.js", function() {
}).emit();
});
+ it("msngr().emit() / on() - Successfully emits a payload that, when modifies, doesn't affect the handler's data", function(done) {
+ var msg = msngr("MyTopicalTopic");
+ var originalPayload = { tester: "yipyup", num: 7 };
+ msg.on(function(payload, message) {
+ expect(payload).to.exist;
+ expect(payload.tester).to.equal("yipyup");
+ originalPayload.stuff = 47;
+ expect(payload.stuff).to.not.exist;
+
+ originalPayload.num = 42;
+ expect(payload.num).to.equal(7);
+ done();
+ });
+
+ msg.emit(originalPayload);
+ });
+
+ it("msngr().emit() / on() - Successfully emits specific message to generic handler and gets the emitted message object", function(done) {
+ msngr("HighlyTopical").on(function(payload, message) {
+ expect(payload).to.exist;
+ expect(payload).to.equal("stuff");
+ expect(message).to.exist;
+ expect(message.topic).to.equal("HighlyTopical");
+ expect(message.category).to.equal("MyCats");
+ expect(message.subcategory).to.equal("OtherCats");
+ done();
+ });
+
+ msngr("HighlyTopical", "MyCats", "OtherCats").emit("stuff");
+ });
+
it("msngr().emit() / on() - Successfully emits and handles a topic only message", function(done) {
var msg = msngr("MyTopic");
msg.on(function(payload) {
@@ -269,10 +300,14 @@ describe("./objects/message.js", function() {
return "testering";
});
- msg.on(function(payload, async) {
+ msg.on(function(payload, message, async) {
var finished = async();
++handled;
expect(payload).to.exist;
+ expect(message).to.exist;
+ expect(message.topic).to.equal("MyTopic");
+ expect(message.category).to.equal("MyCategory");
+ expect(message.subcategory).to.equal("MySubCategory");
expect(payload).to.equal("ThreeHandlers");
finished(42);
});
diff --git a/src/objects/message.bench.js b/src/objects/message.bench.js
new file mode 100644
index 0000000..427257e
--- /dev/null
+++ b/src/objects/message.bench.js
@@ -0,0 +1,86 @@
+if (typeof chai === "undefined" && typeof window === "undefined") {
+ var benchmark = require("benchmark");
+}
+
+if (typeof msngr === "undefined" && typeof window === "undefined") {
+ var msngr = require("../../msngr");
+}
+
+var exporter;
+if (msngr.isBrowser()) {
+ window.benchmarkers = window.benchmarkers || [];
+ exporter = function(fn) {
+ window.benchmarkers.push(fn);
+ };
+} else {
+ exporter = function(fn) {
+ module.exports = fn;
+ };
+}
+
+exporter(function(done) {
+ "use strict";
+ var bench = new benchmark.Suite;
+
+ msngr.debug = true;
+ msngr.internal.reset();
+
+ console.log("Benchmarks starting for message object methods");
+ bench.add("1x registrations + 1x emits", {
+ defer: true,
+ fn: function(deferred) {
+ var msg = msngr("Topic1", "Category1", "Subcategory1");
+ msg.on(function(payload, message) {
+ msg.dropAll();
+ deferred.resolve();
+ });
+ msg.emit("value");
+ }
+ });
+
+ bench.add("3x registrations + 1x emits", {
+ defer: true,
+ fn: function(deferred) {
+ var msg = msngr("Topic1", "Category1", "Subcategory1");
+ msg.on(function(payload, message) {
+ return 1 + 1;
+ });
+ msg.on(function(payload, message) {
+ return 1 + 1;
+ });
+ msg.on(function(payload, message) {
+ return 1 + 1;
+ });
+ msg.emit("value", function(result) {
+ msg.dropAll();
+ deferred.resolve();
+ });
+ }
+ });
+
+ bench.add("1x registrations + 1x persists", {
+ defer: true,
+ fn: function(deferred) {
+ var msg = msngr("Topic1", "Category1", "Subcategory1");
+ msg.on(function(payload, message) {
+ msg.dropAll();
+ msg.cease();
+ deferred.resolve();
+ });
+ msg.persist("value");
+ }
+ });
+
+ bench.on("cycle", function(event) {
+ console.log(String(event.target));
+ msngr.internal.reset();
+ });
+
+ bench.on("complete", function() {
+ console.log("Benchmarks complete for message object methods");
+ msngr.debug = false;
+ done(null);
+ });
+
+ bench.run({ 'async': true });
+});
diff --git a/src/objects/message.js b/src/objects/message.js
index 5c47470..5222ae6 100644
--- a/src/objects/message.js
+++ b/src/objects/message.js
@@ -7,6 +7,9 @@ msngr.extend((function(external, internal) {
"use strict";
internal.objects = internal.objects || {};
+ internal.option = function(opt, handler) {
+ internal.option[opt] = handler;
+ };
var messageIndex = internal.objects.memory();
var payloadIndex = internal.objects.memory();
@@ -52,8 +55,11 @@ msngr.extend((function(external, internal) {
internal.processOpts = function(opts, message, payload, callback) {
var optProcessors = [];
for (var key in opts) {
- if (opts.hasOwnProperty(key) && external.exist(internal.options[key])) {
- optProcessors.push(internal.options[key]);
+ if (opts.hasOwnProperty(key) && external.exist(internal.option[key])) {
+ optProcessors.push({
+ method: internal.option[key],
+ params: [message, payload, opts]
+ });
}
}
@@ -63,7 +69,7 @@ msngr.extend((function(external, internal) {
}
// Long circuit to do stuff (du'h)
- var execs = internal.objects.executer(optProcessors, [message, payload, opts], this);
+ var execs = internal.objects.executer(optProcessors);
execs.parallel(function(results) {
var result = payload;
@@ -104,7 +110,7 @@ msngr.extend((function(external, internal) {
}
if (external.isObject(topic)) {
- msg = topic;
+ msg = external.copy(topic);
} else {
msg = {};
msg.topic = topic;
@@ -118,9 +124,7 @@ msngr.extend((function(external, internal) {
}
}
- // Copy global options
- var options = external.merge({}, internal.globalOptions);
-
+ var options = {};
var counts = {
emits: 0,
persists: 0,
@@ -132,19 +136,23 @@ msngr.extend((function(external, internal) {
var explicitEmit = function(payload, uuids, callback) {
var uuids = uuids || messageIndex.query(msg) || [];
- var methods = [];
- var toDrop = [];
- for (var i = 0; i < uuids.length; ++i) {
- var obj = handlers[uuids[i]];
- methods.push(obj.handler);
- if (obj.once === true) {
- toDrop.push(obj.handler);
+ internal.processOpts(options, msg, payload, function(result) {
+ var methods = [];
+ var toDrop = [];
+ for (var i = 0; i < uuids.length; ++i) {
+ var obj = handlers[uuids[i]];
+ methods.push({
+ method: obj.handler,
+ params: [result, msg]
+ });
+
+ if (obj.once === true) {
+ toDrop.push(obj.handler);
+ }
}
- }
- internal.processOpts(options, msg, payload, function(result) {
- var execs = internal.objects.executer(methods, result, (msg.context || this));
+ var execs = internal.objects.executer(methods);
for (var i = 0; i < toDrop.length; ++i) {
msgObj.drop(toDrop[i]);
@@ -181,7 +189,7 @@ msngr.extend((function(external, internal) {
throw internal.InvalidParametersException("option");
}
- options[key] = value;
+ options[key] = external.copy(value);
counts.options = counts.options + 1;
return msgObj;
@@ -191,7 +199,7 @@ msngr.extend((function(external, internal) {
callback = payload;
payload = undefined;
}
- explicitEmit(payload, undefined, callback);
+ explicitEmit(external.copy(payload), undefined, callback);
counts.emits = counts.emits + 1;
return msgObj;
@@ -204,11 +212,11 @@ msngr.extend((function(external, internal) {
var uuids = payloadIndex.query(msg);
if (uuids.length === 0) {
var uuid = payloadIndex.index(msg);
- payloads[uuid] = payload;
+ payloads[uuid] = external.copy(payload);
uuids = [uuid];
} else {
for (var i = 0; i < uuids.length; ++i) {
- payloads[uuids[i]] = external.extend(payload, payloads[uuids[i]]);
+ payloads[uuids[i]] = external.merge(payload, payloads[uuids[i]]);
}
}
diff --git a/src/objects/net.aspec.js b/src/objects/net.aspec.js
new file mode 100644
index 0000000..384f63d
--- /dev/null
+++ b/src/objects/net.aspec.js
@@ -0,0 +1,204 @@
+if (typeof chai === "undefined" && typeof window === "undefined") {
+ var chai = require("chai");
+}
+
+if (typeof expect === "undefined") {
+ var expect = chai.expect;
+}
+
+if (typeof msngr === "undefined" && typeof window === "undefined") {
+ var msngr = require("../../msngr");
+}
+
+describe("./objects/net.js", function() {
+ "use strict";
+ var HOST_PROTOCOL = "http";
+ var HOST_NAME = "localhost";
+ var HOST_PORT = "8009";
+ var server;
+
+ it("msngr.net() - throws an except when provided bad input", function() {
+ expect(msngr.net.bind(null)).to.throw;
+ expect(msngr.net.bind(undefined)).to.throw;
+ expect(msngr.net.bind(96)).to.throw;
+ expect(msngr.net.bind(new Date())).to.throw;
+ expect(msngr.net.bind("")).to.throw;
+ expect(msngr.net.bind(" ")).to.throw;
+ });
+
+ it("msngr.net(protocol, host, port) - returns a net object when proper input is provided", function() {
+ var net = msngr.net(HOST_PROTOCOL, HOST_NAME, HOST_PORT);
+ expect(net).to.exist;
+ expect(net.protocol).to.equal(HOST_PROTOCOL);
+ expect(net.host).to.equal(HOST_NAME);
+ expect(net.port).to.equal(HOST_PORT);
+ });
+
+ it("msngr.net(protocol, host) - returns a net object when only protocol and host are provided", function() {
+ var net = msngr.net(HOST_PROTOCOL, HOST_NAME);
+ expect(net).to.exist;
+ expect(net.protocol).to.equal(HOST_PROTOCOL);
+ expect(net.host).to.equal(HOST_NAME);
+ expect(net.port).to.equal("80");
+ });
+
+ it("msngr.net(host) - returns a net object when a string including protocol, host and port are provided", function() {
+ var net = msngr.net(HOST_PROTOCOL + "://" + HOST_NAME + ":" + HOST_PORT);
+ expect(net).to.exist;
+ expect(net.protocol).to.equal(HOST_PROTOCOL);
+ expect(net.host).to.equal(HOST_NAME);
+ expect(net.port).to.equal(HOST_PORT);
+ });
+
+ it("msngr.net(host) - returns a net object when a string including protocol and host are provided", function() {
+ var net = msngr.net(HOST_PROTOCOL + "://" + HOST_NAME);
+ expect(net).to.exist;
+ expect(net.protocol).to.equal(HOST_PROTOCOL);
+ expect(net.host).to.equal(HOST_NAME);
+ expect(net.port).to.equal("80");
+ });
+
+ it("msngr.net(host) - returns a net object when a string including protocol and host are provided and extra path is stripped", function() {
+ var net = msngr.net(HOST_PROTOCOL + "://" + HOST_NAME + "/crazy/stuff");
+ expect(net).to.exist;
+ expect(net.protocol).to.equal(HOST_PROTOCOL);
+ expect(net.host).to.equal(HOST_NAME);
+ expect(net.port).to.equal("80");
+ });
+
+ it("msngr.net(host) - returns a net object when a string including protocol, host and port are provided and extra path is stripped", function() {
+ var net = msngr.net(HOST_PROTOCOL + "://" + HOST_NAME + ":" + HOST_PORT + "/crazy/stuff");
+ expect(net).to.exist;
+ expect(net.protocol).to.equal(HOST_PROTOCOL);
+ expect(net.host).to.equal(HOST_NAME);
+ expect(net.port).to.equal(HOST_PORT);
+ });
+
+ it("msngr.net(host) - returns a net object when a string only including host is provided", function() {
+ var net = msngr.net(HOST_NAME);
+ expect(net).to.exist;
+ expect(net.protocol).to.equal("http");
+ expect(net.host).to.equal(HOST_NAME);
+ expect(net.port).to.equal("80");
+ });
+
+ it("msngr.net(protocol, host, port) - throws exception when presented with invalid input", function() {
+ expect(msngr.net.bind(47)).to.throw;
+ expect(msngr.net.bind({ })).to.throw;
+ expect(msngr.net.bind([])).to.throw;
+ expect(msngr.net.bind("")).to.throw;
+ expect(msngr.net.bind(" ")).to.throw;
+ });
+
+ it("msngr.net(protocol, host, port).get(opts, callback) - creates a GET request and returns 200", function(done) {
+ var net = msngr.net(HOST_PROTOCOL, HOST_NAME, HOST_PORT);
+ net.get({
+ path: "/"
+ }, function(err, result) {
+ expect(err).to.not.exist;
+ expect(result).to.exist;
+ expect(result.path).to.equal("/");
+ done();
+ });
+ });
+
+ it("msngr.net(protocol, host, port).get(opts, callback) - creates a GET request with a query string and returns a 200", function(done) {
+ var net = msngr.net(HOST_PROTOCOL, HOST_NAME, HOST_PORT);
+ net.get({
+ path: "/search",
+ query: {
+ term: "search topic"
+ }
+ }, function(err, result) {
+ expect(err).to.not.exist;
+ expect(result).to.exist;
+ expect(result.path).to.equal("/search?term=search%20topic");
+ done();
+ });
+ });
+
+ it("msngr.net(protocol, host, port).post(opts, callback) - creates a POST request and returns 200", function(done) {
+ var net = msngr.net(HOST_PROTOCOL, HOST_NAME, HOST_PORT);
+ net.post({
+ path: "/"
+ }, function(err, result) {
+ expect(err).to.not.exist;
+ expect(result).to.exist;
+ expect(result.path).to.equal("/");
+ done();
+ });
+ });
+
+ it("msngr.net(protocol, host, port).post(opts, callback) - creates a POST request with data and returns a 200", function(done) {
+ var net = msngr.net(HOST_PROTOCOL, HOST_NAME, HOST_PORT);
+ net.post({
+ path: "/users",
+ payload: {
+ headers: {
+ "content-type": "application/json"
+ },
+ username: "kris",
+ email: "redacted@redacted.com"
+ }
+ }, function(err, result) {
+ expect(err).to.not.exist;
+ expect(result).to.exist;
+ expect(result.path).to.equal("/users");
+ result.body = JSON.parse(result.body);
+ expect(result.body.username).to.equal("kris");
+ expect(result.body.email).to.equal("redacted@redacted.com");
+ done();
+ });
+ });
+
+ it("msngr.net(protocol, host, port).put(opts, callback) - creates a PUT request and returns 200", function(done) {
+ var net = msngr.net(HOST_PROTOCOL, HOST_NAME, HOST_PORT);
+ net.put({
+ path: "/user/394859",
+ payload: {
+ headers: {
+ "content-type": "application/json"
+ },
+ username: "kris"
+ }
+ }, function(err, result) {
+ expect(err).to.not.exist;
+ expect(result).to.exist;
+ expect(result.path).to.equal("/user/394859");
+ result.body = JSON.parse(result.body);
+ expect(result.body.username).to.equal("kris");
+ done();
+ });
+ });
+
+ it("msngr.net(protocol, host, port).delete(opts, callback) - creates a DELETE request and returns 200", function(done) {
+ var net = msngr.net(HOST_PROTOCOL, HOST_NAME, HOST_PORT);
+ net.delete({
+ path: "/user/3928024"
+ }, function(err, result) {
+ expect(err).to.not.exist;
+ expect(result).to.exist;
+ expect(result.path).to.equal("/user/3928024");
+ done();
+ });
+ });
+
+ it("msngr.net(protocol, host, port).post(opts, callback) - creates a POST request with data and returns a 200 with plain text", function(done) {
+ var net = msngr.net(HOST_PROTOCOL, HOST_NAME, HOST_PORT);
+ net.post({
+ path: "/users",
+ payload: {
+ headers: {
+ "content-type": "text/plain"
+ },
+ username: "kris",
+ email: "redacted@redacted.com"
+ }
+ }, function(err, result) {
+ expect(err).to.not.exist;
+ expect(result).to.exist;
+ expect(msngr.isString(result)).to.equal(true);
+ done();
+ });
+ });
+});
diff --git a/src/objects/net.js b/src/objects/net.js
new file mode 100644
index 0000000..c1649e6
--- /dev/null
+++ b/src/objects/net.js
@@ -0,0 +1,315 @@
+msngr.extend((function(external, internal) {
+ "use strict";
+
+ // Setup constants
+ external.config("net", {
+ defaults: {
+ protocol: "http",
+ port: {
+ http: "80",
+ https: "443"
+ },
+ autoJson: true
+ }
+ });
+
+ // This method handles requests when msngr is running within a semi-modern net browser
+ var browser = function(server, options, callback) {
+ try {
+ var xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200 || xhr.status === 201) {
+ var obj;
+ if (options.autoJson === true && this.getResponseHeader("content-type") === "application/json") {
+ try {
+ obj = JSON.parse(xhr.response);
+ } catch (ex) {
+ // Don't do anything; probably wasn't JSON anyway
+ // Set obj to undefined just incase it contains something awful
+ obj = undefined;
+ }
+ }
+ callback.apply(undefined, [null, (obj || xhr.response)]);
+ } else {
+ var errObj = {
+ status: xhr.status,
+ response: xhr.response
+ };
+ callback.apply(undefined, [errObj, null]);
+ }
+ }
+ };
+
+ var url = server.protocol + "://" + server.host;
+ if (server.canOmitPort === true) {
+ url = url + options.path;
+ } else {
+ url = url + ":" + server.port + options.path;
+ }
+
+ var datum;
+ if (external.exist(options.payload)) {
+ if (external.isObject(options.payload)) {
+ try {
+ datum = JSON.stringify(options.payload);
+ } catch (ex) {
+ // Really couldn't give a shit about this exception
+ }
+ }
+
+ // undefined has no meaning in JSON but null does; so let's only
+ // and explicitly set anything if it's still undefined (so no null checks)
+ if (datum === undefined) {
+ datum = options.payload;
+ }
+ }
+
+ xhr.open(options.method, url);
+ xhr.send(datum);
+ } catch (ex) {
+ callback.apply(undefined, [ex, null]);
+ }
+ };
+
+ // This method handles requests when msngr is running within node.js
+ var node = function(server, options, callback) {
+ var http = require("http");
+ var request = http.request({
+ method: options.method,
+ host: server.host,
+ port: server.port,
+ path: options.path
+ }, function(response) {
+ response.setEncoding("utf8");
+ var body = "";
+ response.on("data", function(chunk) {
+ body = body + chunk;
+ });
+
+ response.on("end", function() {
+ var obj;
+ if (options.autoJson === true && response.headers["content-type"] === "application/json") {
+ try {
+ obj = JSON.parse(body);
+ } catch (ex) {
+ // Don't do anything; probably wasn't JSON anyway
+ // Set obj to undefined just incase it contains something awful
+ obj = undefined;
+ }
+ }
+ obj = obj || body;
+ var errObj;
+ if (request.statusCode >= 400) {
+ errObj = {
+ status: request.statusCode,
+ response: (obj || body)
+ };
+ obj = null;
+ }
+ callback.apply(undefined, [errObj, obj]);
+ });
+ });
+
+ if (external.exist(options.payload)) {
+ var datum;
+ if (external.isObject(options.payload)) {
+ try {
+ datum = JSON.stringify(options.payload);
+ } catch (ex) {
+ // Really couldn't give a shit about this exception
+ }
+ }
+
+ // undefined has no meaning in JSON but null does; so let's only
+ // and explicitly set anything if it's still undefined (so no null checks)
+ if (datum === undefined) {
+ datum = options.payload;
+ }
+
+ request.write(datum);
+ }
+
+ request.end();
+ };
+
+ var request = function(server, opts, callback) {
+ opts.path = opts.path || "/";
+ opts.autoJson = opts.autoJson || internal.config["net"].defaults.autoJson;
+
+ if (external.exist(opts.query)) {
+ if (external.isString(opts.query)) {
+ opts.queryString = opts.query;
+ }
+
+ if (external.isObject(opts.query)) {
+ opts.queryString = "?";
+ for (var key in opts.query) {
+ if (opts.query.hasOwnProperty(key)) {
+ if (opts.queryString !== "?") {
+ opts.queryString = opts.queryString + "&";
+ }
+ opts.queryString = opts.queryString + encodeURIComponent(key) + "=" + encodeURIComponent(opts.query[key]);
+ }
+ }
+ }
+ }
+
+ opts.path = opts.path + (opts.queryString || "");
+
+ if (external.isBrowser()) {
+ browser(server, opts, callback);
+ } else {
+ node(server, opts, callback);
+ }
+ };
+
+ // This method is crazy; tries to figure out what the developer sent to
+ // the net() method to allow maximum flexibility. Normalization is important here.
+ var figureOutServer = function(protocol, host, port) {
+ var server = { protocol: undefined, host: undefined, port: undefined, canOmitPort: false };
+ var handled = false;
+ var invalid = false;
+ var invalidReason;
+
+ if (external.isEmptyString(protocol)) {
+ invalid = true;
+ invalidReason = "Protocol or host not provided";
+ }
+
+ if (!invalid && !external.isEmptyString(protocol) && external.isEmptyString(host) && external.isEmptyString(port)) {
+ // Only one argument was provided; must be whole host.
+ var split = protocol.split("://");
+ if (split.length == 2) {
+ server.protocol = split[0];
+ server.host = split[1];
+ } else {
+ // Must have omitted protocol.
+ server.host = protocol;
+ server.protocol = internal.config.net.defaults.protocol;
+ }
+
+ var lastColon = server.host.lastIndexOf(":");
+ if (lastColon !== -1) {
+ // There is a port; let's grab it!
+ server.port = server.host.substring(lastColon + 1, server.host.length);
+ server.host = server.host.substring(0, lastColon);
+ } else {
+ // There ain't no port!
+ server.port = internal.config.net.defaults.port[server.protocol];
+ }
+
+ handled = true;
+ }
+
+ if (!invalid && !handled && !external.isEmptyString(protocol) && !external.isEmptyString(host) && external.isEmptyString(port)) {
+ // Okay, protocol and host are provided. Figure out port!
+ server.protocol = protocol;
+ server.host = host;
+
+ var lastColon = server.host.lastIndexOf(":");
+ if (lastColon !== -1) {
+ // There is a port; let's grab it!
+ server.port = server.host.substring(lastColon + 1, server.host.length);
+ server.host = server.host.substring(0, lastColon);
+ } else {
+ // There ain't no port!
+ server.port = internal.config.net.defaults.port[server.protocol];
+ }
+
+ handled = true;
+ }
+
+ if (!invalid && !handled && !external.isEmptyString(protocol) && !external.isEmptyString(host) && !external.isEmptyString(port)) {
+ // Everything is provided. Holy shit, does that ever happen!?
+ server.protocol = protocol;
+ server.host = host;
+ server.port = port;
+
+ handled = true;
+ }
+
+ // Port explicitness can be omitted for some protocols where the port is their default
+ // so let's mark them as can be omitted. This will make output less confusing for
+ // more inexperienced developers plus it looks prettier :).
+ if (!invalid && handled && internal.config.net.defaults.port[server.protocol] === server.port) {
+ server.canOmitPort = true;
+ }
+
+ if (!invalid && !handled) {
+ // Well we didn't handle the input but also didn't think it was invalid. Crap!
+ invalid = true;
+ invalidReason = "Unable to handle input into method. Please open a GitHub issue with your input :)";
+ }
+
+ if (invalid === true) {
+ throw internal.InvalidParametersException("net", invalidReason);
+ }
+
+ // Strip any supplied paths
+ var stripPath = function(input) {
+ var index = input.indexOf("/");
+ return input.substring(0, ((index === -1) ? input.length : index));
+ };
+
+ server.host = stripPath(server.host);
+ server.port = stripPath(server.port);
+
+ return server;
+ };
+
+ return {
+ net: function(protocol, host, port) {
+ var server = figureOutServer(protocol, host, port);
+
+ var netObj = {
+ get: function(options, callback) {
+ var opts = external.merge(options, { });
+ opts.method = "get";
+ request(server, opts, callback);
+ },
+ post: function(options, callback) {
+ var opts = external.merge(options, { });
+ opts.method = "post";
+ request(server, opts, callback);
+ },
+ put: function(options, callback) {
+ var opts = external.merge(options, { });
+ opts.method = "put";
+ request(server, opts, callback);
+ },
+ delete: function(options, callback) {
+ var opts = external.merge(options, { });
+ opts.method = "delete";
+ request(server, opts, callback);
+ },
+ options: function(options, callback) {
+ var opts = external.merge(options, { });
+ opts.method = "options";
+ request(server, opts, callback);
+ }
+ };
+
+ Object.defineProperty(netObj, "protocol", {
+ get: function() {
+ return server.protocol;
+ }
+ });
+
+ Object.defineProperty(netObj, "host", {
+ get: function() {
+ return server.host;
+ }
+ });
+
+ Object.defineProperty(netObj, "port", {
+ get: function() {
+ return server.port;
+ }
+ });
+
+ return netObj;
+ }
+ };
+}));
diff --git a/src/options/cross-window.js b/src/options/cross-window.js
index f43281d..559ffcd 100644
--- a/src/options/cross-window.js
+++ b/src/options/cross-window.js
@@ -7,9 +7,9 @@
msngr.extend((function(external, internal) {
"use strict";
- var CHANNEL_NAME = "__msngr_cross-window";
-
- internal.options = internal.options || {};
+ external.config("cross-window", {
+ channel: "__msngr_cross-window"
+ });
// Let's check if localstorage is even available. If it isn't we shouldn't register
if (typeof localStorage === "undefined" || typeof window === "undefined") {
@@ -17,7 +17,7 @@ msngr.extend((function(external, internal) {
}
window.addEventListener("storage", function(event) {
- if (event.key === CHANNEL_NAME) {
+ if (event.key === internal.config["cross-window"].channel) {
// New message data. Respond!
var obj;
try {
@@ -32,7 +32,7 @@ msngr.extend((function(external, internal) {
}
});
- internal.options["cross-window"] = function(message, payload, options, async) {
+ internal.option("cross-window", function(message, payload, options, async) {
// Normalize all of the inputs
options = options || {};
options = options["cross-window"] || {};
@@ -43,13 +43,13 @@ msngr.extend((function(external, internal) {
};
try {
- localStorage.setItem(CHANNEL_NAME, JSON.stringify(obj));
+ localStorage.setItem(internal.config["cross-window"].channel, JSON.stringify(obj));
} catch (ex) {
throw "msngr was unable to store data in its storage channel";
}
return undefined;
- };
+ });
// This is an internal extension; do not export explicitly.
return {};
diff --git a/src/options/dom.cspec.js b/src/options/dom.cspec.js
index 80604b0..7db5034 100644
--- a/src/options/dom.cspec.js
+++ b/src/options/dom.cspec.js
@@ -51,36 +51,6 @@ describe("./options/dom.js", function() {
msngr("TestTopic").option("dom", ["input"]).emit();
});
- it("dom option - gathers multiple values with a selector that matches multiple elements with no IDs or names using global options", function(done) {
- var input1 = document.createElement("input");
- input1.value = "Kris";
-
- var input2 = document.createElement("input");
- input2.value = "AnEmail@Address.here";
-
- document.body.appendChild(input1);
- document.body.appendChild(input2);
-
- msngr.options("dom", ["input"]);
-
- msngr("TestTopic").on(function(payload) {
- expect(payload).to.exist;
- expect(payload["input0"]).to.exist;
- expect(payload["input0"]).to.equal("Kris");
- expect(payload["input1"]).to.exist;
- expect(payload["input1"]).to.equal("AnEmail@Address.here");
-
- document.body.removeChild(input1);
- document.body.removeChild(input2);
-
- delete msngr.internal.globalOptions["dom"];
-
- done();
- });
-
- msngr("TestTopic").emit();
- });
-
it("dom action - gathers multiple values with a selector that matches multiple elements", function(done) {
var input1 = document.createElement("input");
input1.setAttribute("name", "Name");
diff --git a/src/options/dom.js b/src/options/dom.js
index 39d2bf8..4314141 100644
--- a/src/options/dom.js
+++ b/src/options/dom.js
@@ -6,9 +6,7 @@
msngr.extend((function(external, internal) {
"use strict";
- internal.options = internal.options || {};
-
- internal.options.dom = function(message, payload, options, async) {
+ internal.option("dom", function(message, payload, options, async) {
// Normalize all of the inputs
options = options || {};
options = options.dom || {};
@@ -72,7 +70,7 @@ msngr.extend((function(external, internal) {
return resultMap;
- };
+ });
// This is an internal extension; do not export explicitly.
return {};
diff --git a/src/utils/exceptional.aspec.js b/src/utils/exceptional.aspec.js
index aa5f667..241534b 100644
--- a/src/utils/exceptional.aspec.js
+++ b/src/utils/exceptional.aspec.js
@@ -21,7 +21,7 @@ describe("./utils/exceptional.js", function() {
msngr.debug = false;
});
- it("internal.InvalidParametersException - throws an exception", function() {
+ it("internal.InvalidParametersException(str) - throws an exception", function() {
var myfunc = function() {
throw internal.InvalidParametersException("MyParameter");
};
@@ -29,7 +29,15 @@ describe("./utils/exceptional.js", function() {
expect(myfunc).to.throw();
});
- it("internal.ReservedKeywordsException - throws an exception", function() {
+ it("internal.InvalidParametersException(str, reason) - throws an exception", function() {
+ var myfunc = function() {
+ throw internal.InvalidParametersException("MyParameter", "A real reason");
+ };
+
+ expect(myfunc).to.throw();
+ });
+
+ it("internal.ReservedKeywordsException(keyword) - throws an exception", function() {
var myfunc = function() {
throw internal.ReservedKeywordsException("MyKeyword");
};
@@ -37,7 +45,7 @@ describe("./utils/exceptional.js", function() {
expect(myfunc).to.throw();
});
- it("internal.MangledException - throws an exception", function() {
+ it("internal.MangledException(variable, method) - throws an exception", function() {
var myfunc = function() {
throw internal.MangledException("MyVariable", "MyFunc");
};
diff --git a/src/utils/exceptional.js b/src/utils/exceptional.js
index 1ddc880..837bbf2 100644
--- a/src/utils/exceptional.js
+++ b/src/utils/exceptional.js
@@ -1,12 +1,16 @@
msngr.extend((function(external, internal) {
"use strict";
- internal.InvalidParametersException = function(str) {
- return {
+ internal.InvalidParametersException = function(str, reason) {
+ var m = {
name: "InvalidParametersException",
severity: "unrecoverable",
message: ("Invalid parameters supplied to the {method} method".replace("{method}", str))
};
+ if (!external.isEmptyString(reason)) {
+ m.message = m.message + " " + reason;
+ }
+ return m;
};
internal.ReservedKeywordsException = function(keyword) {
diff --git a/src/utils/misc.aspec.js b/src/utils/misc.aspec.js
index 1066823..6fc3441 100644
--- a/src/utils/misc.aspec.js
+++ b/src/utils/misc.aspec.js
@@ -94,4 +94,10 @@ describe("./utils/misc.js", function() {
var arr2 = ["yeah", "oh", "nice", "chips", "chips", "chips"];
expect(msngr.deDupeArray(arr2).length).to.equal(4);
});
+
+ it("msngr.immediate() - works just like setTimeout(fn, 0)", function(done) {
+ msngr.immediate(function() {
+ done();
+ });
+ });
});
diff --git a/src/utils/misc.bench.js b/src/utils/misc.bench.js
new file mode 100644
index 0000000..3cebd8b
--- /dev/null
+++ b/src/utils/misc.bench.js
@@ -0,0 +1,45 @@
+if (typeof chai === "undefined" && typeof window === "undefined") {
+ var benchmark = require("benchmark");
+}
+
+if (typeof msngr === "undefined" && typeof window === "undefined") {
+ var msngr = require("../../msngr");
+}
+
+var exporter;
+if (msngr.isBrowser()) {
+ window.benchmarkers = window.benchmarkers || [];
+ exporter = function(fn) {
+ window.benchmarkers.push(fn);
+ };
+} else {
+ exporter = function(fn) {
+ module.exports = fn;
+ };
+}
+
+exporter(function(done) {
+ "use strict";
+ var bench = new benchmark.Suite;
+
+ console.log("Benchmarks starting for misc utils methods");
+ bench.add("1x immediate()", {
+ defer: true,
+ fn: function(deferred) {
+ msngr.immediate(function() {
+ deferred.resolve();
+ });
+ }
+ });
+
+ bench.on("cycle", function(event) {
+ console.log(String(event.target));
+ });
+
+ bench.on("complete", function() {
+ console.log("Benchmarks complete for misc utils methods");
+ done(null);
+ });
+
+ bench.run({ 'async': true });
+});
diff --git a/src/utils/misc.cspec.js b/src/utils/misc.cspec.js
new file mode 100644
index 0000000..9accf9b
--- /dev/null
+++ b/src/utils/misc.cspec.js
@@ -0,0 +1,19 @@
+if (typeof chai === "undefined" && typeof window === "undefined") {
+ var chai = require("chai");
+}
+
+if (typeof expect === "undefined") {
+ var expect = chai.expect;
+}
+
+if (typeof msngr === "undefined" && typeof window === "undefined") {
+ var msngr = require("../../msngr");
+}
+
+describe("./utils/misc.js", function() {
+ "use strict";
+
+ it("msngr.isBrowser() - returns true when running in the browser", function() {
+ expect(msngr.isBrowser()).to.equal(true);
+ });
+});
diff --git a/src/utils/misc.js b/src/utils/misc.js
index da7cbfc..f68f1fe 100644
--- a/src/utils/misc.js
+++ b/src/utils/misc.js
@@ -1,6 +1,24 @@
msngr.extend((function(external, internal) {
"use strict";
+ // This chunk of code is only for the browser as a setImmediate workaround
+ if (typeof window !== "undefined" && typeof window.postMessage !== "undefined") {
+ external.config("immediate", {
+ channel: "__msngr_immediate"
+ });
+
+ var immediateQueue = [];
+
+ window.addEventListener("message", function(event) {
+ if (event.source === window && event.data === internal.config["immediate"].channel) {
+ event.stopPropagation();
+ if (immediateQueue.length > 0) {
+ immediateQueue.shift()();
+ }
+ }
+ }, true);
+ }
+
var nowPerformance = function() {
return performance.now();
};
@@ -10,12 +28,14 @@ msngr.extend((function(external, internal) {
};
var nowLegacy = function() {
- return (new Date).getTime();
+ return Date.now();
};
var nowExec = undefined;
var nowExecDebugLabel = "";
var lastNow = undefined;
+ var isBrowserCached;
+ var immediateFn;
return {
id: function() {
@@ -57,7 +77,7 @@ msngr.extend((function(external, internal) {
}
arr.pop();
},
- deDupeArray: function (arr) {
+ deDupeArray: function(arr) {
var hash = { };
var result = [];
var arrLength = arr.length;
@@ -69,6 +89,29 @@ msngr.extend((function(external, internal) {
}
return result;
+ },
+ isBrowser: function() {
+ if (isBrowserCached === undefined) {
+ isBrowserCached = (typeof XMLHttpRequest !== "undefined");
+ }
+ return isBrowserCached;
+ },
+ immediate: function(fn) {
+ if (immediateFn === undefined) {
+ if (typeof setImmediate !== "undefined") {
+ immediateFn = setImmediate;
+ } else if (typeof window !== "undefined" && typeof window.postMessage !== "undefined") {
+ immediateFn = function(f) {
+ immediateQueue.push(f);
+ window.postMessage(internal.config["immediate"].channel, "*");
+ };
+ } else {
+ immediateFn = function(f) {
+ setTimeout(f, 0);
+ };
+ }
+ }
+ immediateFn(fn);
}
};
}));
diff --git a/src/utils/misc.nspec.js b/src/utils/misc.nspec.js
new file mode 100644
index 0000000..ca768cf
--- /dev/null
+++ b/src/utils/misc.nspec.js
@@ -0,0 +1,19 @@
+if (typeof chai === "undefined" && typeof window === "undefined") {
+ var chai = require("chai");
+}
+
+if (typeof expect === "undefined") {
+ var expect = chai.expect;
+}
+
+if (typeof msngr === "undefined" && typeof window === "undefined") {
+ var msngr = require("../../msngr");
+}
+
+describe("./utils/misc.js", function() {
+ "use strict";
+
+ it("msngr.isBrowser() - returns false when running in node", function() {
+ expect(msngr.isBrowser()).to.equal(false);
+ });
+});
diff --git a/src/utils/validation.aspec.js b/src/utils/validation.aspec.js
index 44bd649..b103bfb 100644
--- a/src/utils/validation.aspec.js
+++ b/src/utils/validation.aspec.js
@@ -480,13 +480,6 @@ describe("./utils/validation.js", function() {
expect(msngr.isEmptyString(new Date())).to.equal(false);
});
- // hasWildCard(str)
- it("msngr.hasWildCard(str)", function() {
- expect(msngr.hasWildCard("whatever")).to.equal(false);
- expect(msngr.hasWildCard("")).to.equal(false);
- expect(msngr.hasWildCard("what*")).to.equal(true);
- });
-
// reiterativeValidation(func, inputs)
it("msngr.internal.reiterativeValidation(func, inputs) - func is undefined", function() {
expect(msngr.internal.reiterativeValidation(undefined, [true, false, 15, "534"])).to.equal(false);
diff --git a/src/utils/validation.js b/src/utils/validation.js
index 0b826ad..f386c17 100644
--- a/src/utils/validation.js
+++ b/src/utils/validation.js
@@ -84,9 +84,6 @@ msngr.extend((function(external, internal) {
},
areEmptyStrings: function() {
return internal.reiterativeValidation(external.isEmptyString, external.argumentsToArray(arguments));
- },
- hasWildCard: function(str) {
- return (str.indexOf("*") !== -1);
}
};
}));