Backbone routing regex - backbone.js

Trying to get my head around sorting this routing regex out, so:
'quotes(/:action)': 'quotes',
'quotes/:id(/:params)': 'quotesEdit'
Two URLs:
http://domain.com/#quotes/action=showModal
http://domain.com/#quotes/123
My question:
How can I make sure that the URL with the action= matches on the first Route, but not the second? and for urls like quotes/123 to fall through to the second Route?

try to add routes directly via router`s initialize
initialize: function(options) {
this.route(/^quotes\/([0-9]+)$/, "ids");
this.route(/^quotes\/action=(.*)$/, "act");
},
ids: function(id){
alert('id='+id);
},
act: function(act){
alert('act='+act);
},

You can make this work by over-riding Backbone.history.loadUrl with your special-cases. Essentially, you would be skipping matched routes based on the url parameters...but that seems awfully hack-ish.
An option is to declare a single route and branch on the arguments:
'quotes(/:id)(/:params)': 'quotes'
quotes:function(id,params) {
if (id && id.match(/^\d+$/)) { // if id is a number
this.quotesEdit(id,params);
}
else {
// your quotes logic
}
Instead of the above, you may want to look into changing your routes a bit and your problem is longer an issue.
'quotes(/:action)' : 'quotes',
'quotes/edit/:id(/:params)' : 'quotesEdit'

Related

angular ngRoute optional paths

I have the following routes that I'd like to match
/biblioteca
/biblioteca/whatever
So far now I made two routes, like this:
$routeProvider.when('/biblioteca', { ...
$routeProvider.when('/biblioteca/:path*', { ...
Is there some way to capture both of them with a single route, stating that the :path* part is optional? perhaps something like...
$routeProvider.when('/biblioteca/:?path*', { ...
what would be the correct way to handle such a case?
Put the question mark after the named group.
$routeProvider.when('/biblioteca/:path?', { ...
path can contain optional named groups with a question mark: e.g.:name?.
See docs.angularjs.org/api/ngRoute/provider/$routeProvider

UI Router Prefix

For example you site is hosted on "http://www.domain.com". And you have a products page defined by the url below:
http://www.domain.com/products
Is it possible to define a prefix? Moreover, I want the prefix to have a parameter as well. I would define the products page for different shops using the urls below, where "shop/x" is the prefix with the parameter "x":
http://www.domain.com/shop/1/products
http://www.domain.com/shop/2/products/1
Yes, just use a .state config along the following lines
.config(function($stateProvider) {
return $stateProvider.state('main', {
url: '/shop/:shopID/products/:productID',
...
});
and then in your controller you can refer to $scope.shopID and $scope.productID just as you would expect to.

Build link with parameter (?key=val) with ui-sref directive

I'm using Angular ui-router, my state look like:
.state('detail', {
url: '/detail/{id}',
In the HTML file I prefer to use ui-serf directive to build the link. For example:
<a ui-sref="detail( { id:123 } )">...
How can I build a link with optional query parameter? For example:
/detail/123?mode=json&pretty=true
I think that the ui-router way it's not to use query parameters, but instead of doing:
/detail/123?mode=json&pretty=true
declare the state as
.state('detail', {
url: '/detail/{id}/{format}/{pretty}',
...
and using it like:
/detail/123/json/true
Anyway, if you really want to use the query format, you can do it out-of-the-box like as state in the doc:
You can also specify parameters as query parameters, following a '?':
url: "/contacts?myParam" // will match to url of
"/contacts?myParam=value" If you need to have more than one, separate
them with an '&':
url: "/contacts?myParam1&myParam2" // will match to url of
"/contacts?myParam1=value1&myParam2=wowcool"
See: https://github.com/angular-ui/ui-router/wiki/URL-Routing#query-parameters
Have you tried just adding it? :
<a ui-sref="detail( { id:123, optional: 'moo' } )">...
I know that is how dotJEM angular routing would work ( except the syntax is a bit different ).

Backbone view's model is missing, but I don't understand why

I'm having a simple but confusing problem. I have the following piece of code:
<div id="restaurant_locations"></div>
<script type="text/javascript">
$(function() {
window.router = new Lunchhub.Routers.RestaurantLocationsRouter({
restaurantLocations: <%= #restaurant_locations.to_json.html_safe -%>
});
Backbone.history.start({pushState: true});
});
</script>
which throws this error:
Uncaught TypeError: Cannot call method 'toJSON' of undefined
If I take out the {pushState: true} part, though, and just do Backbone.history.start() with no arguments, it works just fine.
Next to the error, it says show_view.js: 19. Here's what that part of show_view.js looks like:
ShowView.prototype.template = JST["backbone/templates/restaurant_locations/show"];
ShowView.prototype.render = function() {
$(this.el).html(this.template(this.model.toJSON())); // LINE 19
Uncaught TypeError: Cannot call method 'toJSON' of undefined
return this;
}
So I guess this.model is undefined. Here's the show_view CoffeeScript:
Lunchhub.Views.RestaurantLocations ||= {}
class Lunchhub.Views.RestaurantLocations.ShowView extends Backbone.View
template: JST["backbone/templates/restaurant_locations/show"]
render: ->
$(#el).html(#template(#model.toJSON() ))
return this
If I can make #model be what it needs to be, I guess it might fix the problem. But I don't know where #model comes from or anything.
What do I need to do?
Edit: I got a little further. In the show function below, id is set to "restaurant_locations", and there's of course no member of #restaurantLocations with an id of restuarant_locations. The fact that id set set to restaurant_locations makes a certain amount of sense; the URL I'm hitting is http://localhost:3000/restaurant_locations. But it seems like it should be calling the index function, not show, if that's the URL to which I'm going.
class Lunchhub.Routers.RestaurantLocationsRouter extends Backbone.Router
initialize: (options) ->
#restaurantLocations = new Lunchhub.Collections.RestaurantLocationsCollection()
#restaurantLocations.reset options.restaurantLocations
routes:
"new" : "newRestaurantLocation"
"index" : "index"
":id/edit" : "edit"
":id" : "show"
".*" : "index"
newRestaurantLocation: ->
#view = new Lunchhub.Views.RestaurantLocations.NewView(collection: #restaurantLocations)
$("#restaurant_locations").html(#view.render().el)
index: ->
#view = new Lunchhub.Views.RestaurantLocations.IndexView(restaurantLocations: #restaurantLocations)
$("#restaurant_locations").html(#view.render().el)
show: (id) ->
restaurant_location = #restaurantLocations.get(id)
#view = new Lunchhub.Views.RestaurantLocations.ShowView(model: restaurant_location)
$("#restaurant_locations").html(#view.render().el)
edit: (id) ->
restaurant_location = #restaurantLocations.get(id)
#view = new Lunchhub.Views.RestaurantLocations.EditView(model: restaurant_location)
$("#restaurant_locations").html(#view.render().el)
id is set to "restaurant_locations", and there's of course no member of #restaurantLocations with an id of "restuarant_locations".
Sounds like you have a routing problem so let us look at your routes:
routes:
"new" : "newRestaurantLocation"
"index" : "index"
":id/edit" : "edit"
":id" : "show"
".*" : "index"
I see several problems there. First of all, routes are not regexes so ".*" doesn't match what /.*/ does (i.e. any sequence of characters), it actually matches any number of periods (i.e. /^.*$/); you can run it through _routeToRegex yourself to see what happens to '.*':
var namedParam = /:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
//...
_routeToRegExp: function(route) {
route = route.replace(escapeRegExp, '\\$&')
.replace(namedParam, '([^\/]+)')
.replace(splatParam, '(.*?)');
return new RegExp('^' + route + '$');
},
The next problem is that ":id" matches almost anything. routeToRegexp converts ":id" to /^([^/]+)$/; that regex matches any non-empty sequence of non-slashes and in particular, that will match what most of your other routes match.
So when you expect to hit '.*': 'index' from /restaurant_locations, you're actually hitting ':id': 'show' and you're getting lucky that '/new' and '/index' aren't also getting matched by ':id'. Keep in mind that the order of elements in a (Java|Coffee)Script object is implementation defined so the order that they appear in in your source code really doesn't matter; many JavaScript implementations will respect the source order but never depend on it.
Since the route order doesn't matter, you have to look at your routes keys as a simple unordered set and you--the human--must ensure that your route patterns really do match distinct things. If you do need overlapping route patterns and you need them to get matched in a specific order, then you can use the route method in your router's initialize to manually add the routes (as routing patterns or regexes) in the required order.
Executive Summary: Fix your routes so that they match distinct things.

backbone routes - ordering and filtering

In my Backbone app, on my collection I have numerous sorting methods, when rendering the views based on the collection I am currently using a global var set via the route (I do it with a global as other actions add to the collection and I want the last ordering to be used). For example
routes : {
"" : "index",
'/ordering/:order' : 'ordering'
},
ordering : function(theorder) {
ordering = theorder;
listView.render();
},
then in my view
if (typeof ordering === 'undefined') {
d = this.collection.ordered();
}
else if(ordering == 'owners') {
d = this.collection.owners();
}
_.each(d, function(model){
model.set({request : self.model.toJSON()});
var view = new TB_BB.OfferItemView({model : model});
els.push(view.render().el);
});
Where ordered and owners are the 2 ordering methods.
So my first question is, based on routes could someone advice a better way of implementing above? This view gets rendered in multiple places hence me using a global rather than passing a ordered var to the method?
Second question is - I would like to also add some filtering, so lets say I want to sort by 'price' but also do some filtering (lets say by multiple categories id). How could I add a flexible 'route' to deal with filtering.
I guess I could do
routes : {
"" : "index",
'/ordering/:order/:filter1/:filter2' : 'ordering'
},
So the filter1 and filter2 would be the subsequent filtering, but if the filters could be 0 or 100 this will not work. Could anyone offer a solution?
Well, first you should be using Backbone's built-in ability to auto-sort collections. You can take advantage of this by defining a comparator function on your collection. This gives you all kinds of wins right out of the box — for example, the collection will re-sort itself every time you add or remove something from it, based on your comparator. If you want to define multiple sort functions, just define them all as functions and then update comparator when you need to. Then you can ditch that ugly global var.
For your second question, I'm not totally sure what you mean by "if the filters could be 0 or 100 this will not work." If you mean that you'll run into trouble if you don't specifiy all of the filters, then that's true. But you can use a wildcard to fix that. Here's what that might look like:
// your routes look like this:
routes : {
'/ordering/:order/filters/*filters' : 'ordering' // your routes will look like: /ordering/price/filters/filter_one/filter_two/filter_three
},
ordering: function (order, filters) {
filters = filters.split('/'); // creates an array of filters: ['filter_one', 'filter_two', 'filter_three']
listView.render(filters); // pass your filters to the view
}
// listView.render() looks like this:
render: function(filters) {
collection = this.collection;
_.each(filters, function (filter) {
collection = collection.filter(function () {
// your actual filtering code based on what the filter is
});
});
}

Resources