DoneJS StealJS jQuery++ FuncUnit DocumentJS
5.33.3
6.0.0 4.3.0 3.14.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
  • API Docs
    • Observables
      • can-bind
      • can-compute
      • can-debug
      • can-define
      • can-define/list/list
      • can-define/map/map
      • can-define-backup
      • can-define-stream
      • can-define-stream-kefir
      • can-event-queue
      • can-kefir
      • can-list
      • can-map
      • can-map-compat
      • can-map-define
      • can-observable-array
      • can-observable-object
      • can-observation
      • can-observation-recorder
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
      • can-value
    • Views
      • can-attribute-observable
      • can-component
      • can-stache
      • can-stache-bindings
      • can-stache-converters
      • can-stache-element
      • can-stache-route-helpers
      • can-view-autorender
      • can-view-callbacks
      • can-view-import
      • can-view-live
      • can-view-model
      • can-view-nodelist
      • can-view-parser
      • can-view-scope
      • can-view-target
      • steal-stache
    • Data Modeling
      • can-connect
      • can-connect-feathers
      • can-connect-ndjson
      • can-connect-tag
      • can-fixture
      • can-fixture-socket
      • can-local-store
      • can-memory-store
      • can-ndjson-stream
      • can-query-logic
      • can-realtime-rest-model
      • can-rest-model
      • can-set-legacy
      • can-super-model
    • Routing
      • can-deparam
      • can-param
      • can-route
      • can-route-hash
      • can-route-mock
      • can-route-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-diff
      • can-globals
      • can-join-uris
      • can-key
      • can-key-tree
      • can-make-map
      • can-parse-uri
      • can-queues
      • can-string
      • can-string-to-any
      • can-zone-storage
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-child-nodes
      • can-control
      • can-dom-data
      • can-dom-events
      • can-dom-mutate
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-fragment
    • Data Validation
      • can-define-validate-validatejs
      • can-type
      • can-validate
      • can-validate-interface
      • can-validate-legacy
      • can-validate-validatejs
    • Typed Data
      • can-cid
      • can-construct
      • can-construct-super
      • can-data-types
      • can-namespace
      • can-reflect
      • can-reflect-dependencies
      • can-reflect-promise
      • can-types
    • Polyfills
      • can-symbol
      • can-vdom
    • Core
    • Infrastructure
      • can-global
      • can-test-helpers
    • Ecosystem
    • Legacy
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

can-realtime-rest-model

  • npm package badge
  • Star
  • Edit on GitHub

Connect a type to a restful data source and automatically manage lists.

realtimeRestModel(options)

realtimeRestModel is the base model layer that most CanJS applications should use. It requires a properly configured can-query-logic which experimenters might find cumbersome to configure. If you are experimenting with CanJS, or have a very irregular service layer, can-rest-model might be a better fit. For everyone else, use realtimeRestModel as it adds the following behaviors on top of can-rest-model:

  • constructor/store - Unify instances and lists across requests.
  • real-time - Add, remove, and move items between lists automatically.

realtimeRestModel is useful even if you are not building a realtime application. It allows you to make changes to instances and have the lists in the page update themselves automatically. This is detailed in the Purpose section below.

If your service layer matches what realtimeRestModel expects, configuring realtimeRestModel is very simple. For example, the following extends a Todo type with the ability to connect to a restful service layer:

import {Todo, todoFixture} from "//unpkg.com/can-demo-models@5";
import {realtimeRestModel} from "can";

// Creates a mock backend with 5 todos
todoFixture(5);

Todo.connection = realtimeRestModel({
    Map: Todo,
    List: Todo.List,
    url: "/api/todos/{id}"
});

// Prints out all todo names

Todo.getList().then(todos => {
    todos.forEach(todo => {
        console.log(todo.name);
    })
})

Parameters

  1. options {Object}:

    Configuration options supported by all the mixed-in behaviors:

    • Map - The map type constructor function used to create instances of the raw record data retrieved from the server. The type will also be decorated with the following methods:

      • getList
      • get
      • save
      • destroy
      • isSaving
      • isDestroying
      • isNew
    • List - The list type constructor function used to create a list of instances of the raw record data retrieved from the server. _

    • url - Configure the URLs used to create, retrieve, update and delete data. It can be configured with a single url like:

      url: "/services/todos/{_id}"
      

      Or an object that configures how to create, retrieve, update and delete individually:

      url: {
        getListData: "GET /api/todos/find",
        getData: "GET /api/todo/get/{id}",
        createData: "POST /api/todo/create",
        updateData: "POST /api/todo/update?id={id}",
        destroyData: "POST /api/todo/delete?id={id}"
      }
      
    • ajax - Specify a method to use to make requests; can-ajax is used by default, but jQuery's .ajax method can be passed.

    • parseInstanceProp - Specify the property to find the data that represents an instance item.

    • parseInstanceData - Returns the properties that should be used to make an instance given the results of getData, createData, updateData, and destroyData.

    • parseListProp Specify the property to find the list data within a getList response.

    • parseListData Return the correctly formatted data for a getList response.

    • queryLogic - Specify the identity properties of the type. This is built automatically from the Map if can-define/map/map is used.

