3 Stimmen

Asynchrones Vorlagenladen und Post-Render-Aktionen mit Backbone

Ich verwende das Backbone-Boilerplate, um meine Vorlagen zu rendern. Die fetchTemplate-Methode speichert die gerenderten Vorlagen im Cache.

Ich möchte gerne zusätzlichen Code auf dem gerenderten Inhalt ausführen, z. B. Akkordeons initialisieren usw., aber dies mit einer asynchron kompilierten Vorlage ist schwieriger als gedacht.

Hier ist ein Beispiel:

Duel.Views.Home = Backbone.View.extend({
  template: "/templates/duel_home.jade",
  render: function() {
    var view = this;
    statusapp.fetchTemplate(this.template, function(tmpl) {
      $(view.el).html( tmpl({duels: view.collection.toJSON()}) );
      view.postrender();
    });
    return this;
  },
  postrender: function() {
    $('#duel-new').each(function() {
      console.log('Etwas gefunden')
    });
  }
});

Neben dem oben Genannten verwende ich einen View-Handler, wie unter http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/ dargestellt.

Dafür mache ich etwas wie

var view = Duel.Views.Home({model: mymodel})
viewHandler('#content').showView(view)

dies ruft

$('#content').html(view.render().el)

Aber wenn die Vorlage noch nicht im Cache ist, wird zuerst render aufgerufen, und postrender wird rechtzeitig aufgerufen. Andererseits, wenn die Vorlage bereits im Cache ist, wird die Vorlage sofort gerendert, postrender wird aufgerufen, aber view.el ist noch nicht in das DOM eingefügt worden, daher ist $(this.el) eine leere Liste und $('#duel-new').each() ist "leer".

Natürlich könnte ich die postrender-Methode nach dem Aufruf von viewHandler's render hinzufügen, aber das führt zu demselben Problem, jedoch beim ersten Aufruf der render-Methode. Da die Vorlage noch nicht kompiliert ist, wird postrender aufgerufen, bevor ihre Elemente existieren würden, daher könnten keine Handler für diese noch nicht vorhandenen Elemente definiert werden.

Ideen, wie man dieses Problem richtig überwinden kann? Es ist relativ einfach für einfache Klick-Ereignisse mit z.B. .on, aber was ist mit allgemeineren Strukturen wie $('#tabs').tabs() zum Beispiel?

Meine fetchTemplate-Funktion sieht wie folgt aus:

fetchTemplate: function(path, done) {
  window.JST = window.JST || {};

  // Sollte eine sofortige synchrone Möglichkeit bieten, um die Vorlage zu erhalten, wenn sie im JST-Objekt vorhanden ist.
  if (JST[path]) {
    return done(JST[path]);
  }

  // Lade sie asynchron, wenn sie nicht im JST verfügbar ist
  return $.get(path, function(contents) {
    var tmpl = jade.compile(contents,{other: "locals"});
    JST[path] = tmpl;

    return done(tmpl);
  });
},

5voto

ggozad Punkte 13107

Es besteht keine Notwendigkeit für all diese Komplikationen.

Das ursprüngliche fetchTemplate gibt ein jQuery promise zurück. Auch Ihre Version sollte dies tun, falls Sie nichts über die jQuery Deferreds und Promises wissen, ist es eine gute Gelegenheit, sich damit zu befassen. Callbacks sind veraltet ;)

Mit Promises wird alles ganz einfach: Rufen Sie in Ihrem initialize das Template ab und weisen Sie das Promise zu. Rendern Sie dann erst, wenn das Promise erfüllt wurde, zum Beispiel:

Duel.Views.Home = Backbone.View.extend({
    initialize: function () {
       this.templateFetched = statusapp.fetchTemplate(this.template);

    },
    ...

    render: function () {
        var view = this;
        this.templateFetched.done(function (tmpl) {
            view.$el.html( tmpl({duels: view.collection.toJSON()}) );
            ... // Alle Ihre UI-Extras hier...
        });
    }
});

Beachten Sie, dass das Promise, sobald es erfüllt wurde, immer sofort ausgeführt wird. Sie können natürlich dasselbe Muster anwenden, wenn Sie Ihre Ansichten außerhalb der Ansicht in $el ändern, d.h. den Code in view.templatedFetched.done(...) einpacken.

2voto

Parth Thakkar Punkte 5367

Ich habe den Artikel gelesen, auf den du den Link gegeben hast - den Zombies Artikel. Schön war er und soweit ich sehen kann, enthält er bereits die Antwort auf deine Frage, alles was benötigt wird, ist es zu suchen. Was ich denken kann, nachdem ich deine Frage mehrmals gelesen und überdacht habe, ist dass du vielleicht die .NET Methode verwenden möchtest (wie im Zombies Artikel vorgeschlagen). Das heißt, etwas wie:

