angular-ui / ui-router

The de-facto solution to flexible routing with nested views in AngularJS
http://ui-router.github.io/
MIT License
13.53k stars 3k forks source link

way to create optional parameter in url? #108

Closed legomind closed 10 years ago

legomind commented 11 years ago

I fiddled with some regex, but this is beyond me.

I need the last parameter in my state to be optional. In other words: capture it if it is there, but otherwise ignore it.

Something like:

$stateProvider
.state \user.page, {
  url: '/:username/:page?'
  template-url: '...'
  controller: [
  \$stateParams
  ($stateParams) ->
    # The page parameter should be optional.
    # This should activate on both '/legomind/dashboard' as well as '/legomind'
  ]
}
ksperling commented 11 years ago

You can use a custom regex for the parameter if you use curly brace (JAX-RS style) sytax:

url: '/{username}{page:(?:/[^/]+)?}'

Note that in this case the '/' preceding the optional segment is going to be part of the 'page' value. We could look at allowing capture of a specific part of the regexp via capturing parenthesis, but this would require pretty much completely pre-parsing the regexp to see whether it has capturing parenthesis (as opposed to non-capturing or other uses of the '(' and ')' characters).

legomind commented 11 years ago

@ksperling Thanks. Your solution will have to do for now.

ksperling commented 11 years ago

@legomind Another idea to solve the same issue: Use $urlRouterProvider to do a redirect from e.g. '/{username}' to '/{username}/1' or whatever your default page is, then the state.url only needs to handle the case where the parameter is present.

xixixao commented 11 years ago

+1 support

  url: '/:username[/:page[/:question]]'

I'm sure I've seen that syntax in some router framework (can't remember).

amitava82 commented 11 years ago

+1 support as well

 .state('user.add', {
    url: '/add',
    templateUrl: 'app/user/user.add.html',
    controller: 'AddUserController'
  })

I would like to pass few optional param with the url.

 users/add?groupId=12&orgId=2

So a user can visit the users/add directly or from another controller I can transition to user.add state with those param values.

timkindberg commented 11 years ago

You can already do query params. @ksperling are query params optional?

amitava82 commented 11 years ago

I'm afraid it is not unless I'm doing something wrong. If I don't pass groupId to url then it hits my otherwise route.

url: '/add/{groupId}'
timkindberg commented 11 years ago

But what if you do the URL like this? url: '/add?groupId'

Then navigate to either /add or /add?groupId=123

ksperling commented 11 years ago

yes, query parameters are optional. Like @timkindberg shows in his example, you still have to declare them in the URL pattern though, so that $stateProviders knows which parameters you're interested in, i.e. your URL pattern should be something like

url: 'users/add?groupId&orgId'
amitava82 commented 11 years ago

Thanks, that worked! I did try it earlier but got exception from ui.router. I must have done something wrong.

benjamingeorge commented 10 years ago

+1 to add optional paramaters like item/{:id?}

tamtakoe commented 10 years ago

+1

fakingfantastic commented 10 years ago

+1

miraage commented 10 years ago

Upvote :+1:

url: '/:username[/:page[/:question]]',
defaultParams: {page: 1}
stereokai commented 10 years ago

+1 This should be top priority, not 0.4 milestone

One way to get around it, is to check first if the value given to the optional parameter is not the name of a child state.

If it is, then the URL should be treated like it points to the child state, skipping the optional parameter on the parent.

nateabele commented 10 years ago

@stereokai Since it's obviously top priority for you, you're welcome to work on it and submit a patch.

stereokai commented 10 years ago

I am already working on one, using the $stateProvider decorator :) I'll post it here when I'm done On Jan 1, 2014 2:10 AM, "Nate Abele" notifications@github.com wrote:

@stereokai https://github.com/stereokai Since it's obviously top priority for you, you're welcome to work on it and submit a patch.

— Reply to this email directly or view it on GitHubhttps://github.com/angular-ui/ui-router/issues/108#issuecomment-31415793 .

stereokai commented 10 years ago

@nateabele @ksperling

A small update:

