hyper+json client tools for angular.js
Component:
$ component install hypergroup/ng-hyperAdd ng-hyper as a dependency of your angular app.
angular.module('my-app', ['ng-hyper']);$ npm install
$ make buildBy default, tests are run against AngularJS 1.0.8.
$ make testsTo switch AngularJS version, override the NG_VERSION variable with a different value, e.g.
$ make tests NG_VERSION=1.1.0hyper binds the value at the path to the scope. The name of the bound value defaults to the last property in the path.
<div data-hyper=".users">
<!-- 'users' is now available in this scope -->
</div>If a name other than the last property in the path is needed use as.
<div data-hyper=".users as usersList">
<!-- 'usersList' is now available in this scope -->
</div>hyper should always be used when passing data into an angular directive.
<div data-hyper=".account">
<!--
BAD!!!
names.first may be in the reponse right now but the server may change it in the future!
-->
<span data-ng-bind="account.names.first"></span>
<!--
GOOD!!!
by using the data provided by 'hyper' the client will be future proof
-->
<span data-hyper="account.names.first" data-ng-bind="first"></span>
</div>hyper directives may be nested as needed.
<ul data-hyper=".users">
<li data-ng-repeat="user in users">
<span data-hyper="user.name" data-ng-bind="name"></span>
<span>likes</span>
<ul class="likes" data-hyper="user.likes">
<li data-ng-repeat="like in likes">
<span data-hyper="like.name" data-ng-bind="name"></span>
</li>
</ul>
</li>
</ul>Multiple values may be bound on a single element with a comma separated list.
<div data-hyper=".users, .posts, .status as currentStatus">
<!-- 'users', 'posts' and 'currentStatus' are now available in this scope -->
</div>Because it becomes very verbose to use hyper before passing data to non-hyper directives ng-hyper provides some additional directives.
Instead of using a combination of hyper and ng-bind use hyper-bind instead.
<div data-hyper=".account">
<!-- more verbose -->
<span data-hyper="account.name" data-ng-bind="name"></span>
<!-- less verbose -->
<span data-hyper-bind="account.name"></span>
</div>TODO
<ul data-hyper=".users">
<li data-ng-repeat="user in users">
<a data-hyper-link="/users/:user" data-hyper-bind="user.name"></a>
</li>
</ul>TODO
<div data-hyper=".account">
<form data-hyper-form="account">
<!-- 'inputs' is now available in the scope -->
</form>
</div>TODO
<div data-hyper=".account">
<form data-hyper-form="account">
<div data-ng-repeat="input in inputs">
<input data-hyper-input="input" />
</div>
</form>
</div>Instead of using a combination of hyper and ng-src use hyper-img instead.
<div data-hyper=".account">
<!-- more verbose -->
<img data-hyper="account.image.src" data-ng-src="{{src}}"></img>
<!-- less verbose -->
<img data-hyper-img="account.image"></img>
</div>TODO
<div data-hyper-redirect="/users/:.account"></div>ng-hyper uses the hypermedia type hyper+json. It is recommended reading the spec before using ng-hyper.
This toolset can most accurately be thought of as a 'hypermedia transformer' that takes one hypermedia format (hyper+json) and converts into another (HTML), similar to XSLT (only much simpler).
ng-hyper makes it easy to consume hyper+json apis by wrapping traversal of links and properties in a consistent syntax. The path syntax is represented by the 'dot notation'.
Consider the following set of resources:
{
"href": "/",
"status": "ok",
"users": {
"href": "/users"
}
}
{
"href": "/users",
"collection": [
{"href": "/users/1"},
{"href": "/users/2"},
{"href": "/users/3"}
]
}
{
"href": "/users/1",
"name": "Cameron"
}
{
"href": "/users/2",
"name": "Mike"
}
{
"href": "/users/3",
"name": "Tim"
}Values are easily accessed as properties and links are traversed:
.status
// 'ok'
.users
// [{href: ...}, ...]
.users.0.name
// 'Cameron'
.users.1.name
// 'Mike'
Paths starting with . signify starting at the root document. If a path uses a resource as its context, paths without .s refers to the given context.
Consider the following resources:
{
"href": "/users/1",
"name": "Cameron",
"friends": [
{"href": "/users/2"},
{"href": "/users/3"}
]
}
{
"href": "/users/2",
"name": "Mike"
}
{
"href": "/users/3",
"name": "Tim"
}Using /users/1 as the context resource the following paths will yield the proceeding results:
name
// 'Cameron'
friends
// [{href: ...}, ...]
friends.0.name
// Mike
friends.1.name
// Tim
Because the syntax is unaware of links it makes it easy to embed/extract resources from parent resources.
Consider the following resource:
{
"href": "/",
"likes": [
{"name": "hot-dogs"},
{"name": "toasters"},
{"name": "spoons"}
]
}At the moment this resource is a standalone resource. The list of likes can be accessed with:
likes
// [{'name': 'hot-dogs'}, ...]
likes.0.name
// 'hot-dogs'
likes.1.name
// 'toasters'
Suppose the server has decided to split out the likes into their own resources, to make is easier to count the number of total likes of a particular item. This would result in the following resources:
{
"href": "/",
"likes": [
{"href": "/likes/hot-dogs"},
{"href": "/likes/toasters"},
{"href": "/likes/spoons"}
]
}
{
"href": "/likes/hot-dogs",
"count": 3
}
{
"href": "/likes/toasters",
"count": 9
}
{
"href": "/likes/spoons",
"count": 4
}The previously used paths continue to work because ng-hyper transparently follows links until it finds the property.
likes
// [{'name': 'hot-dogs'}, ...]
likes.0.name
// 'hot-dogs'
likes.0.count
// 3
likes.1.name
// 'toasters'
Now suppose the server decided, for caching reasons, it would be better to split the collection of likes from the root resource into its own:
{
"href": "/",
"likes": {
"href": "/likes"
}
}
{
"href": "/likes",
"collection": [
{"href": "/likes/hot-dogs"},
{"href": "/likes/toasters"},
{"href": "/likes/spoons"}
]
}
{
"href": "/likes/hot-dogs",
"count": 3
}
{
"href": "/likes/toasters",
"count": 9
}
{
"href": "/likes/spoons",
"count": 4
}As before, the client will continue to work.