suwanny / can-connect

Model layer utilities for every JavaScript framework! Assemble real-time, high performance, restful data connections.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

@page can-connect @group can-connect.behaviors 1 Behaviors @group can-connect.modules 2 Modules @group can-connect.types 3 Data Types @outline 2

can-connect

Build Status

can-connect provides persisted data middleware. Use it to assemble powerful model layers for any JavaScript framework, not just CanJS. It currently can:

Load data:

  • [can-connect/data-url] - Persist data to restful or other types of services.
  • [can-connect/data-parse] - Extract response data into a format needed for other extensions.

Convert data into special types:

  • [can-connect/constructor] - Create instances of a constructor function or list type.
  • [can-connect/constructor/store] - Create only a single instance for a given id or a single list for a set.

Real time:

  • [can-connect/real-time] - Update lists and instances with server side events.

Caching strategies:

  • [can-connect/fall-through-cache] - Respond with data from the [connection.cacheConnection] and then update the response with data from the raw CRUD Methods.
  • [can-connect/data/inline-cache] - Use an inline cache for initial ajax requests.
  • [can-connect/cache-requests] - Save response data and use it for future requests.
  • [can-connect/data/combine-requests] - Combine overlapping or reduntant requests.

Caching layers:

  • [can-connect/data/localstorage-cache] - LocalStorage caching connection.
  • [can-connect/data/memory-cache] - LocalStorage caching connection.

The following modules glue certain methods together:

  • [can-connect/data/callbacks] - Glues the result of the raw CRUD Methods to callbacks.
  • [can-connect/data/callbacks-cache] - Calls [connection.cacheConnection] methods whenever raw CRUD methods are called.

The following modules are useful to CanJS specifically:

  • [can-connect/can/map] - Create instances of a special can.Map or can.List type.
  • [can-connect/can/super-map] - Create a connection for a can.Map or can.List that uses almost all the plugins.
  • [can-connect/can/model] - Inherit from a highly compatable can.Model implementation.
  • [can-connect/can/tag] - Create a custom element that can load data into a template.

Overview

The "can-connect" module exports a connect function that is used to assemble different behaviors and some options into a connection. For example, the following uses connect and the [can-connect/constructor] and [can-connect/data-url] behaviors to create a todoConnection connection:

import connect from "can-connect";
import "can-connect/constructor/";
import "can-connect/data/url/";

var todoConnection = connect(
  ["constructor","data-url"],
  {
    url: "/services/todos"
  });

A connection typically provides the ability to create, read, update, or delete (CRUD) some data source. That data source is usually accessed through the "Instance Interface" methods:

  • [connection.get]
  • [connection.getList]
  • [connection.save]
  • [connection.destroy]

For example, to get all todos from "GET /services/todos", we could write the following:

todoConnection.getList({}).then(function(todos){ ... });

Behaviors, like [can-connect/constructor] and [can-connect/data-url] implement, extend, or require some set of interfaces. For example, data-url implements the "Data Interface" methods, and [can-connect/constructor] implements the "Instance Interface" methods.

The connect method calls these behaviors in the right order to create a connection. For instance, the [can-connect/cache-requests] behavior must be applied after the [can-connect/data-url] connection. This is because [can-connect/cache-requests], overwrites [can-connect/data-url]'s [connection.getListData] first check a cache for the data. Only if the data is not present, does it call [can-connect/data-url]'s [connection.getListData]. So even if we write:

connect(['cache-requests','data-url'])

or

connect(['data-url','cache-requests'])

... our connection will be built in the right order!

A connection is just an object with each behavior object on its prototype chain and its options object at the end of the prototype chain.

Use

This section covers how to install can-connect and then the basics of its use.

Install

Use npm to install can-connect:

> npm install can-connect --save

Then, depending on your module loader, you'll do one of the following:

StealJS

Import can-connect and its behaviors like:

import connect from "can-connect";
import "can-connect/data/url/"

Browserify

require can-connect and its behaviors like:

var connect = require("can-connect");
import("can-connect/data/url/url");

AMD

Configure a package to can-connect and its dependency can-set:

