Git Product home page Git Product logo

backbone.siren's Introduction

Build Status Coverage Status

Backbone.Siren

A client side adapter that converts resource representations from Siren JSON to Backbone Models and Collections.

Basic Use

To use Backbone.Siren:

bbSirenModel = new Backbone.Siren.Model(sirenObject);
// or
bbSirenCollection = new Backbone.Siren.Collection(sirenObject);

Or, you can just point Backbone.Siren to a url that returns a Siren resource and let it do the rest:

Backbone.Siren.resolve('http://my.api.io/user/123');

If you're building an app that uses a Siren API on the backend:

var sirenApi = new Backbone.Siren('http://my.api.io');

// Request particular endpoints.  In this case `http://my.api.io/user/123` and `http://my.api.io/basket/111`
sirenApi.resolve('user/123').done(function (userModel) {});
sirenApi.resolve('basket/111').done(function (basketModel) {});

// Or
userModel = sirenApi.resolve(['user/123', 'basket/111']).done(function (userModel, basketModel) {});

Example

var sirenApi = new Backbone.Siren('http://my.api.io');

var UserView = Backbone.View.extend({

	template: _.template(...)

	, render: function() {
		this.$el.html(this.template(this.model.attributes));
		return this;
	}

	, initialize: function () {
		var self = this;

		sirenApi.resolve('user/123').done(function (userModel) {
			self.model  = userModel;
			self.render();
		});
	}
});

var userView = new UserView();

Options

{
    autoFetch: 'linked'   // Will automatically fetch sub-entities if enabled. Can be set to 'linked' or 'all'.
    , forceFetch: false  //  @todo
}

Backbone.Siren.FormView

Siren allows you to declare what "actions" your can be taken on a model. Backbone.Siren provides a simple way to render a form for one of these actions.

Backbone.Siren.FormView will generate a default form if passed a bbSiren Model (does not work with bbSiren Collections).

The methods .template() and .render() can be overwritten to customize the look of your form. You can also overwrite the .handleFormSubmit() and .formElementChange() methods which will, by default, submit the form and set model attributes respectively.

FormView Example

Generates a form that will execute the updateUser action when the .submitButton is clicked.

var UpdateUserFormView = Backbone.Siren.FormView.extend({

	event: {
        'click .submitButton': this.action.execute()
	}

	, initialize: function () {
        var self = this
        , updateUserAction = accountModel.getActionByName('updateUser');

	    qi.sirenApi.resolve('user/123').done(function (userModel) {
			self.initializeForm({action: updateUserAction});
			self.render();
        });
	}
});

var updateUserFormView = new UpdateUserFormView();

FormView Options

{
    action: bbSirenAction       // Required, a bbSiren Action.
    , validateOnChange: true    // Optional, Whether or not to validate ui changes.  Default is true
    , formAttributes: {}        // Optional, A mapping of html attribute properties to their values
    , fieldAttributes: {}       // Optional, A mapping of field names to html attribute properties
}

Changelog

