Skip to content
bemson edited this page Jul 25, 2011 · 22 revisions

Use Cases for Flow

Below are use-case scenarios for including Flow in your work. Feel free to add your own use case (real or theorized) by editing this page, adding a task, or emailing the author.

Scenario: Preventing Consecutive Execution

Old Way

In order to prevent consecutive executions, functions require variables (and the management thereof).

var isSubmitting = 0;

var submitForm = function () {
  if (!isSubmitting) {
    isSubmitting = 1;
    document.myForm.submit();
    // console.log('submitting form');
  } else {
    // console.log('already submitting');
  }
};

submitForm(); // -> submitting form
submitForm(); // -> already submitting

New Way

Flow remembers it's position in a program, such that an entered state will not be re-entered when invoked consecutively.

var submitForm = new Flow({
  _in: function () {
    document.myForm.submit();
    // console.log('submitting form');
    this.wait();
  },
  _main: function () {
    // console.log('already submitting');
  }
});

submitForm(); // -> submitting form
submitForm(); // -> already submitting

Note: The "_main" component above is only defined for illustrative purposes. (The wait() method is used to prevent executing the "_main" component, during the first call.)_

Scenario: Redirecting Execution

Old Way

In order to redirect execution, a group of functions use the same branching logic.

var myApp = {
  isAuth: 0,
  login: function () {
    if (this.isAuth) {
      this.run();
    } else {
      this.isAuth = 1;
      // console.log('authenticated user');
      this.run();
    }
  },
  run: function () {
    if (!this.isAuth) {
      this.login();
    } else {
      // console.log('running app');
    }
  }
};

myApp.run(); // -> authenticated user
             // -> running app

New Way

Flow traverses a program sequentially (e.g., from first to last), such that any state can intercept accessing it's older siblings.

var myApp= new Flow({
  login: function () {
    _main: function () {
      this.vars('isAuth', 1);
      // console.log('authenticated user');
    },
    _over: function () {
      if (!this.vars('isAuth')) {
        this.go('@self');
      }
    }
  },
  run: function () {
    // console.log('running app');
  }
});

myApp.run(); // -> authenticated user
             // -> running app

Note: Both examples auto-authenticate the user, for illustrative purposes only.

Scenario: Delaying the Call-Chain

Old Way

In order to delay the call-chain (after delaying a statement), functions must synchronize their execution by duplicating the delay or employing a call-back scheme.

function task() {
  var call_1 = function (callback) {
      setTimeout(function () {
        // console.log('call 1 (delayed)');
        callback();
      }, 1000);
    },
    call_2 = function () {
      // console.log('call 2');
    },
    call_3 = function () {
      // console.log('call 3');
    };

  call_1(call_2);
  setTimeout(call_3, 1000);
};

task(); // -> call 1 (delayed)
        // -> call 2
        // -> call 3

New Way

Flow calls functions by traversing their state, such that delaying navigation delays their execution.

var task = new Flow({
  _main: function () {
    this.go('call_1','call_2','call_3');
  },
  call_1: function () {
    this.wait(1000);
    // console.log('call 1 (delayed)');
  },
  call_2: function() {
    // console.log('call 2');
  },
  call_3: function() {
    // console.log('call 3');
  }
});

task(); // -> call 1 (delayed)
        // -> call 2
        // -> call 3

Scenario: Restricting Execution

Old Way

In order to restrict execution, the called function must have logic that determines when it may execute.

var myApp = {
  onlyModalClicks: 0,
  modal: {
    show: function () {
      this.onlyModalClicks = 1;
      // console.log('modal visible');
    },
    hide: function () {
      this.onlyModalClicks = 0;
      // console.log('modal hidden');
    }
  },
  exit: function () {
    if (!this.onlyModalClicks) {
      // console.log('closing app');
    } else {
      // console.log('restricted call');
    }
  }
};

function showModalBtn() {
  myApp.modal.show();
}

function hideModalBtn() {
  myApp.modal.hide();
}

function exitAppBtn() {
  myApp.exit();
}

showModalBtn(); // -> modal visible
exitAppBtn();   // -> restricted call
hideModalBtn(); // -> modal hidden
exitAppBtn();   // -> closing app

New Way

