2012-05-31 6 views
14

私はbackbone.jsを初めて使用していて、問題を回避するためにいくつかの問題を抱えています。「ウィザード」タイプのプロセス(a.k.a.複数ステップフォーム)を設計しています。このウィザードは、質問に対するユーザーの応答に応じて異なる画面分岐ロジックを処理し、ユーザーが進むにつれて各画面に応答を格納し、最後にすべてのフォーム応答(すべての手順)を1つのラージオブジェクトにシリアル化できるようにする必要があります(おそらくJSON)。ウィザードの質問は毎年変わります。同時に複数の同様のウィザードをサポートする必要があります。backbone.jsでウィザードプロセスを作成

私は、スクリーンを作成する(バックボーンフォームを使用する)まで基本的な考え方を持っていますが、今私はユーザーの入力を保存したいと思っています。それをやる。私が見た例のほとんどは、特定のタイプのオブジェクト(例えばTodo)を持っていて、それらのコレクションを作成するだけです(例えばTodoList)。しかし、異なるタイプのため、Backbone.Modelの定義が混在しています。それはとても単純ではないようです。どのように私はウィザードとその含まれている画面をインスタンス化し、ユーザーの応答を記録する必要があります上の任意のポインター?

もし私が私のビューコードでjsfiddleを投稿することができれば、これまでのところ、前方と後方の画面(ユーザーの入力レスポンスの記録や画面の分岐なし)のみが表示されます。

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 

編集:

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

こんにちは。フェッチしたいものを説明できますか?応答はどのように見えますか?多分あなたは例を挙げることができますか? Btw、非常に良い質問。 – theotheo

+0

こんにちは、フェッチは、ウィザードの質問へのユーザーの以前に保存された応答を取得するためのものです(つまり、私はモデルのために働くセーブメソッドを持っている必要があります)。私はあなたに応答がどのように見えるかの例を挙げたいと思いますが、私はちょうどあなたにそれを与えるためにこのアプリを構築する方法を考えているように思えません。私はより大きな脳が必要です! –

+0

@ user1248256あなたの質問に感謝します。これについてもう少し考えてみましたが、最終的には保存されたウィザードのクエリがJSON(約)に戻るようにするために質問を編集しました。おそらく、私は新しいモデルを作成したり、バックボーンの同期やパース方法の一部を修正しなければならないでしょうか? –

答えて

14

大きなもの:要求されたとして、私は(最終的な目標として)このような何かを探すように保存されているウィザードのJSONの戻り値を見たいのですがワークフローをビュー自体とは別にする必要があります。つまり、ビュー間のワークフローを調整し、ビューに入力されたデータを保持し、ビューの結果を(イベントやその他の手段を使用して)使用して、どこに行くかを把握するオブジェクトを用意する必要があります次。

私はここに、ウィザード形式のインターフェイスの非常に簡単な例で、より詳細に本についてブログまし

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

、ここで:

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

ここですその最初の投稿の基本コードは、ワークフローオブジェクトとそのビューをどのように調整するかを示します。


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、お返事ありがとうございます。私はブログの記事を読んだが、残念ながらこれは私がやっていること(私がそれを理解していると仮定して)に適しているとは思わない。私がやっているウィザードは、私がしっかりと結合している既存の非常に大きくて構造の整ったPDFファイルを自動化するために使用されるデータの塊を作成するためだけに存在します。すべての作品を素敵なモデルに挑戦してください。とにかくPDF構造に戻ってマッピングする必要があるので、実際には一般的な「スクリーン」の配列を持つ方が簡単です。間違いなく、これをMVCのような構造に適合させることは難しいです。 –

+0

中毒なバックボーンユーザーからの返信に+1してください:) –

+0

どのようにこれらのビューに前後のロジックを入れますか?私は冗長なコードをたくさん持たずにコールバックを "前/次の"ロジックで拡張することに問題があります。 – Gambo

5

デリックの答えは、私が持っているものよりも清潔であるとマークしていますが、それは私が素晴らしいモデルに侵入できないことに対処するために50以上のスクリーンを持っているため、私は彼らのコンテンツを与えられているだけで、それらを複製する必要があります。

ここでは、スクリーンスイッチングロジックを処理するために考え出されたハッキーモデルコードを紹介します。私はそれに取り組んでいくうちにリファクタリングを終わらせてしまうだろうと確信しています。 CloudMunch

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

、我々は同様の必要性を持っていたとMarionette-Wizardを構築しました。

w.rtこのウィザードでは、すべてのコンテンツがlocalStorageに保存されており、指定した形式に似たオブジェクトとしてアクセスできます。

関連する問題