hyper+json client tools for angular.js
Component:
$ component install hypergroup/ng-hyper
Add ng-hyper
as a dependency of your angular app.
angular.module('my-app', ['ng-hyper']);
$ npm install
$ make build
By default, tests are run against AngularJS 1.0.8.
$ make tests
To switch AngularJS version, override the NG_VERSION variable with a different value, e.g.
$ make tests NG_VERSION=1.1.0
hyper
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.