Flow tracks where it is in your program, such that you may restrict external functions from targeting states outside the current branch (i.e., a state and it's descendants).

var myApp = new Flow({
  modal: {
    _restrict: 1,
    show: function () {
      // console.log('modal visible');
    },
    hide: function () {
      this.go(1);
      // console.log('modal hidden');
    }
  },
  exit: function () {
    // console.log('closing app');
  }
});

function showModalBtn() {
  myApp.modal.show();
}

function hideModalBtn() {
  myApp.modal.hide();
}

function exitAppBtn() {
  var flowMoved = myApp.exit();
  if (!flowMoved) {
    // console.log('restricted call');
  }
}

showModalBtn(); // -> modal visible
exitAppBtn();   // -> restricted call
hideModalBtn(); // -> modal hidden
exitAppBtn();   // -> closing app

Note: The flowMoved variable and if statement, from the second set of handler functions, are only used for continuity purposes.

Denying External Calls

Old Way

In order to deny external calls, external functions must use internal logic.

var myApp = {
  denyUI: 0,
  init: function () {
    this.splash();
  },
  splash: function () {
    // console.log('showing splash screen');
    setTimeout(function () {
      myApp.run();
    }, 3000);
  },
  run: function () {
    myApp.denyUI = 0;
    // console.log('running app (3 seconds later)');
  }
};

function runAppBtn() {
  if (!myApp.denyUI) {
    myApp.init();
  } else {
    // console.log('denied call');
  }
}

runAppBtn(); // -> showing splash screen
runAppBtn(); // -> denied call
             // -> running app (3 seconds later)

New Way

Flow distinguishes when calls are external to it's program, such that they may be denied dynamically.

var myApp = new Flow({
  init: function {
    this.go('/run');
  },
  splash: {
    _over: function () {
      this.go('@self');
    },
    _main: function () {
      this.lock(1);
      this.wait(3000);
      // console.log('showing splash screen');
    }
  },
  run: function () {
    this.lock(0);
    // console.log('running app (3 seconds later)');
  }
});

function runAppBtn() {
  if (!myApp.init()) {
    // console.log('denied call');
  }
}

runAppBtn(); // -> showing splash screen
runAppBtn(); // -> denied call
             // -> running app (3 seconds later)

Note: The if statement, from the second handler function, is only used for continuity purposes.

Scenario: Altering Arguments

Old Way

In order to alter arguments, the target function must be called by a closure, which performs the alteration(s).

var myApp = {
    greet = function (str) {
      // console.log(str);
    }
  },
  closureGreeter = function (fnc) {
    return function () {
      if (arguments[0] === 'World') {
        // console.log('augmenting arguments');
        arguments[0] = 'Hello!';
      }
      return fnc.apply(this, arguments);
    }
  },
myApp.greet = closureGreeter(myApp.greet);

myApp.greet('World'); // -> augmenting arguments
                      // -> Hello!

New Way

Flow receives arguments before passing them to the _main function of a target state, such that any state's functions may alter them before hand.

var myApp = new Flow({
  greet: {
    _in: function () {
      if (this.args(0) === 'Hello') {
        // console.log('augmenting arguments');
        this.args(0, 'World!');
      }
    },
    _main: function (str) {
      // console.log(str);
    }
  }
});

myApp.greet('World'); // -> augmenting arguments
                      // -> Hello!

Scenario: Sequencing Executions Dynamically

Old Way

In order to sequence executions dynamically, functions must be designed to accept and execute callbacks (other functions).

var myApp = {
  one: function (cb) {
    // console.log('called one');
    cb();
  },
  two: function (cb) {
    // console.log('called two (delayed)');
    window.setTimeout(cb, 1000);
  },
  three: function (cb) {
    // console.log('called three');
  }
};

myApp.three(
  function () {
    myApp.two(myApp.one);
  }
); // -> called three
   // -> called two (delayed)
   // -> called one

New Way

Flow oversees all functions of the states in it's program, such that states may be targeted (for traversal) centrally, regardless of each function's design.

var myApp = new Flow({
  one: function () {
    // console.log('called one');
  },
  two: function () {
    // console.log('call two (delayed)');
    this.wait(1000);
  },
  three: function () {
    // console.log('called three');
  }
}, true);

myApp.go('three','two','one'); // -> called three
                               // -> called two (delayed)
                               // -> called one

Scenario: Executing Prerequisites

Old Way

In order to execute prerequisites, a dependent function must be called within a closure, which handles calling the prerequisite function.

var preClosure = function (pre, fnc) {
    return function () {
      pre();
      fnc();
    }
  },
  myModal = {
    init: function () {
      // console.log('initialize modal');
    },
    login: {
      init: function () {
        // console.log('load and show login modal');
      },
      focus: function () {
        // console.log('focus on username field');
      },
      hide: function () {
        // console.log('hide login modal');
      }
    },
    destroy: function () {
      // console.log('destroy modal');
    }
  };

myModal.login.focus = preClosure(myModal.login.init, myModal.login.focus);
myModal.login.focus = preClosure(myModal.init, myModal.login.focus);
myModal.destroy = preClosure(myModal.login.hide, myModal.destroy);

myModal.login.focus(); // -> initialize modal
                       // -> load and show login modal
                       // -> focus on username field
myModal.destroy(); // -> hide login modal
                   // -> destroy modal

Note: For demonstrative purposes, this example only defines prerequisites of the called methods.

New Way

Flow uses simple encapsulation and specially keyed components, to identify and execute functions when entering and exiting states of the program.

var myModal = new Flow({
  _in: function () {
    // console.log('initialize modal');
  },
  login: {
    _in: function () {
      // console.log('load and show login modal');
    },
    focus: function () {
      // console.log('focus on username field');
    },
    _out: function () {
      // console.log('hide login modal');
    }
  },
  destroy: function () {
    this.target(0);
  },
  _out: function () {
    // console.log('destroy modal');
  }
});

myModal.login.focus(); // -> initialize modal
                       // -> load and show login modal
                       // -> focus on username field
myModal.destroy(); // -> hide login modal
                   // -> destroy modal

Scenario: Reverting Actions

Old Way

In order to revert actions, counter-functions must be explicitly called to undo what has occurred.

var badApp = {
  inited: 0,
  origTitle: '',
  init: function () {
    if (!this.inited) {
      this.inited = 1;
      this.setNameSpace();
    }
  },
  setNameSpace: function () {
    // console.log('setting global');
    window.badGlobal = {};
  },
  changeTitle: function () {
    if (!this.inited) this.init();
    if (!this.origTitle) this.origTitle = document.title;
    // console.log('changing title');
    document.title = 'foo bar';
  },
  revert: function () {
    this.unChangeTitle();
    this.unInit();
  },
  unchangeTitle: function () {
    // console.log('reverting title');
    document.title = this.origTitle;
    this.origTitle = '';
  }
  unInit: function () {
    this.inited = 0;
    this.unsetNamespace();
  },
  unsetNamespace: function () {
    // console.log('removing global');
    delete window.badGlobal;
  },
};

badApp.changeTitle(); // -> setting global
                      // -> changing title
badApp.reset(); // -> reverting title
                // -> removing global

New Way

Flow groups and isolates functionality, such that each state may revert it's own actions when exited.

var badApp = new Flow({
  _in: function () {
    // console.log('setting global');
    window.badGlobal = {};
  },
  changeTitle: {
    _var: 'origTitle',
    _in: function () {
      this.vars('origTitle', document.title);
    },
    _main: function () {
      // console.log('changing title');
      document.title = 'foo bar';
    },
    _out: function () {
      // console.log('reverting title');
      document.title = this.vars('origTitle');
    }
  },
  revert: function () {
    this.target(0);
  },
  _out: function () {
    // console.log('removing global');
    delete window.badGlobal;
  }
});

badApp.changeTitle(); // -> setting global
                      // -> changing title
badApp.reset(); // -> reverting title
                // -> removing global

Scenario: Debouncing Event Handlers

Old Way

In order to debounce event handlers, functions must manage timeout-identifiers that delay calls.

var timeoutId,
  handler = function (e) {
    // console.log('handling resize event');
  },
  debounceResize = function (e) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(
      function () {
        handler(e);
      },
      500
    );
    // console.log('debouncing event');
  };

