How to properly bind a can.Model's objects to a component's template with updates?

I’m a bit new to canjs. I’ve been working on a generic table can.Component that has a can.Model as an attribute property. What I would like to happen is after the component is initialized, it should call the can.Model's findAll method and load the results into the template as rows. There is additional functionality to allow for editing, adding, and deleting rows.

The findAll is working correctly, and the results are displayed as expected. Likewise, the delete is also working correctly. When a model’s destroy method is called, it is removed from the template. However, updates and creating new rows are not being reflected in the template even though the server requests are successful and returning the expected results.

Here are some snippets from the component:

<!-- template.html -->
{{#if objectsPromise.isPending}}
            <p>Loading...</p>
          {{/if}}
          {{#if objectsPromise.isResolved}}
            {{#each objects}}
            <tr>
            {{#if editable}}
              <td class="edit-buttons">
                <i ($click)="toggleSelected(.)" class="fa {{#if isSelected(.)}}fa-check-square-o{{else}}fa-square-o{{/if}}"></i>
                <i ($click)="editObject(.)" class="fa fa-pencil"></i>
                <i ($click)="deleteObject(.)" class="fa fa-trash"></i>
              </td>
            {{/if}}
                {{#each .}}
                  {{#if renderField(%key)}}
                    <td>{{formatValue(.)}}</td>
                  {{/if}}
                {{/each}}
              </tr>
            {{/each}}
          {{/if}}

  {{#if editable}}
    <a href="#" class="btn btn-success" ($click)="createObject">
      <i class="fa fa-plus"></i> Add New
    </a>
    <a href="#" class="btn btn-danger" ($click)="deleteMultiple">
      <i class="fa fa-trash"></i> Remove Selected
    </a>
  {{/if}}

  <form ($submit)="formSubmit">
    <!-- if there's an edit object, show the form fields -->
    {{#if edit_object}}
      {{#each edit_object}}
        <div class="form-group">
          <label for="{{%key}}">{{%key}}</label>
          <input type="text" class="form-control" id="{{%key}}" name="{{%key}}" placeholder="" value="{{.}}">
        </div>
      {{/each}}
      <button type="submit" class="btn btn-default">Submit</button>
      <a class="btn btn-danger" ($click)="deleteObject(edit_object)">Delete</a>
    {{/if}}
  </form>
//viewmodel.js
export const ViewModel = viewModel.extend({
  define: {
    objectModel: {
      value: null
    },
    editable: {
      type: 'boolean',
      value: false
    },
    filter: {
      type: '*',
      value: null
    },
    group: {
      type: 'string',
      value: null
    },
    objectsPromise: {
      get: function() {
        return this.attr('objectModel').findAll({
          filter: this.attr('filter') ? 'q=' + JSON.stringify(this.attr('filter')) : '',
          group: this.attr('group') ? 'group=' + this.attr('group') : ''
        });
      }
    },
    objects: {
      get: function(value, setAttr) {
        this.attr('objectsPromise').then(setAttr);
      }
    },
      formSubmit: function(scope, form, event) {
    var data = can.$(form).serializeArray();
    var edit_object = scope.attr('edit_object');
    //update the object with values that changed
    for (var i = 0; i < data.length; i++) {
      var newData = data[i];
      if (edit_object.attr(newData.name) !== newData.value) {
        edit_object.attr(newData.name, newData.value);
      }
    }
    scope.attr('edit_object', null);
    //prevent the form from submitting
    return false;
  },
  deleteObject: function(obj, skipConfirm) {
    if (obj && (skipConfirm || confirm('Are you sure you want to delete this record?'))) {
      obj.destroy();
      this.attr('edit_object', null);
    }
  },
  createObject: function() {
    var newObject = this.attr('objectModel')();
    this.attr('editObject', newObject);
  },
});

I’ve been stumped on this for a bit, if anyone has a good suggestion, I’d really appreciate your time and advice. Thanks!

With can-connect, this problem is solved.

If you are using can.Model, you have to listen to creates and update your list yourself. TodoMVC has an example of it:

Thanks for the suggestion, I’m open to switching my models to can-connect. I did just notice that in 3.0 models will be depreciated so it seems worth the effort to update now.

I’m still not sure how to bind the component template to the connect-store though. Is it possible to set it up so that when I do a connection.save(object) or connection.destroy(object) it updates/removes that item from the template?

On my cell so a short answer. Go through the donejs quick start guide. It uses this technique. You can also read about can-set on bitovi’s blog which underpins this behavior.

I switched the model to can-connect. But I guess I’m a bit confused on how the new can-connect/can.Model stores the objects. I had thought that the connection maintains a list of objects that will automatically update a stache template when changes occur.

I went through the guide you mentioned, should I be using can-connect/can/tag for this functionality? It brings up an issue though, since I’m having a model, or connection passed to my component viewModel when it is created, the tag name is not determined, and afaik you can’t do something like this in a template

<{{connection.name}}-model get-list="{}">
...

This example below seems like a really clean and simple way to go about it, but what is happening now is while deletes are removed from the template, so are edits, and adds do not get added to the template.

    //connection is instance of superMap
    objects: {
      get: function(){
        return this.attr('connection').getList({
          filter: this.attr('filter') ? 'q=' + JSON.stringify(this.attr('filter')) : '',
          group: this.attr('group') ? 'group=' + this.attr('group') : ''
        });
      }
    },

and

  {{#if objects.isResolved}}
    {{#each objects.value}} 
    ...

I think I’m getting close to what I’m after. I split up my component so that the CRUD operations are being performed in a parent component is passing a list (which is just like the TodoList list on this page https://connect.canjs.com/doc/can-connect|can|super-map.html) to a child component table. I also realized that you can pass the connection.getList as a parameter to the can.List constructor, like this:

    objects: {
      get: function(prev, setAttr) {
        var listConst = this.attr('list');
        var list = new listConst(this.attr('connection').getList({}));
        return setAttr(list);
      }
    },

I am using the one way binding syntax to pass the objects list, but when the object changes, its still not being reflected in the child table, despite the rest call updating the object correctly.

<query-table {objects}="objects" />

You shouldn’t be using https://connect.canjs.com/doc/can-connect|can|model.html , you really want to be using https://connect.canjs.com/doc/can-connect|can|super-map.html for real time behavior.

Read: http://blog.bitovi.com/set-algebra-can-set/ which underpins the behavior you are looking for. And this is the actual behavior: https://connect.canjs.com/doc/can-connect|real-time.html

I’ll hopefully have some of bitballs guide up shortly. It has a lot about how to handle relationships between models, the services, etc.

You lose promise information when you do that.

Have you looked at the place-my-order guide: https://donejs.com/place-my-order.html

its order model makes use of the real-time behavior.

So I should be doing something like this and use objects.value, objects.isResolved…etc in my template.

    objects: {
      get: function(prev, setAttr) {
        return this.attr('connection').getList(this.attr('parameters').attr());
      }
    },

After switching to the can-connect/can/supermap, my template is working correctly! Thanks for your suggestions and help!

Also, as a side note to the donejs devs, using this forum is just an overall great experience, well done on that!