Accessing component property in parent controller in Ember - ember.js

I want to access a property, say selectedItem defined in a component from a parent controller. How to achieve this? I want to access this item so that I can open a modal defined as a partial with this. If anyone can suggest any better solution that is also welcome.

You could bind the property to a property of the controller. Something like this:
App.FooBarComponent = Ember.Component.extend({
selectedItem: null,
// set the property somewhere in your component
});
In your controller
App.SomeController = Ember.Controller.extend({
fooBarSelectedItem: /* empty (null) or a value */
});
In your template
{{foo-bar selectedItem=controller.fooBarSelectedItem}}

I have a "count-down" component, which renders a clock, and when the count-down ends, I need to disable some buttons on the view around the component. The solution is similar to #splattne's answer, but it's newer Ember 3.1 syntax and the shared value is not part of the model.
Component:
export default Component.extend({
'remaining_time': computed('timer_end', 'dummy', function() {
let now = new Date();
let remaining = this.get('timer_end') - now;
if (remaining < 0) {
scheduleOnce('afterRender', this, function(){
this.set('event_listener.expired', true);
});
this.set('should_run', false);
return "Countdown Closed";
}
...
}),
});
Template:
{{count-down timer_end=model.timer_end event_listener=countdown_status}}
Controller:
export default Controller.extend({
countdown_status: Object.create({'expired': false}),
controls_enabled: computed('countdown_status.expired', function() {
return !this.get('countdown_status.expired');
}),
...
});
Note the scheduleOnce('afterRender': it was necessary for https://github.com/emberjs/ember.js/issues/13948. You will only need it if your component changes the shared value before the whole view is rendered, which is unfortunately what mine did.

Related

Force computed.filter() to update?

I've got a filter that works once, with whatever values are pre-initialized in, just at initial initiazation, but never updates. editstate is a service; in this case it just provides those exposed variables. So the idea is that when editstate.filterValue and editstate.filterField are changed, the filter should update.
But it doesn't.
I've tried having them be local computed values also, but no dice. The only programmatic thing that works so far is to unrender and rerender the entire component using a handlebars {{#if toggle.
export default Ember.Component.extend({
store: Ember.inject.service(),
editstate: Ember.inject.service('edit-state'),
filteredList: Ember.computed.filter('model', function(current, index, all) {
return current.get(Ember.get(this.get('editstate'), 'filterField')) == Ember.get(this.get('editstate'), 'filterValue');
}),
What am I missing? I don't see any API for forcing a filter to re-compute, nor to tell it more explicitly to watch these values.
Update: I found a terrible hacky way to accomplish my goal: In my service, I create but don't save a record in the model that I'm filtering, just to force updates. Whenever the filter parameters change, I update that record with the current time (millis). And of course it's always filtered out by the filter.
It's ugly, it's probably evil... is there a better way?
Define every variable (that you use in computed function) at computed definition:
export default Ember.Component.extend({
store: Ember.inject.service(),
editstate: Ember.inject.service('edit-state'),
filteredList: Ember.computed('model', 'editstate.filterField', 'editstate.filterValue', function() {
return this.get('model').filter((current) => {
return current.get(Ember.get(this.get('editstate'), 'filterField')) === Ember.get(this.get('editstate'), 'filterValue');
}
}),
...
or even more readable:
export default Ember.Component.extend({
store: Ember.inject.service(),
editstate: Ember.inject.service('edit-state'),
filteredList: Ember.computed('model', 'editstate.filterField', 'editstate.filterValue', function() {
let filterField = Ember.get(this, 'editstate.filterField');
let filterValue = Ember.get(this, 'editstate.filterValue');
return this.get('model').filterBy(filterField, filterValue);
}),
...

Ember component cannot use access controller property via "needs"

I'm trying to change a controller's property from a component as follows(JSBIN example http://jsbin.com/gevuhu):
App.CategoryManagerController = Ember.Controller.extend({
selectedCategory: null,
});
App.BlogPostComponent = Ember.Component.extend({
needs: ['categoryManager'],
selectedCategory: Ember.computed.alias('controllers.categoryManager.selectedCategory'),
actions:{
selectedCategory: function (){
this.set('selectedCategory',1);
}
}
});
but getting the error Property set failed: object in path "controllers.categoryManager" could not be found or was destroyed.
Is it that we cannot use "needs" in components ?
Ember Components are completely isolated from surrounding context including controllers (see here). That's the bad news. The good news is that if you pass selectedCategory into the component, it will become 2-way bound, so any change to it in the component will be known by your controller.
So, your controller could be something like:
App.ApplicationController = Ember.ObjectController.extend({
needs: ['categoryManager'],
selectedCategory: Ember.computed.alias('controllers.categoryManager.selectedCategory'),
selectedCategoryChanged: function(){
alert("NEW CATEGORY: " + this.get('selectedCategory'));
}.observes('selectedCategory')
});
and then in your application template, you can say
{{ blog-post selectedCategory=selectedCategory }}
See a working example here
In later version like 2.2. We'll be writing this as:
App.ApplicationController = Ember.Controller.extend({
categoryManager: Ember.inject.controller("categoryManager")
});
and now, categoryManager will now have the controller named categoryManager.

Emberjs - Pass an object from the view to controller

I am using a radialProgress as a jQuery plugins (homemade), and I need to implement it for ember but I have some issue to do that.
Quick explanation for the plugins :
var chart = $(yourElement).pieChart(options); // initialise the object to an element
chart.setCompleteProgress( complete, false ); // set how many item you have to complete the task
chart.incrementProgress(); // increment + 1 every time you call it
It's a very simple progress pie.
In my case my task are located inside my controller, but the chart as to select a dom element so I need to initialise it inside my view.
My task in the controller are called from the router from the setupController to reload the model over time.
Here is a small sample of what I would like to do :
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller) {
var promise = controller.getModel();
this._super(controller, promise);
}
})
App.ApplicationController = Ember.ArrayController.extend({
getModel: function() {
// chart.setcompleteProgress();
// A lot of code are here to get some data
// chart.incrementProgress();
return newModel;
}
})
App.ApplicationView = Ember.View.extend({
didInsertElement: function() {
var chart = $(element).pieChart(opts);
}
})
I don't know how to pass the chart object from the view to the controller to be able to have access to my plugin function.
Che chart won't be inserted into the DOM until the didInsertElement therefore you can't attempt to manipulate it in the route during setupController etc. I'd suggest creating a method in the controller setupChart and calling that on didInsertElement.
App.ApplicationView = Ember.View.extend({
prepPieChart: function() {
var chart = $(element).pieChart(opts);
this.get('controller').setupPieChart(chart);
}.on('didInsertElement')
})
App.ApplicationController = Ember.ArrayController.extend({
setupPieChart: function(chart) {
chart.setcompleteProgress();
// A lot of code are here to get some data
chart.incrementProgress();
}
})
All that being said, maybe it belongs in the view, but I'm not sure of what you're completely doing.

Access store from component

i have a component and when user click on component it add some value to store,i try to use this way but i get an error :
OlapApp.MeasureListItemComponent = Ember.Component.extend({
tagName: 'li',
isDisabled: false,
attributeBindings: ['isDisabled:disabled'],
classBindings: ['isDisabled:MeasureListItemDisabled'],
actions: {
add: function(measure) {
var store = this.get('store');
store.push('OlapApp.AxisModel', {
uniqueName: measure.uniqueName,
name: measure.name,
hierarchyUniqueName: measure.hierarchyUniqueName,
type: 'row',
isMeasure: true,
orderId: 1
});
}
}
});
and this is error:
Uncaught TypeError: Cannot call method 'push' of undefined MeasureListItemComponent.js:18
is it posible to push record to store from component? why i cant access to store ?
my model name is 'AxisModel' and application namespace is 'OlapApp'
Since Ember v1.10, the store can be injected to components using initializers, see: http://emberjs.com/blog/2015/02/07/ember-1-10-0-released.html#toc_injected-properties:
export default Ember.Component.extend({
store: Ember.inject.service()
});
In a component the store does not get injected automatically like in route's or controller's when your app starts. This is because components are thought to be more isolated.
What follows below is not considered a best practice. A component should use data passed into it and not know about it's environment. The best way to handle this case would be using sendAction to bubble up what you want to do, and handle the action with the store in the controller itself.
#sly7_7 suggestion is a good one, and if you have a lot of components from where you need access to the store then it might be a good way to do it.
Another approach to get to your store could be to get the store your component surrounding controller has reference to. In this case it doesn't matter which controller this is because every controller has already a reference to the store injected into it. So now to get to your store could be done by getting the component's targetObject which will be the controller surrounding the component and then get the store.
Example:
OlapApp.MeasureListItemComponent = Ember.Component.extend({
...
actions: {
add: function(measure) {
var store = this.get('targetObject.store');
...
}
}
});
See here for a working example.
Hope it helps.
Update in response to your comment having nested components
If for example you child component is only nested one level then you could still refer to parent's targetObject using parentView:
App.ChildCompComponent = Ember.Component.extend({
storeName: '',
didInsertElement: function() {
console.log(this.get('parentView.targetObject.store'));
this.set('storeName', this.get('parentView.targetObject.store'));
}
});
Updated example.
Since Ember 2.1.0
export default Ember.Component.extend({
store: Ember.inject.service('store'),
});
before Ember 2.1.0 - dependency injection way
App.MyComponent = Ember.Component.extend({
store: Ember.computed(function() {
return this.get('container').lookup('store:main');
})
});
before Ember 2.1.0 - controller way
You can pass store as property from controller:
App.MyComponent = Ember.Component.extend({
value: null,
store: null,
tagName: "input",
didInsertElement: function () {
if (!this.get('store')) {
throw 'MyComponent requires store for autocomplete feature. Inject as store=store'
}
}
});
Store is available on each controller. So in parent view you can include component as follows:
{{view App.MyComponent
store=store
class="some-class"
elementId="some-id"
valueBinding="someValue"
}}
Passing properties to component is documented here
The current ember-cli way to do this appears to be with an initializer. Very similar to the #Sly7_7 answer.
To get a basic model use:
ember g initializer component-store-injector
Then edit this to:
// app/initializers/component-store-injector.js
export function initialize(container, application) {
application.inject('component', 'store', 'store:main');
}
export default {
name: 'component-store-injector',
initialize: initialize
};
I believe this will add the store to all components.
Stolen from https://github.com/ember-cli/ember-cli-todos
I don't know if components are intended to be used such a way. But if you want, I think you can declare an initializer and inject the store into all components.
Ember.onLoad('OlaApp', function(OlaApp) {
OlapApp.initializer({
name: 'injectStoreIntoComponents',
before: 'registerComponents',
initialize: function(container, application){
container.register('store:main', App.Store);
container.injection('component', 'store', 'store:main');
}
})
});
Here is a contrived but working example: http://jsbin.com/AlIyUDo/6/edit
The store can be injected with help of dependency injection.
Example
import Ember from 'ember';
export default Ember.Component.extend({
/**
*
*/
store: Ember.inject.service(),
/**
* Initialize the component.
*/
init() {
this.initialize();
this._super();
},
/**
* Initialize the properties and prerequisites.
*/
initialize() {
// Set the component properties
this.todos().then((data) => {
this.set('todoEntries', data);
});
},
/**
* Returns the todo entries.
*
* #returns {*|Promise|Promise.<T>}
*/
todos() {
const store = this.get('store');
return store.findAll('todo');
},
});
Another way which no one has yet mentioned is to simply pass controller.store to the component e.g.
{{my-awesome-component store=controller.store}}

How can catch event after template is rendered at EmberJS?

I have an application that uses masonry and Ember JS I attempt to search DOM an element by selector, but it retrieves null It seems I do it early than template was rendered. Please, help me resolve it.
#GJK answer is correct, I just want to provide a working example: http://jsbin.com/enijad/3/edit
App.IndexView = Ember.View.extend({
didInsertElement: function() {
var $container = $('#container');
$container.masonry({
columnWidth: 150,
itemSelector: '.item'
});
}
});
The didInsertElement function will be called when the view was inserted into the DOM, so it will be safe to initialize additionally libraries.
Also worth mentioning is that if you need some clearing up after the view was removed from the DOM you would do this in didInsertElement's counterpart hook willDestroyElement.
Example:
App.IndexView = Ember.View.extend({
didInsertElement: function() {
// do initialization here
},
willDestroyElement: function() {
// and here you can remove stuff safely
}
});
Hope it helps.
Create a corresponding View for your Route and Template, and then override the didInsertElement method.