奇怪的绑定与Ember.Select值

时间:2014-11-28 18:39:26

标签: ember.js

当我尝试将其值绑定到模型时,我对Ember.Select视图有一个奇怪的问题。 以下是我正在做的摘要,完整的jsbin可以在这里找到:

使用JavaScript:http://jsbin.com/jayutuzazi/1/edit?html,js,output
使用CoffeeScript:http://jsbin.com/nutoxiravi/2/edit?html,js,output

基本上我尝试做的是使用模型的属性来设置另一个模型的属性。

我有Age这样的模型

App.Age = DS.Model.extend({
  label: DS.attr('string'),
  base: DS.attr('number')
});

另一个名为Person的模型就像这样

App.Person = DS.Model.extend({
  name: DS.attr('string'),
  ageBase: DS.attr('number')
});

模板如下所示:

<!-- person/edit.hbs -->
<form>
  <p>Name {{input value=model.name}}</p>
  <p>
    Age
    {{view "select" value=model.ageBase
           content=ages
           optionValuePath="content.base"
           optionLabelPath="content.label"}}
  </p>
</form>

我要做的是在人物编辑表单中有select,其中使用base作为值并使用label作为标签列出年龄。

我希望在加载时选择正确的值,并在所选选项更改时进行更改。

在jsbin输出中可以看到,所选内容已正确填充,但它将已编辑人员的ageBase值设置为undefined,并且不选择任何选项。选择选项时,会正确设置模型值。

我做错了吗?这是一个错误吗?我应该做些什么来完成这项工作?

谢谢:)

4 个答案:

答案 0 :(得分:3)

您可以根据ages的履行情况有条件地呈现,如下所示,因为选择不会处理承诺(更多内容如下):

{{#if ages.isFulfilled}}
    {{view "select" value=ageBase
           content=ages
           optionValuePath="content.base"
           optionLabelPath="content.label"}}
{{/if}}

我更新了JsBin,证明它有效。

我还在JsBin中说明了如何在模板中使用model.进行限定,因为对象控制器是他们装饰的模型的代理。这意味着如果属性来自模型或控制器上的某些计算属性,则您的视图不必关注。

目前有一个PR #9468用于选择视图,我提出了合并到Ember的案例,它解决了选择和选项路径的一些问题。还有meta issue #5259来处理一些选择性视图问题,包括使用promises。

从问题#5259开始,您会发现Ember核心开发人员Robert Jackson有一些候选人选择替代品。我克隆了一个JsBin来运行最新的Ember生产版本。

没有什么可以阻止您在应用中使用Roberts代码作为选择视图替换。异步集合/承诺将起作用(从我看到的基准测试中渲染速度要快得多)。

该组件的模板只是:

{{#if prompt}}
  <option disabled>{{prompt}}</option>
{{/if}}

{{#each option in resolvedOptions}}
  <option {{bind-attr value=option.id}}>{{option.name}}</option>
{{/each}}

组件的js是:

App.AsyncSelectComponent = Ember.Component.extend({
  tagName: 'select',
  prompt: null,

  options: null,
  initialValue: null,

  resolvedOptions: null,
  resolvedInitialValue: null,

  init: function() {
    var component = this;

    this._super.apply(this, arguments);

    Ember.RSVP.hash({
      resolvedOptions: this.options,
      resolvedInitialValue: this.initialValue
    })
      .then(function(resolvedHash){
        Ember.setProperties(component, resolvedHash);

        //Run after render to ensure the <option>s have rendered
        Ember.run.schedule('afterRender', function() {
          component.updateSelection();
        });
      });
  },

  updateSelection: function() {
    var initialValue = Ember.get(this, 'resolvedInitialValue');
    var options = Ember.get(this, 'resolvedOptions') || [];
    var initialValueIndex = options.indexOf(initialValue);
    var prompt = Ember.get(this, 'prompt');

    this.$('option').prop('selected', false);

    if (initialValueIndex > -1) {
      this.$('option:eq(' + initialValueIndex + ')').prop('selected', true);
    } else if (prompt) {
      this.$('option:eq(0)').prop('selected', true);
    }
  },

  change: function() {
    this._changeSelection();
  },

  _changeSelection: function() {
    var value = this._selectedValue();

    this.sendAction('on-change', value);
  },

  _selectedValue: function() {
    var offset = 0;
    var selectedIndex = this.$()[0].selectedIndex;

    if (this.prompt) { offset = 1; }

    return Ember.get(this, 'resolvedOptions')[selectedIndex - offset];
  }
});

答案 1 :(得分:0)

问题在于:

{{view "select" value=model.ageBase

当应用启动时,value未定义且model.ageBasevalue同步到model.ageBase之前的同步。因此,解决方法是跳过初始未定义的value

请参阅:http://jsbin.com/rimuku/1/edit?html,js,console,output

相关部分是:

模板

{{view "select" value=selectValue }}

控制器

App.IndexController = Ember.Controller.extend({

  updateModel: function() {
    var value = this.get('selectValue');
    var person = this.get('model');

    if ( value ) { // skip initial undefined value
      person.set('ageBase', value);
    }
  }.observes('selectValue'),

  selectValue: function() {
    // randomly used this one
    return this.store.find('age', 3);
  }.property()

});

答案 2 :(得分:0)

givanse的答案应该有效。

我不认为它是因为值未定义,但因为值只是一个整数(42)并且不等于任何选择内容,即Person对象({id:2, label:&#39; middle&#39;,base:42})。

你可以做类似于给予建议或使用关系的东西。

模型

//
// Models
//

App.Person = DS.Model.extend({
  name: DS.attr('string'),
  ageBase: DS.belongsTo('age', { async: true })
});

App.Age = DS.Model.extend({
  label: DS.attr('string'),
  base: DS.attr('number')
});

//
// Fixtures
//

App.Person.reopenClass({
  FIXTURES: [
    { id: 1, name: 'John Doe', ageBase: 2 }
  ]
});

App.Age.reopenClass({
  FIXTURES: [
    { id: 1, label: 'young',  base: 2  },
    { id: 2, label: 'middle', base: 42 },
    { id: 3, label: 'old',    base: 82 }
  ]
});

模板:

<h1>Edit</h1>

<pre>
  name: {{model.name}}
  age: {{model.ageBase.base}}
</pre>

<form>
  <p>Name {{input value=model.name}}</p>
  <p>
    Age
    {{view "select" value=model.ageBase
           content=ages
           optionValuePath="content"
           optionLabelPath="content.label"}}
  </p>
</form>

答案 3 :(得分:0)

好的,我找到了一个我认为更令人满意的解决方案。我认为问题来自ages是一个承诺。解决方案是确保在呈现页面之前加载年龄列表。

我是这样做的:

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return Ember.RSVP.hash({
      person: this.store.find('person', 1),
      ages: this.store.findAll('age')
    });
  }
});

那就是它!我需要的就是根据新模型改变视图:

{{#with model}}
  <form>
    <p>Name {{input value=person.name}}</p>
    <p>
      Age
      {{view "select" value=person.ageBase
             content=ages
             optionValuePath="content.base"
             optionLabelPath="content.label"}}
    </p>
  </form>
{{/with}}

可以在此处找到完整的工作解决方案:http://jsbin.com/qeriyohacu/1/edit?html,js,output

再次感谢@givanse和@Larsaronen的回答:)