require.config({
  packages: [{
    name: 'can-connect',
    location: 'node_modules/can-connect/dist/amd',
    main: 'can-connect'
  },{
    name: 'can-set',
    location: 'node_modules/can-connect/node_modules/can-set/dist/amd',
    main: 'src/set'
  }]
});

Then use define to load can-connect and its behaviors like:

define(["can-connect","can-connect/data/url/url"], function(connect){

});

Basic connection

To use can-connect, it's typically best to start out with the most basic behaviors: [can-connect/data-url] and [can-connect/constructor]. [can-connect/data-url] connects the "Data Interface" to a restful service. [can-connect/constructor] adds an "Instance Interface" that can create, read, update and delete (CRUD) typed data using the lower-level "Data Interface".

By typed data we mean data that is more than just plain JavaScript objects. For example, we might to create todo objects with a isComplete method:

var Todo = function(props){
  Object.assign(this, props);
};

Todo.prototype.isComplete = function(){
  return this.status === "complete";
};

And, we might want a special list type with a completed and active method:

var TodoList = function(todos){
  [].push.apply(this, todos);
};
TodoList.prototype = Object.create(Array.prototype);

TodoList.prototype.completed = function(){
  return this.filter(function(todo){
    return todo.status === "complete";
  });
};

TodoList.prototype.active = function(){
  return this.filter(function(todo){
    return todo.status !== "complete";
  });
};

We can create a connection that connects a restful "/api/todos" service to Todo instances and TodoList lists like:

var todoConnection = connect(["constructor","data-url"],{
  url: "/api/todos",
  list: function(listData, set){
  	return new TodoList(listData.data);
  },
  instance: function(props) {
  	return new Todo(props);
  }
});

And then use that connection to get a TodoList of Todos:

todoConnection.getList({}).then(function(todos){
	var todosEl = document.getElementById("todos-list");
	todosEl.innerHTML = "<h2>Active</h2>"+
		render(todos.active())+
		"<h2>Complete</h2>"+
		render(todos.completed());
});

var render = function(todos) {
	return "<ul>"+todos.map(function(todo){
		return "<li>"+todo.name+
				"<input type='checkbox' "+
				(todo.isComplete() ? "checked" : "")+"/></li>";
	}).join("")+"</ul>";
};

The following demo shows the result:

@demo can-connect/docs/demos/basics.html

This connection also lets you create, update, and destroy a Todo instance as follows:

var todo = new Todo({
  name: "take out trash"
})

// POSTs to /api/todos name=take out trash
// server returns {id: 5}
todoConnection.save( todo ).then(function(todo){
  todo.id //-> 5
  todo.name = 'take out garbage'
  
  // PUTs to /api/todos/5 name=take out garbage
  // server returns {id: 5, "take out garbage"}
  todoConnection.save( todo ).then( function(todo){
    
    // DELETEs to /api/todos/5
    // serer returns {}
    todoConnection.destroy( todo ).then( function(todo){
    
    });
    
  });
  
});

Configure behaviors

Whenever connect creates a connection, it always adds the [connect.base] behavior. This behavior defines configurable options that are used by almost every other behavior. For example, if your data uses an _id property to uniquely identify todos, you can specify this with [connect.base.idProp] like:

var todoConnection = connect(["constructor","data-url"],{
  url: "/api/todos",
  idProp: "_id"
});

Other behaviors list their configurable options in their own docs page.

Overwrite behaviors

If configurable options are not enough, you can overwrite any behavior with your own behavior.

For example, the constructors [can-connect/constructor.updatedInstance] behavior sets the instance's properties to match the result of [connection.updateData]. But if the PUT /api/todos/5 name=take out garbage request returned {}, the following would result in a todo with only an id property:

var todo = new Todo({id: 5, name: "take out garbage"})
// PUTs to /api/todos/5 name=take out garbage
// server returns {}
todoConnection.save( todo ).then( function(todo){
     
  todo.id //-> 5
  todo.name //-> undefined
});

The following overwrites the behavior of updateData:

var mergeDataBehavior = {
  updateData: function(instance, data){
    Object.assign(instance, data);
  }
}


