How to make Ember.js load RESTAdapter data before showing view? - javascript

I have a app with RestAdapter that takes proper data from server:
App.AFile= DS.Model.extend({
name: DS.attr( 'string' ),
...
App.Store = DS.Store.extend({
revision: 13,
adapter: DS.RESTAdapter.extend({url: "myapi"})
});
And a map like this:
App.Router.map(function() {
this.resource('allfiles', { path: '/allfiles' });
this.resource('onefile', {path: '/onefile/:onefile_id' });
And routes defined like this:
App.allfilesRoute = Ember.Route.extend({
model: function()
{
return App.AFile.find();
}
});
App.onefileRoute = Ember.Route.extend({
model : function(params)
{
return App.AFile.find(params.onefile_id);
}
});
And those templates:
<script type="text/x-handlebars" data-template-name="allfiles">
{{#each controller}}
{{#linkTo onefile this}}open{{/linkTo}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="onefile">
{{name}}
</script>
It works like this: user opens app and it displays allfiles template with a link called open. The link opens a template called onefile and passes the onefile_id to it.
When i open app and click open it works and displays proper name of one file. URL is set to #/onefile/1 where 1 is the onefile_id. So it works fine.
But when i refresh page (#/onefile/1) than name is not displayed anymore.
I've checked what is going on and in onefileRoute model function before i return the id and it occurs that App.AFile has null values for all fields defined. And after app loads those values are filled properly in the App.AFile object but are not displayed on the view.
So it looks like RestAdapter gets data after view display.
How to make it work?

App.onefileRoute = Ember.Route.extend({
model : function(params)
{
var m = App.AFile.find(params.onefile_id);
// at this point m might not be resolved, so name could be empty
return m;
},
afterModel: function(model, transition){
// at this point it should be resolved, what happens here?
console.log(model.get('name'));
}
});
My guess would be your endpoint is returning the wrong information for a model by id.

Related

Why does not LSAdapter trigger the loading event in Ember.js?

I am working with the LSAdapter , and trying to get the loading event work .
I found this strange problem ,that loading event seems not working properly.
Here is a simple page .
--When we work with DS.FixtureAdapter . The loading event works fine
--When we work with DS.LSAdapter .the loading event does not work ?
Am I making any mistakes?
<script type="text/x-handlebars" data-template-name="application">
<h1>ember-latest jsbin</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{input value = newBookTitle action= 'newBook'}}
<ul>
{{#each}}
<li>{{title}} </li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="loading">
<div id = 'global-loading'>
<h2>GLOBAL LOADING</h2>
</div>
</script>
<script>
App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_VIEW_LOOKUPS: true
});
//Why does LSAdapter doesnot trigger the loading event?
// App.ApplicationAdapter = DS.LSAdapter.extend({namespace: 'App-test-loading'});
// FixtureAdapter works fine with loading event
App.ApplicationAdapter = DS.FixtureAdapter.extend();
App.Book = DS.Model.extend({
title: DS.attr('string'),
});
App.Book.FIXTURES = [
{
id: 1,
title: 'Learn Ember.js',
},
{
id: 2,
title: 'Nothing more'
}
];
App.ApplicationRoute = Ember.Route.extend({
actions: {
loading: function() {
alert('laoding');
}
}
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('book');
}
});
App.IndexController = Ember.ArrayController.extend({
newBookTitle:'',
actions:{
newBook:function(){
var _nb = this.get('newBookTitle');
if(!_nb) return ;
var _book = this.store.createRecord('book',{
id: Math.random().toString(32).slice(2).substr(0, 5),
title: _nb
});
_book.save();
this.set('newBookTitle', '');
}
}
});
</script>
_______UPDATE_________
With the kind help from #GJK
I solve it in this way :
/*IndexRoute model hook*/
var books = this.store.find('book');
return new Ember.RSVP.Promise(function(resolve) {
Ember.run.later(function() {
resolve(books);
}, 10);
});
also get help from
1 How do I create a promise in Ember.js for an Ember-data model
2 http://jsbin.com/ipehoj/1/edit
It's because the FixtureAdapter tries to simulate a remote server while the LSAdapter doesn't. From the FixtureAdapter:
return this.simulateRemoteCall(function() {
The Fixture adapter actually waits a set amount of time to more closely resemble an actual server call (which is the whole purpose of the FixtureAdapter). But look at the LSAdapter:
return Ember.RSVP.resolve(record);
It returns a promise that resolves almost immediately, leaving no time for your loading event to get called.
The LSAdapter is supposed to be a production-ready adapter that stores your data in the browser. Because it's stored in the browser, there are (almost) no loading times. The FixtureAdapter on the other hand, is supposed to simulate a production-ready adapter that will having loading times. Their purposes are different, you shouldn't use them interchangeably.

live update view in ember

I build application using web-socket and I want dynamically change view
I have code in my model:
model: function () {
var store = this.store;
var socket = new_socket();
socket.on('message', function (message) {
store.push('record',message);
});
return store.find('consultation');
}
And data successfully loading into ember store, but ember doesn't update view.
What I need to do?
For example when I use save all works fine:
var record = this.store.createRecord('record',{});
record.save()
UPDATE:
I found this solution:, but it's also not working
My controller (consultation.js):
export default Ember.ObjectController.extend({
needs: ['application'],
records: function() {
console.log(this.get('store'));
return this.get('store').filter('record', function(record) {
console.log(record);
});
}.property('records'),
Template:
{{#each item in controller.records}}
{{/each}}
Model consultation looks like this (with hasMany field):
export default DS.Model.extend({
records: DS.hasMany('record', { async: true }),
});
Second console.log is not printing,
I solved it like this
var obj = store.push('record', jsonObj.message);
this.controllerFor('consultation').get('records').pushObject(obj);
But it's looks like not good. In which way I can make auto synchronization between model and controller
You have computed properties that are dependent on themselves in your controller. Also, you really should not be pushing anything to the store, conceptually, when you receive something on your socket, your model changed, you want to capture that notion in your code, so you really should be updating the model when you get a socket message. The way I would do this would be to do something like the following:
in the router,
model: function() {
var records = this.store.find('consultation',{});
var socket = new_socket();
socket.on('message', function (message) {
records.pushObject(message);
});
return records;
}
The controller does not need to have any computed properties at all.
and in the template, the code would just be:
{{#each content}}
//do something here
{{/each}}
I'm a little confused about your use of consultation and record in the code.

Model reloading with Ember Data

I'm trying to poll for more data using the documented model.reload() function
App.ModelViewRoute = Ember.Route.extend({
actions: {
reload: function() {
this.get('model').reload();
}
}
});
But i'm getting an error message saying...
undefined is not a function TypeError: undefined is not a function
Is there a better way of doing this, it seems like I cannot access the model in this way from the route?
Here is the router
App.Router.map(function() {
this.route('video', { path: '/videos/:video_id' });
});
Here is the route
App.VideoRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('video', params.video_id);
},
actions: {
reloadModel: function() {
// PROBLEM HERE
// this.get('model').reload();
Ember.Logger.log('reload called!');
}
}
});
Here is the model
App.Video = DS.Model.extend({
title: DS.attr('string'),
status: DS.attr('string')
});
And the templates
<script type="text/x-handlebars" data-template-name="application">
<h1>Testing model reloading</h1>
{{#link-to "video" 1}}view problem{{/link-to}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="video">
<h1>Video</h1>
<h2>{{title}}</h2>
{{model.status}}
<p><button {{action 'reloadModel'}}>Reload model</button></p>
</script>
I've made a jsbin of the issue here:
http://jsbin.com/wofaj/13/edit?html,js,output
I really can't understand why the reload gives me this error. Any advice would be much appreciated.
Thanks
Since model already exists as a hook on Ember.Route, you cannot get that as a property.
Instead you can do the following:
this.modelFor('video').reload();
Technically you could do this.get('currentModel').reload(); too, but that's undocumented and probably won't be available in the future.
The refresh method of the route would do what you're after
App.VideoRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('video', params.video_id);
},
actions: {
reloadModel: function() {
this.refresh()
}
}
});
API docs
The route model function provides a hook to load your controller data. There is a specific section at the ember guide.
1) If you want to access your content, it would be like:
reload: function() {
this.controller.get('content');
}
2) reload is a method available of ember-data objects. In your example, you are loading a js object ({ id:2, title:"Test video title 2", status:"downloading"}).

Emberjs nothing handled this action

Error : Uncaught Error: Nothing handled the action 'rollDice'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.
I made sure that the method in the controller had the same name as the action.
???
HTML portion
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" id="index">
{{#linkTo "roll"}}Lets roll dice!{{/linkTo}}
</script>
<script type="text/x-handlebars" id="roll">
<p class="centerme">A Dice Roller.</p>
<p> </p>
<p>Click to play!<br/>
<button id="play" {{action 'rollDice'}}>Roll Dice</button>
</p>
<section id="roll-wrap">Dice stuff</section>
<script>
Controller
DiceRoller.RollController = Ember.ObjectController.extend({
var diceModel = this.get('model');
actions: {
rollDice: function () {
var x=[270,1080,1440,810];
var rand1=Math.floor(Math.random()*4);
var rand2=Math.floor(Math.random()*4);
diceModel.set('rotateXvalue',x[rand1]+"deg");
diceModel.set('rotateYvalue',x[rand2]+"deg");
diceModel.save();
}.property('diceModel.rotateXvalue','diceModel.rotateYvalue')
}
});
Routing
DiceRoller.Router.map(function() {
this.resource("roll");
});
DiceRoller.IndexRoute = Ember.Route.extend({
redirect: function(){
this.transitionTo("roll");
}
});
DiceRoller.DiceRoute = Ember.Route.extend({
model: function() {
return this.store.find('Dice');
}
});
Model
DiceRoller.Dice = DS.Model.extend({
rotateXvalue: DS.attr('string'),
rotateYvalue: DS.attr('string')
});
DiceRoller.Dice.FIXTURES = [
{
rotateXvalue: '40deg',
rotateYvalue: '37deg'
}
];
http://jsbin.com/qosujasi/1/
My JS bin, so far it gives me an error about setting the content of an object proxy.
You've named your controller incorrectly. The correct controller for the roll route would be DiceRoller.RollController.
In the RollController, you should get the model inside the roleDice action and you don't need the list of properties. That's for computed properties, not actions.
DiceRoller.RollController = Ember.ObjectController.extend({
actions: {
rollDice: function () {
var diceModel = this.get('model');
var x=[270,1080,1440,810];
var rand1=Math.floor(Math.random()*4);
var rand2=Math.floor(Math.random()*4);
diceModel.set('rotateXvalue',x[rand1]+"deg");
diceModel.set('rotateYvalue',x[rand2]+"deg");
diceModel.save();
}
}
});
Check out this jsBin.
You need to create the model record to be able to set values on it in your route, like this:
DiceRoller.RollRoute = Ember.ObjectController.extend({
model:function() {
return this.store.createRecord('dice');
}
});
I am fresh new to Ember.js and also struggling, but for me it worked to either move actions: {...} from controller to route:
DiceRoller.DiceRoute = Ember.Route.extend({
model: function() {
return this.store.find('Dice');
},
actions: {...} // move actions here
});
OR to use ApplicationController instead of RollController:
DiceRoller.ApplicationController = Ember.ObjectController.extend({
var diceModel = this.get('model');
actions: {
rollDice: function () {
var x=[270,1080,1440,810];
var rand1=Math.floor(Math.random()*4);
var rand2=Math.floor(Math.random()*4);
diceModel.set('rotateXvalue',x[rand1]+"deg");
diceModel.set('rotateYvalue',x[rand2]+"deg");
diceModel.save();
}.property('diceModel.rotateXvalue','diceModel.rotateYvalue')
}
});
Not saying it is the correct way! Just saying it worked for me - still learning ;-)
When you follow Ember official tutorial, and get to the Templates->Actions chapter, you will probably run into this error on first example because this example uses Components that are explained later. I tried adding action to templates/about.hbs and creating component/about.js with action handler, but these two wouldn't work together. Im guessing the trick is to define hbs file in templates/components/ but before that I got the action working by creating
controllers/about.js like this:
import Ember from 'ember';
export default Ember.Controller.extend({
isBody: false,
actions: {
toggleBody() {
console.log("Look at me go!");
this.toggleProperty('isBody');
}
}
});
This is EmberCli environment, v2.0.0 and they say Controllers and Components will merge into one thing soon, so...

ember.js - define route with optional parameter and default model

I want to define a route in emberjs, that has an optional parameter
eg:
/video
and
/video/123
if no parameter is supplied, I want to use a default model/fixture.
if a parameter is supplied, then I want to obviously lookup the model using the parameter.
if I then go to a different route, and return to the route without the parameter, I want to use the previously loaded model.
eg:
startup app
/video - shows my default/fixture model
/video/123 - shows model 123
/another-route - shows new route
/video - shows model 123
is this possible?
I ended up using a different solution:
this.resource('video', function() {
this.route('index', {path: '/'});
this.route('item', {path: ':id'});
});
These routes support:
/video - shows my default/fixture model
/video/123 - shows model 123
When the user access to /video, the VideoIndexRoute must redirect to VideoItemRoute without any id.
var VideoIndexRoute = Em.Route.extend({
afterModel: function() {
// this is the tricky part
this.replaceWith('video.item', '');
}
});
Now, the VideoItemRoute must check if there is any model associated, and when it is missing, it should use the default fixtures or a new one.
var VideoItemRoute = Em.Route.extend({
model: function(param) {
if (param.id) {
return this.store.find('video', param.id);
}
},
setupController: function (controller, model) {
if (!model) {
model = this.store.createRecord('video',{
name: 'default Name'
});
// or use fixture...
}
this._super(controller, model);
}
});
There is a clean way to do this, although it is slightly "tricky". The idea is to use a nested route to hold the id, but not render it, instead having the parent route be responsible for rendering it using the render helper. When you do it this way, all the logic can live in VideoChoiceController, and it will be used for displaying either the default video or a specific one. When doing it this way, there is no need to explicitly "remember" the last video, the state machine that the route engine represents does it for you.
App.Router.map(function) {
this.resource('video', function(){
this.route('choice', {path: ':video_id'});
});
});
App.VideoRoute = Ember.Route.extend({
model: function(params) {
return App.get('defaultVideo');
},
setupController: function(controller, model) {
var video_choice = this.controllerFor('video.choice')
// this is necessary if you want, for example,
// to display the name or a link to the default video
// when a specific video is being displayed
controller.set('model', model);
if(Ember.isEmpty(video_choice.get('model'))){
video_choice.set('model', model);
}
}
});
App.VideoChoiceRoute = Ember.Route.extend({
model: function(params) {
return this.get('store').find('video', params.video_id);
},
renderTemplate: function() {
// if you don't override renderTemplate to do nothing,
// everything will still work but you will get an assertion
// error that render should only be used once with a
// singleton controller
}
});
<script type="text/x-handlebars">
<div>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name='video'>
<div> attrributes here always refer to the default video: {{name}} </div>
{{render "video.choice"}}
</script>
<script type="text/x-handlebars" data-template-name='video'>
<div>
attrributes here always refer to specific or last video,
or default if a specific video has never been loaded: {{name}}
</div>
</script>
Definitely, you'll have to do something a little funky, like storing the last video in some global variable, but that's up to you.
http://emberjs.jsbin.com/uhoQozu/1/edit
http://emberjs.jsbin.com/uhoQozu/1#/video
http://emberjs.jsbin.com/uhoQozu/1#/video/32
App.Router.map(function() {
this.resource('videoModel', {path:'/video/:video_id'});
this.resource('video'); // this resource can be accessed at /video
});

Categories

Resources