Emberjs - Controller always gets default value from 'needs' controller - javascript

I have a situation where I have an application controller that 'needs' to access a property from another controller, like this:
App.ApplicationController = Ember.Controller.extend({
needs: ['sort'],
actions: {
appClicked: function () {
console.log('controllers.sort.isMenuExpanded');
}
}
});
App.SortController = Ember.Controller.extend({
isMenuExpanded: false,
actions: {
menuClicked: function () {
this.toggleProperty('isMenuExpanded');
}
}
})
As expected, the ApplicationController's appClicked function correctly logs 'false' the first time it is run. However, it continues logging false (the default value set in the SortController) even after the isMenuExpanded property has been changed to 'true' by the SortController.
This may have something to do with the way JavaScript passes values. I think objects are passed by reference, and sure enough, if I change the isMenuExpanded property to:
isMenuExpanded: { expanded: false }
and change the toggle to:
this.toggleProperty('isMenuExpanded.expanded');
the action in the ApplicationController correctly prints the isMenuExpanded.expanded value to the console every time it's updated.
Thanks in advance for your time and expertise!

Try setting it as a computed property.
App.ApplicationController = Ember.Controller.extend({
needs: ['sort'],
isMenuExpanded: Ember.computed.alias('controllers.sort.isMenuExpanded'),
actions: {
appClicked: function () {
console.log(this.get('isMenuExpanded'));
}
}
});
App.SortController = Ember.Controller.extend({
isMenuExpanded: false,
actions: {
menuClicked: function () {
this.toggleProperty('isMenuExpanded');
}
}
})

Related

Vue2 change object property from method param passing into child props

oi, this one seems so simple but it's giving me a headache.
I have a child component with a property passed down:
<dialog-child requests='requests'/>
the passed prop, is an object obtaining varied booleans. The dialog is v-modeled to in this case,
<dialog v-model='request.deleteItem'>
requests { deleteItem: false, editItem: false, syncItem: false, }
When I click on the delete button, I want to make a request to delete an item, and pull up this dialog component. This works fine if i simply change the bool in the object to true, but I need more control by passing the #click to a method and passing a parameter.
<btn #click='makeRequest(deleteItem)'>Activate Dialog</btn>
so in the method, I need to figure out how to say that the passed deleteItem, is request.deleteItem and then I would make it true.
makeRequest(requested){
//somewhow say
this.requests.requested = true
}
How could I pass in the parameter to take control of the objects property?
I could do a long form of multiple if checks, if requested = '' then make this prop true, but that feels gross.
I also need to pass in a second param, item after I figure this out - so to pass in two params do i just say methodName(param1, param2) and on click method(item1, item2) or do I need to create an object like method({item1, item2})?
To have everything nice and neat :) but:
For that we need option "to emit event on child component" but Vue.js does not work like that. So try this:
<template>
<div>
<dialog-child requests='requests' #resetRequest="resetRequest()"/>
<btn #click='makeRequest("deleteItem")'>Delete</btn>
</div>
</template>
<script>
import DialogChild from 'Dialog.vue'
export default {
components: { "dialog-child" : DialogChild },
data() {
return {
requests: {
makeRequest: false, //New Field
deleteItem: false,
editItem: false,
syncItem: false
}
}
},
methods: {
makeRequest(action) {
//So here you can create new request Object with whatever you want. Use action argument to check what you want to do here...
let newRequests = {
makeRequest: true,
deleteItem: true,
editItem: false,
syncItem: false
}
//Then this newRequest object need to copy to this.requests, this will update request object reference, and trigger "watch" in child component
this.requests = newRequests;
},
resetRequest() {
//Request object is again updated with new reference, but makeRequest is false so it will not trigger action in Dialog Child component
this.requests = {
makeRequest: false, //Now this is false,
deleteItem: false,
editItem: false,
syncItem: false
}
}
}
}
</script>
//And child component should be like this
<template>
<div>
</div>
</template>
<script>
export default {
props: {
requests: { Type: Object }
},
watch: {
requests(newVal) {
if(newVal && newVal.makeRequest) {
this.doStuff()
}
}
},
methods: {
doStuff() {
//So after doing what you want, you need to make event to reset requests
this.$emit('resetRequests');
}
}
}
</script>
i fixed this by changing the param to a string, and removing the request for a second item. Onwards to a item pass in too.

Ember: Even after refresh some attributes in the model are not changed

