emberjs / data

WarpDrive is a lightweight data library for web apps — universal, typed, reactive, and ready to scale.
https://api.emberjs.com/ember-data/release
MIT License
3.04k stars 1.33k forks source link

Ember.Select and belongsTo with async:true dosen't work #1405

Closed rickard2 closed 10 years ago

rickard2 commented 11 years ago

When you have a asynchronous belongsTo relationship and bind that to a Ember.Select it seems that the correct value is not selected in the view.

Example here: http://emberjs.jsbin.com/uzUNaQA/1/

Tested with Ember Data Canary as of today and Ember 1.0.0

decasia commented 11 years ago

I believe this is a longstanding, known issue. See ember.js issue 1333 for discussion. They discuss some workarounds in the thread... I've switched to using non-async options for select boxes when possible, to avoid the issue altogether.

ugomeda commented 11 years ago

I'm having the same issue as you, they describe it as "not a bug" in https://github.com/emberjs/ember.js/issues/1333 but it's actually a pain.

It will also switch your select to an empty mode if you call model.save() since Ember Data will set your fields to a promise.

Tested with Ember 1.1.2 and ember data 1.0.0-beta3

marcioj commented 10 years ago

@rickard2 your problem is that item.author returns a PromiseObject instead of the record, when the PromiseObject is resolved, it set a record instance in the content property. So you can update your code to use item.author.content and will works.

Here is your updated jsbin http://emberjs.jsbin.com/uzUNaQA/6/edit

rickard2 commented 10 years ago

@marcioj that seems to solve the problem, still getting used to ember-data 1.0

thanks!

stefanpenner commented 10 years ago

@marcioj & @rickard2 this is an Ember.select bug that we should fix. Please check embers issue list, if no similar issue exists please open one. That being said, I am pretty sure an issue such as this is already open.

WMeldon commented 10 years ago

There is a subtle issue with @marcioj solution to this one that I've tried to highlight: http://emberjs.jsbin.com/uzUNaQA/20/edit?html,js,output

If you do this with a fresh object that doesn't have the belongs to explicitly set to null, accessing the content property isn't going to do you much good.

If you do set the author to null when you instantiate the class, it goes through the correct setters and gives you an object with the content property of null.

The real issue comes in when you load an existing model with a null belongsTo because you can't reasonably force it though the setters and are therefore unable to manipulate the relationship. This isn't really a problem with the select or Ember Data (at least not a simple one), but it is a bit of an unintuitive incompatibility.

You can get around this by binding the properties to the controller and then manually setting them on the object, but it's not my favorite approach and I'd love to hear some alternatives.

karankurani commented 10 years ago

I believe I have a similar issue here - http://stackoverflow.com/questions/22799094/setting-a-belongsto-attribute-via-an-action-on-controller-not-working/22799776?noredirect=1#comment34785844_22799776

Looking at the answer of this post, just having a selectionBinding attached to a belongsTo (async) of a model doesn't render the selection correctly.

I might be wrong in this so wanted to have a second opinion on it.

neverfox commented 10 years ago

My solution for the time being is to do something like this on my controller:

  # I have an ArrayController dedicated to the list
  needs: ["selections"]
  selections: Ember.computed.alias "controllers.selections"
  selectedObject: null # so it doesn't get proxied to the model
  objectSelected: (->
    @set "myModelProperty", @get "selectedObject"
  ).observes "selectedObject"

I have selection=selectedObject on the Select. And in the route:

  setupController: (controller, model) ->
    @_super controller, model
    @store.findAll("selections").then (selections) =>
      @controllerFor("selections").set "model", selections
      # There needs to be a selection made even if the user doesn't touch the element
      controller.set "selectedModel", investmentModels.get "firstObject"
WMeldon commented 10 years ago

Alright, I've made some progress on the workarounds to get this situation to work. I'm sure there are better ways to do this, but this is (so far) the only one I've found to cover all the problems without excessive manual intervention.

