【问题标题】:Updating Backbone.js View becoming a mess更新 Backbone.js 视图变得一团糟
【发布时间】:2012-05-06 21:03:10
【问题描述】:

给定一个模型:

MyModel = Backbone.Model.extend({
  defaults: {
    name: '',
    age: -1,
    height: '',
    description: ''
  }
});

以及将模型呈现为列表的视图:

MyView  = Backbone.View.extend({
  tagName: 'ul',
  className: 'MyView',

  render() {
    var values = {
      name: this.model.get('name'),
      age: this.model.get('age'),
      height: this.model.get('height'),
      description: this.model.get('description')
    }

    var myTemplate = $('#MyView-Template').html();
    var templateWithValues = _.template(myTemplate , values);
  }
});

以及视图加载的模板:

<script type="text/template" id="MyView-Template">  
  <li class="name"><%= name %></li>
  <li class="age"><%= age %></li>
  <li class="name"><%= height%></li>
  <li class="name"><%= description%></li>
</script>

一切正常,虽然这是一个人为的例子,但真实的代码在模型中有很多很多的属性。我遇到的问题是如何处理模型更新。

我创建了一个 HTML 表单,其中每个字段都有一个适当的输入元素。表单被建模并作为模板加载:

<script type="text/template" id="MyEditView-Template">  
  <input type"text" value="<%= name %>" /> <br />
  <input type"text" value="<%= age%>" /> <br />
  <input type"text" value="<%= height%>" /> <br />
  <input type"text" value="<%= description%>" /> 
</script>

并加载到视图中:

MyEditView  = Backbone.View.extend({
      tagName: 'form',
      className: 'MyEditView',

      render() {
        var values = {
          name: this.model.get('name'),
          age: this.model.get('age'),
          height: this.model.get('height'),
          description: this.model.get('description')
        }

        var myTemplate = $('#MyEditView-Template').html();
        var templateWithValues = _.template(myTemplate , values);
      }
    });

当用户保存表单时,新值会在模型 (MyModel) 中设置。但是我不想重新渲染整个原始视图,它需要太长时间并且有很多嵌套元素。我只想更新在模型中更改了其值的 HTML 元素。

问题是如何优雅地将模型的属性链接到 HTML 元素,以便我可以对已经渲染的视图执行以下操作:

  1. 迭代模型的属性。
  2. 确定哪些属性已被修改。
  3. 仅更新已修改属性的 UI。
  4. 隐藏任何以前呈现的不应再显示的属性的 UI。

我有一个 JavaScript 查找表(只是一个对象)的相当丑陋的解决方案,它将属性名称映射到 HTML 元素字符串:

var AttributesMap = {
    name: {
        htmlRef: 'li.name',
        attributeName: 'name'
    },
    age: {
        htmlRef: 'li.age',
        attributeName: 'age'
    }
    ...
}

这感觉很hacky,导致一些相当臃肿的代码。

【问题讨论】:

  • 使用.toJSON 而不是手动创建values 集合:this.model.toJSON() 返回的结构与您手动创建的结构相同。

标签: backbone.js


【解决方案1】:

您的帖子中实际上隐藏了两个问题。您对模型的属性有疑问,并且您不知道如何订阅模型更改事件。幸运的是,使用backbone.js 可以轻松实现这两者。将您的视图代码更改为以下

1

render: function () {
    var model = this.model;
    $(this.el).empty().html(_.template(this.template, this.model.toJSON()))
    return this;
}

其中el 是定义容器的视图的属性。 toJSON() 是一种方法,您可以在模型上调用它来序列化可以通过网络传输的格式。

2

视图应该在其initialize 函数中订阅模型更改事件,或者更恰当地使用delegated event 支持。当模型属性发生变化时,会在该模型上调用 change 事件,您可以像 here 和下面的示例一样订阅该事件。

window.ListView = Backbone.View.extend({
    initialize: function () {
        //pass model:your_model when creating instance
        this.model.on("change:name", this.updateName, this);
        this.model.on("change:age", this.changedAge, this);
    },
    render: function () {
        var model = this.model;
        $(this.el).empty().html(_.template(this.template, this.model.toJSON()))
        return this;
    },
    updateName: function () {
        alert("Models name has been changed");
    },
    changedAge: function () {
        alert("Models age has been changed");
    }
});