// im showView Methode des viewHandler
if (this.currentView) {
    this.currentView.close();
}

this.currentView = view;
this.elem.html( this.currentView.render().el );
if ( this.currentView.onAddToDom )  // DAS IST DER WICHTIGE TEIL.
    this.currentView.onAddToDom();

In der Ansicht fügst du eine 'onAddToDom' Methode hinzu, die aufgerufen wird, wenn deine Ansicht zum dom hinzugefügt wird. Dies kann verwendet werden, um die postrender() Methode aufzurufen oder du könntest postrender() in 'onAddToDom()' umbenennen. Auf diese Weise ist das Problem gelöst. Wie? Die Erklärung folgt.

Du kannst deine Ansicht neu definieren als:

Duel.Views.Home = Backbone.View.extend({
  template: "/templates/duel_home.jade",
  render: function() {
    var view = this;
    statusapp.fetchTemplate(this.template, function(tmpl) {
      $(view.el).html( tmpl({duels: view.collection.toJSON()}) );
    });
    return this;
  },
  onAddToDom: function() {
    $('#duel-new').each(function() {
      console.log('Found something')
    });
  }
});

Jetzt, wenn du etwas wie

var view = Duel.Views.Home({model: mymodel})
viewHandler('#content').showView(view);

gemacht wird, wird dies aufgerufen

$('#content').html(view.render().el)
if(view.onAddToDom)
    view.onAddToDom();

welches (was zuvor als) postrender() Methode aufruft.

Problem gelöst.

Warnung: Aber beachte, dies wird scheitern (das heißt, onAddToDom -- oder sollen wir es postrender nennen? -- wird nicht aufgerufen), wenn view.render() direkt aufgerufen wird und nicht von viewHandler('selector').showView aus, da wir onAddToDom von dort aus aufrufen. Aber sowieso, dies ist nicht notwendig, denn wenn wir etwas nach dem Rendern aufrufen wollten, könnten wir das zur render() Methode hinzufügen. Ich wollte nur sicherstellen, dass es keine Verwirrung gibt, also habe ich diese Warnung gegeben.

0voto

Vincent Briglia Punkte 3068

Können Sie versuchen, die markierten Zeilen zu ändern:

view.$el ist das Gleiche wie das Erstellen Ihres eigenen $(view.el), dies ist syntaktischer Zucker in Backbone 0.9.0+

$('#duel-new') durchsucht den gesamten DOM-Baum, während $('#duel-new', this.$el) nur innerhalb des Bereichs Ihrer aktuellen Ansicht sucht und damit die Menge an Zeit für die DOM-Traversierung erheblich reduziert.

obwohl dies Ihr spezielles und besonderes Problem möglicherweise nicht löst, hatte ich selbst keine Probleme mit

Duel.Views.Home = Backbone.View.extend({
  template: "/templates/duel_home.jade",
  render: function() {
    var view = this;
    statusapp.fetchTemplate(this.template, function(tmpl) {
      view.$el.html( tmpl({duels: view.collection.toJSON()}) ); // ändere das
      view.postrender();
    });
    return this;
  },
  postrender: function() {
    $('#duel-new', this.$el).each(function() { // ändere das
      console.log('Etwas gefunden')
    });
  }
});

0voto

JMM Punkte 24490
  1. Welche Version von Backbone verwendest du?

  2. Verwendest du jQuery, Zepto oder etwas anderes? Welche Version?

  3. Tritt das Problem nur auf, wenn die Vorlage bereits zwischengespeichert ist und nicht asynchron abgerufen wird? Wenn ja, kannst du ein Beispiel in einem jsfiddle erstellen?

  4. In welchem Browser tritt das Problem auf?

  5. also $(this.el) ist eine leere Liste, und $('#duel-new').each() ist "void"

    Bitte definiere genau, was du mit "leere Liste" und "void" meinst. Es sei denn, etwas ist ernsthaft durcheinander, mit jQuery $( this.el ) sollte ein jQuery-Objekt mit der Länge 1 sein. Mit jQuery $( '#duel-new' ).each() sollte ein jQuery-Objekt sein, möglicherweise mit der Länge 0.

    Wie @20100 erwähnt hat, wenn deine Backbone-Version dies unterstützt, ist es besser, this.$el anstelle von $( this.el ) zu verwenden.

  6. hiermit wird aufgerufen

     $('#content').html(view.render().el)

    jQuery.html() wird nur als Akzeptanz eines Zeichenfolgenarguments dokumentiert, daher glaube ich nicht, dass dies eine gute Idee ist, wenn jQuery verwendet wird.

  7. Dabei mache ich etwas wie

     var view = Duel.Views.Home({model: mymodel})
     viewHandler('#content').showView(view)

    Sollte es nicht new Duel.Views.Home( { model : mymodel } ) sein? Andernfalls wird innerhalb des Konstruktors this Duel.Views sein.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X