Is possible to link data by handlebars to Ember.view ? Like Ember.Select with valueBindig.
My attempt:
{{#each models}}
<li>{{id}}</li>
{{/each}}
// -> 1,2,3 (this works fine)
{{view App.TimelineView valueBinding="models.id"}} //this doesn't work
App.TimelineView = Ember.View.extend({
tagName: 'div',
attributeBindings: ["value"],
value: null,
didInsertElement: function(){
console.log(this.value)
...
})
Console log: ---> null
but I need [1,2,3]
Supposing models is an array, models.id is accessing the id property of the array itself, not of each element. What you probably want is to map models to ids.
Define the following property on the controller which models is defined on:
modelIds: Ember.computed.mapBy('models', 'id')
And then use modelIds in the template instead of models.id.
Also, you should access properties this.get-style, not directly (in didInsertElement for example).
You are not accessing value correctly. The right way of doing that is as below. There were some more errors in there that I fixed.
App.TimelineView = Ember.View.extend({
tagName: 'div',
didInsertElement: function() {
console.log(this.get('value'));
},
)};
{{#each models}}
{{view App.TimelineView valueBinding="id"}}
{{/each}}
The context within the loop is this, which represents the current element of the iteration, so you want 'this.id' or simply 'id'.
Addition: If you want to get a list of all the ids at once as an array (as opposed to item-by-item), you can do the following:
App.MyController = Ember.ArrayController.extend({
allIds: function() {
var ids = this.get('content').getEach('id').reduce(function(accum, item) {
return accum.push(item);
}, []);
return ids;
}.property('content.#each.id');
});
Related
I have poured through a ton of documentation and I can't seem to find an answer to a very basic question. I have a component that needs to store a map (key/value) as a property:
App.SimpleTestComponent = Ember.Component.extend({
data: Ember.A(),
actions: {
add: function() {
this.get('data').set('test', 'value');
}
}
});
The template for the component looks like this:
<script type="text/x-handlebars" data-template-name="components/simple-test">
{{#each item in data}}
<p>
<strong>{{item.key}}:</strong>
{{ item.value}}
</p>
{{/each}}
<button {{action 'add'}}>Add</button>
</script>
However, this doesn't work. No items are displayed after clicking the button and the problem seems to be with the {{#each}} block. How do I correctly enumerate over the data property?
Ember.A() is shorthand for Ember.NativeArray. This is why your
code isn't working, you're also calling .set which is a method
inherited from Ember.Observable. So what you're really doing is
just setting an object property on the array rather than its
content.
What you probably want is Ember.Map which is an internal class
but many developers use it anyways. You will still need to return an
array of objects as the following example:
App.SimpleTestComponent = Ember.Component.extend({
// Shared map among all SimpleTestComponents
map: Ember.Map.create(),
// Or per component map
init: function() {
this.set('map', Ember.Map.create());
},
data: function() {
// this doesn't have to be locally scoped...
var arr = Ember.A();
this.get('map').forEach(function(key, value) {
arr.addObject({key: key, value: value});
});
return arr;
}.property('map'),
actions: {
add: function() {
this.get('map').set('test', 'value');
}
}
});
This doesn't really work when you have keys that have multiples
values simply becauses Ember.Map always overwrites on .set
If performance is of concern and you would like to have multiple
values per key then you will need to implement your own
map class with and a handlebars helper to display it.
I have a view that uses a 3rd party library to render additional DOM elements in the didInsertElement hook. After these new elements are added, I need to add some child views inside them, so that they can render dynamic data.
Here's what I tried:
App.MyView = Ember.View.extend({
didInsertElement: function() {
create3rdPartyDomElements();
var element = this.$('someSelector');
childView = this.createChildView(App.SomeViewClass, attributesDict);
childView.appendTo(element);
}
});
(jsbin: http://jsbin.com/idoyic/3)
This renders my views as expected, but gives the following assertion error with Ember RC 7: "You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead."
I have tried extending ContainerView, as advised here and that works, but I have no way of inserting the child views at specific DOM selectors. It just inserts the child views at the beginning of the parent view.
Can someone please help me? Thanks a lot!
This is how I created:
An implementation where you have the main view, in that case codemirror, in the middle. And it's possible add more views, in the top or bottom.
App.EditorView = Ember.View.extend({
templateName: 'editor-view',
topView: Ember.ContainerView.extend(),
bottomView: Ember.ContainerView.extend(),
CodeMirrorView: Ember.TextArea.extend({
didInsertElement: function() {
this.codeMirror = CodeMirror.fromTextArea(this.get('element'));
}
})
});
The template:
<script type="text/x-handlebars" data-template-name="editor-view">
{{view view.topView viewName="topViewInstance"}}
{{view view.CodeMirrorView}}
{{view view.bottomView viewName="bottomViewInstance"}}
</script>
A view to represent a custom component:
App.MyComponent = Ember.View.extend({
templateName: 'click-here',
message: null,
click: function() {
alert(this.get('message'));
}
});
The implementation:
App.MyEditorView = App.EditorView.extend({
didInsertElement: function() {
this._super();
this.get('topViewInstance').pushObject(App.MyComponent.create({ message: "Hello" }));
this.get('bottomViewInstance').pushObject(App.MyComponent.create({ message: "World" }));
}
});
With this is possible to create a new instance, or extend App.EditorView and insert more views in top or bottom. Because the topView and bottomView are Ember.ContainerViews, all views added will have the bindings, events, and other ember features.
Give a look in that jsbin to see it working http://jsbin.com/ucanam/686/edit
You can render child views into parent view's hidden div, and then detach and append them to arbitrary DOM elements in didInsertElement hook.
http://jsbin.com/qaqome/1/
For related issue (components instead of views) see also this question.
try adding a property in your view, something like this:
App.MyView = Ember.View.extend({
childViewsContainer: Em.ContainerView.create({}),
didInsertElement: function() {
create3rdPartyDomElements();
var element = this.$('someSelector');
childViewsContainer.createChildView(App.SomeViewClass, attributesDict);
childView.appendTo(element);
}
});
then, you can access your childViewsContainer and do what ever you want with it
UPDATE Since asking this question I have redesigned my UI such that I no longer need this feature; however I'm leaving this open and active for the sake of helping others who end up with a similar problem.
I'm listing a collection of elements inside a template and each element has a link that opens it up to the right of the list. When one is clicked, I want to hide just that element and show it again when another one is clicked. My current approach to doing this is to set an attribute (active) to true on the model. This feels wrong for three reasons:
This attribute is not actually part of the model's schema, it's just arbitrary; which makes it seem like a controller concern (see below for why that doesn't work)
I have to first set active to false on all models, forcing me to change another router's model, which may be good or bad, I'm not sure
In the recent PeepCode screencast he showed using #each.{attribute} to bind to an attributes in an array; this makes me feel like there must be something similar I could do (like this.set("#each.active", false)) to set them all in one fell swoop
I wanted to use a method on the controller but it doesn't seem I can pass arguments into functions in Handlebars if statements.
Here's the code I'm using to render the list:
{{#each note in controller}}
{{!v-- I was trying to do {{#if isCurrentNote note}} but that seems to be invalid handlebars}}
{{#unless note.active}}
<li class="sticky-list-item">
{{view Ember.TextArea classNames="sticky-note" valueBinding="note.content"}}
{{#linkTo note note classNames="sticky-permalink"}}
∞
{{/linkTo}}
</li>
{{/unless}}
{{/each}}
And here are the routes:
App.NotesController = Ember.ArrayController.extend({
// v-- this is what I was trying to do, but couldn't pass note in the template
isCurrentNote: function(note){
return this.get("currentNote") == note;
}.property("currentNote")
});
App.NoteRoute = Ember.Route.extend({
setupController: function(controller,model){
this.modelFor("notes").forEach(function(note){
note.set("active", false);
});
model.set("active", true);
}
});
Like I said, what I have works, but it feels wrong. Can anyone confirm my suspicion or help ease my soul a bit?
Thanks!
to me this looks like something that should be done mostly by the NotesView with a NotesController that stores the Note selection
Here is the fiddle: http://jsfiddle.net/colymba/UMkUL/6/
the NotesController would hold all the notes and a record of the selected one:
App.NotesController = Ember.ArrayController.extend({
content: [],
selectedNote: null,
selectNote: function(id){
var note = this.get('content').findProperty('id', id);
this.set('selectedNote', note);
}
});
with the NotesViewobserving that selection and showing/hiding elements of the list accordingly
App.NotesView = Ember.View.extend({
templateName: 'notes',
refresh: function(){
var view = this.$(),
selection = this.get('controller.selectedNote');
if (view) {
view.find('li').show();
if (selection) view.find('li.note_'+selection.id).hide();
}
}.observes('controller.selectedNote')
});
Here is the Note object and it's 2 templates (when in a list or displayed in full). The ListView handles the click event and passes the id to the NotesController.
App.Note = Ember.Object.extend({
name: '',
content: ''
});
App.NoteView = Ember.View.extend({
templateName: 'note'
});
App.NoteListItemView = Ember.View.extend({
tagName: 'li',
templateName: 'noteListItem',
classNameBindings: ['noteID'],
noteID: function(){
return 'note_' + this._context.id;
}.property(),
click: function(e){
this.get('controller').selectNote(this._context.id);
}
});
in the NotesView template everything is displayed and if there is a selectedNote, we display the Note again in full:
{{#each note in controller}}
{{#with note}}
{{view App.NoteListItemView}}
{{/with}}
{{/each}}
{{#if selectedNote}}
{{#with selectedNote}}
{{view App.NoteView}}
{{/with}}
{{/if}}
the Routes to put it together
App.Router.map(function() {
this.resource('notes', { path: "/notes" });
});
App.IndexRoute = Ember.Route.extend({
enter: function() {
this.transitionTo('notes');
}
});
App.NotesRoute = Ember.Route.extend({
model: function() {
return [
App.Note.create({id: 1, name: 'Milk', content: '15% fresh milk'}),
App.Note.create({id: 2, name: 'Juice', content: 'Orange with pulp'}),
App.Note.create({id: 3, name: 'Cereals', content: 'Kelloggs Froot Loops'}),
];
},
setupController: function(controller, model) {
controller.set('content', model);
},
renderTemplate: function(controller, model) {
this.render('notes', { outlet: 'content' });
}
});
Below is the jsfiddle describing the issue. First view has elementid as a computed property and second one has a explicit element id. First view's id has not got changed while the second one has the id.
http://jsfiddle.net/LZjEx/
App = Ember.Application.create();
App.MultiView = Ember.View.extend({
templateName : 'appl',
textInput: Ember.TextField.extend({
elementId : function(){
return "disk";
}.property()
})
})
App.MultiView.create().append();
<script type="text/x-handlebars" data-template-name="appl">
{{view view.textInput}}
{{view Ember.TextField elementId="answer"}}
</script>
After some digging and experiments, here is what I found:
In this thread it is explained when the elementId is set: https://github.com/emberjs/ember.js/issues/1549
Since the view registers itself with Ember.View.views on init it needs the elementId defined before init is run. It doesn't have to do with 'inDOM' state.
Here is a fiddle confirming that: http://jsfiddle.net/LLSQD/
App.MultiView = Ember.View.extend({
templateName : 'appl',
textInput: Ember.TextField.extend({
init: function() {
// This view's id will not be set to 'disk'.
return this._super();
this.set('elementId', 'disk');
}
}),
textInput2: Ember.TextField.extend({
init: function() {
// This view's id will be set to 'answer'.
this.set('elementId', 'answer');
return this._super();
}
})
})
In this thread it is explained, that computed properties are not computed before initialization: https://github.com/emberjs/ember.js/issues/777
This has been discussed before. create extends prototypes, it's not for setting computed property values. This isn't something that is going to change. You can consider using setProperties if you want this.
Overall, because the elementId is used at prototype initialisation, it cannot be changed after the object is constructed, thus using a computed property to determine the id is wrong. The best you can do is set the id in the init method and then call this._super();
Given this controller:
ItemController= Ember.Controller.extend({
subItems: Ember.ArrayController.create({
content: App.store.find(App.models.SubItem),
sortProperties: ['name']
}),
currentItemIdBinding: 'App.router.mainController.currentItemId',
item: function() {
return App.store.find(App.models.SubItem, this.get('currentItemId'));
}.property('currentItemId'),
currentSubItems: function () {
return this.get('subItems.content')
.filterProperty('item_id', this.get('item.id'));
}.property('item', 'subItems.#each')
});
and this each block in the template:
{{#each subItem in currentSubItems}}
{{view App.SubItemView}}
{{/each}}
How would I gain access to the "subItem" in the controller for the SubItemView?
Edit:
I stumbled upon a way to do this. If I change the each block slightly:
{{#each subItem in currentSubItems}}
{{view App.SubItemView subItemBinding="subItem"}}
{{/each}}
and add an init method to the SubItemView class:
init: function() {
this._super();
this.set('controller', App.SubItemController.create({
subItem: this.get('subItem')
}));
})
I can get access to the subItem in the controller. This however just feels wrong on more levels than I can count.
Interesting...while browsing ember.js, I found this: https://stackoverflow.com/a/14251255/489116 . It's a little different from what you're asking, but may solve the problem: if I'm reading it correctly, it would automatically associate a subItemController with each subItemView and subItem, without you having to pass the model around. Not released yet though. I'd still like to see other solutions!
How about using Ember.CollectionView instead of {{each}} helper see the following:
App.SubItemsView = Ember.CollectionView.extend({
contentBinding: "controller.currentSubItems",
itemViewClass: Ember.View.extend({
templateName: "theTemplateYouUsedForSubItemViewInYourQuestion",
controller: function(){
App.SubItemController.create({subItem: this.get("content")});
}.property()
})
})
Use it in handlebars as follows
{{collection App.SubItemsView}}