JsBin 示例

http://jsbin.com/exuvum/2

【讨论】:

  • 您回答的第 1 部分的问题是值可能未定义,在这种情况下,我根本不希望模板为该属性呈现 UI 元素。使用 toJSON() 加载所有属性并不能解决该问题。它仍然只是属性的转储。
  • 在第 2 部分中,处理特定属性更新的方法仍然需要在 DOM 中“找到”它们各自的 HTML 元素并对其进行更新。尽管第 2 部分确实帮助了我并且会清理我的代码!谢谢。
  • @Jack 您需要覆盖defaults:{} 才能击败评论#1。对于评论 #2 Offcouse,您想将单个属性更新为 UI 元素,对吗?如果不是,您只需要订阅 change 事件,而无需将属性名称挂钩渲染函数作为 this 的回调
  • 因此,如果一个属性的值与defaults{} 中定义的值匹配 - 它不会包含在从toJSON() 返回的对象中?
  • @Jack 您好,抱歉耽搁了,我在 jsbin 中添加了一个示例供您学习。请注意,空字符串需要通过validate 方法进行验证。如果未指定defaults,则采取
【解决方案2】:

我遇到了类似的问题,我希望模板只显示有数据的字段。我从下划线模板方面解决了这个问题,因为 引发了异常。 The solution,对我来说,是将包装器对象传递给包含模型数据的模板。模型数据的包装如下:

this.$el.html(this.template({my_data: this.model.toJSON()}));

您的模板检查是否存在所需的属性:

<% if(my_data.phone) { %><p><%= my_data.phone %> </p><% } %>

您可以在每次模型更改时自动渲染整个视图。使用此方法,新值将出现,删除的值将从 UI 中消失。

与您的要求相关的更多信息:

迭代模型的属性。确定哪些属性有 被修改。

如果您想知道自上次触发模型的“更改”事件以来哪些属性发生了变化,您可以使用Backbone Model's changedAttributes 方法。

仅更新已修改属性的 UI。隐藏以前的任何用户界面 不应再显示的渲染属性。

代替为发生的每个属性更改渲染整个视图,您可以通过让每个 UI 字段成为离散的主干视图来仅更新 UI 中已更改属性的部分。所有视图都将监听共享模型以获取特定模型属性的更改事件:

    this.model.on("change:phone", this.render, this);

【讨论】:

    【解决方案3】:

    嗯,Backbone 使用事件委托,并且在替换 this.el 内容后不需要重新订阅元素事件。但是每次模板同步都会破坏 DOM 子树,这意味着您会丢失表单的状态。只需尝试为您的模型订阅输入/更改事件。所以用户输入输入元素和表单验证。恢复控件的状态(比如 input[type=file])将具有挑战性并且需要大量资源。

    我相信最好的方法是使用基于 DOM 的模板引擎,它只在需要的地方更新目标元素。例如。我的是https://github.com/dsheiko/ng-template

    你可以有这样的模板:

    <form id="heroForm" novalidate>
      <div class="form-group">
        <label for="name">Name</label>
        <input id="name" type="text" class="form-control" required >
        <div class="alert alert-danger" data-ng-if="!name.valid">
          Name is required
        </div>
      </div>
      <div class="form-group">
        <label for="power">Hero Power</label>
        <select id="power" class="form-control"  required>
          <option data-ng-for="let p of powers" data-ng-text="p" >Nothing here</option>
        </select>
        <div class="alert alert-danger" data-ng-if="!power.valid">
          Power is required
        </div>
      </div>
       <button type="submit" class="btn btn-default" data-ng-prop="'disabled', !form.valid">Submit</button>
    </form>
    

    在这里我们绑定了模型namepowerform。每当他们的状态发生变化时(例如,当用户打字时),模板就会做出反应。它可能隐藏/显示错误消息,或禁用/启用提交按钮。

    如果它很有趣 - 如何将它与 Backbone 捆绑在一起,这里有一本免费的在线小书 https://dsheiko.gitbooks.io/ng-backbone/

    【讨论】:

      猜你喜欢
      • 2016-11-06
      • 2021-11-21
      • 2014-12-01
      • 1970-01-01
      • 2014-05-25
      • 2011-06-18
      • 1970-01-01
      • 2018-12-05
      • 1970-01-01
      相关资源
      最近更新 更多