Package to define and execute routes #26

Closed
bemson opened this Issue Mar 3, 2013 · 3 comments

Comments

Projects
None yet
1 participant
Owner

bemson commented Mar 3, 2013

Flow needs an api to support route like paths. This functionality should be added via a new unnamed package. Possible package names (so far) are "router" and "path".

Routes are similar to the Flow path syntax, except they often reflect a user-friendly, browser url (rather than an internal code-path) and can be interspersed with parameters.

This issue will look at how to implement the following example route:

/blogs/:blog_id/highlight/:term/:color

bemson was assigned Mar 3, 2013

Owner

bemson commented Mar 3, 2013

Base Program Structure

The new package should be unobtrusive as possible to a program's existing structure. Some additions and alterations are expected, but it is also reasonable for a package to work with already working programs, for simpler integration.

The following Flow instance should be used as the base program that will be route-enabled.

myApp = new Flow({
  blogs: {
    _on: function (blogid) {
      /* show selected blog */
    },
    highlight: function (term, color) {
      /* highlight selected term  in the given color */
    }
  }
});

Invoking Routes

The simpler part of this problem is deciding the API for parsing and executing our sample route. The proposed package would introduce a proxy.route() method. The method would set a destination state and instruct Flow to navigate a program. As with the target method, .route() would clear exising waypoints.

The .route() method would return true if the targeted route was successfully matched by the instance. If the given route were invalid, it would return false. Below demonstrates how you might use the .route() method.

myApp.route('/blogs/8/highlight/foobar!/ff0');
// returns boolean

Mapping routes to program states

Ultimately, I see routes as a series of navigation instructions, each having their own parameters. In other words, it's a way of passing parameters/arguments to multiple states, deeper and deeper down a program branch. For instance, our example route would break down into the following instructions:

  • Go to the state "//blogs/"
  • Pass (or make available) the parameter "blog_id"
  • Go to a descendent state "highlight"
  • Pass (or make avialable) the parameters "term" and "color"

Thus the route syntax appears to handle a case where parameters must be set at various stages of traversing towards a program state. That is, as Flow navigates toward the "highlight" state (with various parameters), it targets the "blogs" state (again, with various parameters).

Owner

bemson commented Mar 5, 2013

Route-ability

While this new routing capability is designed to make it easy for rest-call style access to a flow, it's critical that a user can control which states are route-able. By default, I would think this feature would enable routing for all states. However, given the number of states a program may contain, it's more likely that a developer would not want a state to be route-able. This may end up being a configurable initialzation option or something easily configured per state - in a descendant fashion.

The compilation of routeable states will result in a route tree, which - when states are excluded - would presumably differ from the program tree. This package would introduce a _route tag for identifying which states will be in the route tree. The tag's value would cascade, such that it's descendents would also be in/ex-cluded from the route tree. Descendants that are excluded may independently add themselves and their branch to the route tree.

Route Tree vs Program Tree

There are many cases where a route tree is desirable as an api, in contrast to the program tree. In all cases, the route-tree uses a subset of program states, but does not necessarily have to be a sub-section of the program tree.

For example, if I want to code opening various modal dialogs with Flow, I might end up with the following program paths.

  • "//modal/show/alert"
  • "//modal/show/confim"
  • "//modal/show/info"
  • "//modal/hide"

However, I want to expose the following routes.

  • "/modal/alert"
  • "/modal/confirm"
  • "/modal/prompt"
  • "/exit"

There is overlap between the paths and their corresponding trees, but the route tree also has paths that do not match the program-tree. The _route tag needs to support this kind of configuration as well. Below is how the program might look, when including the _route tag.

myFlow = new Flow({
  modal: {
    show: {
      _route: false,
      alert: {
        _route: true
      },
      confirm: {
        _route: true
      },
      info: {
        _route: 'prompt'
      },
    }
    hide: {
      _route: '/exit'
    }
  }
});

During compilation, the above program would generate a tree that supports the aforementioned routes. Note how the _route tag supports a boolean or string. As false the route instructs the state to be excluded from the available routes. When true, the state is added to the route-tree, using the state name. When a string, the state name can indicate that it is part of the route branch, or define a new route branch with a forward-slash prefix. The compiled route tree and their correspoding program endpoints would look something like this:

modal ->      //modal/
  alert ->    //modal/show/alert
  confirm ->  //modal/show/confirm
  prompt ->   //modal/show/info
exit ->       //modal/hide

Note: There is a general ugliness in dealing with the cascading aspect of the _route tag. It forces us to specify _route: true for all child states... which can be tedious and hard to read. We could either configure the _route to cascade or remove cascading all together, and simply default to including each state to the route tree... Not sure which just yet.

The resulting routes don't match up with all of what we wanted: now "modal" is an available route - side-effect of using it within other routes. Maybe there is a way to make a state part of a route but not an endpoint in and of itself? In the end, attempting to define a route tree using the program may make things more verbose than desired.

Identifying Parameters

The sets of route parameters possible in a route, represent one or more parts appended to a route-able subpath. Each state msut somehow identify the number of parts to excise from a route, in order for the route to be verified and pursued by the Flow instance.

Using the _route tag as a configuration object, a params option might accept the following formats.

  • The number of parameters to excise from the route.
  • An array of regular expressions. Each expression must match that part of the route, in order to become a parameter and for all parameters to be excised from a route.
  • A boolean. When false, no parameters will be accepted. When true, the arity of the state's _on handler will be used to determine the number of parameters.

Capturing regular expressions will be passed as an array of the captured parameters, not the matched part of the route.

Below are examples of how parameters might be define for states.

myFlow = new Flow({
  number: {
    _route: {
      params: 2
    },
  },
  array: {
    _route: {
      params: [
        /\d/,
        /(\w+)/
      ]
    }
  },
  boolean: {
    _route: {
      params: true
    }
    _on: function (a,b,c) {}
    again: {
      _route: {
        params: false
      }
      _on: function (a,b,c) {}
    }
  }
});

Routes that match the above flow:

/number/any/thing
/array/800/-5-munch-
/boolean/one/two/three
/boolean/again/

The nested option presents a problem in general, regarding route precedence. For instance, how to determine if the next part of a route is a parameter versus a nested routed? Should we just prefer route paths, or is there a way to let the author set a preference or option?

Owner

bemson commented May 6, 2013

This idea seems a little rushed and may be overkill - given the paths already available by way of the program states. In other words, hiding parts of a path seems less important than preventing that path from being executed - the _conceal tag (amongst other access limitation options), covers this concern well enough. I'm going to close this issue for now, and give it a backlog tag.

bemson closed this May 6, 2013

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment