使用Ember + Handlebars在运行时动态选择视图

时间:2012-01-11 18:31:23

标签: ember.js handlebars.js

我使用Ember,Ember Data和Handlebars来显示包含多种不同类型模型的时间轴。我目前的实现虽然运行正常,但似乎可以通过约定和帮助器大幅改进。但是,我无法弄清楚如何使用已定义的模板。

这就是我所拥有的:

{{#view App.AccountSelectedView contentBinding="App.selectedAccountController.everythingSorted"}}
  {{#with content}}
    <ol class="timeline">
      {{#each this}}
        {{#is constructor="App.Design"}}
        ... stuff about the design
        {{/is}}
        {{#is constructor="App.Order"}}
        ... stuff about the order
        {{/is}}
        {{#is constructor="App.Message"}}
        ... stuff about the message
        {{/is}}
      {{/each}}
    </ol>
  {{/with}}
{{/view}}

......还有帮助者......

Handlebars.registerHelper('is', function(options) {
  if (this.constructor == options.hash["constructor"]) {
    return options.fn(this);
  }
});

我宁愿依靠一些约定来确定要渲染的视图。例如:

<script type="text/x-handlebars-template" data-model="App.Design" id="design-view">
... stuff about the design
</script>

<script type="text/x-handlebars-template" data-model="App.Order" id="order-view">
... stuff about the order
</script>

也许数据模型属性可用于确定对象的呈现方式。

{{#view App.SelectedAccountView contentBinding="App.selectedAccountController.everythingSorted"}}
  {{#with content}}
    <ol class="timeline">
      {{#each this}}
        {{viewish this}}
      {{/each}}
    </ol>
  {{/with}}
{{/view}}

唉,我无法弄清楚如何从帮助者访问模板。

Handlebars.registerHelper('viewish', function(options) {
   // Were I able to access the templates this question
   // would be unnecessary.
   // Handlebars.TEMPLATES is undefined...
});

另外,这是我应该用Handlebars做的事吗?

4 个答案:

答案 0 :(得分:5)

使用ViewStates,请参阅以下示例:

http://jsfiddle.net/rsaccon/AD2RY/

答案 1 :(得分:4)

我通过使用mixin建立自己的约定来解决这个问题。模型对应于具有相似名称的视图。例如,App.Design模型实例对应于视图App.DesignView。

App.ViewTypeConvention = Ember.Mixin.create({
  viewType: function() {
    return Em.getPath(this.get('constructor') + 'View');
  }.property().cacheable()
});

我把它混合到我的模特中......

App.Design.reopen(App.ViewTypeConvention);
App.Order.reopen(App.ViewTypeConvention);

...并迭代这样的混合集合:

{{#each content}}
  {{view item.viewType tagName="li" contentBinding="this"}}
{{/each}}

这样,我避免在模型中明确定义约定。感谢Gordon,我意识到视图可以通过使用对象上的属性来指定。我仍然真的想听听解决这个问题的“正确”方法。

答案 2 :(得分:1)

这只是我的头脑:我会为每种模型类型创建一个单独的模板/视图。例如。会有DesignViewOrderView等。其中每个都会指定与templateName一起使用的模板(所有代码coffeescript):

App.DesignView = Em.View.extend
  templateName: 'design'

App.OrderView = Em.View.extend
  templateName: 'order'

每种类型的所有自定义渲染都将在视图/模板中完成。

此时我们需要一些模板逻辑来决定为每个项目显示哪个视图。 最简单的要做的事情是将viewType存储在模型上。

App.Design = Em.Model.extend
  viewType: App.DesignView

App.Order = Em.Model.extend
  viewType: App.OrderView

然后模板看起来像:

{{#collection contentBinding="App.selectedAccountController.everythingSorted"}}
  {{view content.viewType contentBinding="content"}}
{{/collection}}

然而,这并不理想,因为我们不希望模型知道视图层。相反,我们可以创建一些工厂逻辑来为模型创建视图。然后我们可以在控制器上创建一个计算属性,该属性包含模型的数组及其相应的视图:

App.selectedAccountController = Em.ArrayController.create
  ..
  viewForModel: (model) ->
    # if model is instance of Design return DesignView, Order return OrderView etc.
  everythingSortedWithViews: ( ->
    everythingSorted.map (model) ->
      {model: model, viewType: @viewForModel(model)}
  ).property('everythingSorted')

模板看起来像这样:

{{#collection contentBinding="App.selectedAccountController.everythingSortedWithView"}}
  {{view content.viewType contentBinding="content.model"}}
{{/collection}}

可能有更好的方法来做到这一点。我很想听到一个更接近Ember核心的人提出解决方案。

答案 3 :(得分:1)

这是我用于类似场景的内容。

模特&#39;页面&#39; hasMany&#39;活动&#39;。

// App.PageModel
export default DS.Model.extend({
    index     : DS.attr('number'),
    activity  : DS.hasMany('activity',  { async: true })
});

模特&#39;活动&#39;有财产&#39;类型&#39;引用哪个模板用于另一个属性&#39;配置&#39;。

中的内容
// App.ActivityModel
export default DS.Model.extend({
    activityId    : DS.attr('string'),
    type          : DS.attr('string'),
    page          : DS.belongsTo('page', { async: true }),
    configuration : DS.attr()
});

请注意配置缺少属性类型。这提供了存储随机结构对象集合的方法。对于结构一致的对象,我建议使用Ember-Data.Model-Fragments

主要模板:

{{! page.hbs }}
{{#with activity}}
    {{#each}}
        {{partial type}}
    {{/each}}
{{/with}}

对于type:&#39; static&#39;,它使用{{{3 mustache option}}}来呈现html字符串。

{{! static.hbs }}
{{{configuration.content}}}

其他选项要复杂得多,但仍使用&#39;进行简化。即:对于类型:&#39; multiplechoice&#39;,

{{! multiplechoice.hbs }}
{{#with configuration}}
    {{#each options}}
    <label {{bind-attr class=":label selected:checked:unchecked"}}>
        {{view Ember.Checkbox checkedBinding="selected" }}
        {{#if text.content}}
            {{{text.content}}}
        {{else}}
            {{text}}
        {{/if}}
    </label>
    {{/each}}
    {{ ...etc... }}
{{/with}}

对于部分内容,请记住根据您的环境考虑命名和/或文件夹结构,即&#39; _partialname.hbs&#39;或者&#39; viewname / partialname.hbs&#39;