Both ArrayController and CollectionView have same functionality to render 'content' array using template, except that collection view have 'append..' methods to append in to anywhere in DOM. Am I right? Is there any other diff? Which one will be more useful?
An ArrayController is just an extension of Ember.ArrayProxy. It provides an interface to working with an array set as its contents. It handles dispatching array mutation notifications to any observers. It does not handle anything to do with the DOM, and is completely isolated from events or the view layer.
A CollectionView is a view (which means it manipulates the DOM and handles events) which takes an ArrayController (or anything that meets the expectations of an Ember.Array) and reflects its contents as Ember.Views. It listens to the mutation events put out by the ArrayController, and updates itself to match. It works by specifically listening to arrayDidChange messages. Without those messages coming from its content, it wouldn't know to create or destroy its views.
When you use the {{#each YourApp.yourArrayController}} helper, you're actually instantiating an Ember.CollectionView, that takes the controller as its content.
ArrayController is a Controller. CollectionView is a View. That's a pretty fundamental conceptual difference. In theory, ArrayController requires a View to render an array using a template. That's why the View has the append methods and the Controller does not.
Related
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.
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.
I've gone through the source code comments and the emberjs api but I don't really feel as though I've gotten a very clear idea at the differences between the two types of Ember.View. If anyone could delineate the situations where one might use Ember.ContainerView as opposed to Ember.CollectionView, and vice versa, I would be very grateful. Thanks!
CollectionView = the same child view over and over
This useful when you want to have a view object for every element in an array. For example, if have a list of posts and want to show a PostSummary view for each of them. A typical ember application will get this done by using the handlebars {{each}} helper, which has been implemented using CollectionView.
ContainerView = different child views
Ember.ContainerView when you need to manage an arbitrary list of child views programmatically. CollectionView extends ContainerView. As an alternative you can use handlebars helpers to insert child templates using conditionals around {{view}} helpers instead.
My view contains an element. What is the best way to respond to mouseEnter and mouseLeave events for only that element?
Does ember want me to convert that element into a view?
Does ember want me to call this.$().on('mouseEnter', '.my-child-element', someHandler) and .off it myself in didInsertElement and willDestroyElement respectively?
Or am I missing a more appropriate approach?
There are several ways to approach this, but I always come to the conclusion that you create a view for anything that needs to respond to events. You could also use an action handler for very simple events in a template.
{{action "my_action" on="mousedown"}}
I would still recommend using a view though as you will most likely need to know more info about what happened and want access to the view and models bound to that element.
I'm trying to create a reusable component which consist of a textfield and under the textfield, i want to have a collectionView to display a filtered list of elements.
My problem is that I want itemViewClass of the containerView to be customized when creating the component. Currently, I pass a parameter listItemView to the container view and declare
itemViewClassBinding: 'parentView.listItemView' instead of having an hardcoded templates.
This leads me to a problem where Ember assert that itemViewClass must be an instance of Ember.View:
Uncaught Error: assertion failed: itemViewClass must be a subclass of
Ember.View, not function () {
Did anybody ran into a similar problem?
Thank you
Sub-classing your ContainerView class is one option. Here is an example: http://jsfiddle.net/ethan_selzer/kcjzw/240/
This pastie may be a little easier to read: http://pastie.org/4256407
Ethan
I have created this functionality very recently in my ember app. The way I did it was by binding to a controller property. When the user types in the textfield it needs to set the filter text as a controller property. Then your controller will have another property that observes the filter field text property and produces a filtered list of the content data based on the filter text. Then your filtered view would be bound to that filtered content of the controller instead of the usual (all) content. This way your two views don't need to know about each other and the controller provides the data.