3
votes

To learn Ember.js I started writing a small bookmark application. I'm struggling with issues related to wrong data handling now.

To Explain The Application

  • User can add label
  • User can add links to selected labels
  • A Label can have n links
  • A Link can have n labels
  • Show links by selecting the associated labels

The Issue
Link data is not writtern to the data store. Because of this, local updates to the links model due selection changes are overwritten. Also, implementing true persistence later wouldn't work.

Narrowing down the issue

In IndexRoute I initialize the model:

model: function(params) {
    return {
        labels: this.get("store").find("label"),
        // TODO: this is probably wrong.
        links: Ember.A()
    };
}

On the one side I fetch label data from the data store. On the other side I initialize link data with an empty ember array.

In my opinion this is the root of the malfunction but I don't know how to implement this properly. I tried to replace this with bogus references to the storage adapter:

this.get("store").find("label").filter("_")

This is neither right nor does it work properly. Then it continues to the point where I then can't use the storage adapter to update records:

// TODO: this is probably wrong.
this.get("links").addObject({
    name: linkName,
    url: linkUrl,
    labels: this.selectedLabels
});
/*store.push("link", {
    name: newLink,
    url: linkUrl,
    labels: this.selectedLabels
});*/

And so on.

Jsbin: http://jsbin.com/ucanam/1751/edit

How to store link data properly so changing the local data of the controler won't break the application?

Edit:
I think I found my conceptual mistake with your advice. Does that mean that I always have to handle the local copy aswell?

var link = this.get("model");
link.deleteRecord();
link.save().then(function(link) {
    var indexController = this.get('controllers.index');
    indexController.get("links").removeObject(link);
});

In your example you used a promise to add the object to the controller. In this code sample the promise will never fulfill- therefore it only works without.

Also in the LabelController the remove method should also deletes associated links. If I use deleteRecord in the forEach loop it only deletes one label and then it somehow brings the loop to exit. Is this intentionally or have I made a mistake?

I have updated your JsBin.
http://jsbin.com/ucanam/1987/edit

2

2 Answers

3
votes

I modified your JSbin http://jsbin.com/ucanam/1975/

If you want to persist records you must create them in the store with createRecord() and then save() them. The new newLink function

    newLink: function() {
        var store = this.get("store"),
            linkName = this.get("linkName"),
            linkUrl = this.get("linkUrl"),
            links = this.get("links"),
            selectedLabels = this.get('selectedLabels');

        if(selectedLabels.get('length') > 0) {
            if(linkName.length > 0 && linkUrl.length > 0) {
              var newLink = store.createRecord('link',
                {
                    name: linkName,
                    url: linkUrl
                });
              selectedLabels.forEach(function(label){
                newLink.get('labels').addObject(label);
              });
              newLink.save().then(function(link){
                links.addObject(link);
              });
              this.set("linkName", "");
              this.set("linkUrl", "");
            }
        } else {
            alert("You have to select a label!");
        }
    },

For deleting records there are problems using forEach because the result of a find to the store is a live array. You can see this discussion in GitHub https://github.com/emberjs/data/issues/772. So your remove label function should be (note the use of toArray() to make a static copy of the live array)

    remove: function() {
        var indexController = this.get('controllers.index'),
            label = this.get('model'),
            linksPromise = this.get('store').find('link');

        linksPromise.then(function(links){
            links.toArray().forEach(function(link){
                var linkLabels = link.get('labels'),
                    linkLabelsIds = linkLabels.mapProperty('id');

                if(linkLabelsIds.contains(label.get("id"))) {
                    if(linkLabelsIds.get("length") == 1) {
                        console.log("delete link: "+link.get("name"));
                        indexController.get("links").removeObject(link);
                        link.deleteRecord();
                        link.save();
                    }
                }
            });

            label.deleteRecord();
            label.save();
        });
    }

Final note, don't forget to make a save() on the records after deleting them, jsBin here: http://jsbin.com/ucanam/1989/

1
votes

Have you defined your models?

App.Label = DS.Model.extend({
    title: DS.attr('string'),
    links: DS.hasMany('link')
});

App.Link = DS.Model.extend({
    name: DS.attr('string'),
    url: DS.attr('string'),
    labels: DS.hasMany('label')
});

App.IndexRoute = Ember.Route.extend({
    model: function() {
        this.store.find('label')
    }
});

App.LabelController = Ember.ObjectController.extend({
    actions:
        <!-- functions here -->
});

<script type='text/handlebars' data-template-name="index">
    {{#each label in model itemController='label'}}
        {{label.title}}
            {{#each link in label.links}}
                {{link.title}}
            {{/each}}
    {{/each}}
</script>