使用Backbone在模型更改上呈现表单会导致表单UI错误

时间:2012-08-22 20:03:00

标签: javascript backbone.js

我有一个简单的视图,当模型发生变化时,它会将自身绑定到重绘,因为大多数指南都指示:

this.model.bind('change', this.render, this);

此视图的表单带有输入框和提交按钮。我绑定到输入框的“更改”事件以更改模型上的相关项。 (我可以手动执行此操作,也可以使用ModelBinder项目,以相同的方式工作。)

用户更改表单输入框中的值,然后单击提交按钮。更新模型,重新呈现视图和表单。提交按钮的单击事件被压缩。

我正在使用Backbone的事件属性:

events : {
    'submit form' : 'save'
},

我知道事件被忽略了,因为点击的DOM元素不再存在。我可以在render()中放置一个小的setTimeout,以防止HTML被换出,并且事情按预期工作,但这需要等待。

我不能成为第一个与此斗争的人 - 捕获表单“更改”事件,更新模型以及重新绘制视图而不丢失某些关键点击或按键信息的标准方法是什么?

同样,如果我有多个表单项,则在重新绘制表单后,用户无法在更改内容后在项目之间切换。

更新1 - 3天后

仍在努力寻找一个好的解决方案。我尝试过的事情:

  • 将视图内容移动或克隆到页面上的其他区域。仍然没有收到点击事件。
  • 使用$(document).on或$(document).live而不是标准视图事件对象注册click事件
  • 分离出表单,以便整个表单(输入和按钮)保持在一起而不重新绘制。重绘父元素(依赖于表单值)并重新插入已绘制的表单。这解决了无法跨元素选项卡的相关问题,但不修复单击事件。
  • 在firefox 4中按预期工作,但不是ie9或chrome。 *

更新2 - 使用示例代码

其中一条评论要求提供一些代码。我已经将代码大量简化为一页并将其包含在下面。实际的应用程序要复杂得多。下面的代码很简单,我可以手动重新渲染页面的部分内容。在实际的应用程序中,我使用的是dustjs模板,即使我不重新渲染表单,但重新渲染包含表单的元素,我点击提交时也会遇到问题。我希望有一种典型的骨干应用程序“模式”,包括复杂的页面,模型和表单。

大多数“演示”应用程序甚至我使用主干网看到的网站似乎主要是以演示为中心的应用程序,实际上并未从用户那里收集大量输入。如果你知道一个基于主干的良好的数据收集重点应用程序/演示会有所帮助。

代码:

<!doctype html>
<html lang="en">
<head>
    <script src="libs/jquery.js"></script>
    <script src="libs/underscore.js"></script>
    <script src="libs/backbone.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/template" id="edit_user_template">
    <h3>Edit User <%=id%> : <%=name%></h3>
    <form>
        Name: <input type="text" name="name" value="<%=name%>"><br/>
        Email: <input type="text" name="email" value="<%=email%>"><br/>
        <input type="submit">
    </form>
</script>
<script>
    var UserModel = Backbone.Model.extend({});
    var model = new UserModel({id: '1', name:'Joe', email:'a@a.com'});
    var UserView = Backbone.View.extend({
        events : {
            'submit form' : 'save',
            'change input' : 'inputChange'
        },
        initialize: function(){
            this.model.bind('change', this.render, this);
        },
        render: function(){
            var template = _.template($('#edit_user_template').html(), this.model.toJSON());
            this.$el.html(template);
        },
        inputChange : function(evt){
            var target = $(evt.currentTarget);
            this.model.set(target.attr('name'), target.val());
        },
        save : function(event){
            console.log('saving');
            event.preventDefault();
            //this.model.save();
        }
    });
    var view = new UserView({model: model, el: '#container'});
    view.render();
</script>
</body>
</html>

5 个答案:

答案 0 :(得分:1)

