2012-05-31 4 views
14

Ich bin neu bei backbone.js und habe Probleme, meinen Kopf um ein Problem zu wickeln, das ich beim Entwerfen eines "Wizard" -Typ-Prozesses (ein mehrstufiges Formular) erstellt habe. Dieser Assistent sollte in der Lage sein, unterschiedliche Bildschirmverzweigungslogik abhängig von der Antwort des Benutzers auf Fragen zu behandeln, die Antworten auf jedem Bildschirm während des Benutzerfortschritts zu speichern und am Ende in der Lage sein, alle Formularantworten (jeden Schritt) in ein großes Objekt zu serialisieren (wahrscheinlich JSON). Die Fragen des Assistenten werden sich von Jahr zu Jahr ändern, und ich muss in der Lage sein, mehrere ähnliche Assistenten gleichzeitig zu unterstützen.Einen Wizard-Prozess in backbone.js erstellen

Ich habe die Grundlagen so weit wie die Erstellung der Bildschirme (mit Backbone-Formen), aber ich bin jetzt an dem Punkt, wo ich Benutzereingaben speichern möchte und ich kann nicht den besten Weg zu denken TU es. Die meisten der Beispiele, die ich gesehen habe, haben einen bestimmten Objekttyp (zB Todo) und Sie erstellen einfach eine Sammlung von ihnen (zB TodoList), aber ich habe eine gemischte Tasche von Backbone.Model-Definitionen wegen der unterschiedlichen Bildschirmtypen es scheint nicht ganz so einfach zu sein. Irgendwelche Hinweise darauf, wie ich meinen Assistenten und seine enthaltenen Bildschirme instanziieren und Benutzerreaktionen aufzeichnen sollte?

Wenn es hilft, kann ich eine jsfiddle mit meiner Ansicht Code, der bisher nur vorwärts und rückwärts Bildschirme (keine Benutzereingabe Antwort Aufzeichnung oder Bildschirm Verzweigung) geht.

var Wizard = Backbone.Model.extend({ 

    initialize: function(options) { 
     this.set({ 
      pathTaken: [0] 
     }); 
    }, 

    currentScreenID: function() { 
     return _(this.get('pathTaken')).last(); 
    }, 

    currentScreen: function() { 
     return this.screens[this.currentScreenID()]; 
    }, 

    isFirstScreen: function(screen) { 
     return (_(this.screens).first() == this.currentScreen()); 
    }, 

    // This function should be overridden by wizards that have 
    // multiple possible "finish" screens (depending on path taken) 
    isLastScreen: function(screen) { 
     return (_(this.screens).last() == this.currentScreen()); 
    }, 

    // This function should be overridden by non-trivial wizards 
    // for complex path handling logic 
    nextScreen: function() { 
     // return immediately if on final screen 
     if (this.isLastScreen(this.currentScreen())) return; 
     // otherwise return the next screen in the list 
     this.get('pathTaken').push(this.currentScreenID() + 1); 
     return this.currentScreen(); 
    }, 

    prevScreen: function() { 
     // return immediately if on first screen 
     if (this.isFirstScreen(this.currentScreen())) return; 
     // otherwise return the previous screen in the list 
     prevScreenID = _(_(this.get('pathTaken')).pop()).last(); 
     return this.screens[prevScreenID]; 
    } 
}); 

var ChocolateWizard = Wizard.extend({ 
    nextScreen: function() { 
     //TODO: Implement this (calls super for now) 
     //  Should go from screen 0 to 1 OR 2, depending on user response 
     return Wizard.prototype.nextScreen.call(this); 
    }, 
    screens: [ 
     // 0 
     Backbone.Model.extend({ 
      title : "Chocolate quiz", 
      schema: { 
       name: 'Text', 
       likesChocolate: { 
        title: 'Do you like chocolate?', 
        type: 'Radio', 
        options: ['Yes', 'No'] 
       } 
      } 
     }), 
     // 1 
     Backbone.Model.extend({ 
      title : "I'm glad you like chocolate!", 
      schema: { 
       chocolateTypePreference: { 
        title: 'Which type do you prefer?', 
        type: 'Radio', 
        options: [ 'Milk chocolate', 'Dark chocolate' ] 
       } 
      } 
     }), 
     //2 
     Backbone.Model.extend({ 
      title : "So you don't like chocolate.", 
      schema: { 
       otherSweet: { 
        title: 'What type of sweet do you prefer then?', 
        type: 'Text' 
       } 
      } 
     }) 
    ] 
}); 

wizard = new ChocolateWizard(); 

// I'd like to do something like wizard.screens.fetch here to get 
// any saved responses, but wizard.screens is an array of model 
// *definitions*, and I need to be working with *instances* in 
// order to fetch 