I am new to EmberJS, and facing problem with model data updation
Controller :
export default Ember.Controller.extend({
queryParams: ['jobid'],
jobid: null,
currentCount: null,
actions: {
onOffToggle(e) {
var isChanged = this.get('model.b.isChanged');
this.set('model.b.enable', e.target.checked);
this.set('model.b.isChanged', !isChanged);
console.log(this.get('model.b.isChanged'))
},
iterationCountChange() {
var currentCount = this.get('currentCount');
var isCountChanged =
(currentCount != this.get('model.b.count')) ? true : false;
this.set('model.b.isCountChanged', isCountChanged);
}
}});
Route:
export default Ember.Route.extend({
ajax: Ember.inject.service(),
beforeModel: function (transition) {
this.jobid = transition.queryParams.jobid;
},
model(){
return Ember.RSVP.hash({
a: this.store.queryRecord('a', {jobid: this.get("jobid")}),
b: this.store.queryRecord('b', {id: this.get("jobid")})
});
},
setupController: function(controller, model) {
this._super(controller, model)
controller.set('currentCount', model.b.get('iterationCount'))
},
actions: {
paramChange(a, b)
Ember.$.ajax({
url: "/rest/test",
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
b: b,
a: a
})
}).then(response => {
this.refresh();
})
},
error(error, transition) {
if (error.errors[0].status == 404) {
return this.transitionTo('not-found', { queryParams: {'previous': window.location.href}});
}
}
}
});
Here in controller I am keeping track if some value have changed, and if they have changed then update the flag related to their change, these flags like isChanged and isCountChanged are the part of the model's data, after user cliks submit button , paramChange
action is called and then a post call is made to update the db for respective property changes, and then this.refresh() is called to render the latest model data.
But the problem is, once isChanged and/or isCountChanged are changed from their default value, then they don't reset to the new value present in the model data, e.g. after refresh the value to both these flags should be reset to false but it comes always true, I checked the value in the setUpController hook for the values of these flags and it confirms to true.
According to me it has something to with the controller, since any value which is used in controller once is not resetting to new value coming after refresh.
Kindly help I am spent a lot of time in this and got nothing till now, do inform if any extra information is required.
Ember version: 2.6
From docs,
Refresh the model on this route and any child routes, firing the
beforeModel, model, and afterModel hooks in a similar fashion to how
routes are entered when transitioning in from other route. The current
route params (e.g. article_id) will be passed in to the respective
model hooks, and if a different model is returned, setupController and
associated route hooks will re-fire as well.
So, if your model data doesn't change, setupController() is not called. My approach is to have a custom method to update controller with model data. Then, I call this method from model() hook (for this.refresh()) and from setupController().
model() {
return this.store.query(....).then(data => this.updateControllerData(data));
}
setupController(controller, model) {
this._super(...arguments);
this.updateControllerData(data);
}
updateControllerData(data = {}) {
if (!this.controller) {
return;
}
this.controller.setProperties(data);
}
Note that if setupController() is not fired, the controller data is always updated.

Why Don't I have 'this' context inside of a .then() in my Ember component

I'm fairly new to Ember.
This is the code for my component:
import Ember from 'ember';
export default Ember.Component.extend({
profiles: Ember.inject.service(),
tagName: 'td',
classNames: ['grey'],
classNameBindings: ['unreadMessages'],
unreadMessages: null,
onInit: function() {
const id = this.get('conversation.id');
return this.get('profiles').getMessages(id)
.then(function(bool) {
this.set('unreadMessage', bool);
});
}.on('init')
});
This throws:
TypeError: Cannot read property 'set' of undefined
So I can gather that I don't have the this context that I need to call this.set inside of the .then()
I need to assign the result of return this.get('profiles').getMessages(id) to the unreadMessages property in my component. So that I can use it for the classNameBinding.
Here is the method I'm calling from the service
getMessages(id){
return this.get('ajax').request('/messages?id=' + id)
.then((obj) => {
const unreadMessages = obj.messages.filter((e) => e.read === false);
if (unreadMessages === []) {
return false;
} else {
return true;
}
});
}
I've only been able to access the boolean value that getMessages returns inside of its .then() and I am not able to call this.set() inside of the .then() I'm looking for a work around. I think I'm close and am struggling to due to my lack of experience with Ember.
getMessages makes a 'GET' request to my back end and filters through the messages to check if there are any that are unread and then returns true or false. The purpose of the classNameBinding is to notify the user of whether they have any unread messages for that thread. This is a very simple email style messaging app that I am building for practice.
Thanks!
Change
onInit: function() {
const id = this.get('conversation.id');
return this.get('profiles').getMessages(id)
.then(function(bool) {
this.set('unreadMessage', bool);
});
}.on('init')
});
to
onInit: function() {
const id = this.get('conversation.id');
return this.get('profiles').getMessages(id)
.then((bool) => {
this.set('unreadMessage', bool);
});
}.on('init')
});
The thing here is, the scope changes when you write function(){} inside then and this won't refer to the component this.
That's why in ES6 the concept of lexical this was introduced. This will retain the this. So use Arrow function instead and it'll work smoothly..

Ember js service and filterBy