window.addEventListener('resize', debounceResize, false);

New Way

Flow provides a single timing construct, such that setting a delay automatically clears the previous one.

var debounceResize = new Flow({
  _main: function () {
    this.go('handler');
    this.wait(500);
    // console.log('debouncing event');
  },
  handler: function (e) {
    // console.log('handling resize event');
  }
});

window.addEventListener('resize', debounceResize, false);

Scenario: Avoiding Stack-Overflows

Old Way

In order to avoid stack-overflows, recursive routines must use iteration and nested functions.

function fibonacci(n, prev, cur) {
  var fibCalc = function () {
      var tmp = prev;
      prev = cur;
      cur += tmp;
    };
  while (n--) {
    // console.log(cur);
    fibCalc();
  }
}

fibonacci(1000, 0, 1); // -> 1
                       // -> 1
                       // -> 2
                       // -> 3
                       // -> 5
                       // -> ...

New Way

Flow navigates states instead of calling functions, such that recursive iterations do not increase the call-stack.

var fibonacci = new Flow({
  _main: function (n, prev, cur) {
    // console.log(cur);
    if (n--) {
      this.target('@self', n, cur, cur + prev);
    }
  }
}

fibonacci(1000, 0, 1); // -> 1
                       // -> 1
                       // -> 2
                       // -> 3
                       // -> 5
                       // -> ...