Использование Backbone для визуализации формы при изменении модели вызывает ошибки пользовательского интерфейса

У меня есть простой вид, который привязывает себя к перерисовке, когда модель изменяется, поскольку большинство направляющих указывают:

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

Этот вид имеет форму с полем ввода и кнопкой отправки. Я привязываюсь к событию "change" в поле ввода, чтобы изменить связанный элемент модели. (Я могу сделать это вручную или с проектом ModelBinder, работает одинаково в любом случае.)

Пользователь меняет значение в поле ввода формы, затем нажимает кнопку отправки. Модель обновляется, вид и форма повторно отображаются. Событие нажатия кнопки отправки распадается.

Я использую свойство Backbone events:

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

Я знаю, что событие игнорируется, потому что элемент DOM, на который был нажат, больше не существует. Я могу поместить небольшой setTimeout внутри render(), чтобы предотвратить замену HTML, и все работает так, как ожидалось, но для этого требуется ожидание.

Я не могу быть первым человеком, который мог бы бороться с этим - какой стандартный способ захвата формы "изменить" события, обновить модель и перерисовать представление, не теряя при этом нажатия клавиш или нажатия клавиш?

Аналогично, если у меня есть несколько элементов формы, пользователь не может заносить вкладку между элементами после изменения содержимого по мере того, как форма перерисовывается.

Обновление 1 - 3 дня спустя

По-прежнему пытается найти хорошее решение. Вещи, которые я пробовал:

  • перемещение или клонирование содержимого представления в другую область на странице. Событие клика по-прежнему никогда не принимается.
  • регистрация события click с $(document).on или $(document).live вместо объекта стандартных событий представления
  • отделяя форму так, чтобы вся форма (входы и кнопки) оставалась вместе, не перерисовываясь. Перерисовать родительские элементы (которые полагаются на значения формы) и повторно вставить уже нарисованную форму. Это устраняет связанную с этим проблему неспособности выполнить вкладку по элементу, но не фиксирует события щелчка.
  • работает как угодно в firefox 4, но не ie9 или хром. *

Обновление 2 - с примером кода

Один из комментариев задал какой-то код. Я значительно упростил код на одной странице и включил его ниже. Фактическое приложение намного сложнее. С помощью кода, приведенного ниже, я мог бы просто вручную повторно отобразить части страницы при изменении. В фактическом приложении я использую шаблоны dustjs, и даже если я не повторно визуализую форму, но повторно переопределяю элементы, содержащие форму, на которую возникают проблемы, нажав на submit. Я надеюсь на "шаблон", который типичен для базовых приложений, включая сложные страницы, модели и формы.

Большинство "демонстрационных" приложений и даже сайтов, которые я видел с использованием магистральной сети, как правило, представляют собой приложения, ориентированные на презентацию, которые на самом деле не собирают много ввода от пользователя. Если вы знаете о хорошем приложении/демонстрации, ориентированном на сбор данных, на основе основы, которая была бы полезной.

код:

<!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:'[email protected]'});
    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>

Ответы

Ответ 1

Вы просите шаблон в позвоночнике. Шаблон должен использовать модели для хранения информации о форме (например, вы делаете), а затем обновлять определенные элементы на странице вместо повторного рендеринга каждый раз, когда изменяется модель. ModelBinder предлагает хороший способ сделать все это (https://github.com/theironcook/Backbone.ModelBinder).

Я не думаю, что вы найдете способ избежать возможности представить форму, которая больше не существует. Вам нужно выполнить то, что вы хотите по-другому.

Ответ 2

Если я правильно понимаю, что вам нужно сделать, это разделить представление на 2 более мелкие. Один будет повторно отображать при изменении, другой будет отображать форму.

В зависимости от вашего случая вы можете или не захотите также третьего вида связать эти два вместе.

Боковой комментарий:

При использовании шаблона проектирования MVC он имеет тенденцию быть рекурсивным. Я думаю, что это так. Подумайте о данных в форме как о модели, о представлении повторного рендеринга как о "представлении" MVC, а в виде вида "контроллер".

Ответ 3

Не совсем понимаю, что вы пытаетесь сделать, наблюдая за кодом, который я понимаю, что вы хотите имитировать модель привязки 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:'[email protected]'});

    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>

Ответ 4

В вашей проблеме должно быть два возможных решения:

  • Привязать к вводу ввода вместо самого события отправки.

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

    Это должно выдержать полную замену html.

  • Повторно делегировать события вручную после рендеринга. Это обычно необходимо, если вы повторно просматриваете представление с суб-представлениями, но оно будет применяться и здесь.

    this.delegateEvents()

Ответ 5

Я не знаю, буду ли я полностью полагаться на позвоночник, чтобы решить вашу проблему. Я думаю, что это одна из тех ситуаций, когда позвоночник позволяет вам заполнить свой собственный код. Вы можете немного изменить шаблоны и прислушаться к изменениям в модели, а затем решить, что представление о погоде должно быть повторно отображено или просто обновлено. Я не думаю, что попробую перепрофилировать части форм, если модель не будет reset

<!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:'[email protected]'});
    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>