With Ember 2.2.0, I have a Route:
Dash.BacklogReportsRoute = Ember.Route.extend
model: ->
#store.queryRecord "report", newest: true
and a Component:
Dash.BacklogReportComponent = Ember.Component.extend
report: null
actions:
generate: ->
# Send the request.
$.ajax
type: "post",
url: "/reports/generate/#{new Date().getFullYear()}",
success: =>
# Update the model.
# TODO - how to reload the model in place? Or at least just refresh
# the Route so we don't have to reload the entire app.
window.location.reload()
which includes as part of the template:
<p>
Report was generated {{report.timestamp}}.
</p>
where timestamp is a computed property on the Report model.
The Route's model is the most recent Report object, and via the POST the generate action causes the server to create a newer (thus more recent) Report. I want to update the timestamp of that [newer] Report.
My question is, in Ember 2, how can I cause the Route to refresh itself? I'm new to Ember so I'm not sure if it's possible to access the Route directly from within a Component (or if I can, if it's a good idea). I don't think just reloading the model would work, because after a successful POST a different model (i.e. a record with a different id) will be returned by #store.queryRecord "report", newest: true than when the page loaded. The existing model will be the same after the POST.
Alternatively, I'd be perfectly happy completely rerendering the Route (it's a very simple page) or transitioning to "this" page again, but I can't figure out how to do that.
For now I'm simply forcing an old-school page window.location.reload().
Looking at this another way, perhaps there's a more idiomatic way to approach this problem with Ember.
Should I be loading in all (or some) Reports with the Route, and forcing a refresh on that Collection after the POST? I see Enumerable has a .firstObject property - is that a computed property (in other words, if my Routes model is a Collection of Reports and I push on to the front of that Collection, would that change propagate through .firstObject references automatically?)
Well you can't actually access the route directly from the component. But that is no problem since you can use actions to achieve the same effect.
So first you need to send an action from your generate action in your component:
Dash.BacklogReportComponent = Ember.Component.extend
report: null
actions:
generate: ->
# Send the request.
$.ajax
type: "post",
url: "/reports/generate/#{new Date().getFullYear()}",
success: =>
#sendAction('didGenerate')
Next you need to hook up the action in the template, so your controller/route gets notified when it is called:
{{backlog-report report=report didGenerate='didGenerateReport'}}
And now you can "catch" that action in your controller or route. We will do it in ther route so we can refresh it:
Dash.BacklogReportsRoute = Ember.Route.extend
actions:
didGenerateReport: ->
#refresh()
model: ->
#store.queryRecord "report", newest: true
Sorry if I made any mistakes with CoffeScript (didn't use it for quiet some time). Also there are other things you could improve. Generally I wouldn't put the ajax call in the component, I would put it in the controller/route if possible and call it with an action (I just have the experience that it's best to avoid putting ajax calls in a component, though there are usecases for it).
I would generally propose to read all of the ember guides if you didn't yet do so. They are pretty good and while you won't understand everything from the first read, you will at least more or less know what to look for, or what is possible.
If you need to manually reload the page, there really should be a better way. Ember as other similar frameworks are an SPA (single page application) and do their magic when updating only parts of the page very efficiently.
that being said, if you want to access the router from the controller/component by sending an action to the router and handling the changes in that action.
see action bubbling
https://guides.emberjs.com/v1.10.0/templates/actions/
Related
For various reasons I will not go into, I have successfully wrapped the ITHit Ajax File browser inside of an Angular Controller, which itself is loaded and wrapped in an Angular-UI-Router UI-View.
All of the settings are configured through preceding service calls (to support a cloud environment with shifty urls), and I've even gotten through all of my CORS (Cross Origin Request) issues, and we've wired in a custom Oath2 implementation on the DAV server. All of this is successfully working with the ITHit File Browser as a pretty centerpiece for our content-browsing implementation.
As of now, when I navigate certain areas, the Angular-ui-router tweaks the Url, the view responds, and the Angular Controller wrapping ITHit responds to the view change, and (without reloading the view) re-fetches the appropriate DAV url with available IT Hit commands ( e.g. SetSelectedFolderAsync )
Here's my (hopefully simple) challenge: when I navigate to certain areas - Angular-UI-router simply reloads the containing UI-View with new content, but when I return - the ITHit Ajax File Browser does not redraw.
Here are some guidelines to my challenge (ignore-able if you offer something I can work with):
I'd prefer to avoid having to "hide" the ITHit container (because
it's irrelevant and I don't want to have to manage keeping it up to
date as state changes in the view. These changes affect DAV paths). Also I don't want to worry about unnecessary network traffic.
I'd really like to let Angular-UI-Router do its thing with the
ui-view in which the browser is resting.
I'd like to keep
whatever calls need to be made invokable to the Angular Controller
(it's managing authentication, path resolution, and contextual
settings config - which change as users navigate).
Everything (well most important things) generated by the ITHit solution is
stored in a Singleton ('DavBrowserService') - so when I return to
the file-browser view, I have everything stored from the initial
instantiation including:
an instance of ITHit Object
the produced instance of the ITHit.Loader
an instance of the previously produced AjaxFileBrowser.Controller Object (ITHit.oNS.Controller)
an instance of the previously produced WebDavSession Object ( ITHit.oNS.WebDavSession)
With the above in place - I'm hoping that I can simply re-wire these instances back on to the now-returned dom-node ('afb-content-div').
Any help is MUCH appreciated!
UPDATE: The below "answer" while appearing to be functional - indeed was NOT. However, I have worked around this issue by grabbing the DOM instance and storing it memory when the user navigates away, and re-attaching it after the user has navigated back to the appropriate area. This way all of the ITHit Magic is still tied to the right DOM node, and I don't have to worry about the partial re-instantiation weirdness. Seems to be pretty solid now.
I figured it out!!! It looks like if I re-instantiate the controller by calling:
var controllerInstance = new ITHit.oNS.Controller( originalSettingsObj );
Everything rewires magically! I've wrapped the above code with some detection for whether the 'afb-content-div' HTML DOM node has children.
After much digging in the code, it looks like this is the argument object returned to as a parameter to ITHitLoader.oninit callback (From the AjaxFileBrowserLoader instance).
Thanks for playing!
An Ember Component needs to fetch data from serve, however i think put AJAX call inside the component is not a good practise.
Or use the Route to fetch data, then pass data to component.But the route's method can't share easily between different routes.
In general, you are right, it is not a good idea to put ajax calls in components. However, in a case where the data to be retrieved and displayed is intimately connected to the view--auto-completion could be one example--it should not be considered an anti-pattern.
If you think it is important to segregate the ajax call, you could consider using a {{render}} helper inside the component's template, and do the ajax work in a separate controller with an associated view where the results are displayed. Routes are not really relevant here because they are related to navigation and URLs.
A component should depend only on the input that are passed to it.
If component has some internal dependency (AJAX / Other data) for input it is an anti pattern.
Ember way will be
1) Create a Route : (Get data to you application)
Sometime you dont have a route for such data (like in your case). Here you can use application route or any other parent route if have. Use the setupController to inject this data to relevant controller
2) Pass down data to component
Now your data should be a controller. Pass this data like any other to required component
{{my-comp-here data="here" ajaxData=fromRoute }}
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_;
}
I have a simple OAuth verification set up using Backbone, and it's working fairly well. My question is somewhat nitpicky (though... I am also new to Backbone), but I'm hoping to find somebody who might know how to solve this.
I have a Session model which, at initialization, sets a #authenticated value based on the presence of a value in localStorage. There's also a method in here, authenticate(), which checks the #authenticated value for pass/fail. If the value check fails, it uses my router to navigate to the login route. If the value check passes, an optional callback passed in by the user is run.
In my main AppView (the first View run at application start) I run Session.authenticate(), and if it passes, route to "#home" (and my Router handles loading additional Views).
My question is this: as an un-authenticated user, if I type http://url.com/#home into my browser, I am successfully routed to "#login", but if I bring up my DevTools, I can see a request being made for an image in my "HomeView" view. What am I not understanding about how Backbone flows through this process? Shouldn't the route for "#home" not even run until after the application initializes, and therefore not even attempting to load the "HomeView"?
What kind of templating engine are you using? If your templates are for instance inline, inside the HTML template where your backbone app lives, then I believe any images inside those are rendered when the page loads. I may be wrong. Also, ensure your HomeView is not running by logging something to the console in the view's initialize method.
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.