I've got a node.js (server) and backbone.js (client) app - I can load and init my backbone app on a page... and init the router, but my default route (".*") is not getting called. I can manually call the index function after I initialize the router, but I don't have to take that step when I've built backbone apps over rails.
Does anyone have a clue as to why this is happening?
adding code (in coffeescript):
class NodeNetBackbone.Routers.RegistryPatients extends Backbone.Router
routes:
'' : 'index'
'.*' : 'index'
'/index' : 'index'
'/:id' : 'show'
'/new' : 'new'
'/:id/edit' : 'edit'
initialize: ->
console.log 'init the router'
#registry_patients = new NodeNetBackbone.Collections.RegistryPatients()
# TODO: Figure out why this isn't sticking...
#registry_patients.model = NodeNetBackbone.Models.RegistryPatient
# TODO: Try to only round trip once on initial load
# #registry_patients.reset($('#container_data').attr('data'))
#registry_patients.fetch()
# TODO: SSI - why are the routes not getting processed?
this.index()
index: ->
console.log 'made it to the route index'
view = new NodeNetBackbone.Views.RegistryPatients.Index(collection: #registry_patients)
# $('#container').html('<h1>Patients V3: (Backbone):</h1>')
$('#container').html(view.render().el)
Backbone routes are not regexes (unless you manually add a regex route using route). From the fine manual:
Routes can contain parameter parts, :param, which match a single URL component between slashes; and splat parts *splat, which can match any number of URL components.
[...] A route of "file/*path" will match #file/nested/folder/file.txt, passing "nested/folder/file.txt" to the action.
And if we check the source, we'll see this:
// Backbone.Router
// -------------------
//...
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
var namedParam = /:\w+/g;
var splatParam = /\*\w+/g;
So your '.*' route should only match a literal '.*' rather than matching "anything" as you're expecting.
I think you want something more like this:
routes:
'' : 'index'
#...
'*path' : 'index'
Make sure your *path route is at the bottom of your route list:
// Bind all defined routes to `Backbone.history`. We have to reverse the
// order of the routes here to support behavior where the most general
// routes can be defined at the bottom of the route map.
This assumption about the "order" of elements in an object seems rather dangerous and ill-considered to me as there is no guaranteed order:
The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified.
I think you'd be better off adding your default *path route manually in your initialize method:
class NodeNetBackbone.Routers.RegistryPatients extends Backbone.Router
routes:
'' : 'index'
'/index' : 'index'
'/:id' : 'show'
'/new' : 'new'
'/:id/edit' : 'edit'
initialize: ->
console.log 'init the router'
#route '*path', 'index'
#...
Related
I'm trying to replace our current backend service using Nestjs library,
however, I want to create a route with 2 optional parameters in the URL something like :
/route/:param1/config/:OptionalParam3?/:OptionalParam3?
that means the route should catch :
route/aa/config
route/aa/config/bb
route/aa/config/bb/cc
how can I achieve that, I have tried to use ? and () but it's not working well.
If you are looking for how to annotate an optional query parameter, you can do it like so:
#ApiQuery({
name: "myParam",
type: String,
description: "A parameter. Optional",
required: false
})
async myEndpoint(
#Query("myParam") myParam?: string
): Promise<blah> {
[...]
}
Router params name should be unique. The correct route path is:
Existing one is:
/route/:param1/config/:OptionalParam3?/:OptionalParam3?
Correction:
/route/:param1/config/:OptionalParam3?/:OptionalParam4?
Opinion: You can use query params if the params are optional. It is never a good idea to create optional param routes (disagreements agreed). Both serve the same purpose, but having them as the query params makes it more understandable for debugging and fellow developers.
I solved this problem by using #Query decorator as below:
Here is my controller:
#Get()
async getAll(#Query('someParameter') someParameter?: number) {
return this.service.getAll(someParameter);
}
Here is my client (Angular) service:
getAll(someParameter?: number) {
return this.http.get(`apiUrl/controllerAddress?someParameter=${someParameter}`
);
}
You can use this structure:
-route
-aa
-config
-[[...id]].js
It will work for the routes :
route/aa/config/{anything}
I seem to be having an issue accessing a value from a mixin when trying to use bracket notation. I have the following setup:
// in webpack plugins
new HtmlWebpackPlugin({
hash: true,
template: './assets/template/about.pug',
filename: 'about-us.html',
inject: true,
page: 'about',
locals: require('./assets/data.json'),
chunks: ['about']
}),
The json
// data.json (snippet)
{
"pages" : {
"about" : {"title" : "About Us","metaDesc" : ""},
}
}
Pug mixin
mixin pageTitle(thePage)
title= htmlWebpackPlugin.options.locals.pages[thePage].title
Using pug mixin
+pageTitle(htmlWebpackPlugin.options.page)
I get an error Cannot read property 'title' of undefined.
If I change that to htmlWebpackPlugin.options.locals.pages.about.title it will parse just fine.
If I change that to htmlWebpackPlugin.options.locals.pages[thePage] it will return [object Object] but I can't access any properties.
If I change that to htmlWebpackPlugin.options.page or just use thePage then "about" will be rendered.
I've done a typeof to check if it's a string. It is. I've tried putting it into a variable first. Same issue.
Any thoughts?
Why do you need notation in brackets? What is the purpose of this?
This record works title = thePage.pages.about['title']
I prefer the following entry ;)
In the file about.pug, make an entry at the very top.
block variables
- var path = self.htmlWebpackPlugin.options
You pass on to the function
// locals is the path to your json file
+pageTitle(path.locals)
Mixin should look like this
mixin pageTile(thePage)
title = thePage.pages.about.title
Here you have the whole example in addition with generating multiple subpages with pug-loader → photoBlog
So let's say we have the following url:
http://example.com/shops/map/search
I want to access the second segment (map) and check its value.
How can I achieve this in Express? Thanks in advance!
you have to configure your express routes to accept url segments.
app.get('/shops/:type/search', function (req, res) {
res.send(req.params)
})
For a request like this
http://example.com/shops/map/search
req.params will contain required URL segment.
Request URL: http://example.com/shops/map/search
req.params: { "type": "map" }
You can access the url segments by splitting the url into an array.
Like this:
let requestSegments = req.path.split('/');
You can use a route parameters with a constant set of values.
Express uses path-to-regexp to parse the strings you provide for routes. That package permits providing a custom pattern with a parameter to limit the values that can be matched.
app.get('/shops/:kind(list|map)/search', searchShops);
The contents of the parenthesis, (...), are a partial RegExp pattern, in this case equivalent to:
/(?:list|map)/
# within a non-capturing group, match an alternative of either "list" or "map" literally
Then, within searchShops, you can determine which value was given with req.params:
function searchShops(req, res) {
console.log(req.params.kind); // 'list' or 'map'
// ...
}
Alternatively, you can leave the parameter open, checking the value within the handler, and invoke next('route') when the value isn't acceptable:
app.get('/shops/:kind/search', searchShops);
var searchKinds = ['list', 'map'];
function searchShops(req, res, next) {
if (!searchKinds.includes(req.params.kind)) return next('route');
// ...
}
The original answer does the job, but leaves you with an empty element in the array. I'd use the following slight variation to solve this too.
let requestSegments = req.path.split('/').filter((s) => { return s !== ''});
...guess I'm the first to ask about this one?
Say you have the following routes, each declared on a different controller:
[HttpGet, Route("sign-up/register", Order = 1)]
[HttpGet, Route("sign-up/{ticket}", Order = 2)]
... you could do this in MVC 5.0 with the same code except for the Order parameter. But after upgrading to MVC 5.1, you get the exception message in the question title:
Multiple controller types were found that match the URL. This can
happen if attribute routes on multiple controllers match the requested
URL.
So the new RouteAttribute.Order property is only controller-level? I know in AttributeRouting.NET you can do SitePrecedence too. Is the only way to have routes like the above when all actions are in the same controller?
Update
Sorry, I should have mentioned these routes are on MVC controllers, not WebAPI. I am not sure how this affects ApiControllers.
If you know that ticket will be an int you can specify that type in the route to help resolve the route:
[HttpGet, Route("sign-up/register")]
[HttpGet, Route("sign-up/{ticket:int}")]
This approach worked for me, per user1145404's comment that includes a link to Multiple Controller Types with same Route prefix ASP.NET Web Api
In case of Attribute routing, Web API tries to find all the controllers which match a request. If it sees that multiple controllers are able to handle this, then it throws an exception as it considers this to be possibly an user error. This route probing is different from regular routing where the first match wins.
As a workaround, if you have these two actions within the same controller, then Web API honors the route precedence and you should see your scenario working.
There are two ways to fix this:
A regex constraint, like here: MVC Route Attribute error on two different routes
Or a custom route constraint, like here: https://blogs.msdn.microsoft.com/webdev/2013/10/17/attribute-routing-in-asp-net-mvc-5/
You can create custom route constraints by implementing the IRouteConstraint interface. For example, the following constraint restricts a parameter to set of valid values:
public class ValuesConstraint : IRouteConstraint
{
private readonly string[] validOptions;
public ValuesConstraint(string options)
{
validOptions = options.Split('|');
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
object value;
if (values.TryGetValue(parameterName, out value) && value != null)
{
return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase);
}
return false;
}
}
The following code shows how to register the constraint:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
var constraintsResolver = new DefaultInlineConstraintResolver();
constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint));
routes.MapMvcAttributeRoutes(constraintsResolver);
}
}
Now you can apply the constraint in your routes:
public class TemperatureController : Controller
{
// eg: temp/celsius and /temp/fahrenheit but not /temp/kelvin
[Route("temp/{scale:values(celsius|fahrenheit)}")]
public ActionResult Show(string scale)
{
return Content("scale is " + scale);
}
}
In my opinion, this isn't great design. There are no judgments about what URL you intended and no specificity rules when matching unless you explicitly set them yourself. But at least you can get your URLs looking the way you want. Hopefully your constraint list isn't too long. If it is, or you don't want to hard-code the route string parameter and its constraints, you could build it programmatically outside the action method and feed it to the Route attribute as a variable.
I am working on my first project using requirejs. I have a router and a view and I would like to have access to my Router.navigate() method from the view when an item is clicked. I using CoffeeScript. how can I make router global?
router.coffee:
define [
'jquery'
'backbone'
'application/views/mainView'
'application/models/app'
],($,Backbone,MainView,App)->
class Router extends Backbone.Router
routes:
'organisation': 'organisationScreen'
'*actions': 'organisationScreen'
constructor:() ->
super #routes
initialize:()->
Backbone.history.start() #pushState: true
console.log " The Route Initialized"
organisationScreen:()->
$('.slides').fadeOut()
$('.confBlock').removeClass('onshow')
$('.organisationsBlock').addClass('onshow')
view.coffee
define [
'jquery'
'backbone'
'application/views/conferenceView'
],($,Backbone,ConferenceView)->
class OrganisationView extends Backbone.View
#el: '#appcontainer'
tagName : 'li'
className : 'organisation'
events:
'click .org-item' : 'choice'
template : _.template($('#Organisation-template').html())
initialize : ()->
...
render: ()->
...
choice:(ev)->
# Call Router.navigate()
All Router::navigate does is call Backbone.history.navigate so I would just use that. Here is the source
choice: (ev)->
Backbone.history.navigate(...);
Simply store your router along with other global variables you need in the Backbone object.
in your router init do the following:
Backbone.application = {};
Backbone.application.router = this;
then use it like this:
define(["backbone"], function (Backbone) {
Backbone.application.router.myaction();
});
I see three options for your problem:
Pass router in view constructor and use as local variable #options.router
Make circular dependency between two modules (check this answer https://stackoverflow.com/a/16314250/329226)
Trigger custom navigation event from view and listen to it from within the router