Backbone.js: получить текущий маршрут

139

Могу ли я получить имя текущего маршрута, используя Backbone? Я знаю, как выполнить привязку к событиям изменения маршрута, но я хотел бы иметь возможность определять текущий маршрут в другое время, между изменениями.

Дрю Дара-Абрамс
источник
Под «именем» вы имеете в виду функцию, к которой привязан этот маршрут, или просто текущий URL или хеш-часть URL?
Джон Манч
1
О да, я должен был быть более конкретным. Хотелось бы узнать название функции. (Я знаю, как получить хеш-часть URL-адреса, выполнив location.hash или Backbone.history.fragment.)
Дрю Дара-Абрамс,

Ответы:

210

Если вы создали экземпляр Router в своем приложении, следующая строка возвращает текущий фрагмент:

Backbone.history.getFragment();

Из документации Backbone.js :

"[...] История служит глобальным маршрутизатором (для каждого кадра) для обработки событий hashchange или pushState, сопоставления с соответствующим маршрутом и запуска обратных вызовов. Вам никогда не придется создавать один из них самостоятельно - вы должны использовать ссылку в Backbone.history, который будет создан автоматически, если вы используете маршрутизаторы с маршрутами. [...] "

Если вам нужно имя функции, связанной с этим фрагментом, вы можете сделать что-то вроде этого внутри области видимости вашего маршрутизатора:

alert( this.routes[Backbone.history.getFragment()] );

Или вот так извне вашего роутера:

alert( myRouter.routes[Backbone.history.getFragment()] );
Роберт
источник
Спасибо, отлично. Кажется, я никогда не упоминал Backbone.history.fragment в документе. Это новое дополнение или просто недокументированное?
Дрю Дара-Абрамс
Я думаю, это недокументировано. Собственно, я не помню, где впервые увидел, в каком-нибудь блоге или, возможно, в исходном коде магистрали.
Роберт
25
К вашему сведению, это не работает с маршрутами типа 'foo /: id' или 'bar * params'
wprl
2
Лучше использовать Backbone.history.getFragment(), потому что Backbone.history.fragmentэто частная скрытая собственность.
Vad
1
Я исправил ответ после предложения @Vad. Я больше не использую Backbone, но его комментарий мне кажется правильным. (Предыдущий ответ был: Backbone.history.fragment вместо Backbone.history.getFragment ())
Роберт
57

Ответ Роберта интересен, но, к сожалению, он будет работать только в том случае, если хеш будет точно таким, как определено в маршруте. Например, если у вас есть маршрут для user(/:uid)него, он не будет сопоставлен, если Backbone.history.fragmentбудет либо, "user"либо "user/1"(оба являются двумя наиболее очевидными вариантами использования для такого маршрута). Другими словами, он найдет подходящее имя обратного вызова только в том случае, если хэш точный "user(/:uid)"(очень маловероятно).

Поскольку мне нужна была эта функциональность, я расширил ее Backbone.Routerс помощью current-функции, которая повторно использует часть кода, используемого объектом History и Router, для сопоставления текущего фрагмента с определенными маршрутами для запуска соответствующего обратного вызова. Для моего варианта использования он принимает необязательный параметр route, который, если задано что-либо правдивое, вернет соответствующее имя функции, определенное для маршрута. В противном случае он вернет текущий хеш-фрагмент из Backbone.History.fragment.

Вы можете добавить код в существующий Extend, где вы инициализируете и настраиваете Backbone router.

var Router = new Backbone.Router.extend({

    // Pretty basic stuff
    routes : {
        "home" : "home",
        "user(:/uid)" : "user",
        "test" : "completelyDifferent"
    },

    home : function() {
        // Home route
    },

    user : function(uid) {
        // User route
    },

    // Gets the current route callback function name
    // or current hash fragment
    current : function(route){
        if(route && Backbone.History.started) {
            var Router = this,
                // Get current fragment from Backbone.History
                fragment = Backbone.history.fragment,
                // Get current object of routes and convert to array-pairs
                routes = _.pairs(Router.routes);

            // Loop through array pairs and return
            // array on first truthful match.
            var matched = _.find(routes, function(handler) {
                var route = handler[0];

                // Convert the route to RegExp using the 
                // Backbone Router's internal convert
                // function (if it already isn't a RegExp)
                route = _.isRegExp(route) ? route :  Router._routeToRegExp(route);

                // Test the regexp against the current fragment
                return route.test(fragment);
            });

            // Returns callback name or false if 
            // no matches are found
            return matched ? matched[1] : false;
        } else {
            // Just return current hash fragment in History
            return Backbone.history.fragment
        }
    }
});