I have just spent 3 hours with the UI-Router code - which was a stimulating and intriguing journey. I have dug into the depths of $UrlRouterProvider and I now understand the relationship between it and the UrlMatchers, as well as how UI-Router applies this relationship when handling location changes and state transitions. Pretty effing awesome code there, guys.

Okay, so as I mentioned above, one way I can see which makes optional parameters possible, is checking if the value of a parameter is in fact the URL segment of a child state. So, to give an example:

    Parent state URL: /parent[/:optionalParam]
    Child state URL: /child

In this example, if optionalParam holds the value "child", it can't be captured and the transition should be delegated to the next rule, i.e. the child state.

So far what I did is attach the UrlMatchers to every rule, just so I can access them from within UrlMatcher.exec.

I'm going with the syntax suggested by @xixixao above, so next I will be adding the parsing of those square brackets to the placeholder regex. Regular expressions have never been my strong side, so for now, I won't be taking care of nested optional parameters.

To access the UrlMatchers from the exec function, I also had to define the rules array outside of $UrlRouterProvider. It's only 1 closure jump, so it's not so bad. However, that strategy might still suck - I haven't yet thought that one out well enough, but executing this check on all child states might pose a significant impact on performance, as it could lead to loops over loops on the rules array and the objects within. Perhaps building a hierarchy object, optimized for recursion, in the UrlMatcher constructor's while loop, is a better direction.

Another way to optimize is to set up a flag on rules which have optional parameters, so rules without them will completely skip on looking for clashes with state paths and spare some precious CPU cycles.

Okay, that's it so far. I'll update next week as I progress. Sorry if I don't make sense, I'm not a native English speaker

bfricka commented 10 years ago

I like the syntax :) If you need any help, let me know.

stereokai commented 10 years ago

@brian-frichette Thanks! I appreciate it. I'm picking up work from where I stopped this weekend, so I'll keep you posted :)

bfricka commented 10 years ago

:+1:

JakobJingleheimer commented 10 years ago

+1

It would be nice if the optional flag was the same as angular-router (/required/:optional?/:anotherOptional?), but as long as it works!

diogobeda commented 10 years ago

Any update on this issue?

+1 to any of those syntaxes. /:required/:optional?/:anotherOptional? and /:username[/:page[/:question]]

towr commented 10 years ago

I think as a start it should be fairly easy to support /url/{optionalpar:regex}?/child, because the regex avoids the problem of having to check whether the optional parameter might be a child. e.g. https://gist.github.com/towr/e721aa022481b3ceae93

I suppose the (zend-like) /:username[/:page[/:question]] syntax is a bit nicer, because you don't have to mess around with the previous/next segment to handle the // issue for empty optional parameters. (And of course it gives more options, such only allowing an optional question if there is a page)

stereokai commented 10 years ago

@diogobeda Was very occupied in the past month, and this task was pushed back, but according to my schedule I will focus on this towards the end of next week. That means Wednesday or so.

dustinrjo commented 10 years ago

Rooting for you @stereokai. Viel Erfolg!

stereokai commented 10 years ago

Danke Dustin!

Sorry for the big promises and even bigger delays! We had an unexpected breakthrough with an unexpected investor, and there was a lot of pressure with preparing an ocean of things for next week.

I will continue with this issue later today in the afternoon and will post my updates here as I go! Hold tight

stereokai commented 10 years ago