i have a service to manage all the errors and alerts in my app. and the code looks like this
Service
import Ember from 'ember';
export default Ember.Service.extend({
messages: null,
init() {
this._super(...arguments);
this.set('messages', []);
},
add: function (severity, msg, messageType) {
if (severity === 'error') {severity = 'danger';}
var msgObject ={
severity: severity,
messageType: messageType,
msg: msg,
msgId: new Date()
};
this.get('messages').pushObject(msgObject);
},
remove(msgId) {
this.get('messages').removeObject(msgId);
},
empty() {
this.get('messages').clear();
}
});
Component
import Ember from 'ember';
export default Ember.Component.extend({
messageType:'global',
messageHandler: Ember.inject.service(),
messages: function(){
return this.get('messageHandler.messages').filterBy('messageType',this.get('messageType'));
}.property('messageHandler.messages'),
actions : {
dismissAllAlerts: function(){
this.get('messageHandler').empty();
},
dismissAlert: function(msgId){
this.get('messageHandler').remove(msgId);
}
}
});
Initializer
export function initialize(container, application) {
application.inject('component', 'messageHandler', 'service:message-handler');
}
export default {
name: 'message-handler',
initialize : initialize
};
Template
import Ember from 'ember';
export default Ember.Component.extend({
messageType:'global',
messageHandler: Ember.inject.service(),
messages: function(){
return this.get('messageHandler.messages');
}.property('messageHandler.messages'),
actions : {
dismissAllAlerts: function(){
this.get('messageHandler').empty();
},
dismissAlert: function(msgId){
this.get('messageHandler').remove(msgId);
}
}
});
and whenever there is an error i will add it like this
this.get('messageHandler').add('error',"Unable to get ossoi details","global");
my problem is the filterBy in the component is not working. if i remove the filterBy() it works and i can see the error in the template. am kinda new to ember so if anyone can help me figure out what am missing here or if there is a better way of doing this please let me know
filterBy usage is good and it should be working well. but messages computed property will not be recomputed whenever you add/remove item from messageHandler.messages.
messages: Ember.computed('messageHandler.messages.[]', function() {
return this.get('messageHandler.messages').filterBy('messageType', this.get('messageType'));
}),
In the above code I used messageHandler.messages.[] as dependant key for the messages computed property so that it will be called for add/remove items.
Refer:https://guides.emberjs.com/v2.13.0/object-model/computed-properties-and-aggregate-data/
Computed properties dependent on an array using the [] key will only
update if items are added to or removed from the array, or if the
array property is set to a different array.

Ember pushObjects() not notifying computed property

I'm trying to use ember-light-table and I'm having some troubles on updating my array of objects.
When I use the action updateList(), I can see both arrays changing (adding/removing objects to the list), but the computed property tableModel is not triggered!
I thought pushObjects() would do the trick, but it's not notifying for some reason (it's adding). I also tried to initialize select_people with Ember.A(), although [] should already be an ember array...
My mixin:
// table-testing
import Ember from 'ember';
import Table from 'ember-light-table';
export default Ember.Mixin.create({
table: null,
tableColumns: null,
tableModel: null,
init() {
this._super(...arguments);
let table = new Table(this.get('tableColumns'), this.get('tableModel'), { enableSync: this.get('enableSync') });
this.set('table', table);
}
});
My controller
import Ember from 'ember';
import TableTesting from '../mixins/table-testing';
const { computed } = Ember;
export default Ember.Controller.extend(TableTesting, {
tableColumns: computed(function() {
return [{
label: 'First Name',
valuePath: 'firstName',
width: '50%',
sortable: false,
}, {
label: 'Last Name'
valuePath: 'lastName',
width: '50%'
}]
}),
tableModel: computed('selected_people.#each.firstName', function() {
// THIS MESSAGE ONLY SHOW WHEN VIEW IS RENDERED
// I've tried .[], .#each, .length... none of them worked and I believe #each.firstName would be the most appropriated from what I've read
console.log('computed method not showing',this.get('selected_people'));
return this.get('selected_people');
}),
init() {
this._super(...arguments);
this.set('selected_people',[]);
},
actions: {
updateList(item, moveToList) {
let removeFromList, fromList, toList;
if (moveToList === 'people') {
removeFromList = 'selected_people'
} else if (moveToList === "selected_people") {
removeFromList = 'people';
}
// get the object lists
fromList = this.get(removeFromList);
toList = this.get(moveToList);
// update list ---> HERE I UPDATE USING KOVL METHOD
toList.pushObjects(item);
fromList.removeObjects(item);
console.log('update!',this.get('selected_people'));
}
}
Just make sure tableModel should be accessed/required by the template or in code.
Computed property is lazy, so it will be calculated when you ask for it either inside the code or in a template. otherwise, it will not be called.
First time when we call, it will return result and it will be cached. and subsequent access will get it from cache. Changing any of the dependent properties causes the cache to invalidate so that the computed function runs again on the next access.
Mostlty copied from guides.emberjs

Categories