Edit: Wie gewünscht, würde Ich mag einen JSON Rückgabewert für einen Assistenten, um zu sehen, die ungefähr so ​​aussehen (als Endziel) gespeichert wurden:

wizardResponse = { 
    userID: 1, 
    wizardType: "Chocolate", 
    screenResponses: [ 
     { likesChocolate: "No"}, 
     {}, 
     { otherSweet: "Vanilla ice cream" } 
    ] 
} 
+0

Hallo. Kannst du erklären, was du holen willst?Wie sieht die Antwort aus? Vielleicht kannst du ein Beispiel geben? Btw, sehr nette Frage. – theotheo

+0

Hallo, der Abruf wäre zum Abrufen der zuvor gespeicherten Antworten des Benutzers auf die Fragen des Assistenten (was bedeutet, dass ich auch eine Speichermethode für das Modell arbeiten müsste). Ich würde Ihnen gerne ein Beispiel geben, wie die Antwort aussehen würde, aber ich kann einfach nicht darüber nachdenken, wie ich diese App so strukturieren könnte, dass ich Ihnen das gebe. Ich brauche ein größeres Gehirn! –

+0

@ user1248256 OK, dank Ihrer Frage habe ich ein bisschen mehr darüber nachgedacht und die Frage bearbeitet, um einzuschließen, was ich schließlich möchte, dass eine gespeicherte Zaubererabfrage in JSON (ungefähr) zurückkommt. Vielleicht muss ich ein neues Modell erstellen oder einige der Backbone-Methoden zur Synchronisierung oder Analyse patchen? –

Antwort

14

Die große Sache Sie müssen den Arbeitsablauf von den Ansichten selbst trennen. Das heißt, Sie sollten ein Objekt haben, das den Arbeitsfluss zwischen den Ansichten koordiniert, sich an den Daten festhält, die in die Ansichten eingegeben wurden, und die Ergebnisse der Ansichten (über Ereignisse oder andere Mittel) verwendet, um herauszufinden, wohin es gehen soll Nächster.

Ich habe über diese im Detail, mit einem sehr einfachen Beispiel eines Assistenten-ähnliche Schnittstelle gebloggt, hier:

http://lostechies.com/derickbailey/2012/05/10/modeling-explicit-workflow-with-code-in-javascript-and-backbone-apps/

und hier:

http://lostechies.com/derickbailey/2012/05/15/workflow-in-backbone-apps-triggering-view-events-from-dom-events/

Hier der Basiscode aus diesem ersten Post, der das Workflow-Objekt und die Koordinaten der Ansichten anzeigt:


orgChart = { 

    addNewEmployee: function(){ 
    var that = this; 

    var employeeDetail = this.getEmployeeDetail(); 
    employeeDetail.on("complete", function(employee){ 

     var managerSelector = that.selectManager(employee); 
     managerSelector.on("save", function(employee){ 
     employee.save(); 
     }); 

    }); 
    }, 

    getEmployeeDetail: function(){ 
    var form = new EmployeeDetailForm(); 
    form.render(); 
    $("#wizard").html(form.el); 
    return form; 
    }, 

    selectManager: function(employee){ 
    var form = new SelectManagerForm({ 
     model: employee 
    }); 
    form.render(); 
    $("#wizard").html(form.el); 
    return form; 
    } 
} 

// implementation details for EmployeeDetailForm go here 

// implementation details for SelectManagerForm go here 

// implementation details for Employee model go here 
+0

+1, danke für die Antwort. Ich habe die Blogposts gelesen und ich glaube leider nicht, dass dies für meine Arbeit gut passt (vorausgesetzt, ich verstehe es). Der Assistent, den ich gerade ausführe, existiert nur, um einen Datenblock zu erstellen, der zum automatischen Ausfüllen einer vorhandenen sehr großen und nicht gut strukturierten PDF-Datei verwendet wird, mit der ich eng verbunden bin. Das kann ich nicht nimm alle Teile in schöne Modelle heraus; Es ist tatsächlich einfacher, nur eine Reihe von generischen "Bildschirmen" zu haben, da ich am Ende wieder in die PDF-Struktur mappen muss. Es war definitiv schwierig es in eine MVC-ähnliche Struktur zu integrieren. –

+0

+1 für die Zeit, die Sie damit verbracht haben, ... von einem süchtigen Backbone-Benutzer zu beantworten :) –

+0

Wie würden Sie die Logik für diese Ansichten hin und her legen? Ich habe Probleme zu verstehen, um die Rückrufe mit einer "vorherigen/nächsten" Logik zu erweitern, ohne viel redundanten Code zu haben. – Gambo

5

