Passing unsaved record to Ember.js route - javascript

Inside an application we allow users to create new records, related to an existing record. To achieve this, we use actions something like this:
createUser() {
var route = this;
var model = this.store.createRecord('user', {
client: route.modelFor('client'),
});
route.transitionTo('user.update', model);
},
The user.update route renders a user-form component, using the model that was passed in the transition. The same route is also used to update existing users.
The issue with this approach is as follows; when refreshing the page, the page errors because the route fails to find the respective record when querying the store (at this point, the URL is /users/null/update). Ideally I'd pass the client (or client.id) argument in the URL so that:
The page can be reloaded without issue.
The client associated with the user is set correctly.
How can I achieve this in Ember.js? I know that this can easily be done using nested routes (by nesting the user.update route inside a client route), but this doesn't make sense visually.
The relevant parts of the router are as follows:
this.route('clients');
this.route('client', {path: 'clients/:id'}, function() {
this.route('users');
});
this.route('user', {path: 'users/:id'}, function() {
this.route('update');
});
All I do in the user/update.hbs template is {{user-form user=model}}

The problem is that the model you just created has no id at that point because it is not saved, ember can´t route to a model without an id, if possible save the model before you try to transition to the route, if you don´t want to save the model because the user can cancel the action check this thread where a user had the same problem (if I understand you problem correctly), I provided a solution for that problem that I´m using in my own project
https://stackoverflow.com/a/33107273/2214998

Related

Ember - Nested child route loses model upon route refresh/reload

I have a nested route, which when refreshed loses the model and displays blank page. Code as below:
Router:
app.Router.map(function(){
this.resource('main',{path:''}, function(){
this.route('summary',{path:'/main/summary'},function(){
this.route('details',{path:'/details'})
})
})
})
File structure:
-app
-route
-main-route.js
-main-summary-route.js
-main-summary-index-route.js
-main-summary-details-route.js
-main-loading-route.js
-controller
-main-controller.js
-main-summary-controller.js
-main-summary-index-controller.js
-main-summary-details-controller.js
-templates
-main
-summary
-index.hbs
-details.bhs
-summary.hbs
-loading.hbs
-main.hbs
-application.hbs
Brief about code:
Templates main.hbs and application.hbs have {{outlet}} defined in them.
summary.hbs has {{outlet}} in it as well, so that when url /main/summary is hit, it shows only contents from summary/index.hbs and when url /main/summary/details is hit, it only shows the one in details by rendering into summary.
My ajax call goes in model hook of "main-summary-route", and while its waiting, i show loading template.
"main-summary-details-controller.js" extends from "main-summary-index-controller.js" so that code could be reused.
Similarly, "main-summary-details-route.js" gets the same model as the one in "main-summary-route.js" via model hook in details route as -
model: function(){
return this.modelFor('mainSummary')
}
This is because the ajax call brings together the data for both summary and routes together.
Problem statement: When I hit url main/summary, i get the page and then from there, on a click, goto main/summary/details , i see the page updated with details as well, but when i refresh this (/main/summary/details) manually in browser, the page is blank, i observe that there is no model returned in model hook in details route this time.
My thoughts on solution:
I thought that this would work ideally, since on refresh, it would ask for summary route first (being in parent child relation), call ajax (loading alongside) and when data comes through, it would show details. But i guess thats not happening.
I am not getting it, as to whats going wrong. The thought which comes to my mind is that, do i need to probably catch the refresh event in details route and call ajax again, so that it fetches data.
Ember version used - 1.13.0
I did solve this by eventually calling ajax again on refreshing the details page. I did place a check though, that if "this.modelFor('mainSummary')" is available then return this model only, otherwise get model from ajax. This worked for me.
But I am still wondering, that ideally it should have been by itself, i.e. the as i refresh, the ajax on /main/summary should have been called itself and thereby rendering details, once model was available.

Meteor: Change the Iron Router Data Reactive Variable Without Page Reload

In my meteor app, I am using iron-router's waitOn method to subscribe to all relevant documents and the data method to return just one document's contents to the template as a reactive variable. (Since these documents are large, I want to load just one at a time as a reactive variable for speed concerns).
//...
waitOn: function() {
// subscribe to ohlcOneMin, ohlcThreeMin, etc.
return Meteor.subscribe(this.params.exchange, this.params.market);
},
data: function() {
return {
ohlc: Widgets_Exchanges_Btcchina.findOne({market: this.params.market, dataType:'ohlcOneMin'})
};
},
//...
By default on route load, iron-router will store data from the ohlcOneMin document under the Router.current().data().ohlc reactive variable. And that works great in my template. Now, imagine I have a button in my template. On click, it would change the iron-router data source to the contents of the ohlcThreeMin document... essentially toggling iron-router's Router.current().data() reactive variable between different document data which the user is subscribed to, without having to re-load the route or have all the data from all the documents stored in a reactive variable at the same time. How can this be achieved? Thanks!

Loading and waiting for "global" meteor subscriptions

