How can I have a component inherit several attributes from the controller? - ember.js

New to Ember.js.
I'm trying to create robust components that will delegate to the controller for various attributes. I know that I can bind individual attributes like this, but it seems verbose.
{{#my-component attr1=attr1 attr2=attr2}}
Hello
{{/my-component}}
It seems like it might be a common case to want to delegate everything to the controller. Is there a recommended pattern for doing so?

Your example code would be considered the correct way.
You could technically pass in a single object (possibly composed from the controller), but I would recommend against that. A component shouldn't really know much about where the data is coming from as attr1 could be a model property and attr2 could be a property of the view which doing some sort of computation. By passing in the single object you limit the flexibility of the component.
In addition to flexibility I tend to prefer the long argument list as it is clear what your component accepts; even if it is a bit more verbose. If your component is taking a huge list of arguments it may be a sign that the component is trying to do too much and you may want to break it down into smaller components.

App.MyComponentComponent = Ember.Component.extend({
attr1: function() {
return this.get('targetObject.attr1');
}.property('targetObject.attr1'),
attr2: function() {
return this.get('targetObject.attr2');
}.property('targetObject.attr2')
});
From Ember docs:
"If the component is currently inserted into the DOM of a parent view, this property will point to the controller of the parent view."
Use with caution.

Related

What's difference between Mixins and Services on Emberjs

I'm trying to follow the best practice Ember and their potentiality and this question comes to my mind what is the difference between Mixins and Services and how you are using each of them?
I have some services/mixins and work pretty good but I want to be sure I'm doing it right.
A mixin is when you want different objects to have the same behaviour/data. Say you want several controllers to trigger the same action, but change one argument:
// app/mixins/change-name.js
export default Ember.Mixin.create({
actions: {
changeName(item) {
item.set('name', this.get('name'));
}
}
});
// app/controllers/some-controller
import ChangeName from '<app-name>/mixins/change-name';
export default Ember.Controller.extend(ChangeName, {
name: 'Some Controller'
});
Notice that the controllers will have the same action, but it's not shared, each has its own. You can also extend mixins from the object itself since they're added to the _super() chain.
One gotcha to keep in mind is that primitive by-reference data types like the array are still pass by reference. So if you have a Mixin with an array property, make sure to use Ember.computed on the array to create new instances each time the Mixin is used. Otherwise simply using a generic [] will lead to all the Mixin uses pointing to the same array. Different reference values, all pointing to the same thing.
Services can be seen as a sort of mutable shared data. If you have a set of data or behaviour that needs to be accessed from different parts of your application, it's a good candidate for a service.
One such example would be a shopping basket, for example. Regardless of where you are in your application, you will need to refer to the same shopping basket in order to manipulate its data.

didInsertElement for only single element EmberJs

I want to call didInsertElement on just one element but not on others, I have a component template with multiple elements, but I just want to use it on specific element.
Any Idea how to do this? Is it possible, if yes, good practice or not..and component having multiple elements pointing towards other components, is that okay?
Assuming I'm understanding you correctly and you want to call didinsertelement on one instance of a component, but not on the other instances of the component.
With that assumption the simplest approach would be to pass in some parameter to the component that states whether or not to execute the logic handled in the didinsertelement.
{{some-comp dologic='false'}}
setup: Ember.on ('didInsertElement', function (){
if (this.get ('dologic')) ....
})

Ember.js: sending actions to components?

I have a mixin for Ember components, named in-view, the job of which is to request that that the element be brought in view. It is provided an attribute whose value is an piece of content to be brought into view, and if that attribute matches the component's content then I call scrollIntoView or the equivalent. The code looks something like this:
// calling template
{{#each items as |item|}}
{{my-item content=item inViewItem=inViewItem}}
}}
// mixins/in-view.js
scrollIntoView() {
if (this.get('content') === this.get('inViewItem'))
this.get('element').scrollIntoView();
}.on('didInsertElement')
// components/my-item/component.js
import InView from 'mixins/in-view';
export default Ember.Component.extend(InView,
This works fine. The question I have arises when I want to change the item in view. I can have the in-view mixin observe the inviewItem attribute:
}.on('didInsertElement').observes('inViewItem')
and this also works, but seems like a bit of a code smell.
In addition, my actual code structure is that there is a controller which knows which item is supposed to be in view, and then its template calls a my-item-list component which displays the scrollable div containing the item list, and that in turn calls the my-item component. This means I have to pass the inViewItem attribute from the controller down through two levels, as in
// resource/controller.js
inViewItem: something
// resource/template.js
{{my-item-list items=item inViewItem=inViewItem}}
// components/my-item-list/template.js
{{#each items as |item|}}
{{my-item content=item inViewItem=inViewItem}}
}}
I could avoid this by having the my-item template hard-wired to access the inViewItem attribute on the controller:
scrollIntoView() {
if (this.get('content') === this.get('controller.inViewItem'))
this.get('element').scrollIntoView();
}.on('didInsertElement')
but that's another code smell; I don't want to build this kind of dependency on a specific controller field into the mixin. Instead I could possibly pass the component the name of the controller attribute to watch, but this seems unduly clumsy, and it's tricky to observe an attribute whose name is variable. More importantly, I don't think this will work when controllers go away in 2.0.
What I want essentially is a way to "ping" or somehow send a message to a template. I know that in principle this violates the DDAU principle, but in this particular case what I need is exactly to somehow send an "action down"--an action telling the component to adjust itself to bring itself into view.
Of course, I could give up on the entire idea of the in-view mixin and simply have the controller dig down into the generated HTML to find the item to bring into view and issue the scrollIntoView on it directly. However, this seems to violate some principle of separation of concerns; the my-item template would no longer be in complete control of itself.
What is the recommended design pattern for this kind of case?
The solution here is to go the opposite direction that you have. Your component here is a localized scope, and the pain you are feeling is that your localized scope needs to access and mutate global state (the app's scroll position).
Some people use a scroll-service for keeping track of and mutating state, I've used several variations on that myself.
It sounds though like you're dealing with a scrollable list, perhaps a div, and that what item is in view isn't merely a function of page state, but programmatically may change. For instance, a new item has been inserted and you want to scroll the new item into view.
A plugin like jquery.scrollTo or similar (collectively "scroller") would be better for that than simply jumping to the new position as it preserves the user's contextual awareness to where they are on page.
With a scrollable div or list or similar, you might choose to have your top level component control scroll state. The scroll state is still localized in this case, but instead of being localized to each item it's been localized to the scrollable region as a whole, which is where it better belongs.
There are a number of patterns for list items to register themselves with a parent list-component. In a robust scenario, I might do so, but a quick and not very dirty approach is to do something wherein on didInsertElement the new child emits an action to the parent containing it's context, which the parent then uses to check if it's the active item and if so triggers the scrollTo.

Ember.js converting controller / view architecture to components

I have an existing DetailController and DetailView in my app that has some pretty complicated UI / data manipulation logic (hotkeys, copy paste, duplication, autocomplete, etc) -- the view sends UI events to the controller; the controller handles the logic.
I want to convert this to an Ember component.
Does this basically mean I merge the view and controller into DetailComponent? This seems messy and wrong to me.
If not, how do I use controllers and views internally within a component? That is, I still want the complete isolation and well-defined public interface of the component, but internally within the component, I'd like to use controllers and views for organization. Is that possible?
Is it possible to use {{render}}, {{view}}, {{partial}} within the component template?
Does this basically mean I merge the view and controller into DetailComponent? This seems messy and wrong to me.
Yes that is what it means.
internally within the component, I'd like to use controllers and views for organization. Is that possible?
So component basically replaces a single view/controller pair. Beyond that a component is just an extension of Ember.View and can be organized just like any other view.
Is it possible to use {{render}}, {{view}}, {{partial}} within the component template?
Yes. Any of those helpers will work.

In an Ember view, what's the best way to run code after the rerendering of an internal child view?

I found a lot of questions about how to initialize jQuery plugins with dynamic data etc and usually the answer is that you should use the didInsertElement hook. My problem is that when using ember-data, initially the view is inserted empty and the template vars are filled afterwards through the bindings. Thus in didInsertElement the elements I need are not there.
I found a solution that works for me but I think there must be a better solution.
In addition to the didInsertElement method I also observe the attributes in the controller and call my code from there, too, wrapped in a Ember.run.next so the child view is rendered before.
Is there a better solution to react to updated child views?
It depends a bit on the size of your application, and the number of elements your view is rendering.
Edit: You might successfully observer the isLoaded property of the RecordArray. Some details here: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/record_arrays/adapter_populated_record_array.js#L21
One option that I have used for views that have a single object as their content is something like:
MyApp.CustomView = Ember.View.extend({
content: null,
contentObserver: function() {
this.rerender();
}.observes('content')
}
If your view contents is a list, it makes little sense to re render the view for each item in the list, though.
However, I think this approach is fairly similar to what you are already doing, though.
Another approach is to implement your own adapter with Ember Data. This way, you can tell the rest of your application that it's finished loading your data. Something like:
MyApp.Adapter = DS.Adapter.create({
//Finding all object of a certain type. Fetching from the server
findAll: function(store, type, ids) {
var url = type.url;
jQuery.getJSON(url, function(data) {
store.loadMany(type, data);
});
//Perform some custom logic here...
}
}
This should work, but its not as generic as it should/could be though.
Also, you might want to take a look at this commit, which allows for registering a one-time event.
https://github.com/emberjs/ember.js/commit/1809e65012b93c0a530bfcb95eec22d972069745#L0R19
I had this problem and came up with a solution: make a handlebars helper that tells you when the sub-section has rendered. Hope that helps some other folks!