Skip to content

Commit

Permalink
Merge pull request #27 from KrisSiegel/2.0.0
Browse files Browse the repository at this point in the history
Initial PR for 2.0.0
  • Loading branch information
KrisSiegel committed May 31, 2015
2 parents 8a57f80 + efdbc4c commit eb983a8
Show file tree
Hide file tree
Showing 53 changed files with 4,580 additions and 3,111 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ node_modules
# msngr.js specific
specRunner.html
specRunner.min.html
crossWindowVerifier.html
crossWindowVerifier.min.html
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ node_js:
- "0.12"
- "0.11"
- "0.10"
- "iojs"
before_install: npm install -g grunt-cli mocha-phantomjs phantomjs
42 changes: 27 additions & 15 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ module.exports = (function (grunt) {
var paths = [
"src/main.js",
"src/utils/*.js",
"src/builders/*.js",
"src/store/*.js",
"src/objects/*.js",
"src/messengers/*.js",
"src/actions/*.js",
"src/options/*.js",
"src/module.exports.js",
"!**/*.aspec.js",
"!**/*.cspec.js",
Expand Down Expand Up @@ -83,11 +83,11 @@ module.exports = (function (grunt) {
var pkg = grunt.file.readJSON('package.json');

var main = fs.readFileSync("src/main.js", { encoding: "utf8" });
var indexOfVersion = main.indexOf("version: ");
var indexOfNextComma = main.indexOf(",", indexOfVersion);
var indexOfVersion = main.indexOf("external.version = ");
var indexOfNextSemiColon = main.indexOf(";", indexOfVersion);
var ified = main.substring(0, indexOfVersion);
ified = ified + "version: \"" + pkg.version + "\"";
ified = ified + main.substring(indexOfNextComma, main.length);
ified = ified + "external.version = \"" + pkg.version + "\"";
ified = ified + main.substring(indexOfNextSemiColon, main.length);

fs.writeFileSync("src/main.js", ified, { encoding: "utf8" });
});
Expand Down Expand Up @@ -122,21 +122,33 @@ module.exports = (function (grunt) {
};
var fs = require("fs");
var path = require("path");
var tests = [];
var dirs = fs.readdirSync("./src/");

for (var i = 0; i < dirs.length; ++i) {
if (fs.statSync("./src/" + dirs[i]).isDirectory()) {
var files = fs.readdirSync("./src/" + dirs[i]);
for (var j = 0; j < files.length; ++j) {
tests.push(path.join("./", "./src/", dirs[i], files[j]));
var tests = [];
var testPaths = ["./", "./src/", "./docs/"];

for (var k = 0; k < testPaths.length; ++k) {
var dirs = fs.readdirSync(testPaths[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]);
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);
}
}
} else {
var p = path.join("./", testPaths[k], dirs[i]);
if (tests.indexOf(p) === -1) {
tests.push(p);
}
}
} else {
tests.push(path.join("./", "./src/", dirs[i]));
}
}

var scriptHtml = "";

if (tests !== undefined && tests.length > 0) {
var file = tests.shift();
while (tests.length > 0) {
Expand Down
62 changes: 62 additions & 0 deletions README.cspec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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);
});
});
178 changes: 36 additions & 142 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,168 +1,62 @@
# msngr.js
[![npm version](https://badge.fury.io/js/msngr.svg)](http://badge.fury.io/js/msngr) [![Bower version](https://badge.fury.io/bo/msngr.js.svg)](http://badge.fury.io/bo/msngr.js) [![Build Status](https://travis-ci.org/KrisSiegel/msngr.js.svg)](https://travis-ci.org/KrisSiegel/msngr.js/) [![Dependency Status](https://gemnasium.com/KrisSiegel/msngr.js.svg)](https://gemnasium.com/KrisSiegel/msngr.js)
[![npm version](https://badge.fury.io/js/msngr.svg)](http://badge.fury.io/js/msngr) [![Bower version](https://badge.fury.io/bo/msngr.js.svg)](http://badge.fury.io/bo/msngr.js) [![Build Status](https://travis-ci.org/KrisSiegel/msngr.js.svg)](https://travis-ci.org/KrisSiegel/msngr.js/)

msngr.js is a small library to manage messages between components with the goal of isolating business logic from a user interface or server framework. For example messages can be bound directly to DOM elements and activities can gather values allowing the handling of click events to know absolutely zero about the user interface itself.
## What is msngr.js?
msngr.js is a small library for facilitating communication between components through abstract messages within the same application be it server or client side. It also provides binding messages directly to DOM elements and even sending payloads between browser tabs / windows.

What does that mean, exactly? Read on. Want to just jump right in? Check out [this jsfiddle](http://jsfiddle.net/jnjaosfz/) page with msngr.js already included with a "Hello, World!" example.
The following example shows how to bind a message to a click event of a DOM element while gathering up the values in the related inputs for payload delivery.

### Quick note regarding upcoming 2.x release
The 2.0 branch of msngr is feature complete and includes a very different API than the current 1.x version. This was necessary to ensure consistency and an easier to use API. Additional testing, benchmarking and documentation need to be completed before 2.0 is fully ready for release at the end of May. If you'd like a preview take a look at the [2.0.0 branch](https://github.com/KrisSiegel/msngr.js/tree/2.0.0).

## Quick Start
Installation can occur via bower or npm (alternatively just download msngr.js or msngr.min.js).

```batch
bower install msngr.js
```

```batch
npm install msngr
```

Once installed simply include it into your project.
```html
<script type='text/javascript' src="msngr.min.js"></script>
```

```javascript
var msngr = require("msngr");
```

Now you're ready to start messaging!

### Binding messages to DOM elements
One of the most handy things msngr.js can do is bind a message directly to a DOM element. Let's look at the following example.

index.html
```html
<input type="text" name="Name" />
<br />
<input type="text" name="Email" />
<br />
<input type="submit" name="Save" />
```HTML
<input type="text" name="Username" value="Kris" />
<input type="password" name="Password" value="hunter2" />
<button>Submit</button>
```

userinterface.js
```javascript
msngr.bind("input[type=submit]", "click", "Profile", "Save");
msngr("User", "Save")
.bind("button", "click")
.option("dom", ["input"])
.on(function (payload) {
console.log(payload.Username); // Prints "Kris"
console.log(payload.Password); // Prints "hunter2"
});
```

business.js
```javascript
msngr.on("Profile", "Save", function () {
console.log("The profile has been saved!");
});
```
## Getting msngr.js
If you want to use msngr.js on the server-side via npm simply install it via ```npm install msngr```.

As you can see ```msngr.bind()``` can accept typical selectors, an event and then a message which can be made up of a topic, category and dataType (omit the ones you do not want to use).
If you want to use it within a web browser then either install via ```bower install msngr``` or manually download msngr.js or msngr.min.js file(s) from this repository.

So this is cool, the UI and the frontend can be separated, right? Well how do you get those name and email values? Put DOM accessing code in business.js? Heck no! You can use the DOM activity to specify what values should be grabbed.
## Would you like to know more?
While msngr.js isn't very large the documentation has been split up for easy reading.

index.html
```html
<input type="text" name="Name" />
<br />
<input type="text" name="Email" />
<br />
<input type="submit" name="Save" />
```
[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.

userinterface.js
```javascript
msngr.bind("input[type=submit]", "click", {
topic: "Profile",
category: "Save",
dom: ["input[name=Name]", "input[name=Email]"]}
);
```
[Messaging patterns](docs/messaging patterns.md) - Explains how to use the basic messaging features of msngr.js with some typical patterns.

business.js
```javascript
msngr.on("Profile", "Save", function (payload) {
console.log(payload.Name);
console.log(payload.Email);
});
```
[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.

Now the payload will include an object with the values of each input specified. You can even simplify the selector even more to get the same data back like so:

```javascript
msngr.bind("input[type=submit]", "click", {
topic: "Profile",
category: "Save",
dom: ["input"]
});
```

Aggregated values are always stored with their name as the key. If the name doesn't exist then it uses an id. Should an id not exist then it defaults to tagname + count (so "input0", "input1");

### What about JavaScript that doesn't touch the DOM?
So msngr.js can also be used outside of situations that involve the DOM and be just as handy! A common example is abstracting away a specific library through the use of messages. An example is outlined below.

elasticsearch.js
```javascript
msngr.on("Profile", "Save", "application/json", function (payload) {
// Save profile object into ElasticSearch
});
```

business.js
```javascript
msngr.emit("Profile", "Save", "application/json", profile);
```

So in the example above we can save a Profile object without actually knowing who or what is going to save this. This let's us to, later on, use a configuration to allow alternative data stores such as a Mock store without changing the business.js file. So that may look like:

mock.js
```javascript
msngr.on("Profile", "Save", "application/json", function (payload) {
// Save profile object into a mock data store
});
```

### So what are activities anyway?
An activity is simply a registered method, executed synchronously, designed to be called before payload delivery should any properties within the message object match the registered method's property.

For example the built-in DOM activity is registered with the 'dom' property. Therefore anytime someone emits a message with the 'dom' property the registered method is called before being delivered. This allows extending msngr.js in various ways without changing any method signatures.

For instance if you want to create an activity that added two numbers together.

```javascript
msngr.action("add", function (message, wrap) {
wrap.payload.result = wrap.payload.number1 + wrap.payload.number2;
});

msngr.on("Addition", function (payload) {
console.log(payload.result); // Outputs 7
});

msngr.emit({ topic: "Addition", add: { } }, { number1: 5, number2: 2 });
```
[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 :)

Note that what is typically supplied to the action's property in the message object is any related options the particular action may need. In this add example nothing was necessary so an empty object was passed in but for others, such as the dom action, you would pass in an array of html selectors.
[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 :)

## API
Below are the methods exposed via the msngr object, their parameters and return values.
## 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!

#### msngr.on(message, function) / msngr.on(topic, category, dataType, function)
This method accepts a JavaScript object with the properties "topic", "category" and "dataType" or simply fill in the first 3 parameters with those values. The function is a callback that's executed when a matching message is received and provides a payload parameter.
### What's Next?
Below is what's being worked on for future 2.x releases.

#### msngr.emit(message, payload) / msngr.emit(topic, category, dataType, payload)
This method sends a message by either providing a JavaScript object with the properties "topic", "category" and "dataType" or by simply entering each values as a parameter. The payload can be anything you want to send to a receiving callback.
* Web Socket Messaging - Easy web socket communication with messages between a server and client.

#### msngr.drop(message, handler) / msngr.drop(topic, category, dataType, handler)
This method removes a message from being executed. Specify the handler to remove or drop all handlers by specifying undefined.
* 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).

#### msngr.bind(element, event, message) / msngr.bind(element, event, topic, category, dataType)
This method takes an HTML element (can be an element or selector), an event and a message then binds all 3 together. When the specified event occurs on the element the message will be emitted. Optionally add the 'dom' property to the message object to supply selectors you wish msngr would gather values from and return in the payload.
* 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.

#### msngr.unbind(element, event) / msngr.unbind(element, event)
This method stops an element's event from emitting a previously bound message.
* 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.

#### msngr.action(property, function)
This method provides a way of extending msngr. The property is anything (except for 'topic', 'category' or 'dataType') that is supplied within a message object. If a message is emitted with a matching property the function is called with the message and an object that allows stopping the emitting entirely (via calling ```obj.preventDefault()```) or modifying the payload itself.
### 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.

#### msngr.inaction(property)
This method removes the action being called on a property.
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
32 changes: 32 additions & 0 deletions crossWindowVerifier.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<html>
<head>
<title>Cross-Window Verifier</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type='text/javascript' src="msngr.js"></script>
<script type="text/javascript">
var msg = msngr("CrossWindow", "Message");
msg.option("cross-window");

// Let's do this craziness to ensure they are sent in crazy orders
// but still received and operated on as expected.
for (var i = 0; i < 100; ++i) {
(function (m, index) {
var rand = Math.floor(Math.random() * (25 - 0 + 1)) + 0;
setTimeout(function () {
m.emit({
id: index,
data: {
meaningOfLife: 42,
OPDelivers: false,
text: "something"
}
});
}, rand);
}(msg, i));
}
</script>
</head>
<body>

</body>
</html>
Loading

0 comments on commit eb983a8

Please sign in to comment.