// Example uses:
// Location: /home
// console.log(Router.current()) // Outputs 'home'
// Location: /user/1
// console.log(Router.current(true)) // Outputs 'user'
// Location: /user/2
// console.log(Router.current()) // Outputs 'user/2'
// Location: /test
// console.log(Router.current(true)) // Outputs 'completelyDifferent'

Я уверен, что можно было бы кое-что улучшить, но это хороший способ начать работу. Кроме того, эту функциональность легко создать без расширения объекта Route. Я сделал это, потому что это был наиболее удобный способ для моей настройки.

Я еще не тестировал это полностью, поэтому, пожалуйста, дайте мне знать, если что-то пойдет не так.


ОБНОВЛЕНИЕ 25.04.2013

Я внес некоторые изменения в функцию, поэтому вместо того, чтобы возвращать хэш или имя обратного вызова маршрута, я возвращаю объект с фрагментом, параметрами и маршрутом, чтобы вы могли получить доступ ко всем данным из текущего маршрута, так же, как вы бы из маршрута- событие.

Вы можете увидеть изменения ниже:

current : function() {
    var Router = this,
        fragment = Backbone.history.fragment,
        routes = _.pairs(Router.routes),
        route = null, params = null, matched;

    matched = _.find(routes, function(handler) {
        route = _.isRegExp(handler[0]) ? handler[0] : Router._routeToRegExp(handler[0]);
        return route.test(fragment);
    });

    if(matched) {
        // NEW: Extracts the params using the internal
        // function _extractParameters 
        params = Router._extractParameters(route, fragment);
        route = matched[1];
    }

    return {
        route : route,
        fragment : fragment,
        params : params
    };
}

См. Предыдущий код для дальнейших комментариев и объяснений, они в основном выглядят одинаково.

Саймон Кьельберг
источник
1
Собирался написать это сам, но сначала искал решение в Google. Рад, что сделал. Ваш делает именно то , что я хотел, спасибо!
Дэн Абрамов
Вот немного более подробная версия, которую, как мне кажется, легче понять с первого взгляда.
Дэн Абрамов
4
Вау, спасибо за это! Вы только что получили подтверждение в нашей кодовой базе. ; P Мы немного подправили его для Marionette. Кстати, в качестве ярлыка вы можете установить третий параметр (задает контекст) _.findфункции как this, тем самым устраняя необходимость в Routerпеременной (@DanAbramov уже сделал это).
Марк
@Mark Это хорошие новости. Но как я могу использовать эту функцию в марионетке? В документации ничего об этом нет.
darksoulsong
2
@darksoulsong Если вы используете Marionette.AppRouter, расширить маршрутизатор с функцией выше, но замена Router.appRoutesдля Router.routes.
Марк
11

Чтобы получить маршрут вызова (или URL-адрес) от вызываемого обработчика маршрута, вы можете получить его, проверив

Backbone.history.location.href ... полный URL
Backbone.history.location.search ... строка запроса, начиная с?

Я попал сюда в поисках этого ответа, поэтому, думаю, мне следует оставить то, что я нашел.

Йоши
источник
6

Если вы используете корневую настройку для маршрутизатора, вы также можете включить ее, чтобы получить «настоящий» фрагмент.

(Backbone.history.options.root || "") + "/" + Backbone.history.fragment
user1310662
источник
6

Вот чуть более многословным (или, в зависимости от вашего вкуса, более удобным для чтения) версии ответа Саймона :

current: function () {
  var fragment = Backbone.history.fragment,
      routes = _.pairs(this.routes),
      route,
      name,
      found;

  found = _.find(routes, function (namedRoute) {
    route = namedRoute[0];
    name = namedRoute[1];

    if (!_.isRegExp(route)) {
      route = this._routeToRegExp(route);
    }

    return route.test(fragment);
  }, this);

  if (found) {
    return {
      name: name,
      params: this._extractParameters(route, fragment),
      fragment: fragment
    };
  }
}
Дан Абрамов
источник
4

Если вы посмотрите на источник Router, вы увидите, что когда маршрутизатор запускает событие, говоря, что что-то меняется, он передает имя с ним как «route: name».

http://documentcloud.github.com/backbone/docs/backbone.html#section-84

Вы всегда можете подключить событие "route" к маршрутизатору и сохранить его, чтобы получить текущий маршрут.

Брайан Генизио
источник
Хорошо, я предполагаю, что это что-то вроде «построил сам».
Дрю Дара-Абрамс
Да, но вы не получите routeсобытие, если trigger его не будет true (рекомендуется и по умолчанию).
Дэн Абрамов
0
router = new Backbone.Router.extend({
  routes: { "stuff/:id" : "stuff" },
  stuff: function(id) {}
});

router.on('route', function(route) {
  this.current = route;
});

Теперь, если вы /stuff/1перейдете к , router.current будет "stuff"

Дэйв Аарон Смит
источник