I have just begun to port a layered single page js app onto backbone.js and was trying to understand how to handle composite url parameters with routes and spalts in backbone.js. The backend has rails and sends JSON.
There are various entities (models) like filters, dimensions, features, questions which can be passed via request parameters.
URL 1
/display/#widget?id=42&fon=1,2,4&foff=6,9,19&q=1a2bc3abc4d
URL 2
/display/#widget?id=42&compare=345,567,90&fon=1,2,4&foff=6,9,19&q=1a2bc3abc4d
How to i structure these non-restful urls, keep the same functionality and allow bookmarkability.
Thanks
Backbone's router, for the purpose of invoking views, cares only about the hash portion of window.location. However, it does keep track of the search portion for the purpose of maintaining the browser history.
Therefore, the decision about bookmarkability is your responsibility: the hash will invoke a specific route, and what views that route hides or shows is up to you. How those views parse the search string and react is also up to you.
I can see what you want to do: change a model through the search function, then render it. It's a bit of a two-step trigger: hash-change -> model-sync -> show-view. Structuring that sounds like it'll be fun. But Backbone is capable.
Related
I'm working on a web application where a large set of data can be filtered using JavaScript. When a user selects filters, I want to update the URL of the page to reflect the selected filters so that the user can share that URL with someone else, and that person can load the page and my app will apply the same filters. I don't have a need for the back buttons in the browser to cycle thru the previous filters that were selected.
I think I have two approaches here:
I can create a representation of the filters and add them to the fragment of the current page via window.location.hash. I can parse them on page load to see if there are any already set.
I can create a representation of the filters as query string params, and manipulate the URL using the history API. I would use the replaceState method.
Is there a reason to chose one over the other? Again, I want to emphasize that I'm not concerned with any routing or browser history manipulation. I just want to provide a way for someone to put certain params in the URL that my JS code will parse and apply as the filters.
Using the Vue router and may be also vuex for state management should help you save some time. There is also a little helper library for url encoding / decoding --> qs.
To your question "history vs hash": that depends on your application, the system which hosts the application (e.g. part of a content management system with its own url handling) and the meaning of the params.
History mode generates better looking paths and features some more control, as long as you stay in your application. But: as your path segments have no identifiers, the sort order matters.
Scenario: You have an application which can have three params:
/value1/value2/value3 means something else as /value1/value3/value2
With a query string you don't need to take care about sort order, as every value has its key:
key1=value1&key2=value2&key3=value3 is the same as key1=value1&key3=value3&key2=value2
I am interested in having a route that could respond to a request with a file eg express's res.sendFile() based on the URL's base parameter i.e. www.example.com/:parameter. The problem is that the URLs are completely user generated and completely dynamic. Similar to that of Github, www.github.com/username could render a user's profile or www.github.com/project could render a project—but they are both strings that don't have a pattern and the machine has no way of knowing that www.github.com/username refers to a user view unless it does some type of check.
app.all('/*', function(req, res) {
res.sendfile('index.html', { root: config.server.distFolder });
Github responds to server requests with different views based on the parameter, even though they have no predefined pattern.
i.e. it would be easy to know that www.github.com/user/username is a user route and the server can respond with a user view (the pattern to match would be www.github.com/user/:user But when the string is completely dynamic it becomes more difficult. (How does express know if it should respond with a user or a project view with a url like example.com/cococola)?
I believe you would somehow be able to check the URL parameter, understand that it refers to (in this case) either a project or a user's page, and then render that view. How do you do this without making a synchronous call and forcing the user to wait for the server to check what view-type the parameter string refers to before responding?
I'm using angular, are there other ways to respond to server requests with different pages based on URL's that have no predeterminable matching pattern? The reason being that, I would like to separate my site into many different apps. www.example.com/username might require a user's profile SPA, whereas www.example.com/projectname might require a user's project SPA—but, as these are user defined, there is no way to respond based on the parameter's matching pattern. Would like to keep the URL as minimal as possible :-)
Any help is appreciated. Thanks :-)
Just use a database / some kind of key value store where the key is the url parameter and the value is the view type. Then you just need to do a simple lookup.
I am trying to use Ember data with already built REST api. It works fine for top level routes, for example I have route for courses on api side like following:
app.get('/courses', app.controllers.courses.findAll)
app.get('/courses/:id', app.controllers.courses.findById)
Above works fine and I can easily get data with Ember Data. But problem arises with routes that have multiple levels. For example, courses can have multiple topics and topics can have multiple lectures so routes for them are defined like this on api side.
Topics:
app.get('/courses/:course_id/topics', app.controllers.topics.findAll)
app.get('/courses/:course_id/topics/:id', app.controllers.topics.findById)
Lectures:
app.get('/courses/:course_id/topics/:topic_id/lectures', app.controllers.lectures.findAll)
app.get('/courses/:course_id/topics/:topic_id/lectures/:id', app.controllers.lectures.findById)
So if I want to get all lectures inside a course I need to specify course id and topic id as well (not in the query but in url body, as you can see from url structures in backend api).
In Ember I have models for course, topic and lecture but I dont know how can I specify custom urls so that Ember Data can use those urls when I make requests.
One way could be to manually make ajax requests but this way records will not be populated in Ember Data Store.
Or I could have defined relationships between models in Ember but that would require changes on backend api also which is not an option for me.
So is there any nice way to solve this problem?
I am using:
Ember: v1.6.0-beta.2
Ember-Data: v1.0.0-beta.7
Avoid Ember Data, Ember works just fine without it.
If you really want to use it, make ajax calls then use store.pushPayload('type', data) to sideload the data into the store if you really want it in the store.
I'm building a front end web application from an existing (RESTful) API.
What is the best way to go about this? I'm assuming the new standard way to do this is through something like backbone.js.
I also want the pages to be different URL's and not have a single page application. That said, I'm guessing it's bad practice to request the page and then fire off async api requests when we might as well load the data from the server to start, right? What sort of architecture or technology should I be looking at so that I can reuse the API but not send off two requests to the server back to back, one to load the page, and one to load the data?
Backbone and other JavaScript "MV*" frameworks are definitely a great choice for performant event-driven UIs. You can use this design and have different pages and URLs. It just makes it easier and faster to do the asynch operations on a given page because you will have the relevant data in a JSON model and use a pub / sub pattern in an environment where views automatically update when data changes. Another advantage of this type of design is that you can have your data in models as opposed to walking the DOM to get data.
In addition to Girardi's answer, your concern about doing two request on initial page load - one for the page and one for the actual data - is a real problem.
One of the solutions is to bootstrap the initial data right into the page so you can skip the additional async request. This is called Model Bootstrapping. For example, you can place and additional <script></script> tag which will holds the bootstrapped model:
<script>
window.I_MODEL = [
{id: 1, name: "foo"},
{id: 2, name: "bar"}
]
</script>
then construct your model on the server side and with the help of some templating mechanism print the serialized model right into the page.
Search for backbone model bootstrapping, here is a right-to-the-point example: http://ricostacruz.com/backbone-patterns/#bootstrapping_data
Well, it's not a bad practice to load page first and then requesting async data. keeping template(html) and data calls separate, you can exploit benefits of localStorage, browserCaching to maximum extent. Backbone doesn't inject any magic into your application, it's just provides a framework to organise your code and helps in avoiding some boilerplate code again and again.
I'm in disagreement with a co-worker about the responsiblities of a Router. Inside of our single page application, it's my impression to have network requests (AJAX) handled via events being triggered (i.e. Backbone.Events), like so:
events : {
'click a#getUsers' : 'updateModels'
}
updateModels: function() {
$.ajax(); or this.Model.fetch();
}
Yet it is his understanding that network requests should be handled via the router on URL change (depending on a click which changes the url like a[href="#getThings"] like so:
var App = Backbone.Router.extend({
routes: {
"" : "main",
"thing" : "getThings"
}
getThings: function() {
this.newView = new NewView();
$.ajax(); // which populates the view with data
}
});
I wanted to know what the backbone reason is for doing network requests.
The vast majority of your AJAX requests should start with a backbone model or collection and be handled via the backbone.sync function. The key methods are model.fetch(), collection.fetch(), and model.save().
Backbone is flexible and only handles a very primitive set of use cases, so there are cases when you need to step outside these boundaries, but any call to $.ajax in a view or a router is a strong code smell of MVC failure.
To address some of your specifics.
Can a view call model.fetch() to load data?
Yes, this is perfectly fine and idiomatic backbone.
Can a view emit an event which will eventually load data?
Yes, this is perfectly fine. This is only necessary beyond just calling model.fetch() as your application grows in complexity and the decoupling that event emitting allows is valuable and warranted. For a todo list, it's usually overkill. For a huge application, this can be a clean approach.
Does every model fetch cause a change in the browser URL?
No. In fact many single page applications simply live at "/" forever. Routing to unique URLs is optional. Some applications lend themselves easily to it, others not so much. Don't equate "the application needs data X" with "the browser URL needs to be X". These are orthogonal. If a user clicks on the view for "Bill Clinton" in a list of presidents and you want to change the URL to "/presidents/clinton", then go ahead and fire a new route and that makes perfect sense, but sometimes you just want some data without changing the URL.
To summarize the responsibilities of the router, I think of it as follows:
Based on the URL, which View is supposed to be displayed?
Also based on the URL, are there model IDs that need to be extracted and instantiated into model instances?
Wire things up and render the view
So a typical router method pseudocode might be:
when the URL matches /presidents/:name, respond as follows
grab the :name parameter and make a President model
instantiate a PresidentView, passing it the model
call presidentView.render() and swap the view's element into the DOM at the appropriate spot in the overall page layout
With Backbone, you should let Backbone be doing most of the ajax requests for you, which will be more often than not triggered from your view, not the router. And I would never use a jQuery ajax request when you could use a Backbone request instead. this.model.fetch() is the right way to get the model. I see the router as a way to orient your page, but the finer details should be left to the views. However, choosing between logic in the router vs. in the views/collections/models is more art than science, and I would take a look at other Backbone examples to get more guidance. I think, in a lot of cases, the network requests are handled in the views because it's easier.
In response to your comment below, whenever data is returned from the server, Backbone uses parse to get the information it needs, so rewriting fetch would be a poor decision. You can overwrite the parse method for your model to get the information that you want, and nothing more. i.e.:
var YourModel = Backbone.Model.extend({
parse: function(response) {
//You can manipulate the object in other ways as well,
//but here's how you'd delete info
delete response.junk;
return response;
}
});
Similarly, every time you do this.model.save(), it uses toJSON() before the model gets sent to the server, which you can also overwrite:
toJSON: function() {
// default -> return _.clone(this.attributes);
return _new code here_;
}