Returns

{connection}:

A connection that is the combination of the options and all the behaviors that realtimeRestModel adds.

realtimeRestModel(url)

Create a connection with just a url. Use this if you do not need to pass in any other options to configure the connection.

For example, the following creates a Todo type with the ability to connect to a restful service layer:

import {todoFixture} from "//unpkg.com/can-demo-models@5";
import {realtimeRestModel} from "can";

// Creates a mock backend with 5 todos
todoFixture(5);

const Todo = realtimeRestModel("/api/todos/{id}").Map;

// Prints out all todo names

Todo.getList().then(todos => {
    todos.forEach(todo => {
        console.log(todo.name);
    })
})

Parameters

  1. url {String}:

    The url used to create, retrieve, update and delete data.

Returns

{connection}:

A connection that is the combination of the options and all the behaviors that realtimeRestModel adds. The connection includes a Map property which is the type constructor function used to create instances of the raw record data retrieved from the server.

Purpose

realtimeRestModel extends can-rest-model with two new features:

  • Automatic list management
  • Unified instances and lists across requests with list and instance stores.

Automatic list management

realtimeRestModel allows you to make changes to instances and have the lists in the page update themselves automatically. This can remove a lot of boilerplate from your application. For example, if you make a simple component that displays only completed todos sorted by name like:

<completed-todos />

<script>
import {Component} from "can";
import {Todo} from "../models/todo";

Component.extend({
    tag: "completed-todos",
    view: `
        <h2>Completed Todos</h2>
        {{#each(todosPromise.value)}}
            <li>{{name}}</li>
        {{/each}}
    `,
    ViewModel: {
        todosPromise: {
            default: () => Todo.getList({
                filter: {complete: true},
                sort: "name"
            })
        }
    }
})
</script>

If other components are creating, updating, or destroying todos, this component will update automatically. For example:

  • Creating a completed todo as follows will automatically insert the todo in the <completed-todos> element's list sorted by its name:

    new Todo({name: "walk dog", complete: true}).save();
    

    This works because can-query-logic is able to know that the query { filter: {complete: true}, sort: "name" } doesn't contain data like {name: "walk dog", complete: true}.

  • Creating a todo with complete: false will not update the <completed-todos>'s list:

    new Todo({name: "walk dog", complete: false}).save();
    

    This works because can-query-logic is able to know that the query { filter: {complete: true}, sort: "name" } doesn't contain data like {name: "walk dog", complete: false}.

  • Updating a todo's name or complete value will move the todo in or out of the <completed-todos>'s list and put it in the right position. For example, a todo that's not complete can be moved into the list like:

    todo.complete = true;
    todo.save();
    
  • Destroying a todo will remove the todo from all lists:

    todo.destroy();
    

The following code example uses realtimeRestModel to create a list of todos that automatically updates itself when new todos are created.

<todo-create></todo-create>
<todo-list></todo-list>

<script type="module">
import {realtimeRestModel, Component} from "can";
import {Todo, todoFixture} from "//unpkg.com/can-demo-models@5";

// Creates a mock backend with 5 todos
todoFixture(5);

Todo.connection = realtimeRestModel({
    Map: Todo,
    List: Todo.List,
    url: "/api/todos/{id}"
});

Component.extend({
    tag: "todo-list",
    view: `
    <ul>
        {{# if(todosPromise.isResolved) }}
            {{# each(todosPromise.value) }}
                <li>
                    <label>{{name}}</label>
                </li>
            {{/ each }}
        {{/ if}}
    </ul>
    `,
    ViewModel: {
        todosPromise: {
            get(){
                return Todo.getList();
            }
        }
    }
});

Component.extend({
    tag: "todo-create",
    view: `
        <form on:submit="createTodo(scope.event)">
            <p>
                <label>Name</label>
                <input on:input:value:bind='todo.name'/>
            </p>
            <button disabled:from="todo.isSaving()">Create Todo</button>
            {{# if(todo.isSaving()) }}Creating ....{{/ if}}
        </form>
    `,
    ViewModel: {
        todo: {
            Default: Todo
        },
        createTodo(event) {
            event.preventDefault();
            this.todo.save().then((createdTodo) => {
                this.todo = new Todo();
            })
        }
    }
});
</script>

List and instance stores

