Skip to content

Instantly share code, notes, and snippets.

@TimNZ
Forked from mxriverlynn/marionette.gauntlet.js
Last active December 12, 2015 05:28
Show Gist options
  • Select an option

  • Save TimNZ/4722337 to your computer and use it in GitHub Desktop.

Select an option

Save TimNZ/4722337 to your computer and use it in GitHub Desktop.

Revisions

  1. TimNZ revised this gist Feb 6, 2013. 1 changed file with 301 additions and 233 deletions.
    534 changes: 301 additions & 233 deletions marionette.gauntlet.js
    Original file line number Diff line number Diff line change
    @@ -6,239 +6,307 @@
    //
    // Copyright (C) 2012 Muted Solutions, LLC.
    // Distributed under MIT license

    Marionette.Gauntlet = (function(Backbone, Picky, Marionette, $, _){
    // Wizard Steps
    // ------------

    // A selectable model that represents an individual step
    // in the wizard / workflow
    var WizardStep = Backbone.Model.extend({
    initialize: function(){
    var selectable = new Picky.Selectable(this);
    _.extend(this, selectable);
    }
    });

    // A collection of wizard steps, defining the over-all workflow
    // of the wizard.
    var WizardStepCollection = Backbone.Collection.extend({
    model: WizardStep,

    initialize: function(){
    this.emptyStep = new this.model({isEmpty: true});
    this.finalStep = new this.model({isFinal: true, name: "Done"});

    var selectable = new Picky.SingleSelect(this);
    _.extend(this, selectable);
    },

    // Get the next step in the workflow
    getNext: function(){
    var index, nextIndex, nextStep;

    if (!this.selected){
    index = 0;
    } else {
    index = this.indexOf(this.selected);
    }

    if (index < this.length-1){
    nextIndex = index + 1;
    nextStep = this.at(nextIndex) || this.emptyStep;
    } else {
    nextStep = this.finalStep;
    }

    return nextStep;
    },

    // Get the previous step in the workflow
    getPrevious: function(){
    var index, nextIndex;

    if (!this.selected){
    index = 0;
    } else {
    index = this.indexOf(this.selected);
    }

    if (index > 0){
    nextIndex = index - 1;
    } else {
    nextIndex = -1;
    }

    return this.at(nextIndex) || this.emptyStep;
    }
    });

    // Guantlet Controller
    // -------------------

    var Gauntlet = Marionette.Controller.extend({
    constructor: function(options){
    Marionette.Controller.prototype.constructor.apply(this, arguments);

    if (!options || !options.workflowSteps){
    var error = new Error("Wizard needs `workflowSteps` badly.")
    error.name = "NoWorkflowStepsError";
    throw error;
    }

    this.steps = new WizardStepCollection(options.workflowSteps);
    this.filters = {};
    this.onSteps = {};
    },

    // Get the list of steps that run this workflow
    getSteps: function(){
    return this.steps;
    },

    // Update the list of steps that run this workflow
    updateSteps: function(workflowSteps) {
    var _currentStep = this._currentStep;
    var currentStepId = _currentStep ? _currentStep.id : null;
    var currentStep = null;

    this.steps.reset(workflowSteps);

    if (currentStepId && (currentStep = this.steps.get(currentStepId))) {
    this._setCurrentStep(currentStep);
    }
    },

    // Move to the next step in the workflow
    nextStep: function(){
    var nextStep = this.steps.getNext();
    this.moveTo(nextStep);
    },

    // Move to the previous step in the workflow
    previousStep: function(){
    var previousStep = this.steps.getPrevious();
    this.moveTo(previousStep);
    },

    // Complete the workflow and move on
    complete: function(){
    var onComplete = this.filters.onComplete;

    if (_.isObject(onComplete)){
    onComplete.fn.call(onComplete.context);
    }

    this.trigger("complete");
    this._clearCurrentStep();
    },

    // Add a step handler by key, providing a handler
    // callback function and an optional context object
    // for executing the handler callback
    onStep: function(stepKey, handler, context){
    var stepList = this.onSteps[stepKey];

    if (!stepList){
    stepList = [];
    this.onSteps[stepKey] = stepList;
    }

    stepList.push({
    handler: handler,
    context: context
    });
    },

    // Run a callback before moving to another step
    beforeMove: function(fn, context){
    this.filters.beforeMove = {fn: fn, context: context};
    },

    // Run a callback after the workflow has been completed
    onComplete: function(fn, context){
    this.filters.onComplete = {fn: fn, context: context};
    },

    // Move to a given step by the step's "key"
    setStepByKey: function(stepKey){
    stepKey = stepKey || "";
    var step = this.steps.where({key: stepKey})[0];
    if (step){
    this._setCurrentStep(step);
    }
    },

    // Move to a specified step
    moveTo: function(step){

    // build a function to call when we're ready to move
    var done = _.bind(function(){

    // set the current step, then run all the step callbacks
    this._setCurrentStep(step, function(){
    var key = step.get("key");
    this._runStepCallbacks(key);
    });

    }, this);

    // only run the beforeMove filter if this is not the first
    // step to be shown in this instance of the gauntlet
    var isFirstUse = (!this._currentStep);
    var beforeMove = this.filters.beforeMove;

    if (!isFirstUse && _.isObject(beforeMove)){
    beforeMove.fn.call(beforeMove.context, done);
    } else {
    done();
    }
    },

    // Set the current step and optionally run a callback
    // function after the current step has been set.
    _setCurrentStep: function(step, cb){
    var key = step.get("key");

    this.trigger("step", key);
    this.trigger("step:" + key);

    if (this._currentStep !== step){
    this._currentStep = step;
    step.select();
    if (cb){ cb.apply(this); }
    }
    },

    // Clear the current step
    _clearCurrentStep: function(){
    if (this._currentStep){
    delete this._currentStep;
    }
    },

    _runStepCallbacks: function(stepKey){
    var stepList = this.onSteps[stepKey];
    if (!stepList){ return; }

    var length = stepList.length;
    for (var i=0; i<length; i++){
    var config = stepList[i];

    if (!_.isFunction(config.handler)){
    var error = new Error("Cannot run step for '" + stepKey + "'. Handler not found.");
    error.name = "StepHandlerNotFound";
    throw error;
    // Modifications by Tim Shnaider
    //

    Marionette.Gauntlet = (function (Backbone, Picky, Marionette, $, _)
    {
    // Wizard Steps
    // ------------

    // A selectable model that represents an individual step
    // in the wizard / workflow
    var WizardStep = Backbone.Model.extend({
    initialize: function ()
    {
    var selectable = new Picky.Selectable(this);
    _.extend(this, selectable);
    }

    config.handler.apply(config.context);
    }
    },

    });

    // Export Public API
    // -----------------
    return Gauntlet;
    });

    // A collection of wizard steps, defining the over-all workflow
    // of the wizard.
    var WizardStepCollection = Backbone.Collection.extend({
    model: WizardStep,

    initialize: function ()
    {
    this.emptyStep = new this.model({ isEmpty: true });
    this.finalStep = new this.model({ isFinal: true, name: "Done" });

    var selectable = new Picky.SingleSelect(this);
    _.extend(this, selectable);
    },

    // Get the next step in the workflow
    getNext: function ()
    {
    var index, nextIndex, nextStep;

    if (!this.selected)
    {
    index = 0;
    } else
    {
    index = this.indexOf(this.selected);
    }

    if (index < this.length - 1)
    {
    nextIndex = index + 1;
    nextStep = this.at(nextIndex) || this.emptyStep;
    } else
    {
    nextStep = this.finalStep;
    }

    return nextStep;
    },

    // Get the previous step in the workflow
    getPrevious: function ()
    {
    var index, nextIndex;

    if (!this.selected)
    {
    index = 0;
    } else
    {
    index = this.indexOf(this.selected);
    }

    if (index > 0)
    {
    nextIndex = index - 1;
    } else
    {
    nextIndex = -1;
    }

    return this.at(nextIndex) || this.emptyStep;
    }
    });

    // Guantlet Controller
    // -------------------

    var Gauntlet = Marionette.Controller.extend({
    constructor: function (options)
    {
    Marionette.Controller.prototype.constructor.apply(this, arguments);

    if (!options || !options.workflowSteps)
    {
    var error = new Error("Wizard needs `workflowSteps` badly.")
    error.name = "NoWorkflowStepsError";
    throw error;
    }

    this.steps = new WizardStepCollection(options.workflowSteps);
    this.filters = {};
    this.onSteps = {};
    },

    // Get the list of steps that run this workflow
    getSteps: function ()
    {
    return this.steps;
    },

    // Update the list of steps that run this workflow
    updateSteps: function (workflowSteps)
    {
    var _currentStep = this._currentStep;
    var currentStepId = _currentStep ? _currentStep.id : null;
    var currentStep = null;

    this.steps.reset(workflowSteps);

    if (currentStepId && (currentStep = this.steps.get(currentStepId)))
    {
    this._setCurrentStep(currentStep);
    }
    },
    getCurrentStep: function()
    {
    return this._currentStep;
    },
    getNextStep: function ()
    {
    return this.steps.getNext();
    },
    getPreviousStep: function ()
    {
    return this.steps.getPrevious();
    },
    // Move to the next step in the workflow
    nextStep: function ()
    {
    var nextStep = this.steps.getNext();
    this.moveTo(nextStep);
    },

    // Move to the previous step in the workflow
    previousStep: function ()
    {
    var previousStep = this.steps.getPrevious();
    this.moveTo(previousStep);
    },

    // Complete the workflow and move on
    complete: function ()
    {
    var onComplete = this.filters.onComplete;

    if (_.isObject(onComplete))
    {
    onComplete.fn.call(onComplete.context);
    }

    this.trigger("complete");
    this._clearCurrentStep();
    },

    // Add a step handler by key, providing a handler
    // callback function and an optional context object
    // for executing the handler callback
    onStep: function (stepKey, handler, context)
    {
    var stepList = this.onSteps[stepKey];

    if (!stepList)
    {
    stepList = [];
    this.onSteps[stepKey] = stepList;
    }

    stepList.push({
    handler: handler,
    context: context
    });
    },

    // Run a callback before moving to another step
    beforeMove: function (fn, context)
    {
    this.filters.beforeMove = { fn: fn, context: context };
    },

    // Run a callback after moving to another step
    afterMove: function (fn, context)
    {
    this.filters.afterMove = { fn: fn, context: context };
    },

    // Run a callback after the workflow has been completed
    onComplete: function (fn, context)
    {
    this.filters.onComplete = { fn: fn, context: context };
    },

    // Move to a given step by the step's "key"
    moveToByKey: function (stepKey)
    {
    stepKey = stepKey || "";
    var step = this.steps.where({ key: stepKey })[0];
    if (step)
    {
    this.moveTo(step);
    }
    },

    // Move to a specified step
    moveTo: function (step)
    {

    var oldStep = this.getCurrentStep();
    // build a function to call when we're ready to move
    var done = _.bind(function (doChangeStep)
    {
    if (doChangeStep)
    {
    // set the current step, then run all the step callbacks
    this._setCurrentStep(step, function ()
    {
    var key = step.get("key");
    this._runStepCallbacks(key);

    var afterMove = this.filters.afterMove;
    if (_.isObject(afterMove))
    {
    this.filters.afterMove.fn.call(afterMove.context, oldStep && oldStep.attributes, step.attributes);
    }
    });
    }

    }, this);

    // only run the beforeMove filter if this is not the first
    // step to be shown in this instance of the gauntlet
    var isFirstUse = (!this._currentStep);
    var beforeMove = this.filters.beforeMove;

    if (/*!isFirstUse && */_.isObject(beforeMove))
    {
    beforeMove.fn.call(beforeMove.context, oldStep && oldStep.attributes, step.attributes, done);
    } else
    {
    done(true);
    }
    },

    // Set the current step and optionally run a callback
    // function after the current step has been set.
    _setCurrentStep: function (step, cb)
    {
    var key = step.get("key");

    this.trigger("step", key);
    this.trigger("step:" + key);

    if (this._currentStep !== step)
    {
    this._currentStep = step;
    step.select();
    if (cb) { cb.apply(this); }
    }
    },

    // Clear the current step
    _clearCurrentStep: function ()
    {
    if (this._currentStep)
    {
    delete this._currentStep;
    }
    },

    _runStepCallbacks: function (stepKey)
    {
    var stepList = this.onSteps[stepKey];
    if (!stepList) { return; }

    var length = stepList.length;
    for (var i = 0; i < length; i++)
    {
    var config = stepList[i];

    if (!_.isFunction(config.handler))
    {
    var error = new Error("Cannot run step for '" + stepKey + "'. Handler not found.");
    error.name = "StepHandlerNotFound";
    throw error;
    }

    config.handler.apply(config.context);
    }
    },

    });

    // Export Public API
    // -----------------
    return Gauntlet;

    })(Backbone, Backbone.Picky, Marionette, $, _);
  2. Derick Bailey revised this gist Jan 21, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion marionette.gauntlet.js
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@
    // --------------------------
    //
    // Build wizard-style workflows with an event-emitting state machine
    // http://github.com/derickbailey/marionette.guantlet
    // Requires Backbone.Picky (http://github.com/derickbailey/backbone.picky)
    //
    // Copyright (C) 2012 Muted Solutions, LLC.
    // Distributed under MIT license
  3. Derick Bailey created this gist Jan 5, 2013.
    244 changes: 244 additions & 0 deletions marionette.gauntlet.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,244 @@
    // Marionette.Gauntlet v0.0.0
    // --------------------------
    //
    // Build wizard-style workflows with an event-emitting state machine
    // http://github.com/derickbailey/marionette.guantlet
    //
    // Copyright (C) 2012 Muted Solutions, LLC.
    // Distributed under MIT license

    Marionette.Gauntlet = (function(Backbone, Picky, Marionette, $, _){
    // Wizard Steps
    // ------------

    // A selectable model that represents an individual step
    // in the wizard / workflow
    var WizardStep = Backbone.Model.extend({
    initialize: function(){
    var selectable = new Picky.Selectable(this);
    _.extend(this, selectable);
    }
    });

    // A collection of wizard steps, defining the over-all workflow
    // of the wizard.
    var WizardStepCollection = Backbone.Collection.extend({
    model: WizardStep,

    initialize: function(){
    this.emptyStep = new this.model({isEmpty: true});
    this.finalStep = new this.model({isFinal: true, name: "Done"});

    var selectable = new Picky.SingleSelect(this);
    _.extend(this, selectable);
    },

    // Get the next step in the workflow
    getNext: function(){
    var index, nextIndex, nextStep;

    if (!this.selected){
    index = 0;
    } else {
    index = this.indexOf(this.selected);
    }

    if (index < this.length-1){
    nextIndex = index + 1;
    nextStep = this.at(nextIndex) || this.emptyStep;
    } else {
    nextStep = this.finalStep;
    }

    return nextStep;
    },

    // Get the previous step in the workflow
    getPrevious: function(){
    var index, nextIndex;

    if (!this.selected){
    index = 0;
    } else {
    index = this.indexOf(this.selected);
    }

    if (index > 0){
    nextIndex = index - 1;
    } else {
    nextIndex = -1;
    }

    return this.at(nextIndex) || this.emptyStep;
    }
    });

    // Guantlet Controller
    // -------------------

    var Gauntlet = Marionette.Controller.extend({
    constructor: function(options){
    Marionette.Controller.prototype.constructor.apply(this, arguments);

    if (!options || !options.workflowSteps){
    var error = new Error("Wizard needs `workflowSteps` badly.")
    error.name = "NoWorkflowStepsError";
    throw error;
    }

    this.steps = new WizardStepCollection(options.workflowSteps);
    this.filters = {};
    this.onSteps = {};
    },

    // Get the list of steps that run this workflow
    getSteps: function(){
    return this.steps;
    },

    // Update the list of steps that run this workflow
    updateSteps: function(workflowSteps) {
    var _currentStep = this._currentStep;
    var currentStepId = _currentStep ? _currentStep.id : null;
    var currentStep = null;

    this.steps.reset(workflowSteps);

    if (currentStepId && (currentStep = this.steps.get(currentStepId))) {
    this._setCurrentStep(currentStep);
    }
    },

    // Move to the next step in the workflow
    nextStep: function(){
    var nextStep = this.steps.getNext();
    this.moveTo(nextStep);
    },

    // Move to the previous step in the workflow
    previousStep: function(){
    var previousStep = this.steps.getPrevious();
    this.moveTo(previousStep);
    },

    // Complete the workflow and move on
    complete: function(){
    var onComplete = this.filters.onComplete;

    if (_.isObject(onComplete)){
    onComplete.fn.call(onComplete.context);
    }

    this.trigger("complete");
    this._clearCurrentStep();
    },

    // Add a step handler by key, providing a handler
    // callback function and an optional context object
    // for executing the handler callback
    onStep: function(stepKey, handler, context){
    var stepList = this.onSteps[stepKey];

    if (!stepList){
    stepList = [];
    this.onSteps[stepKey] = stepList;
    }

    stepList.push({
    handler: handler,
    context: context
    });
    },

    // Run a callback before moving to another step
    beforeMove: function(fn, context){
    this.filters.beforeMove = {fn: fn, context: context};
    },

    // Run a callback after the workflow has been completed
    onComplete: function(fn, context){
    this.filters.onComplete = {fn: fn, context: context};
    },

    // Move to a given step by the step's "key"
    setStepByKey: function(stepKey){
    stepKey = stepKey || "";
    var step = this.steps.where({key: stepKey})[0];
    if (step){
    this._setCurrentStep(step);
    }
    },

    // Move to a specified step
    moveTo: function(step){

    // build a function to call when we're ready to move
    var done = _.bind(function(){

    // set the current step, then run all the step callbacks
    this._setCurrentStep(step, function(){
    var key = step.get("key");
    this._runStepCallbacks(key);
    });

    }, this);

    // only run the beforeMove filter if this is not the first
    // step to be shown in this instance of the gauntlet
    var isFirstUse = (!this._currentStep);
    var beforeMove = this.filters.beforeMove;

    if (!isFirstUse && _.isObject(beforeMove)){
    beforeMove.fn.call(beforeMove.context, done);
    } else {
    done();
    }
    },

    // Set the current step and optionally run a callback
    // function after the current step has been set.
    _setCurrentStep: function(step, cb){
    var key = step.get("key");

    this.trigger("step", key);
    this.trigger("step:" + key);

    if (this._currentStep !== step){
    this._currentStep = step;
    step.select();
    if (cb){ cb.apply(this); }
    }
    },

    // Clear the current step
    _clearCurrentStep: function(){
    if (this._currentStep){
    delete this._currentStep;
    }
    },

    _runStepCallbacks: function(stepKey){
    var stepList = this.onSteps[stepKey];
    if (!stepList){ return; }

    var length = stepList.length;
    for (var i=0; i<length; i++){
    var config = stepList[i];

    if (!_.isFunction(config.handler)){
    var error = new Error("Cannot run step for '" + stepKey + "'. Handler not found.");
    error.name = "StepHandlerNotFound";
    throw error;
    }

    config.handler.apply(config.context);
    }
    },

    });

    // Export Public API
    // -----------------
    return Gauntlet;

    })(Backbone, Backbone.Picky, Marionette, $, _);