Here's my scenario:
I have a layout template that needs to check to see if a User belongs to at least one Team. If not, then display a div across the entire site. A user can only see the teams they belong to, so I created a simple publication that works: (code samples are CoffeeScript)
Meteor.publish 'teams', ->
return null if !#userId
Teams.find {'members._id': #userId}
This works great and Teams.find().fetch() in console gives expected results.
However, if I put this code in, say, the Template.layout.rendered, it doesn't work.
Template.layout.rendered = ->
teams = Teams.find().fetch()
hasTeams = teams.length > 0
if !hasTeams
...do stuff..
Obviously this doesn't work because the Teams find is async and not loaded when it needs to make the decision. With a normal template / page I would just use the IronRouter waitOn() but what do I do on the layout?
I could do a waitOn in my router, but since the data is "global" and going to be used everywhere, and because a user could deep link into the site all over the place, I don't want to add that waitOn to EVERY single route.
So what is the proper pattern? How do I get the meteor client to load global data and wait for the data before running the route?
More thinking and searching found the answer right here on SO: struggling to wait on subscriptions and multiple subscriptions
I changed my Router.configure to this:
Router.configure
layoutTemplate: 'layout'
waitOn: ->
return [
Meteor.subscribe('teams')
]
Multiple subscriptions can be added to the return array, and I believe it will wait on all of them.
I had a similar issue with iron router subscribing to a chat in two different publications with waitOn.
Meteor.subscribe('chats')
Only when I switched positions of the following publish blocks, would it let me see any data on the route. The following is the correct order for the two.
Meteor.publish("chats", function () {
return Chats.find();
});
Meteor.publish("chats", function(id) {
return Chats.find({eventRoom: id});
});

Ember Query Params

In my application I want to read the parameters user is entering and then I want to use that parameter. http://responsive.beta.postify.com/X I want to read that X value. But first how do I ensure that the router expects a parameter?
My router is like this
Cards.Router.map(function ()
{
this.resource('cards', {path: '/'}, function ()
{
// additional child routes
this.resource('selectImage');
this.resource('message');
this.resource('recipient');
this.resource('orderStatus');
this.resource('thankyou');
this.resource('accountInfo');
this.resource('recentOrders');
this.resource('howTo');
this.resource('faq');
});
});
I want that parameter whenever the app loads. That is going to be my clientID which I would be using to fetch data from server depending upon the client.
Any thoughts on it?
When I do something like this
Cards.Router.map(function ()
{
this.resource('cards', {path: ':clientID'}, function ()
{
// additional child routes
this.resource('selectImage');
this.resource('message');
this.resource('recipient');
this.resource('orderStatus');
this.resource('thankyou');
this.resource('accountInfo');
this.resource('recentOrders');
this.resource('howTo');
this.resource('faq');
});
});
and in my browser if I put like this http://responsive.beta.postify.com/#/26 then its working but if I do like http://responsive.beta.postify.com/26 then it is not working.
To answer your question directly, to use a parameter in a route you would do something like this:
this.resource('cards', { path: '/:user_id' });
Then in your route
App.CardsRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('post', params.user_id);
}
});
This is how you can get a parameter in a certain route. Now as far as your application goes, using the code I posted above should get you that parameter as long as they access the root ('/') of your application on first load and have the user_id in the url.
I would suggest a different strategy maybe for getting the client_id and storing it for later user in your application. For example, in my application I have an Ember.Application.initializer({}) where I store the client_id. All depends on your server configuration and how your app is built, but I would definitely try and get the client_id a different way if you can!
Good luck.

EmberJS: How to transition to a router from a controller's action

I have an action:
{{action create target="controller"}}
which I have targeted to the bound controller (rather than the router) like this:
App.AddBoardController = Ember.Controller.extend
create: ->
App.store.createRecord App.Board, {title: #get "boardName"}
App.store.commit()
//TODO: Redirect to route
How do I redirect back to a route from the controller action?
Use transitionToRoute('route') to redirect inside an Ember controller action:
App.AddBoardController = Ember.Controller.extend({
create: function(){
...
//TODO: Redirect to route
this.transitionToRoute('route_name');
}
...
In fact, this is not Ember idiomatic. From what I know, and what I have learnt from Tom Dale himself, here are some remarks about that code:
First, you should not transitionTo from elsewhere than inside the router: by doing so, you are exposing yourself to serious issues as you don't know in which state is the router, so to keep stuff running, you will quickly have to degrade your design, and by the way the overall quality of you code, and finally the stability of your app,
Second, the action content you are showing should be located inside the router to avoid undesired context execution. The router is indeed a way to enforce a coherent behavior for the whole app, with actions being processed only in certain states. While you are putting the actions implementation into Controllers, those actions can be called at anytime, any including wrong...
Finally, Ember's controllers are not aimed to contain behavior as they rather are value-added wrappers, holding mainly computed properties. If you nevertheless want to factorize primitives, maybe the model can be a good place, or a third party context, but certainly not the Controller.
You should definitely put the action inside the router, and transitionTo accordingly.
Hope this will help.
UPDATE
First example (close to your sample)
In the appropriated route:
saveAndReturnSomewhere: function (router, event) {
var store = router.get('store'),
boardName = event.context; // you pass the (data|data container) here. In the view: {{action saveAndReturnSomewhere context="..."}}
store.createRecord(App.Board, {
title: boardName
});
store.commit();
router.transitionTo('somewhere');
}
Refactored example
I would recommend having the following routes:
show: displays an existing item,
edit: proposes to input item's fields
Into the enclosing route, following event handlers:
createItem: create a new record and transitionTo edit route, e.g
editItem: transitionTo edit route
Into the edit route, following event handlers:
saveItem: which will commit store and transitionTo show route, e.g
EDIT: Keep reading, Mike's answer discusses some of the problems with this approach.
You can just call transitionTo directly on the router. If you are using defaults this looks like App.router.transitionTo('route', context).

Categories

Resources