Additionally, realtimeRestModel unifies record and list instances. This means that if you request the same data twice, only one instance will be created and shared. For example, if a <todo-details> widget loads the id=5 todo, and another <todo-edit> widget loads the same data independently like:

<todo-edit></todo-edit>
<br /></br />
<todo-details></todo-details>

<script type="module">
import {realtimeRestModel, Component} from "can";
import {Todo, todoFixture} from "//unpkg.com/can-demo-models@5";

// Creates a mock backend with 6 todos
todoFixture(6);

Todo.connection = realtimeRestModel({
    Map: Todo,
    List: Todo.List,
    url: "/api/todos/{id}"
});

Component.extend({
    tag: "todo-details",
    view: `
        Todo Name is: {{todoPromise.value.name}}
    `,
    ViewModel: {
        todoPromise: {
            default: () => Todo.get({
                id: 5
            })
        }
    }
})

Component.extend({
    tag: "todo-edit",
    view: `
        Todo name is: <input value:bind="todoPromise.value.name"/>
    `,
    ViewModel: {
        todoPromise: {
            default: () => Todo.get({
                id: 5
            })
        }
    }
})

</script>

If the user changes the todo's name in the <todo-edit> widget, the<todo-details> widget will be automatically updated.

Furthermore, if you wish to update the data in the page, you simply need to re-request the data like:

// Updates the id=5 todo instance
Todo.get({id: 5})

// Updates all incomplete todos
Todo.getList({ filter: { complete: false } })

Use

Use realtimeRestModel to build a connection to a restful service layer. realtimeRestModel builds on top of can-rest-model. Please read the "Use" section of can-rest-model before reading this "Use" section. This "Use" section details knowledge needed in addition to can-rest-model to use realtimeRestModel, such as:

  • Configuring queryLogic
  • Updating the page from server-side events
  • Simulating server-side events with can-fixture

Configuring queryLogic

realtimeRestModel requires a properly configured queryLogic. If your server supports getList parameters that match can-query-logic's default query structure, then no configuration is likely necessary. The default query structure looks like:

Todo.getList({
    // Selects only the todos that match.
    filter: {
        complete: {$in: [false, null]}
    },
    // Sort the results of the selection
    sort: "-name",
    // Selects a range of the sorted result
    page: {start: 0, end: 19}
})

This structures follows the Fetching Data JSONAPI specification.

There's a:

  • filter property for filtering records,
  • sort property for specifying the order to sort records, and
  • page property that selects a range of the sorted result. The range indexes are inclusive. Example: {page: 0, end: 9} returns 10 records.

NOTE: can-connect does not follow the rest of the JSONAPI specification. Specifically can-connect expects your server to send back JSON data in a format described in can-rest-model.

If you control the service layer, we encourage you to make it match the default Query. The default query structure also supports the following Comparison Operators: $eq, $gt, $gte, $in, $lt, $lte, $ne, $nin.

For more information on this query structure and how to configure a query logic to match your service layer, read the configuration section of can-query-logic.

Updating the page from server-side events

If your service layer can push events, you can call the connection's createInstance, updateInstance, and destroyInstance to update the lists and instances on the page. For example, if you have a socket.io connection, you can listen to events and call these methods on the connection as follows:

import io from 'steal-socket.io';

const todoConnection = realtimeRestModel({
    Map: Todo,
    List: TodoList,
    url: "/api/todos/{id}"
});

socket.on('todo created', function(todo){
  todoConnection.createInstance(todo);
});

socket.on('todo updated', function(todo){
  todoConnection.updateInstance(todo);
});

socket.on('todo removed', function(todo){
  todoConnection.destroyInstance(todo);
});

Simulating server-side events with can-fixture

can-fixture does not allow you to simulate server-side events. However, this can be done relatively easily by calling createInstance, updateInstance, or destroyInstance on your connection directly as follows:

test("widget response to server-side events", function(assert){
    let todoList = new TodoListComponent();

    todoConnection.createInstance({
        id: 5,
        name: "learn how to test",
        complete: false
    }).then(function(){
        // check to see that the list has been updated
        assert.ok( /learn how to test/.test(todoList.element.innerHTML) );
    });
});

While this works, it doesn't make sure the record's data is in the fixture store. The fixture store also will add a unique id property. To make sure the record is in the store, use store.createInstance on the store and pass the result to connection.createInstance as follows:

test("widget response to server-side events", function(assert){
    let todoList = new TodoListComponent();

    todoStore.createInstance({
        name: "learn how to test",
        complete: false
    }).then(function(record){
        return todoConnection.createInstance(record)
    }).then(function(){
        // check to see that the list has been updated
        assert.ok( /learn how to test/.test(todoList.element.innerHTML) );
    });
});

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 5.33.3.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news