0.3.0

  • .resolve() now accepts an array of urls (#21)
  • You can now have multiple store instances
  • new Backbone.Siren() will now create a new API client instance with its own store (#22)
  • Collections and partial entities are now cached in the store (#19 and #35)
  • Request promises are now removed from the store once a request is fulfilled. (#35)
  • Entity filtering via .entities() is now done using class instead of classname
  • dropped the ability to filter links by rel when calling .links()
  • dropped the ability filter actions when calling .actions()
  • Improved test coverage
  • Other bug fixes

Bitdeli Badge

backbone.siren's People

Contributors

abhishekbh avatar apsoto avatar aswintyv avatar coderintherye avatar emuvente avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

backbone.siren's Issues

Bug in implementation of Collection.prototype.filter

bbSiren's implementation of .filter() calls underscore's .filter(), however, it fails to pass along an array of models as the first parameter such that the callback can iterate over each model, with each model being the first argument to the callback.

Handling multiple rels

Calling entity.rel() will only return the first rel in the entities rel array.

Related to issue #4 :
We use .rel() internally for naming a sub-entity.

I don't have any use-cases that uses multiple rels so this works fine for me so far but this behavior will probably need to be revisited at some point.

dev environment setup

Hey There,

Starting to use this in my project. I've never developed in node, so I'm a bit lost on how to get a dev environment 'up'.

I did the npm install, but I'm getting errors:

> [email protected] prepublish /Users/asoto/projects/github/apsoto/backbone.siren
> bower install

sh: bower: command not found
npm ERR! [email protected] prepublish: `bower install`
npm ERR! `sh "-c" "bower install"` failed with 127
npm ERR!
npm ERR! Failed at the [email protected] prepublish script.
npm ERR! This is most likely a problem with the backbone.siren package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     bower install
npm ERR! You can get their info via:
npm ERR!     npm owner ls backbone.siren
npm ERR! There is likely additional logging output above.

npm ERR! System Darwin 12.2.1
npm ERR! command "/opt/local/bin/node" "/opt/local/bin/npm" "install"
npm ERR! cwd /Users/asoto/projects/github/apsoto/backbone.siren
npm ERR! node -v v0.10.0
npm ERR! npm -v 1.2.14
npm ERR! code ELIFECYCLE

npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR!     /Users/asoto/projects/github/apsoto/backbone.siren/npm-debug.log
npm ERR! not ok code 0

Any tips would be appreciated.

Override backbone .fetch() with current .resolve() implementation

I think I was initially a bit uneasy about the design and implementation of backbone.siren and so I didn't override the backbone.model/collection.prototype.fetch() method, instead opting to create a .resolve() method to wrap it.

This leads to problems when integrating with 3rd party libraries that use .fetch, thus bypassing .resolve().

Essentially, this ticket is to rename .resolve() to .fetch()

Rewrite of the store

We currently store:

  • models
  • ajax requests for models and collections in the form of deferred objects

Storing ajax requests allows us to:
* avoid multiple requests to a resource that's already been requested but is still pending
* as a side effect, allows us to cache collections via the deferred object of its request.
* unfortunately, it also complicates things by adding another layer of caching to have to deal with

  • Models are stored when they are "parsed" via the "parse" method
  • Ajax requests are stored via the Backbone.Siren.resolve() method

Bugs:

  • There is a problem with overriding previously stored models in the store by the same url - instead existing models of the same url should be "updated".
  • We need to make sure all linked entities are updated to fully hydrated models when a fully hydrated model loads (#19)

Features/Notes:

  • We'll probably need to add some kind of collection cache, currently, I imagine something that uses some kind of "type" to keep, add to, and persist collections on the client.
  • We'll need to keep differentiating between "nested entities" and "linked entities". (currently, we just don't store "linked entities" and this may or may not be good enough)
  • Might need to use a "queue" instead of caching ajax requests...?

Provide deep linking down the entity chain

Following up on Kevin's comments in this thread: #15

...and as mentioned in this twitter thread: https://twitter.com/dhh/status/281779048978472960 and in this post: http://37signals.com/svn/posts/3373-getting-hyper-about-hypermedia-apis

We need some kind of deep linking.

I still need to figure out how to define the api for this but the idea is you should be able to do:

getSomeEntity('baseEntity#nestedEntityName#anotherNestedEntityName');

The idea behind the '#' is to be somewhat similar to css selectors. Eventually, if we want to support selecting by "class" we might want to do something like:

getSomeEntity('baseEntity.someClassName');

...just for now...yet to see how it can work out...

Should syncing be limited only to defined actions?

When it comes to syncing I think we will have to probably override the default sync method. One outstanding design question is whether it should limit 'sync' to only be via the defined actions on the resource instead of the standard Rails REST/CRUD that backbone uses. From a purist standpoint, I say yes, limit syncing to only the defined actions, but I can see that causing lots of boilerplate actions on the standard api responses.

Field validation

This is another area that's not specified by Siren. However, it seems some simple html5-like validation can be used in an action's field object and this could take care of the 80% case:

{
   actions: [
      {
         "name": ["edit"]
         , "href": "x.io/edit"
         , "fields": {
            { "name": "firstName", "type": "text", "maxlength": 50}
            , { "email": "[email protected]", "type": "email"}
         }
      }
   ]
}

Because people may implement validation differently, this may need to be a separate module (eventually)

Figure out a way to use a defined Model class when resolving entities

I noticed that I added a method to a model class, but when I reference the model via a sub-entity it's a generic Backbone.Siren.Model, so none of the methods I defined are accessible.

I'm not sure how this can be determined unless there's some rel -> class mapping or it's inferred via the class attribute of an entity.

So far it hasn't been a killer for me, but thought I'd document this issue as a nice to have.

Updating a collection from the server results in duplicate models

Say you have a collection (x.io/my/collection) and it has 4 models and it's already loaded on your client.

If you update the collection:

siren.resolve('x.io/my/collection', {forceFetch: true})

Even if the server still sends over the same 4 models, they will get appended to the collection instead of being merged in. The result is duplicate models for a collection size of 8.

Provide for different instances of Backbone.Siren

Use cases:

  • An application will be able to have multiple instances of a Siren API

Each instance will be able to have its own:

  • store
  • pre-set host so you can call mySirenApi.resolve('entityName') without having to retype the entire url

Linked entities are not being updated to fully hydrated models

Say you have a linked entity:

myModel.get('myLinkedEntity');

If code, in some other part of the app, loads the fully hydrated version of the linked entity, the one referenced in the myModel object should also show the updated data.

However, this is currently not the case.

Currently there are times that the store will show the fully hydrated model, but for the "myModel" to still only show model representation of the linked entity.

Add streaming support

backbone.siren currently provides a clean wrapper for getting entities from a server to the client. Would be great if it also provided a clean way for a server to push entities to the client.

Add deferred resolves

There are a currently a few different tools for "resolving" an entity. The most straightforward being Backbone.Siren.resolve(urlOrChain, options).

All of the current options, however, create a model or collection for you.

There are times when you need to create the model/collection ahead of time and defer the actual fetching of data till later or to another part of your app. (see this SO response http://stackoverflow.com/a/15108001 ).

Caching collections

Collections do not get cached. It would be useful, however if we could cache them to the extent that anytime a new Model that "qualfies" as a member of the collection gets automatically added to said collection.

Set default options for all requests to the Siren endpoints

If there are options that should be set on every ajax request to the Siren endpoints, I should be able to just set them once and have them always get sent.

example:

sirenApi = new Backbone.Siren('http://api.io/root', {myOption: 'foo'});

// Now every time I request an entity endpoint, this option should be set
sirenApi.resolve('someEntity') // Will set myOption = 'foo';

V2, Update Docs + Issues

Many of the issues have been resolved in one way or another, they should be closed and documentation needs to be updated.

Removing models from the store

There should be an api method for removing models from the store.

Use case: When you destroy a model that memory should be freed up.

Referencing an entities sub-entities

Some additional thinking is needed around what name to use when referencing sub-entities from within an entity.

The current solution is to use the "rel" attribute but strip off everything before the last slash. However, this has some limitations since a rel is a "global" relationship definition and identifying a sub-entity should, ideally, be local to the given entity.

Given the example provided on the Siren readme:

I would want to do:

order.get('items')

and not:

order.get('order-items')

Validating nested entities

How would we want to validate nested entities?

On .set():
This ones easy, just use the given validation method.

On .save():
This ones seems a bit trickier since we are saving the parent entity and don't have a direct reference to which sub-entity action to use. Would it be overly hacky to do something like

fields: [
   { "name": "address", "type": "entity", "action": "edit"}
]

Where address is a nested entity and the "action" is the name of the sub-entities action we want to use for validiting the given sub-entity.

I still haven't confronted this issue so I'm kinda taking a shot in the dark, however, .set() and .save() should be common use-cases.

.request() should use the store

the .request() method should check in the store if the object already exists. It should also handle adding items to the store.

Circular references within entities

In order to support circular dependencies the .toJSON() method should stop parsing an entity chain once it comes across an entity that's already been parsed.

For example, say you have the following entity chain:

user->orders->order->user

In this case .toJSON should know not to serialize the user a second time.

BbSiren to detect if link is an entity

Would be great if BbSiren could detect if a link is an entity (perhaps if the link has the same basePath as the BbSiren instance?)

Use case:

myModel.link({rel: 'me'}).fetch().done(function (meModel) {
});

using a linked sub-entity collection

I've been designing my API to use linked sub-entities for collections so the main response isn't too bloated.
For example:

{
...
entities:[
{
"rel": [ "follows", "collection" ],
"href": "http://api.wurl.dev/api/users/1/follows",
}
],
...
}

It looks like bbSiren assumes a sub-entity with rel 'collection' is a collection of full entity representations, so in my app the collection is empty?

I think that bbSiren should handle a 'collection' entity that is either full or linked. I'll problably implement that and send a pull request, but I wanted to get your input on what your use cases are like for collection sub-entites.

Improved object traversal (aka deep linking)

Following up on the deep linking feature proposed in #16.

This feature his has been super useful and while I haven't documented it yet, this is what the API currently looks like:

Backbone.Siren.resolve('baseEntity#nestedEntityName#anotherNestedEntityName');

I'd like to build on this by (in sequence):

  1. addressing the invalid url issue mentioned by @apsoto
  2. provide ability to fetch more than one entity at a time.
  3. provide ability to traverse into a collection entity

Item 1 can be addressed by splitting the url into a "url" and a "selector" (Also, calling it "deep linking" is not accurate, its probably better described as "traversal")

Backbone.Siren.resolve('baseEntity', '#nestedEntityName#anotherNestedEntityName');

Or, alternatively:

Backbone.Siren.resolve({
     entity: 'baseEntity'
     , selector: '#nestedEntityName#anotherNestedEntityName'
});

A server might even be able to support something like this via a query parameter.

Item 2 can be addressed by simply accepting an array of the entity resource urls and selectors

Item 3 can be done using js notation:

Backbone.Siren.resolve('baseEntity', '#nestedCollectionEntity[0]anotherNestedEntityName');

Siren allows properties and action fields to not match

Backbone.Siren expects property names and action field names to match. Based on this expectation, Backbone.Siren is able to map between the two.

However, Siren does not add this constraint.

For now, I'm tempted to continue requiring this constraint.

Say we didn't add this constraint, the implementor of the API would need to provide some kind of mapping to the client. In this case It would be simpler if the implementor added this mapping on the server side.

should complain when collections are not arrays

This is a bug in my API that I discovered when I returned only a direct entity instead an array with one item in the entities collection.

This ticket is just documenting that bbSiren should log an error when any of the collections (entities, links, actions) is NOT an array?

Backbone objects are not being updated

When a linked sub-entity is fetched from the server, the store is correctly updated with the new model/collection, however, the reference that exists in the parent model/collection is not being updated.

ex:

// The following is failing:
// Assuming myStuff self-url is "url/to/parent/myStuff"
store.get('url/to/parent').get('myStuff') === store.get('url/to/parent/myStuff') 

Representing Errors

Following up on this discussion: kevinswiber/siren#5

Since Siren does not provide a standard for representing errors we may want to do something super simple like checking for an "error" class on an entity and then just providing some tools for client side devs to plug-in and create their own error parsers...?

I'm also fine just using something like the sample provided near the bottom of the thread.

For the most part, however, I'm punting on this for now...

Update to Bower v1+

The new version of Bower has an updated API + it saves files in a different location. This is probably what's causing problems with Travis tests

Fix assumption that responseText will be JSON

The JSON.parse(jqXhr.responseText || '{}') line assumes responseText will be a json object. However, there is a chance it will not be (such as in some 404 responses).

Backbone.Siren.ajax(rootUrl, options)
                            .done(function (entity) {
                                var bbSiren = Backbone.Siren.parse(entity);
                                deferred.resolve(bbSiren);
                                handleRootRequestSuccess(bbSiren, chain, chainedDeferred, options);
                            })
                            .fail(function (jqXhr) {
                                var entity = JSON.parse(jqXhr.responseText || '{}')
                                , bbSiren = Backbone.Siren.parse(entity);

                                deferred.reject(bbSiren, jqXhr);
                                chainedDeferred.reject(bbSiren, jqXhr);
                            });

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.