你要求骨干模式。模式是使用模型来保存表单信息(就像您正在做的那样),然后更新页面上的特定元素,而不是每次模型更改时重新呈现所有内容。 ModelBinder提供了一种很好的方法(https://github.com/theironcook/Backbone.ModelBinder)。

我认为你不会找到一种无法提交不再存在的表格的方法。你需要以不同的方式完成你想要的东西。

答案 1 :(得分:1)

如果我理解正确,您需要做的是将视图拆分为2个较小的视图。一个人会在改变时重新渲染,另一个人会在表格中显示。

根据您的情况,您可能也可能不希望第三个视图将这两个视图绑定在一起。

旁注:

当您使用MVC设计模式时,它往往是递归的。我想是这样的。将表单中的数据视为模型,将重新呈现视图视为MVC的“视图”部分,将表单视图视为“控制器”。

答案 2 :(得分:0)

不太明白你要做什么,看着我明白你想要模拟knockoutjs的绑定模型的代码,这很棒。

无论如何,如果你分成两个在同一型号上运行的视图,你可以获得类似的«效果»。一个更新模型,另一个观察更改和自动更新。

以下是代码:

<!doctype html>
<html lang="en">
<head>
    <script src="libs/jquery.min.js"></script>
    <script src="libs/underscore.min.js"></script>
    <script src="libs/backbone.min.js"></script>
</head>
<body>
    <div id="header"></div>
    <div id="container"></div>
<script type="text/template" id="user_template">
    <h3>Edit User <%=id%> : <%=name%></h3>
</script>
<script type="text/template" id="edit_user_template">
    <form>
        Name: <input type="text" name="name" value="<%=name%>"><br/>
        Email: <input type="text" name="email" value="<%=email%>"><br/>
        <input type="submit">
    </form>
</script>
<script>
    var UserModel = Backbone.Model.extend({});
    var model = new UserModel({id: '1', name:'Joe', email:'a@a.com'});

    var UserView = Backbone.View.extend({
        initialize: function(){
            this.model.on("change", this.render, this);
        },
        render: function(){
            var template = _.template($('#user_template').html(), this.model.toJSON());
            this.$el.html(template);
            return this;
        },
    });

    var FormView = Backbone.View.extend({
        events : {
            'submit form' : 'save',
            'keyup input' : 'inputChange'
        },
        render: function(){
            var template = _.template($('#edit_user_template').html(), this.model.toJSON());
            this.$el.html(template);
            return this;
        },
        inputChange : function(evt){
            console.log('change');
            var target = $(evt.currentTarget);
            this.model.set(target.attr('name'), target.val());
        },
        save : function(event){
            console.log('saving');
            event.preventDefault();
            //this.model.save();
        }
    });

    var user = new UserView({model: model, el: '#header'});
    user.render();

    var form = new FormView({model: model, el: '#container'});
    form.render();
</script>
</body>
</html>

答案 3 :(得分:0)

您的问题应该有两种可能的解决方案:

  1. 单击提交输入,而不是提交事件本身。

    events: { 'click input[type=submit]' : 'save' }

    这应该在完全替换html后继续存在。

  2. 渲染后手动重新委派事件。如果您要重新渲染具有子视图的视图,这通常是必要的,但它也适用于此。

    this.delegateEvents()

答案 4 :(得分:0)

我不知道我是否完全依赖骨干来解决你的问题。我认为这是骨干允许您填写自己的代码的情况之一。您可以稍微修改模板并聆听模型中的更改,然后确定需要重新呈现视图的天气,或者只是更新。我不认为我会尝试重新渲染表单部分,除非重置模型

<!doctype html>
<html lang="en">
<head>
<script src="libs/jquery.min.js"></script>
<script src="libs/underscore.min.js"></script>
<script src="libs/backbone.min.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/template" id="edit_user_template">
    <h3>Edit User <span class="f-id"><%=id%></span> : <span class="f-name"><%=name%></span></h3>
    <form>
        Name: <input type="text" name="name" value="<%=name%>"><br/>
        Email: <input type="text" name="email" value="<%=email%>"><br/>
        <input type="submit">
    </form>
</script>
<script>
    var UserModel = Backbone.Model.extend({});
    var model = new UserModel({id: '1', name:'Joe', email:'a@a.com'});
    var UserView = Backbone.View.extend({
        events : {
            'submit form' : 'save',
            'change input' : 'inputChange'
        },
        initialize: function(){
            //you could have the change event trigger updates instead of re-rendering the form
            //this.model.bind('change', this.update, this);
        },
        render: function(){
            var template = _.template($('#edit_user_template').html(), this.model.toJSON());
            this.$el.html(template);
        },
        inputChange : function(evt){
            var target = $(evt.currentTarget);
            this.model.set(target.attr('name'), target.val());
        },
        update:function(){
            // you could trigger other things here if you need other views to know what is going on.
            $(".f-id").text(this.model.get("id"));
            $(".f-name").text(this.model.get("name"));          
        },
        save : function(event){
            console.log('saving');
            this.update();
            event.preventDefault();
        }
    });
    var view = new UserView({model: model, el: '#container'});
    view.render();
</script>
</body>
</html>