2012-05-31 19 views
14

Soy nuevo en backbone.js y tengo problemas para resolver un problema. Estoy diseñando un proceso de tipo "asistente" (por ejemplo, un formulario de pasos múltiples). Este asistente debería poder manejar diferentes lógica de bifurcación de pantalla dependiendo de la respuesta del usuario a las preguntas, almacenar las respuestas en cada pantalla a medida que el usuario progresa y, al final, ser capaz de serializar todas las respuestas del formulario (cada paso) en un objeto grande (probablemente JSON). Las preguntas del asistente cambiarán de año en año, y debo ser capaz de admitir múltiples asistentes similares que existen al mismo tiempo.Crear un proceso de asistente en backbone.js

Tengo lo básico en cuanto a la creación de pantallas (utilizando formas de red troncal), pero ahora estoy al punto en el que quiero guardar la entrada del usuario y no puedo pensar en la mejor manera de hazlo. La mayoría de los ejemplos que he visto tienen un tipo específico de objeto (por ejemplo, Todo) y usted acaba de crear una colección de ellos (por ejemplo, TodoList), pero tengo una mezcla de definiciones Backbone.Model debido a los diferentes tipos de pantalla, por lo no parece tan simple. ¿Alguna sugerencia sobre cómo debería crear una instancia de mi asistente y sus pantallas, y registrar las respuestas de los usuarios?

Si me ayuda puedo publicar un jsfiddle con mi código de vista que hasta ahora solo va hacia adelante y hacia atrás (no hay registro de respuesta de entrada del usuario o bifurcación de pantalla).

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 

Editar: Conforme a lo solicitado, me gustaría ver un valor de retorno JSON para un mago que se ha guardado a ser algo como esto (como un objetivo final):

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

Hola. ¿Puedes explicar lo que quieres buscar?¿Cómo se ve la respuesta? Tal vez puedas dar un ejemplo? Por cierto, muy buena pregunta. – theotheo

+0

Hola, la búsqueda sería para recuperar las respuestas previamente guardadas del usuario a las preguntas del asistente (lo que significa que también tendría que haber un método de guardar para el modelo). Me encantaría darte un ejemplo de cómo sería la respuesta, pero parece que no puedo pensar en cómo estructurar esta aplicación lo suficiente como para darte eso. ¡Necesito un cerebro más grande! –

+0

@ user1248256 OK, gracias a su pregunta, he pensado un poco más sobre esto y he editado la pregunta para incluir lo que finalmente me gustaría que una consulta guardada del asistente devuelva en JSON (aproximadamente). Tal vez tendré que crear un nuevo modelo o parchar algunos de los métodos de sincronización o análisis sintáctico de Backbone. –

Respuesta

14

Lo más importante lo que debe hacer es separar el flujo de trabajo de las vistas. Es decir, debe tener un objeto que coordine el flujo de trabajo entre las vistas, retenga los datos que se ingresaron en las vistas y use los resultados de las vistas (a través de eventos u otros medios) para descubrir a dónde ir siguiente.

He blog acerca de esto con más detalle, con un ejemplo muy simple de una interfaz de asistente de estilo, aquí:

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

y aquí:

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

Aquí es el código básico de esa primera publicación, que muestra el objeto de flujo de trabajo y cómo coordina las vistas:


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, gracias por la respuesta. He leído las publicaciones del blog y, desafortunadamente, no creo que sea una buena opción para lo que estoy haciendo (suponiendo que lo entiendo). El asistente que estoy haciendo solo existe para crear una burbuja de datos que se utilizará para automatizar el llenado de un archivo PDF existente muy grande y no bien estructurado con el que estoy estrechamente vinculado, por lo que no puedo desentrañar todas las piezas en buenos modelos; en realidad es más fácil tener una serie de "pantallas" genéricas, ya que tengo que volver a mapear en la estructura de PDF de todos modos. Definitivamente resulta difícil ajustar esto en una estructura tipo MVC. –

+0

+1 por el tiempo que pasó respondiendo eso ... de un adicto adicto al usuario :) –

+0

¿Cómo colocaría la lógica de ida y vuelta para esas vistas? Tengo problemas para entender para extender las devoluciones de llamada con una lógica "anterior/siguiente" sin tener un montón de código redundante. – Gambo

5

Estoy marcando la respuesta de Derick como aceptada, ya que es más limpia que la que tengo, pero no es una solución que pueda usar en mi caso ya que tengo más de 50 pantallas para tratar que no puedo dividir en modelos agradables. Me han dado su contenido y simplemente tengo que replicarlos.

Aquí está el código de modelo hacky que se me ocurrió que maneja la lógica de conmutación de pantalla. Estoy seguro de que terminaré refaccionando mucho más a medida que sigo trabajando en ello.

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

En CloudMunch, tuvimos una necesidad similar y construyeron Marionette-Wizard.

w.r.t la Q explícita, en este asistente, todo el contenido se almacena en localStorage y se puede acceder como un objeto similar al formato que ha especificado.

Cuestiones relacionadas