I'm working on a project that requires that most UI state is reproducible via URL. In a traditional (server-side) app, I could use both URL parameters like:
/resources/:id
and unordered optional query string parameters, like:
/resources/:id?page=5&sort=date
Is there an idiomatic way to achieve this with Backbone/Marionette routing? I don't want to have to configure routes for every possible combination of parameters.
The fact that I don't see this addressed much makes me think I may be barking up the wrong tree, approach-wise, but I do think being able to represent as much UI state as possible in the URL is pretty important to a lot of projects.
It looks like the best option is the now-orphaned backbone-query-parameters project.
It supports routes exactly in the form I'm looking for:
#resources/:id?flag=true
URL parameters are not really enforced by Backbone/Marionette. One possible reason is that URL parameters are not SEO friendly.
Instead, you can configure optional URL fragments which will work pretty much like URL parameters, this way:
/resources/:id(/page/:page)(/sort/:sort)
If you do this way, the only gotcha here is that this sequence of "parameters" need to be ordered.
HOWEVER if you need it to be unordered, you can simply use Regular Expressions with router.route() method inside your initialize, as explained in Router#route
Related
The Problem:
I'd like a single ui-router state to match the following urls:
#/.../update?id
#/.../update?username
#/.../update?customerId&sandwichId
If/when the 'update' state is active (it's a routed component), the component's controller knows enough to expect which params will be there (via a provided array of Strings that should/will match whatever query params are in fact present).
Current Solutions (none of which I'm 100% happy with)
I could just leave the url at '#/.../update' with the implicit understanding that I cannot navigate to that url from anywhere but it's parent state, and just pass in its primary key fields as a component binding. The drawback obviously being if I just type the URL in my browser, it will not have enough information to load.
As per an example I found here, I defined my 'update' state with single query parameter: pkKeys. I then define pkKeys upon my state transition to be the entire query parameter string I desire. This effectively achieves what I am looking for. However, typing this just doesn't feel right:
#/.../update?pkKeys=username=test
Moreover, it looks ugly on the more complicated situations:
#/.../update?pkKeys=customerId%3D3ae2e6eb-3bf7-42f8-a09c-9c690c8dbe15%26sandwichId%3D2cb6d513-06a3-4aa4-93bb-e53d279d95cb
Ideal Target State
Ideally, I'm looking for a way to configure my state in a way that matches the above patterns of urls. I did not have much success with $location service, but something tells me that has something to do with it. It appeared to me that $location.search() was cleared whenever $state.go( ... ) was invoked.
Thanks for any help in advance! Happy to elaborate on anything that might not be clear. It's a rather unique problem set I find myself in here...
On Further Research
For reference, I'm using Angular UI Router 1.x. I have found they have the notion of dynamic parameters, which seems to nearly solve the problem I am encountering (see the GitHub issue for this change). However, where this fails me is the state configuration still expects you to list the dynamic parameter's name. As my query param names are data-driven, the set of names is not a finite set. So I need dynamic parameters...with dynamic names.
I'm trying to make a dynamic hash Url so i can book mark it so angular routing will pick it up. I have over 50 search fields that could be appended to the hash. Is there a way to create a generic route that captures all the optional hash values using $routeParams instead of having to specify every single search parameter?
I'm using $location.path(path, false) to inject my new url with the hash so the screen doesnt refresh, but angular routing picks it up.
Sample Url(s)
#/search/a/1/b/4/c/0
#/search/c/111/d/2/e/0
$routeProvider.
when('/search/a/:a?/b/:b?/c/:c?/', {
templateUrl: '/app/partials/search.html',
controller: 'SearchCtrl'
}).
query params
Even after exchanging comments with you, I'm not entirely sure what your end-goal is regarding the URL. I think with the dynamic nature of your params you'd probably fair better by using traditional query params for a single state.
'/search?{param1}{param2}{param3}....{param50}'
That assumes that you could have any number of combinations of parameters that may or may not be related to one another.
borrowing from a RESTful approach
This assumes that you're not happy with the query param approach and indeed are seeking a more stateful way of doing this. It's a tad more RESTful looking assumes that you have a set number of parameters per resource position (meaning "/position1/pos2/pos3"). In other words, you are not going to have all and any possible combinations but a set number reasonable set of combinations.
'/search/make/{make_id:apple|windows|android}/model/{model_id:ipad|iphone|win_phone|galaxy|nexus}/style/{style_id:mini|nano|giant}'
note - I am borrowing from the way ui.router defines URL parameters, with the assumption that the non-ui.router router handles the regexp similarly.
I'm trying to figure out the best way to pass data through my routes. I know I can use params but there are certain types of data that don't belong in params.
For example: I have an index page that displays a list of applications. Each application has a button next to it which will route you to the view for that application.
I want to pass the application itself to the Application handler. However, it doesn't make sense to pass the entire application through params. Though it does make sense to pass the application identifier to params (i.e. :id or :name)
So the way I think I should be doing this is pass the application identifier to params, then in the Application component search my ApplicationStore for the appropriate application given the identifier.
Though, wouldn't it be easier and faster to pass the application itself? Is there a way to do this. Is there a reason not to do this?
Here is some code:
<Link to="showApplication" params={{name: application.name}}>View</Link>
or
<Link to="showApplication" params={{application: application}}>View</Link>
Thanks in advance!
The problem is that when the person refreshes, or in some other way directly loads the url, it needs to show the correct view. Because URLs are just strings, you need to represent the route as a string. You can't (easily) shove an actual application into the url.
The job of a router is to take that string (the URL) and map it to the actual code (the handler), and provide any extra data (the params, query, and/or hash).
Your job as a router user is to ensure there's enough information in the URL for the router to pick the right handler, and for the handler to have enough information to do its job (e.g. which application to show).
If the url is something like mysite.com/showApplication, there's clearly not enough information.
If it's something like:
mysite.com/showApplication/React.createClass(%7Brender%3A%20function()%7Breturn%20React.createElement('div'%2C%20null%2C%20%22I'm%20an%20application!%22%7D%7D)%3B
i.e. putting an application in the url, that's too much information, and generally a very bad idea.
But mysite.com/showApplication/applicationName is just right :-)
I'll provide an easy and hacky way, have a global object which you use to share information across routes eg.
window.CONTEXT = {'share':'this'}
Note that it's quite important only to use this way if the object you want to share can be recreated by the route itself, as FakeRain mentioned above the route has to contain just enough information for it to give the user the same experience if they reload.
The only reason you'd use this is to save bandwidth if you need to request information for what you want to share but yet you don't want a huge link.
I would like to format my URL like so:
/teacher/page:2
to
/teacher
I would like to achieve this result by using as less code as possible (perhaps only from routes.php?), without modifying how the PaginatorHelper behaves.
Thank you for your help!
You can accomplish this with:
ajax
form posts instead of gets (requires altering the pagination
helper)
But I would advise against it. This is designed for good usability. If you change it, It will make your site less user friendly than you may want it to be. I know I would be frustrated trying to jump to a specific page only to find out I have to click my way to page 35. Yuck!
I'm trying to wrap my head around backbone.js but I'm finding it hard due to the lack of (IMO) good examples.
First of all, what is the best way of getting a link to an object.
If I want to get the edit url of an Album model I could do album.url() + '/edit', is this really the best way?
Also, I'm trying to make my application work 100% without javascript so I don't want my URLs/links to say /albums/#1/edit, I want it to be /albums/1/edit and override this in JS.
I'm thinking I create normal URLs and use jQuery.live to call router.navigate in backbone.js
I never got this to work however, when I call router.navigate('/albums/2', true) the URL changes but my show action is never called. If I refresh it's called so the route is matched.
What am I missing?
The basic answer, which is kind of frustrating, is "there is no preferred way!". Backbone.js doesn't tell you how to set up links, you can do it any way you like. I found this flexibility just as annoying as you do, at least at first.
So here's the way I'm approaching this on my current project, with the (big) caveat that this is just one of many ways to do things in Backbone:
For the most part, I don't use actual links. There's no explicit reason not to, but it means you have to keep track of a bunch of URL strings that have to be consistent. I would rather stick all the URL formatting in my routers and not deal with it elsewhere.
To open a new "top-level" view, like an editing screen, I set something that fires an event. In the application I'm currently working on, I have a global State model, and to open a new view I call state.set({ topview: MyTopView }). This causes the state object to trigger change:topview.
Any piece of the UI that needs to change when the top-level view changes has an update method bound to change:topview. When the event fires, they look at state.get('topview') and update as necessary.
I treat my routers as only marginally specialized parts of the UI - they're essentially views that render in the browser address bar, rather than the window. Like other views, they update the state object on UI events (i.e. a new URL), and like other views, they listen to the state object for changes that cause them to update. The logic that the editing screen has the URL albums/<albumid>/edit is fully encapsulated in the router, and I don't refer to it anywhere else.
This works well for me, but it adds an entirely new pattern, the global State object, to the Backbone structure, so I can hardly call this the "preferred" approach.
Update: Also note that .url(), in the Backbone idiom, refers to the model's URL in the back-end API, not the front-end URL (it's not like Django's get_absolute_url). There is no method in the default Backbone setup that gives you a user-facing URL for your model - you'd have to write this yourself.
Also, I'm trying to make my application work 100% without javascript; so I don't want my URLs/links to say /albums/#1/edit, I want it to be /albums/1/edit and override this in JS.
you can do exactly this w/ pushState. just enable it in your Backbone.history.start call:
Backbone.history.start({pushState: true})
this tells Backbone to use the HTML5 History API (a.k.a. "PushState"), which uses full URLs exactly like you're wanting.
read up on the history api here: http://diveintohtml5.ep.io/history.html
and I wrote up a 2 part series on using pushstate w/ the second part focusing on progressive enhancement in backbone, to do what you're needing:
http://lostechies.com/derickbailey/2011/09/26/seo-and-accessibility-with-html5-pushstate-part-1-introducing-pushstate/
and
http://lostechies.com/derickbailey/2011/09/26/seo-and-accessibility-with-html5-pushstate-part-2-progressive-enhancement-with-backbone-js/
hope that helps :)