使用Backbone在模型更改时渲染表单会导致表单UI错误

Using Backbone to render a form on model change causes form UI errors

本文关键字:表单 错误 UI Backbone 模型 使用      更新时间:2023-09-26

我有一个简单的视图,当模型发生变化时,它会绑定自己来重新绘制,正如大多数指南所示:

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

此视图有一个带有输入框和提交按钮的表单。我正在绑定到输入框的"更改"事件,以更改模型上的相关项。(我可以手动完成,也可以使用ModelBinder项目,无论哪种方式都可以。)

用户更改表单输入框中的值,然后单击提交按钮。模型将更新,视图和形状将重新渲染。提交按钮的单击事件被压扁。

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

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

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

我不是第一个遇到这种情况的人——在不丢失一些按键或按键信息的情况下,捕捉表单"更改"事件、更新模型和重新绘制视图的标准方法是什么?

类似地,如果我有多个表单项,则在重新绘制表单时更改内容后,用户无法在这些项之间切换。

更新1-3天后

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

  • 将视图的内容移动或克隆到页面上的其他区域。单击事件仍然从未收到
  • 使用$(document).on或$(document).live而不是标准视图事件对象注册单击事件
  • 分离出表单,使整个表单(输入和按钮)保持在一起而不被重新绘制。重新绘制父元素(依赖于表单值)并重新插入已绘制的表单。这解决了无法在元素之间进行制表的相关问题,但不能解决单击事件
  • 在firefox4中按需工作,但不是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>

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

我不认为你会找到一种方法来避免无法提交一份已经不存在的表格。你需要用不同的方式来完成你想要的。

如果我理解正确,您需要做的是将视图拆分为两个较小的视图。一个将在更改时重新渲染,另一个将显示形式本身。

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

附带评论:

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

不太明白你想做什么,看着代码我明白你想模拟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>

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

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

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

    这应该在html被完全替换后仍然有效。

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

    this.delegateEvents()

我不知道我是否会完全依靠骨干来解决你的问题。我认为这是主干网允许您用自己的代码填充的情况之一。您可以稍微修改一下模板,听取模型中的更改,然后决定是需要重新渲染视图,还是只更新视图。我不认为我会尝试重新渲染表单部分,除非模型重置

<!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>