Search code examples
backbone.jsmarionette

Overriding Backbone Navigate for Refresh Routes


This has been discussed a lot before, on the specific level, but I haven't found a good recipe to override the generic case of navigating backbone routes to the same hash route. There's even some discussion on why this is a bad idea, which is why Backbone won't change their approach, despite lots of requests.

Specifically, I have a few instances of displaying data (in modals/dialogs/popovers) in a Backbone application that can be closed out without changing the path. I go ahead and 'true-up' the path manually in these situations, as to not refresh the page and navigate traditionally.

Unfortunately, Backbone has some specific code to avoid this use case, so I'd like to extend Backbone to allow for these requests, without any Handlers responding to the clicks of the path changes. That is, I'd like to keep our Backbone View's DRY and not manually call a navigate, but just use HTML href="#path" instead.

So, essentially, I'm trying to create a Backbone extension that overrides their default navigate method to our own. I've found this example that I've tried to update with no success. Barring any load-order issues, I feel it should work, but can't ever seem to get that code to trigger.

      _.extend(Backbone.History.prototype, {
        refresh: function() {
            this.loadUrl(this.fragment);
        }
    });

    var routeStripper = /^[#\/]/;
    var origNavigate = Backbone.History.prototype.navigate;
    Backbone.History.prototype.navigate = function (fragment, options) {
        var frag = (fragment || '').replace(routeStripper, '');
        if (this.fragment == frag)
            this.refresh();
        else
            origNavigate.call(this, fragment, options);
    };

For now, I've run some specific click handlers that respond only when Backbone doesn't respond, but this doesn't meet my codesmell standards:

AView = Backbone.Marionette.ItemView.extend({
  template: HandlebarsTemplates['a/template'],
  initialize: function (data) {
    ...
  },
  events: {
    'click a[href]': function(event) {
      var routeStripper = /^[#\/]/;
      var new_frag = ($(event.target).attr("href") || '').replace(routeStripper, '');
      if(new_frag == Backbone.history.getFragment()) {
        Backbone.history.loadUrl(Backbone.history.getFragment());
      }
    }
  },
});

Does anyone have any functioning patterns to solve this convoluted corner-case?


Solution

  • After a very frustrating 3 days on this single problem, I've resolved that I cannot override Backbone.history, due to it's strange initiation pattern my limited JavaScript skill isn't familiar with OR the fact that we're using Marionette's AppRouter which doesn't pick up the override for one reason or another.

    Either way, this problem manifested itself by having a modal closed manually not navigating to the non-modal hash. This has been corrected by calling navigate manually on the non-modal hash and re-positioning the window to where it was before (no flickering or visible redrawing noticed).

    Some code:

    var scrolled_top = window.parent.pageYOffset || window.parent.document.documentElement.scrollTop;
    var scrolled_left = window.parent.pageXOffset || window.parent.document.documentElement.scrollLeft;
    App.router.navigate('');
    window.parent.scrollTo(scrolled_left, scrolled_top); //reset screen location after navigation sets it to top of new view