14

私は backbone.js を初めて使用し、「ウィザード」タイプのプロセス (別名マルチステップ フォーム) を設計している問題に頭を悩ませています。このウィザードは、質問に対するユーザーの応答に応じてさまざまな画面分岐ロジックを処理し、ユーザーの進行に応じて各画面への応答を保存し、最後にすべてのフォーム応答 (すべてのステップ) を 1 つの大きなオブジェクトにシリアル化できる必要があります。 (おそらくJSON)。ウィザードの質問は年々変化するため、複数の同様のウィザードが同時に存在することをサポートできる必要があります。

画面の作成 (バックボーン フォームを使用) までの基本は理解しましたが、ユーザー入力を保存したいところまで来ましたが、それを行う最善の方法が思いつきません。私が見たほとんどの例には、特定のタイプのオブジェクト (例: 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

編集: 要求に応じて、(最終目標として) 次のように保存されたウィザードの JSON 戻り値を確認したいと思います。

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

3 に答える 3

14

あなたがしなければならない大きなことは、ワークフローをビュー自体から分離することです。つまり、ビュー間のワークフローを調整し、ビューに入力されたデータを保持し、(イベントまたはその他の手段を通じて) ビューの結果を使用してどこに行くべきかを判断するオブジェクトが必要です。次。

これについては、ウィザード スタイルのインターフェイスの非常に単純な例を使用して、より詳細にブログに書いています。

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
于 2012-05-31T17:07:14.380 に答える
5

デリックの回答は、私が持っているものよりもクリーンであるため、受け入れられたものとしてマークしていますが、50以上の画面を処理する必要があるため、私のケースでは使用できるソリューションではありません。それらのコンテンツと単にそれらを複製する必要があります。

これは、画面切り替えロジックを処理する、私が思いついたハック モデル コードです。作業を続けるうちに、さらに多くのリファクタリングを行うことになると確信しています。

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!"
        }
    ]
});
于 2012-06-01T16:07:21.977 に答える
0

CloudMunchでも同様のニーズがあり、Marionette-Wizardを作成しました。

明示的な Q に関しては、このウィザードではすべてのコンテンツが localStorage に保存され、指定した形式と同様のオブジェクトとしてアクセスできます。

于 2016-12-21T07:34:53.513 に答える