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.
<divdata-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.
<divdata-hyper=".users as usersList"><!-- 'usersList' is now available in this scope --></div>hyper should always be used when passing data into an angular directive.
<divdata-hyper=".account"><!-- BAD!!! names.first may be in the reponse right now but the server may change it in the future! --><spandata-ng-bind="account.names.first"></span><!-- GOOD!!! by using the data provided by 'hyper' the client will be future proof --><spandata-hyper="account.names.first" data-ng-bind="first"></span></div>hyper directives may be nested as needed.
<uldata-hyper=".users"><lidata-ng-repeat="user in users"><spandata-hyper="user.name" data-ng-bind="name"></span><span>likes</span><ulclass="likes" data-hyper="user.likes"><lidata-ng-repeat="like in likes"><spandata-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.
<divdata-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.
<divdata-hyper=".account"><!-- more verbose --><spandata-hyper="account.name" data-ng-bind="name"></span><!-- less verbose --><spandata-hyper-bind="account.name"></span></div>TODO
<uldata-hyper=".users"><lidata-ng-repeat="user in users"><adata-hyper-link="/users/:user" data-hyper-bind="user.name"></a></li></ul>TODO
<divdata-hyper=".account"><formdata-hyper-form="account"><!-- 'inputs' is now available in the scope --></form></div>TODO
<divdata-hyper=".account"><formdata-hyper-form="account"><divdata-ng-repeat="input in inputs"><inputdata-hyper-input="input" /></div></form></div>Instead of using a combination of hyper and ng-src use hyper-img instead.
<divdata-hyper=".account"><!-- more verbose --><imgdata-hyper="account.image.src" data-ng-src="{{src}}"></img><!-- less verbose --><imgdata-hyper-img="account.image"></img></div>TODO
<divdata-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.