Подтвердить что ты не робот

Отключение обхода div для Backbone.Marionette.ItemView

Я смотрю на обучающие сообщения Angry Cats Backbone/Marionette здесь

http://davidsulc.com/blog/2012/04/15/a-simple-backbone-marionette-tutorial/

http://davidsulc.com/blog/2012/04/22/a-simple-backbone-marionette-tutorial-part-2/

и я столкнулся с тем же вопросом/вопросом, который был здесь размещен:

Backbone.js отключает обертку с помощью div в рендеринге

Но я могу получить это только для работы с Backbone.Views, а не с Backbone.Marionette.ItemViews.

Например, из простых ссылок на основные марионеточные руководства для марионетки, возьмите AngryCatView:

AngryCatView = Backbone.Marionette.ItemView.extend({
  template: "#angry_cat-template",
  tagName: 'tr',
  className: 'angry_cat',
  ...
});

Шаблон #angry_cat-template выглядит следующим образом:

<script type="text/template" id="angry_cat-template">
  <td><%= rank %></td>
  <td><%= votes %></td>
  <td><%= name %></td>
  ...
</script>

Что мне не нравится, так это то, что AngryCatView должен иметь

  tagName: 'tr',
  className: 'angry_cat',

- если взять tagName out, тогда angry_cat-template будет обернут <div>.

Я хотел бы указать HTML в одном месте (шаблон angry_cat) и не иметь большинство HTML (все теги <td>) в шаблоне angry_cat и немного HTML (тег <tr>) в AngryCatView. Я хотел бы написать это в шаблоне angry_cat:

<script type="text/template" id="angry_cat-template">
  <tr class="angry_cat">
    <td><%= rank %></td>
    <td><%= votes %></td>
    <td><%= name %></td>
    ...
  </tr>
</script>

Он просто чувствует себя более чистым для меня, но я решаюсь с ответом Дерика Бейли в "Backbone.js, который отключает обертку с помощью div в рендеринге" и не может заставить его работать на Backbone.Marionette.

Любые идеи?

4b9b3361

Ответ 1

2014/02/18 - обновлен, чтобы учесть улучшения, отмеченные @vaughan и @Thom-Nichols в комментариях


Во многих моих элементах/макетах я делаю это:

var Layout = Backbone.Marionette.Layout.extend({

    ...

    onRender: function () {
        // Get rid of that pesky wrapping-div.
        // Assumes 1 child element present in template.
        this.$el = this.$el.children();
        // Unwrap the element to prevent infinitely 
        // nesting elements during re-render.
        this.$el.unwrap();
        this.setElement(this.$el);
    }

    ...

});

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

В вашем случае .children() вернет <tr class="angry_cat">, поэтому это должно работать идеально.

Я согласен, он сохраняет шаблоны намного чище.

Одно замечание:

Этот метод не заставляет только 1 дочерний элемент. Он слепо захватывает .children(), поэтому, если вы неправильно построили шаблон для возврата более одного элемента, например, пример первого шаблона с 3 <td>, он не будет работать.

Он требует, чтобы ваш шаблон возвращал один элемент, как и во втором шаблоне с корневым элементом <tr>.

Конечно, это может быть написано для проверки на это, если это необходимо.


Вот пример для любопытных: http://codepen.io/somethingkindawierd/pen/txnpE

Ответ 2

В то время как я уверен, что есть способ взломать внутренние элементы render, чтобы заставить его вести себя так, как вам хочется, этот подход означает, что вы будете бороться с соглашениями Backbone и Marionette через всю разработку обработать. ItemView должен иметь связанный $el, и, условно, это a div, если вы не укажете tagName.

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

Ответ 3

Это решение работает для повторной рендеринга. Вам нужно переопределить render.

onRender трюки не будут работать для повторной обработки. Они будут вызывать развёртывание при каждом повторном рендеринге.

BM.ItemView::render = ->
  @isClosed = false
  @triggerMethod "before:render", this
  @triggerMethod "item:before:render", this
  data = @serializeData()
  data = @mixinTemplateHelpers(data)
  template = @getTemplate()
  html = Marionette.Renderer.render(template, data)

  #@$el.html html
  $newEl = $ html
  @$el.replaceWith $newEl
  @setElement $newEl

  @bindUIElements()
  @triggerMethod "render", this
  @triggerMethod "item:rendered", this
  this

Ответ 4

Не было бы более чистым использовать vanilla JS вместо jQuery для выполнения этого?

var Layout = Backbone.Marionette.LayoutView.extend({

  ...

  onRender: function () {
    this.setElement(this.el.innerHTML);
  }

  ...

});

Ответ 5

Для IE9 + вы можете просто использовать firstElementChild и childElementCount:

var Layout = Backbone.Marionette.LayoutView.extend({

  ...

  onRender: function () {
      if (this.el.childElementCount == 1) {
          this.setElement(this.el.firstElementChild);
      }
  }

  ...

});

Есть веская причина, по которой Marionette автоматически вставляет обертку DIV. Это только тогда, когда ваш шаблон состоит из одного элемента, когда вы можете его удалить. Следовательно, проверка количества дочерних элементов.

Другой вариант - использовать метод attachElContent, присутствующий в каждом представлении Marionette. Его реализация по умолчанию означает, что переопределения представления будут перезаписывать внутренний HTML-код корневого элемента. Это в конечном итоге приводит к бесконечному гнезду, упомянутому в ответе Bejonbee.

Если вы предпочитаете не перезаписывать onRender и/или не нуждаться в решении pure-JS, следующий код может быть только тем, что вы хотите:

var Layout = Backbone.Marionette.LayoutView.extend({

  ...

  attachElContent: function (html) {
      var parentEl = this.el.parentElement;
      var oldEl;

      //View already attached to the DOM => re-render case => prevents
      //recursive nesting by considering template top element as the
      //view when re-rendering
      if (parentEl) {
          oldEl = this.el;
          this.setElement(html);                   //gets new element from parsed html
          parentEl.replaceChild(this.el, oldEl);   //updates the dom with the new element 
          return this;

      //View hasn't been attached to the DOM yet => first render 
      // => gets rid of wrapper DIV if only one child
      } else {
          Marionette.ItemView.prototype.attachElContent.call(this, html);
          if (this.el.childElementCount == 1) {
              this.setElement(this.el.firstElementChild);
          }
          return this;
      }
  }

  ...

});

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