var todoConnection = connect([
    "constructor",
    "data-url", 
    mergeDataBehavior
  ],{
  url: "/api/todos"
});

You can add your own behavior that overwrite all base behaviors by adding it to the end of the behaviors list.

CanJS use

If you are using CanJS, you can either:

  • use the [can-connect/can/map] behavior that overwrites many methods and settings to work with can.Map and can.List.
  • use the [can-connect/can/super-map] helper to create a connection that bundles "can/map" and many of the other extensions.

Using [can-connect/can/map] to create a connection looks like:

var Todo = can.Map.extend({ ... });
Todo.List = can.List.extend({Map: Todo},{});

var todoConnection = connect([
    "data-url",
    "can/map",
    "constructor",
    "constructor-store"
  ],{
  Map: Todo,
  url: "/todos"
});

When you bind on a Todo instance or Todo.List list, they will automatically call [can.connect/constructor-store.addInstanceReference] and [can.connect/constructor-store.addListReference].

Using [can-connect/can/super-map] to create a connection looks like:

var Todo = can.Map.extend({ ... });
Todo.List = can.List.extend({Map: Todo},{});

var todoConnection = superMap({
  Map: Todo,
  url: "/todos"
});

ReactJS use

Help us create a special ReactJS behavior that integrates a connection with React's observable life-cycle. Read more here.

Angular use

Help us create a special AngularJS behavior that integrates a connection with Angular's observable life-cycle. Read more here.

Backbone use

Help us create a special BackboneJS behavior that integrates a connection with Backbone's observable life-cycle. Read more here.

Other use

Integrating can-connect with your framework is typically pretty easy. In general, the pattern involves creating a behavior that integrates with your framework's observable instances. The [can-connect/can/map] behavior can serve as a good guide. You'll typically want to implement the following in your behavior:

.instance - Creates the appropriate observable object type.
.list - Creates the appropriate observable array type.
.serializeInstance - Return a plain object out of the observable object type.
.serializeList - Return a plain array out of the observable array type.

.createdInstance - Update an instance with data returned from createData.
.updatedInstance - Update an instance with data returned from updateData.
.destroyedInstance - Update an instance with data returned from destroyData.
.updatedList - Update a list with raw data.

And, in most frameworks you know when a particular observable is being used, typically observed, and when it can be discarded. In those places, you should call:

[can.connect/constructor-store.addInstanceReference] - Call when an instance is being used.
[can.connect/constructor-store.deleteInstanceReference] - Call when an instance is no longer being used.
[can.connect/constructor-store.addListReference] - Call when a list is being used.
[can.connect/constructor-store.deleteListReference] - Called when a list is no longer being used.

Interfaces

The following is a list of the most important interface methods and properties implemented or consumed by the core behaviors.

Identifiers

.id( props | instance ) -> String - Returns a unique identifier for the instance or raw data.
.idProp -> String="id" - The name of the unique identifier property.
.listSet(list) -> set - Returns the set a list represents.
.listSetProp -> String="__set" - The property on a List that contains its set.

Implemented by [connect.base].

Instance Interface

The following methods operate on instances and lists.

CRUD methods:

.getList(set) -> Promise<List> - retrieve a list of instances.
.getList(set) -> Promise<Instance> - retrieve a single instance.
.save(instance) -> Promise<Instance> - creates or updates an instance.
.destroy(instance) -> Promise<Instance> - destroys an instance.

Implemented by [can-connect/constructor]. Overwritten by [can-connect/constructor/store].

Instance callbacks

.createdInstance(instance, props) - An instance is created.
.updatedInstance(instance, props) - An instance is updated.
.destroyedInstance(instance, props) - An instance is destroyed.
.updatedList(list, updatedListData, set) - A list has been updated.

Implemented by [can-connect/constructor]. Overwritten by [data-connect/real-time], [can-connect/constructor/callbacks-once].

Hydrators and Serializers