I've got an update, it's working very well! I am adding annotations and will commit to my own fork (I don't have tests, also I couldn't think of a way to make the "unbalanced test group" check work with optionals, so that's definitely a breaking change).

JakobJingleheimer commented 10 years ago

Sweet, thanks @stereokai !

stereokai commented 10 years ago

The syntax I used just for starters is url: '/:username/[page]/[question]' - note that the slash is outside the brackets.

Subviews will work, i.e.: url: '/userprofile/[id]/photos/' (haven't tested without trailing slash yet)

You can see the changes and get it at: https://github.com/stereokai/ui-router/commit/6723a21fd9c7b9fda25f7c7ad56f8120e1d70d08

Run grunt build to get the compiled file.

Looking forward to questions and feedback!

stereokai commented 10 years ago

I would like to hear your opinions regarding the parameter syntax.

I used [oppam] because it made sense to me, it's familiar from practically everywhere in the programming-o-sphere, and I believed it's friendly to newcomers not familiar with the UI-Router syntax.

However, looking at it now, it breaks the current pattern, which in turn might cause even more confusion with newbies. Another downside comes up when considering nested optionals: /[oppam1/[oppam2/[oppam3]]] or the even less consistent /[oppam1[oppam2[oppam3]]]. Both would have to translate anyway to /oppam1/oppam2/oppam3 eventually.

So I'm thinking about changing the syntax to [/:oppam1] as proposed above. Your feedback would be highly appreciated here!

towr commented 10 years ago

I think I would prefer /literal1[/:oppam1][/literal2[/:oppam2]] style, because it also allows you to denote which slashes and literals are optional. (And it's familiar to me from zend2)

diogobeda commented 10 years ago

+1 for @towr 's opinion

stereokai commented 10 years ago

There was a missing line of code in my commit earlier, I fixed that now.

JakobJingleheimer commented 10 years ago

I find it odd to have the slash outside of the container, and I find nested optionals unnecessary because there's implicit hierarchy: /library[/section][/book][/chapter][/page][/line]. It's pretty clear that there won't be chapter without book.

It's also kind of strange to have an optional where subsequent params can still be defined when it is not (in your example :userprofile/[id]/photos). Photos can't operate without an id (and probably nothing else could either).

stereokai commented 10 years ago

@jshado1 of course it can: :userprofile would take me to my own profile when I'm logged in, while :userprofile/[id] would take me to my friend's profile. In the same manner, :userprofile/photos would take me to my photos library and :userprofile/[id]/photos to my friend's. Just to illustrate.

I do agree with your outlook on implicit hierarchy.

JakobJingleheimer commented 10 years ago

That's not RESTful because /userprofile/photos for me is not the same for you.

bfricka commented 10 years ago

Who said the URL structure needs to be RESTful? That's up to the implementers to decide.

stereokai commented 10 years ago

I thought RESTful properties only apply to server-side APIs? I don't see how it has anything to do with client-side routing...

bfricka commented 10 years ago

It would be great to hear from @ksperling and @nateabele on this.

stereokai commented 10 years ago

Indeed, guys, give me your final feedback, and your thoughts on where to take it next - and I'll keep the commits pouring in :)

I do need a litte help with tests, though. I don't have any experience with writing them...

JakobJingleheimer commented 10 years ago

RESTful definitely applies to client-side, and it was the intention for Angular's original router.

I can help with tests. I'm somewhat new to them myself, but now I write about 2 per week.

mping commented 10 years ago

RESTful is per application. IMHO ui-router should support RESTful, not necessarily enforce it. Btw, can you nest optionals? :userprofile[/details[/id]]

stereokai commented 10 years ago

Not yet - that's a whole other degree of complexity. To be honest I'm not planning on taking it to that level by myself. Just making sure my solution is robust and up to standard with this project, while hopefully resolving the requirements of the people in this thread. But not that one, no :)

timkindberg commented 10 years ago

@nateabele and I are too busy to help with this. There seem to be enough people in here that are interested, so get it where you want it amongst yourselves and submit a PR. Thanks for your efforts guys!

timkindberg commented 10 years ago

@stereokai fwiw my opinion is to do similar to what you have now but with slashes inside the brackets and no trailing slash (because we don't really recommend trailing slash, even though I know people want it).

stereokai commented 10 years ago

@timkindberg Cheers. I will go with [/oppam], without nesting. Will be done with it this week. Somebody will have to take care of the tests though. Anybody cares to take responsibility? Worst case just tell me how to do it and I will try to push it into my schedule as well

stereokai commented 10 years ago

I will try to pick up from where I stopped tomorrow. Will update here.

joshrtay commented 10 years ago

no need to differentiate from angular-router. let's use ? syntax for optional params.

timkindberg commented 10 years ago

@joshrtay that's fine with me