Somehow the computed property of a controller I'm accessing through the needs property won't work in the template. Other regular string properties work as expected. Below is my code.
To be clear, what I'm trying to achieve is to access properties of the userController and userModel in actually all of my templates/routes (some of which are computed). However, 'user' itself doesn't have a page, so that's why it's not added in the Router.map. It's just a really important class that handles everything user-related and handles access to the user-model.
I hope somebody with a bit more ember experience knows what I'm doing wrong. Or maybe got some advice on how to do this the ember-way? Any help is greatly appreciated!
PS: I tried to be as complete as possible, if I'm forgetting smt let me know, I'll add it.
App.Router.map(function() {
this.resource('signup');
this.resource('login');
this.resource('profile');
this.resource('practice');
this.resource('overview');
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return this.store.find('user');
}
});
App.LoginRoute = Ember.Route.extend({
controllerName: 'application',
model: function () {}
});
App.UserRoute = Ember.Route.extend({
model: function () {
return this.store.find('user');
}
});
//UserController
App.UserController = Ember.ArrayController.extend({
//pagetitle property to test. Working.
pageTitle: 'Usercontroller',
//userArray property to test, but didn't work.
// Makes sense since the Arraycontroller return an array, so you'll have to use #each-helper
userArray: function(){
return this.get('content');
},
//particularUser computed property to test, but also didn't work.
// Neither did looping #each through the userArray property
particularUser : Ember.computed.filterBy('content' , 'username', 'Sunchild')
});
//LoginController
App.LoginController = Ember.ArrayController.extend({
needs: ['user'],
pageTitle: 'test-title loginController'
});
// Login template feeded into an outlet in the application template
<script type="text/x-handlebars" id="login">
<div class="content">
<form class="input-group">
<div class="input-row">
<label>Username</label>
<input type="text" placeholder="Username">
</div>
<div class="input-row">
<label>Email</label>
<input type="email" placeholder="ratchetframework#gmail.com">
</div>
<button class="btn btn-primary btn-block btn-outlined">Login</button>
</form>
<h3> test1:{{controllers.user.pageTitle}}</h3>
<h3> test2:{{controllers.user.userArray}}</h3>
{{#each user in controllers.user.particularUser}}
<div class="card_wrapper">
<p><h3>Username: {{{user.username}}}</h3><p>
<p>email: {{user.email}}</p>
</div>
{{/each}}
</div>
</script>
Without a user route in your router, the model hook in your UserRoute will never run. That means that the model of your UserController will always be empty. So the way you've set things up won't work for your use case. But, you have the same model on your ApplicationRoute, so why not use that controller instead?
App.ApplicationController = Ember.ArrayController.extend({
// Never use the `content` property, always use the `model` property, they are different
userArray: Ember.computed.alias('model'),
particularUser: function() {
// Be sure to only grab the first item, not an array with the first item in it
return this.get('userArray').filterBy('username', 'Sunchild').get('firstObject');
}.property('userArray.#each.username')
});
This is nice because your application route runs before any other route, so this data will always be available. So in your LoginController:
App.LoginController = Ember.ArrayController.extend({
needs: ['application'],
userArray: Ember.comptued.alias('controllers.application.userArray']
});
Hopefully that clears things up a bit.
Related
I am wondering why i am getting an empty instance each time i try to save a newly created record and it fails. I see like an empty record being added in my posts object only when i display errors.
Also i tried sorting posts which let me see the top recent created posts but the behavior is kinda odd, because as soon as the post is created, it shows at the end and then immediately goes to the top. I am wondering if this is normal or maybe there is a way to wait for the server response and push the record with some sort of fade in effect, etc. Thanks.
index.hbs
<form {{action "savePost" post on="submit"}}>
<div class="form-group {{if errors.error-content "has-error has-feedback"}}">
{{textarea value=post
rows=3
placeholder="What are you thinking?"
id="content"
class="form-control"}}
<small class="help-block text-danger">
{{errors.error-content}}
</small>
</div>
<div class="clearfix">
<div class="pull-right">
<button type="submit"
class="btn btn-sm btn-success">
Create new post
</button>
</div>
</div>
</form>
{{#each sortedPosts as |post|}}
<article class="wrapper">
<p>{{post.content}}</p>
</article>
{{else}}
<article class="wrapper">
<p>NO CURRENT POSTS TO SHOW!</p>
</article>
{{/each}}
post.js
export default DS.Model.extend({
content: DS.attr('string'),
created_at: DS.attr('date')
});
index.js route
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model() {
return this.store.findAll('post');
},
actions: {
savePost(content) {
const newPost = this.store.createRecord('post', {
content: content
});
newPost.save().then(() => {
this.controller.set('post', '');
this.controller.set('errors', []);
}).catch((resp) => {
let errors = {};
resp.errors.forEach((error) => {
errors[`error-${error.field}`] = error.messages[0];
});
this.controller.set('errors', errors);
});
},
willTransition() {
this.controller.get('post').rollbackAttributes();
this.controller.set('errors', []);
}
}
});
index.js controller
export default Ember.Controller.extend({
sortProp: ['created_at:desc'],
sortedPosts: Ember.computed.sort('model', 'sortProp')
});
Well, your this.store.createRecord will always create a new record. So if your .save() fails, you can unload the record, or you don't create your new record there, or save it for the next .save().
The question is a bit why your .save() fails. If its some kind of validation error, and the user fixes them and saves the post again, I would recommend to use the same, earlier created post again, modify it and try to .save() it again.
Another approach is to filter the records you display on the isNew flag.
With that I would bind the textarea directly to a fresh record. A computed property like this could be nice:
newRecord: Ember.computed('_newRecord.isNew', {
get() {
if(!get(this, '_newRecord') || !get(this, '_newRecord.isNew')) {
set(this, '_newRecord', this.store.createRecord('post'));
}
return get(this, '_newRecord');
}
})
Then you can directly bind your texturea's value to newRecord.content and just .save() this in your action.
For all kind of animations, checkout liquid fire.
I'm creating a simple user CRUD page (based on: http://www.smashingmagazine.com/2013/11/07/an-in-depth-introduction-to-ember-js/) that allows editing, creation and deletion of users. After adding the editing functionality, I noticed that Edit button doesn't get disabled or removed during editing, and I wanted to add this functionality.
However I can't seem to come up with the solution because the edit button is in the parent template (user template, and I am in user.edit)
The closest I've got is by setting a property editMode in the UserController to true, and setting that property back in save action in UserEditController back to false. And it worked fine until I pressed the back button or changed the url.
I also tried other solutions like having editMode property that is true in the first controller, and false in the other, but the template doesn't seem to recognize this.
Here are my codes without the solutions above:
Router
App.Router.map(function() {
this.resource('users', function() {
this.resource('user', { path: '/:user_id'}, function() {
this.route('edit');
});
this.route('create');
});
});
user
<script type="text/x-handlebars" id="user">
<button {{action "edit"}}>Edit</button>
<div class="user-profile">
<img {{bind-attr src="avatarUrl"}} alt="User's avatar" />
<h2>{{name}}</h2>
<span>{{email}}</span>
<p>{{bio}}</p>
<span>Created {{creationDate}}</span>
</div>
{{outlet}}
</script>
UserController
App.UserController = Ember.ObjectController.extend({
deleteMode: false,
actions: {
edit: function() {
this.transitionToRoute('user.edit');
}
}
})
UserEditController
App.UserEditController = Ember.ObjectController.extend({
needs: ['user'],
actions: {
save: function(){
var user = this.get('model');
// this will tell Ember-Data to save/persist the new record
user.save();
// then transition to the current user
this.transitionToRoute('user', user);
}
}
});
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"}).
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...
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
});