For existing records

For existing records, you need to do something like the following in your route:

// your-route
beforeModel: function() {
  var self = this;
  return Em.RSVP.hash({
    firstBelongsTo: this.store.find('first-belongs-to'),
    secondBelongsTo: this.store.find('second-belongs-to')
    }).then(function (models) {
      self.controllerFor('this-route').setProperties(models);
  });

And in your controller, be sure to declare the properties before setting them as Ember tries to throw then into content when they don't exist:

// your-controller
App.MyController = Ember.Controller.extend({
  firstBelongsTo: null,
  secondBelongsTo: null
});

By returning a promise in the beforeModel hook, you are telling the route to resolve the promise BEFORE loading the model, which also mean before any rendering occurs. This gives your application time to load the data up front before binding it to the select boxes.

For NEW records.

New records present an unexpected quirk in this situation because the belongsTo relationships aren't null, they are undefined. This (quietly) blows up the select boxes.

To solve this, I'm currently manually setting all of the relationships to null in the model hook:

// your-route
model: function() {
  this.store.createRecord('your-record', { 
    firstbelongsTo: null, 
    secondBelongsTo: null
});

Not ideal, but by their powers combined, you can use async belongTo without tossing weird proxy properties in your controller.

There is undoubtably more to this issue, but this seems to have drastically improved the situation for me.

bmac commented 10 years ago

Thanks for this @WMeldon. I ran into this problem recently and your comments helped me figure out what I was doing wrong.

leejt489 commented 10 years ago

Has this been fixed?

johnnyoshika commented 10 years ago

I'm also curious to know if this is something that will be fixed soon. Thanks.

gopeter commented 10 years ago

Any news? :) :+1:

stefanpenner commented 10 years ago

use relation.content as the property path and all will work fine, ember select needs to be aware of async relationships, currently it doesn't monitor the change events correctly.

This is not an ember-data issue.

gopeter commented 10 years ago

I don't know if this is the right place, so I posted my question at Stackoverflow also: http://stackoverflow.com/questions/26543093/how-to-preselect-a-value-on-an-ember-select-view


Thanks for your help. My select works now, but I don't know how to preselect a value dynamically - or isn't it possible at the moment?

This is my select:

{{view select
  class="uk-width-1-1"
  content=services
  optionLabelPath="content.name"
  optionValuePath="content.id"
  prompt="Service"
  selectionBinding="selectedService"
}}

It works fine when I try to get the current active value: this.get('selectedService'), but when I try to set a specific customer, nothing happens:

var service = timetracking.get('service');
this.set('selectedService', service);

These are my models:

App.Timetracking = DS.Model.extend({
  duration:  DS.attr('number'),
  day:       DS.attr('date'),
  notice:    DS.attr('string'),
  project:   DS.belongsTo('project', {async: true}),
  service:   DS.belongsTo('service', {async: true}),
  user:      DS.belongsTo('user', {async: true})
});

App.Service = DS.Model.extend({
  name:           DS.attr('string'),
  description:    DS.attr('string'),
  timetrackings:  DS.hasMany('timetracking', {async: true}),
  archived:       DS.attr('boolean')
});
ahacking commented 10 years ago

Thanks @stefanpenner !

I found using relation.content works well with an alias on your controllers:

export default Ember.ObjectController.extend({
  relation: Ember.computed.alias('content.relation.content')
});

Be aware the relation.content trick will not work correctly if you are using a buffered proxy on your controllers since the extra .content key path segment will effectively go via the proxy buffer to the underlying promise object content thus mutating the model directly. I was able to work around this issue with a computed property helper that uses the buffer value if its been set on the controller, and otherwise obtains the value from relation.content.

As a result when using Ember.Select in my templates I don't have to worry about async belongsTo relationships, they can just use selection=relation until Ember.Select is taught to handle promise proxies.