Some states are easy to map to routes. For example:
'users/:id' --> #users/123
The state here is just a simple integer, and so it's quite easy to create a sensible route and pass that state into the route handler.
But what strategy should be used when your state is much more complex (e.g. JSON object, array of arrays, etc.)?
I notice that Gmail "tokenizes" its state in what's seemingly a base-64 encoded token, e.g. something like this:
#inbox/h223r488v8vHh4fa9a9qyUTkmb9334mN9O2s8
...and then I imagine it somehow reverses that token back into meaningful state, but this process is what I'm confused about.
Relevant: http://lostechies.com/derickbailey/2011/12/27/the-responsibilities-of-the-various-pieces-of-backbone-js/
Why not use a base64 javascript library? There are a ton out there. Here's one. Pretty sure you'll need to JSON.stringify before encoding and JSON.parse afterwards.
Then you just decode it if it's already in the URL. Then you don't need to store it, you can just decode and encode.
routes: {
'encoded/:code': 'decode'
},
decode: function(code) {
// using the linked library
var decoded = window.atob(code);
// do what you want
}
base64 is built into some browsers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
Related
For my client I'm creating something like quiz web app in react with redux based on websockets (socket.io) with a huge, very unique data. No user interaction, just presentation layer. It works like this: I get websocket event with url to my layout and payload data, and then I render given url and fire redux action with data as argument, which becomes app's state. Simple as that. BUT I noticed that on first render initial state is loading, not given from websocket as argument to action. As I said data I get is huge and unique so I didn't want declare in reducer something like this:
pageData: {
assets: [],
questions: [],
details: []
And so on. It's much more complicated btw it's just an example. Instead of this I made something like this:
pageData: {}
And I was hoping that on view (using connect) I can get this data like this:
this.props.view.pageData.questions
But then it turned out that I can not get this because it's undefined on first render. So my questions are:
Is there a way to access to this data on first render without
declaring whole structure?
If not, should I reconstruct given data in reducer?
Should I then create reducers for each page (there are like over 20 views
with unique data)
Of course I can declare everything in reducers but I feel it's very hard to maintain so much data.
But you know, maybe I'm just too lazy and I should declare initial state for each page and this question does not have sense ;).
I think you may have a few options here:
Define fallback data in your components if undefined
Don't render your page (or components) until you have received the data
Define your initialState explicitly as you already suggested
All or most your components expect or should expect data of a certain kind and in a certain format. For this reason, laying out the structure beforehand (#3) seems to be most appropriate. Ask yourself this: would my app still display correctly if the format of the web socket event data changes?
To answer your questions specifically:
Is there a way to access to this data on first render without
declaring whole structure?
Yes, you could use the || operator in your bindings to fall back (#1) to an empty array or object or value. Example <MyComponent listOfItems={this.props.items || []}. This effectively creates an empty state, however, IMO this should be standardized in the reducer/store with initialState.
Should I then create reducers for each page[?]
Not necessarily a reducer for each page, but a store with all pertinent data to your application. It is hard to say for sure without knowing more about the architecture of your app, but keeping small, well defined chunks of information is generally easier than one big blob.
I strongly advocate defining your data beforehand. It might sound cumbersome at first, but it will pay off greatly and helps others understand what the app might look like with live data.
that's because you haven't added default case in reducer
default:
return state;
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
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 want to serialize all of the data in my realtime document to a string with which I can later initialize a new realtime document. I want to do this so my users can make copies of their drive files, save different versions, etc, and I can re-initialize the relevant realtime documents from the string.
I see I can call document.getModel().getRoot().toString() to get a string representation of the root CollaborativeMap, but I don't see any easy way to load that string back IN to a CollaborativeMap. Also, the string returned is not JSON, so I can't easily use JSON.parse to turn it back into a normal JS object and iterate from there.
I can make this work by hand. Is there any easy automated way?
You can do that on the service side using the realtime.get() and update() methods:
https://developers.google.com/drive/v2/reference/realtime
http://jsfiddle.net/EjyW4/
Essentially, I am trying to post an array of objects from the client using AngularJS with the resources module, and instead of sending a JSON object, Angular is sending a useless toString representation over the wire.
Unfortunately, the code in the fiddle itself doesn't do much -- the intent is outlined here with more context, though it still is very raw and do not yet resemble anything looking like the right way) But this seems to be an angular issue rather than grails, at least from looking at the Chrome console.
Query String Parameters:
callback:JSON_CALLBACK
tests:%5Bobject+Object%5D,%5Bobject+Object%5D
There seems to be an angular.toJson -- http://docs.angularjs.org/api/angular.toJson -- but it doesn't seem to work in this case. The documentation I've seen doesn't seem to cover more than sending a basic int. If I have to, I'll send over a comma separated string, but this seems like it should be a common use case.
The $resource function actually returns a new $resource object constructor, which you then set properties on, then call methods like save on.
So your problem in your fiddle is you're trying to save a $resource with no data set on it! All you have is a config property, tests, which it doesn't know what to do with.
You instead want to:
Set up your constructor for a new resource using $resource factory/method.
Create a new instance of your new resource.
Set a property on it (eg myNewResource.tests = $scope.tests);
Save it (myNewResource.$save())
http://jsfiddle.net/EjyW4/2/
It looks like what you were trying to do originally is better suited for $http (I put an example of that in the fiddle too).