Ich stimme Dericks Antwort als akzeptiert, da es sauberer ist als das, was ich habe, aber es ist keine Lösung, die ich in meinem Fall verwenden kann, da ich über 50 Bildschirme habe, mit denen ich nicht in schöne Modelle einbrechen kann - Ich habe ihnen ihren Inhalt gegeben und muss sie einfach replizieren.

Hier ist der Hacky-Modellcode, den ich entwickelt habe, der Bildschirmschaltlogik handhabt. Ich bin mir sicher, dass ich es später noch viel besser umgestalten werde, wenn ich weiter daran arbeite.

var Wizard = Backbone.Model.extend({ 

    initialize: function(options) { 
     this.set({ 
      pathTaken: [0], 
      // instantiate the screen definitions as screenResponses 
      screenResponses: _(this.screens).map(function(s){ return new s; }) 
     }); 
    }, 

    currentScreenID: function() { 
     return _(this.get('pathTaken')).last(); 
    }, 

    currentScreen: function() { 
     return this.screens[this.currentScreenID()]; 
    }, 

    isFirstScreen: function(screen) { 
     screen = screen || this.currentScreen(); 
     return (_(this.screens).first() === screen); 
    }, 

    // This function should be overridden by wizards that have 
    // multiple possible "finish" screens (depending on path taken) 
    isLastScreen: function(screen) { 
     screen = screen || this.currentScreen(); 
     return (_(this.screens).last() === screen); 
    }, 

    // This function should be overridden by non-trivial wizards 
    // for complex path handling logic 
    nextScreenID: function(currentScreenID, currentScreen) { 
     // default behavior is to return the next screen ID in the list 
     return currentScreenID + 1; 
    }, 

    nextScreen: function() { 
     // return immediately if on final screen 
     if (this.isLastScreen()) return; 
     // otherwise get next screen id from nextScreenID function 
     nsid = this.nextScreenID(this.currentScreenID(), this.currentScreen()); 
     if (nsid) { 
      this.get('pathTaken').push(nsid); 
      return nsid; 
     } 
    }, 

    prevScreen: function() { 
     // return immediately if on first screen 
     if (this.isFirstScreen()) return; 
     // otherwise return the previous screen in the list 
     prevScreenID = _(_(this.get('pathTaken')).pop()).last(); 
     return this.screens[prevScreenID]; 
    } 

}); 

var ChocolateWizard = Wizard.extend({ 

    initialize: function(options) { 
     Wizard.prototype.initialize.call(this); // super() 

     this.set({ 
      wizardType: 'Chocolate', 
     }); 
    }, 

    nextScreenID: function(csid, cs) { 
     var resp = this.screenResponses(csid); 
     this.nextScreenController.setState(csid.toString()); // have to manually change states 
     if (resp.nextScreenID) 
      // if screen defines a custom nextScreenID method, use it 
      return resp.nextScreenID(resp, this.get('screenResponses')); 
     else 
      // otherwise return next screen number by default 
      return csid + 1; 
    }, 

    // convenience function 
    screenResponses: function(i) { 
     return this.get('screenResponses')[i]; 
    }, 

    screens: [ 
     // 0 
     Backbone.Model.extend({ 
      title : "Chocolate quiz", 
      schema: { 
       name: 'Text', 
       likesChocolate: { 
        title: 'Do you like chocolate?', 
        type: 'Radio', 
        options: ['Yes', 'No'] 
       } 
      }, 
      nextScreenID: function(thisResp, allResp) { 
       if (thisResp.get('likesChocolate') === 'Yes') 
        return 1; 
       else 
        return 2; 
      } 
     }), 
     // 1 
     Backbone.Model.extend({ 
      title : "I'm glad you like chocolate!", 
      schema: { 
       chocolateTypePreference: { 
        title: 'Which type do you prefer?', 
        type: 'Radio', 
        options: [ 'Milk chocolate', 'Dark chocolate' ] 
       } 
      }, 
      nextScreenID: function(thisResp, allResp) { 
       return 3; // skip screen 2 
      } 
     }), 
     // 2 
     Backbone.Model.extend({ 
      title : "So you don't like chocolate.", 
      schema: { 
       otherSweet: { 
        title: 'What type of sweet do you prefer then?', 
        type: 'Text' 
       } 
      } 
     }), 
     // 3 
     Backbone.Model.extend({ 
      title : "Finished - thanks for taking the quiz!" 
     } 
    ] 
}); 
0

Bei CloudMunch hatten wir ein ähnliches Bedürfnis und gebaut Marionette-Wizard.

w.r. das explizite Q, in diesem Wizard wird der gesamte Inhalt in localStorage gespeichert und kann als Objekt ähnlich dem von Ihnen angegebenen Format aufgerufen werden.