Code Monkey home page Code Monkey logo

Comments (13)

bobholt avatar bobholt commented on August 19, 2024

I've been using History.js for my Aura routing. I tried augmenting Backbone's built-in routing, but it turned into kind of a rabbit hole I didn't want to dive down at the time.

I think if we could get that to work with Aura, it would be ideal, so I may sit down again and see if I can get it up and running.

from aura.

addyosmani avatar addyosmani commented on August 19, 2024

@dustinboston In order for routes to publish events, wouldn't we have to either generalize the routing to outside a framework or only implement this in the backbone-aura extension? Did you have a POC that describes this concept better in mind that you might be able to share?

from aura.

sindresorhus avatar sindresorhus commented on August 19, 2024

Better fit as an extension or use of a routing lib directly IMHO.

from aura.

kloy avatar kloy commented on August 19, 2024

@addyosmani Would it not make more sense to treat routes as another widget? This prevents the use case of two widgets implementing the same route potentially as well as centralizing all routing. You also get the described feature of Routes having events.

from aura.

tony avatar tony commented on August 19, 2024

So what are routers in front end js.

Let's be good scholars and dig in :D

  1. They monitor hashchange.
      var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));

      if (oldIE) {
        this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
        this.navigate(fragment); // ancient ie just navigates to fragments.
      }

      // cross-browser events against window's popstate and hashchange
      if (this._hasPushState) {
        $(window).bind('popstate', this.checkUrl);
      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
        $(window).bind('hashchange', this.checkUrl);
      } else if (this._wantsHashChange) {
        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
      }

Insight: Backbone.History doesn't store a collection of previous hashes, instead it lets the browser handle back and forward between hashes.

  1. Routing

This is where it gets complicated.

We have 3 components:

Backbone.History, which is often seen as Backbone.history. Adding a route to a new Backbone.Router will start up Backbone.history automatically.

Backbone.history handles the hash changes.

      // cross-browser events against window's popstate and hashchange
      if (this._hasPushState) {
        $(window).bind('popstate', this.checkUrl);
      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
        $(window).bind('hashchange', this.checkUrl);
      }

and poll with window.setInterval:

   else if (this._wantsHashChange) {
        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
      }

Backbone.history will checkUrl:

    checkUrl: function(e) {
      var current = this.getFragment();
      if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
      if (current == this.fragment) return false;
      if (this.iframe) this.navigate(current);
      this.loadUrl() || this.loadUrl(this.getHash());
    },

Backbone.history.checkUrl finds the route changed from this.getFragment(), Backbone.history.loadUrl is ran:

    loadUrl: function(fragmentOverride) {
      var fragment = this.fragment = this.getFragment(fragmentOverride);
      var matched = _.any(this.handlers, function(handler) {
        if (handler.route.test(fragment)) {
          handler.callback(fragment); // it's hitting a callback function
          return true;
        }
      });
      return matched;
    },

this.handlers (of Backbone.history.handlers) is from Backbone.Router.prototype.route. It's ran against every route when you do a normal router initialization:

Where does the Router come in to play?

var Workspace = Backbone.Router.extend({

  routes: {
    "help":                 "help",    // #help
    "search/:query":        "search",  // #search/kiwis
    "search/:query/p:page": "search"   // #search/kiwis/p7
  },

  help: function() {
    ...
  }
  search: function(query, page) {
    ...
  }

Backbone.Router sifts through this.routes with Backbone.Router.prototype._bindRoutes and pumps it to Backbone.Router.prototype.route instance one by one. Here's what happens:

    route: function(route, name, callback) {
      Backbone.history || (Backbone.history = new History);
      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
      if (!callback) callback = this[name];
      Backbone.history.route(route, _.bind(function(fragment) {
        var args = this._extractParameters(route, fragment);
        callback && callback.apply(this, args);
        this.trigger.apply(this, ['route:' + name].concat(args));
        Backbone.history.trigger('route', this, name, args);
      }, this));
      return this;
    },

So as of Backbone 0.9.2, the router verifies initialization of Backbone.History into Backbone.history. If it's not initialized, it does it now.

Then it will use underscore method _.isRegExp to see if the route is a RegExp object.

The !callback confuses me, as this[name] and the argument from _bindRoutes's iterator passes in the same thing, this.route(routes[i][0], routes[i][1], this[routes[i][1]]). I thought it may been being like Marionette's AppRouter and perhaps allowing a more complex routing possibility but nah.

It will use Backbone.history.route which accepts the RegExp of the route and an anonymous function, let's zoom in:

_.bind(function(fragment) {
        var args = this._extractParameters(route, fragment);
        callback && callback.apply(this, args);
        this.trigger.apply(this, ['route:' + name].concat(args));
        Backbone.history.trigger('route', this, name, args);
      }, this)

This dance is wrapping the callback to accept the parameter arguments and trigger a history event. And making sure to bind it against Backbone.Router's instance context - as it's being accessed from Backbone.history. With the context binded,_extractParameters can be utilized against the reference to fragment, which is just Backbone.history.getFragment().

Backbone.history.route adds this to this.handlers, being sure to pass callback in the hash for loadUrl().

    route: function(route, callback) {
      this.handlers.unshift({route: route, callback: callback});
    },

So when Backbone.history detects a hashchange, with all of the parameters extracted as an argument, which are then passed in via callback.apply.

passes out

edit: fixes.

from aura.

Francisc avatar Francisc commented on August 19, 2024

hands glass of water to Tony

Are you better?
Awesome post.

from aura.

tony avatar tony commented on August 19, 2024

POC for routing in branch https://github.com/tony/aura/tree/history.

#view/month

#view/month is calendar:changeView:basicWeek

Trigger this.calendar.fullCalendar and pass changeView and basicWeek in as arguments.

#view/:year/:month/:date

Above: #view/:year/:month/:day to calendar:gotoDate.

this.calendar.fullCalendar must pass gotoDate as an action. This case must accept parameters.

The widget is called router. It has no DOM object on the page being used, but one is being passed to prevent error.

The lambda in Backbone.Router.prototype.route is reused. It will only accept a route with a controller endpoint and have extracted args ready.

sandbox.publish('router', name, args); publishes what would normally be a function prototype being extended into Router. Instead of using a callback function, the router triggers sandbox.publish.

The Calendar widget subscribes and then does stuff.

#view/:year/:month and #view/:year will default to current date arguments when not filled. Entering a date takes you to a date view.

from aura.

addyosmani avatar addyosmani commented on August 19, 2024

I can't believe I almost missed this. That's really awesome work @tony :) I also really appreciate all of the research work you put into figuring out a good routing strategy.

If we could add a routing support example for the Todo widget as well (e.g https://github.com/addyosmani/todomvc/tree/gh-pages/architecture-examples/backbone) I'd be happy with us merging your POC in :)

from aura.

tony avatar tony commented on August 19, 2024

thank you @addyosmani . sounds like a plan.

from aura.

tony avatar tony commented on August 19, 2024

@addyosmani just a pulse, router poc is alive using router pattern from that repo. will pr tomorrow when todo is wired in.

https://github.com/tony/aura/tree/history

from aura.

addyosmani avatar addyosmani commented on August 19, 2024

Awesome! :)

from aura.

addyosmani avatar addyosmani commented on August 19, 2024

@tony is there anything else we're waiting on for this issue or is it good to close? :)

from aura.

addyosmani avatar addyosmani commented on August 19, 2024

Closing due to age and relevance to direction in master.

from aura.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.