Hi I would like to know what's the proper way to update a property on a component from the route?.
A little background on what I want to do:
I have two custom Buttons that I called CardButtons (based on material desing) next to one blank area called description, what I want is to create a hover event that triggers an ajax call to retrive detailed data from a data base and render it on the description area.
CHECK UPDATE
So far I have created a route like this:
export default Ember.Route.extend({
selectedModule: '',
model: function () {
return {
selectedModule: 'employeeModule'
};
},
actions: {
showDescription: function (params) {
this.set('model.selectedModule', params);
}
}});
My route template call my component like this:
<div class="row">
{{sis-db-description-render idTitle=model.selectedModule}}
</div>
and the component is defined like this:
export default Ember.Component.extend({
info: null,
ready: false,
didInsertElement: function () {
this.queryData();
},
queryData: function (){
/** Does an Ember.$.post request to the API with the idTitle as param**/
}
});
the first time this executes it load perfectly the detail data but when I try to refresh the data the event does not trigger a second call. I bealive it is beacause I'm not updating the model in a proper way.
Any idea on how to update the component property?
UPDATE:
Thanks to #kumkanillam I was able to find a way on my route I added the next code:
setupController: function (controller, model) {
this._super(...arguments); //if its new/index.js route file then you can use controller argument this method.
controller.set('selectedModule', 'employeeModule');
},
actions: {
showDescription: function (params) {
console.info(params);
this.controllerFor('new.index').set('selectedModule', params);
}
}
By doing so now the view updates the content every time, I still don't know if this is the correct way to do it but it works for now.
In the below code, model is not defined in route. it's defined in corresponding controller through setupController hook.
showDescription: function (params) {
this.set('model.selectedModule', params);
}
So in your case either you can define action in controller and update model.selectedModule
If you want to do it in route,
showDescription: function (params) {
let cont = this.controllerFor('route-name');
cont.set('model.selectedModule', params);
}
Related
I am trying to update a record in the Ember store. When I try to do this, it returns the following error:
Uncaught Error: Assertion Failed: Cannot delegate set('name', test) to the 'content' property of object proxy : its 'content' is undefined.
The controller looks like this:
import Ember from 'ember';
export default Ember.Controller.extend({
model: null,
event: {
name: "test",
id: "adfg8943224xcvsdf"
},
actions: {
editEvent (event) {
var Event = this.store.find('event', event.id);
Event.set('name', event.name);
Event.save()
}
}
});
The route looks like this:
import Ember from 'ember';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model () {
return {
event: this.store.find('event')
}
},
setupController (controller, model) {
controller.set('model', model);
}
});
The template triggers the action, sending along a object called event, which has properties like name and id. The values of the event object come from the controller and have been set before triggering the editEvent action:
<form {{action 'editEvent' event on="submit"}}>
I believe what is happening is that your model hook is returning a POJO that contains a promise that will resolve. If you want to pass that to your action then you need to do
<form {{action 'editEvent' model.event on="submit"}}>
That being said you should really just return a promise from your model hook so that Ember will wait for your data to load before rendering the template. With the way you have it setup now, if your data takes a long time to load, someone could submit the form before the model is loaded and you'll get an error.
I think you want your route to look like this (no need to override setupController):
import Ember from 'ember';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model () {
return this.store.find('event');
}
});
Then in your template:
<form {{action 'editEvent' model on="submit"}}>
If you need to load multiple models then you should use Ember.RSVP.hash.
See this answer: EmberJS: How to load multiple models on the same route?
Also, I'm not quite sure what your action is trying to do but you don't need to find the record again. The code you posted for your action doesn't actually do anything. It gets the event and then sets the event's name to its own name.
actions: {
editEvent (event) {
// you already have the event, you passed it in as a parameter
// you don't need to query the store again for it.
var Event = this.store.find('event', event.id);
// This doesn't do anything as it just sets the event.name to itself
Event.set('name', event.name);
Event.save()
}
}
I think you mean to do this:
actions: {
editEvent (event) {
event.set('name', 'updated name');
event.save();
}
}
I am currently developing an ember application which has two components.
One component represents a map the other one represents a friendslist.
Both components are placed in the same handlebar template.
What I try to achieve is that a user can check a checkbox in the friendslist component and in the next step his or her posts are loaded asynchronously from facebook (the friend itself was already loaded in the beforeModel hook). Those asynchronously loaded posts should be append to the already existing friend object in the model. Afterwards the map component should be informed about the changes and refresh itself or call a function which will draw points on the map.
At the moment I am trying to set the checked property of a single friend (which would be the same approach as appending the posts but will be easier for now):
// index.hbs
{{map-widget posts=model.posts friends=model.friends}}
{{friends-list checkedFriend='checkedFriend' friends=model.friends}}
// friends-list.hbs (component)
<ul>
{{#each friends as |friend|}}
<li>
{{input type="checkbox" id=friend.facebookID checked=friend.checked change=(action checkedFriend)}} <p>{{friend.name}}</p>
</li>
{{/each}}
</ul>
// friends-list.js (component)
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
checkedFriend: function () {
this.sendAction('checkedFriend');
}
}
});
// index.js (route)
export default Ember.Route.extend(AuthenticatedRouteMixin, {
...
model: function() {
return Ember.RSVP.hash({
posts: this.get('currentUserPosts'),
friends: this.get('friends')
});
},
actions: {
checkedFriend: function () {
// Update just the first friend here to see if the approach works
// Get the friends array from the model
const model = this.controller.get('model');
const friends = model.friends;
// Update the friend
Ember.set(friends[0], 'checked', true);
// Map component receives an update here,
// but "DEPRECATION: You modified (mut model.friends) twice in a single render." warning occurs
this.set('friends', friends);
}
}
})
The current approach works more or less. However, I get a depreciation warning that I modified the model twice in a single render which in my opinion is a sign for a bad design from myside.
What I would like know is how a good approach for my task described above would look like. If I am already on the right way I would be glad if anyone could tell me why this double rendering error appears.
The core problem is how to correctly update the model and how to inform the components especially the component which did not set the action about the changes so that those are refreshed.
Thank you in advance.
You could make a Class - FriendEntry. By calling its constructor you will recieve an instance of FriendEntry. Now you will be modifying instance instead of original record (which indeed is not right).
var FriendEntry = Ember.Object.extend({
init: function() {
this._super(...arguments);
this.set('somethingFriendly', true);
}
});
export default Ember.Controller.extend({
friendsEntries: Ember.computed.map('model.friends', function(friend) {
// Call the constructor
return FriendEntry.create({
friend: friend,
checked: false,
posts: []
})
})
});
Ok so your component would look something like this.
{{friends-list checkedFriend='changeFriendCheckedStatus' entries=friendEntries}}
// friends-list.hbs (component)
<ul>
{{#each entries as |entry|}}
{{input type="checkbox" checked=entry.friend.checked change=(action checkedFriend entry)}} <p>{{entry.friend.name}}</p>
{{/each}}
</ul>
// friends-list.js (component)
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
checkedFriend: function (entry) {
this.sendAction('checkedFriend', entry);
}
}
});
Back to controller
actions: {
changeFriendCheckedStatus(friendEntry) {
ic.ajax.request(API.HOST + '/someUrlForPosts/' + friendEntry.get('id)).then(givenFriendPosts => {
entry.get('posts').pushObjects(givenFriendPosts);
})
}
}
If i understood correctly you have 2 models I friends and posts (DS.belongsTo('friend')). You would need to encapsulate both into friendEntry (friend, posts).
So your map-widget would also look like this {{map-widget friendEntries=friendEntries}}
Instead of querying posts in model you could encapsulate them like this.
friendsEntries: function() {
return DS.PromiseArray.create({
promise: Ember.RSVP.all(this.get('model.friends')).then(friends => {
return friends.map(friend => {
return FriendEntry.create({
friend: friend,
checked: false,
posts: store.query('posts', { friend: friend.get('id') }
});
});
})
});
}.property('model.friends.[]')
I'm giving Vue.js a try and so far I'm loving it because it's much simpler than angular. I'm currently using vue-router and vue-resource in my single page app, which connects to an API on the back end. I think I've got things mostly working with a the primary app.js, which loads vue-router and vue-resource, and several separate components for each route.
Here's my question: How do I use props to pass global data to the child components when the data is fetched using an asynchronous AJAX call? For example, the list of users can be used in just about any child component, so I would like the primary app.js to fetch the list of users and then allow each child component to have access to that list of users. The reason I would like to have the app.js fetch the list of users is so I only have to make one AJAX call for the entire app. Is there something else I should be considering?
When I use the props in the child components right now, I only get the empty array that the users variable was initialized as, not the data that gets fetched after the AJAX call. Here is some sample code:
Simplified App.js
var Vue = require('vue');
var VueRouter = require('vue-router')
Vue.use(VueRouter);
var router = new VueRouter({
// Options
});
router.map({
'*': {
component: {
template: '<p>Not found!</p>'
}
},
'/' : require('./components/dashboard.js'),
});
Vue.use(require('vue-resource'));
var App = Vue.extend({
ready: function() {
this.fetchUsers();
},
data: function() {
return {
users: [],
};
},
methods: {
fetchUsers: function() {
this.$http.get('/api/v1/users/list', function(data, status, response) {
this.users = data;
}).error(function (data, status, request) {
// handle error
});
}
}
});
router.start(App, '#app')
Simplified app.html
<div id="app" v-cloak>
<router-view users = "{{ users }}">
</router-view>
</div>
Simplified dashboard.js
module.exports = {
component: {
ready: function() {
console.log(this.users);
},
props: ['users'],
},
};
When dashboard.js gets run, it prints an empty array to the console because that's what app.js initializes the users variable as. How can I allow dashboard.js to have access to the users variable from app.js? Thanks in advance for your help!
p.s. I don't want to use the inherit: true option because I don't want ALL the app.js variables to be made available in the child components.
I believe this is actually working and you are being misled by the asynchronous behavior of $http. Because your $http call does not complete immediately, your console.log is executing before the $http call is complete.
Try putting a watch on the component against users and put a console.log in that handler.
Like this:
module.exports = {
component: {
ready: function() {
console.log(this.users);
},
props: ['users'],
watch: {
users: {
handler: function (newValue, oldValue) {
console.log("users is now", this.users);
},
deep: true
}
}
}
};
In the new version of Vue 1.0.0+ you can simply do the following, users inside your component is automatically updated:
<div id="app" v-cloak>
<router-view :users="users"></router-view>
</div>
My objective is to display a fancy "loading..." graphic on my page while Ember fetches model data through the Ember route.
This led me to http://emberjs.com/guides/routing/loading-and-error-substates/. That inspired me to create an action on my page's controller which would show the "loading" overlay window in the DOM. For example, here's my controller:
controllers/users.js:
export default Ember.ArrayController.extend({
...
actions: {
displayLoading: function() {
// Show the DOM element that says "Loading..."
},
...
}
});
I'd like to call that while my data is loading, so I then define a route as follows:
routes/users.js:
export default Ember.Route.extend({
model: function( params ) {
return this.store.find('user', params );
},
actions: {
loading: function(transition, originRoute) {
transition.send('displayLoading');
}
}
});
But when I do this, I get this error:
Uncaught Error: Nothing handled the action 'displayLoading'. 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.
So my question is where can I define this action so that my loading method will be able to call it?
Note that trying this.send('displayLoading') gave me this error:
Can't trigger action 'displayLoading' because your app hasn't finished transitioning into its first route. To trigger an action on destination routes during a transition, you can call .send() on the Transition object passed to the model/beforeModel/afterModel hooks..
Update: I am able to catch this action on the route itself, but then I still can't call the action on my controller.
Update #2: Thanks to #kingpin2k's answer, I've resolved this. For those interested, here is a full solution:
controllers/users.js:
export default Ember.ArrayController.extend( {
actions: {
showLoading: function() {
this.set('isLoading', true);
},
hideLoading: function() {
this.set('isLoading', false);
},
}
});
routers/users.js:
export default Ember.Route.extend({
model: function( params ) {
return this.store.find('user', params );
},
actions: {
loading: function() {
this.controllerFor('users').send('showLoading');
},
didTransition: function() {
this.controllerFor('users').send('hideLoading');
}
}
});
A key insight was that I can set an isLoading property on my controller which determines whether my modal "Loading..." window is showing in the Handlebars template.
use controllerFor, http://emberjs.com/api/classes/Ember.Route.html#method_controllerFor
loading: function(transition, originRoute) {
var controller = this.controllerFor('foo');
controller.send('displayLoading');
}
I sent action from view to currents route controller, then to another controller, in order to write code once.
this.get('controller.controllers.study/study').send('processPersonData', data);
**DEPRECATION: Action handlers implemented directly on controllers are deprecated in favor of action handlers on an actions object ( action: processPersonData on )
at Ember.ControllerMixin.Ember.Mixin.create.deprecatedSend
What is the right way to implement this send action?
FYI: the send action works correctly.
This message indicates that the method handling the action should be under an 'actions' hash on the object, like so:
App.SomeController = Ember.ObjectController.extend({
someVariable: null,
actions: {
processPersonData: function(context) {
//implementation
},
otherAction: function(context) {
//implementation
}
}
});
It is just new semantics for action handling.
If you are trying to call an action in your controller from your view, you should use the Em.ViewTargetActionSupport mixin as follows:
App.DashboardView = Em.View.extend(
Ember.ViewTargetActionSupport, { // Mixin here
functionToTriggerAction: function() {
var data = this.get('data'); // Or wherever/whatever the data object is
this.triggerAction({
action: 'processPersonData',
actionContext: data,
target: this.get('controller') // Or wherever the action is
});
},
});
App.DashboardController = Em.ObjectController.extend(
// The actions go in a hash, as buuda mentioned
actions:
processPersonData: function(data) {
// The logic you want to do on the data object goes here
},
},
});