.instance(props) -> Instance - Creates an instance given raw data.
.list({data: Array<Instance>}) -> List - Creates a list given an array of instances.
.hydrateInstance(props) -> Instance - Provides an instance given raw data.
.hydrateList({ListData}, set) -> List - Provides a list given raw data.
.hydratedInstance(instance) - Called whenever an instance is created in memory.
.hydratedList(list, set) - Called whenever a list is created in memory.
.serializeInstance(instance) -> Object - Returns the serialized form of an instance.
.serializeList(list) -> Array<Object> - Returns the serialized form of a list and its instances.

Implemented by [can-connect/constructor]. Overwritten by [can-connect/constructor/store], [can-connect/fall-through-cache].

Data Interface

The raw-data connection methods.

CRUD methods

.getListData(set) -> Promise<ListData> - Retrieves list data.
.updateListData(listData[, set]) -> Promise<ListData> - Update a list's data.
.getSets() -> Promise<Array<Set>> - Returns the sets available to the connection.

.getData(params) -> Promise<Object> - Retrieves data for a particular item.
.createData(props, cid) -> Promise<props> - Creates instance data given the serialized form of the data. A client ID is passed of the instance that is being created.
.updateData(props) -> Promise<props> - Updates instance data given the serialized form of the data.
.destroyData(props) -> Promise<props> - Destroys an instance given the seralized form of the data.

.clear() -> Promise - Clears all data in the connection.

Implemented by [can-connect/data-url], [can-connect/data/localstorage-cache], [can-connect/data/memory-cache]. Overwritten by [can-connect/cache-requests], [can-connect/data/combine-requests], [can-connect/data/inline-cache], [can-connect/fall-through-cache]. Consumed by [can-connect/constructor].

Data Callbacks

.gotListData(listData, set) -> ListaData - List data is retrieved.
.gotData( props, params) -> props - Instance data is retreived.
.createdData( props, params, cid) -> props - An instance's data is created.
.updatedData( props, params) -> props - An instance's data is updated.
.destroyedData( props, params) -> props - An instance's data is destroyed.

Implemented by [can-connect/data/callbacks]. Overwritten by [can-connect/data/callbacks-cache], [can-connect/real-time].

Response parsers

.parseListData(*) -> ListData - Given the response of getListData, return the right object format.
.parseInstanceData(*) -> props - Given the response of getData, createData, updateData, and destroyData, return the right object format.

Implemented by [can-connect/data-parse].

Store Interface

.addInstanceReference(instance) - Signal that memory-unsafe actions can be performed on the instance.
.deleteInstanceReference(instance) - Signal that memory-unsafe actions should be removed. .addListReference(list) - Signal that memory-unsafe actions can be performed on the list.
.deleteListReference(list) - Signal that memory-unsafe actions should be removed.

Implemented by [can-connect/constructor/store].

Real-time Methods

createInstance( props ) -> Promise<instance> - Inform the connection an instnace has been created.
updateInstance( props ) -> Promise<instance> - Inform the connection an instnace has been updated.
destroyInstance( props ) -> Promise<instance> - Inform the connection an instnace has been destroyed.

Implemented by [can-connect/real-time].

Creating Behaviors

To create your own behavior, call connect.behavior with the name of your behavior and a function that returns an object that defines the hooks you want to overwrite or provide:

connect.behavior("my-behavior", function(baseBehavior){
  return {
    // Hooks here
  };
})

For example, creating a simple localStorage behavior might looks like:

connect.behavior("localstorage", function(baseBehavior){
  return {
    getData: function(params){
      var id = this.id(params);
      return new Promise(function(resolve){
        var data = localStorage.getItem(baseBehavior.name+"/"+id);
        resolve( JSON.parse(data) )
      });
    },
    createData: function(props){
      var id = localStorage.getItem(baseBehavior.name+"-ID") || "0";
      
      var nextId = ++JSON.parse( id );
      localStorage.setItem(baseBehavior.name+"-ID"), nextId);
      var id = this.idProp;
      return new Promise(function(resolve){
        props[id] = nextId;
        localStorage.setItem(baseBehavior.name+"/"+nextId, props);
        resolve( props )
      });
    },
    updateData: function(){ ... },
    destroyData: function(){ ...}
  };
})

About

Model layer utilities for every JavaScript framework! Assemble real-time, high performance, restful data connections.


Languages

Language:JavaScript 96.4%Language:HTML 3.6%