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.
Related
I have a requirement where I need to add html after the DOM has been rendered.
I was wondering if it is possible to manipulate the DOM after creation and dynamically add html and also specifying an associated ember action.
E.g. The intension of what I want to achieve:
$('.add').on("click", function(e){
e.preventDefault();
i += 1;
var content = "<div class=\"item dodgerBlue\"><h1>"+i+"</h1></div>";
var content + "{{action "owlItemClicked" titleModel.id titleModel.index titleModel on="click" }}"
owl.data('owlCarousel').addItem(content);
});
Specifically I want to add another Item to my carousel:
http://owlgraphic.com/owlcarousel/demos/manipulations.html
I'm not sure that the triple-stash, which just includes content without escaping it, will work with an {{action}}. In any case, it looks to me that you'd be better off simply defining the html within an each block and letting Ember handle the content addition.
{{#each model as |titleModel index|}}
<div class=\"item dodgerBlue\">
<h1>{{index}}</h1>
</div>
{{action "owlItemClicked" titleModel.id titleModel.index titleModel on="click" }}
{{/each}}
I noticed you have a titleModel.index property, so maybe you don't need the index in each block and can use the model's property instead.
That would be the Ember way to do it. However, it looks like this OwlCarousel widget wants to have the html passed directly. But there's also a reinit method, so maybe that would be sufficient to tell it that new content has been added through Ember. Something like the following:
carouselOptions: {
// ...
},
didInsertElement: function() {
Ember.run.scheduleOnce('afterRender', this, function() {
var $c = Ember.$('#the-carousel');
if ($c.length) {
$c.owlCarousel( this.get('carouselOptions') );
}
});
},
actions: {
addCarouselItem: function(e){
e.preventDefault();
// Add a new item to your array.
// Not shown because i have no idea of your code.
// Ember will handle DOM insert.
// Then reinit carousel.
$("#the-carousel").data('owlCarousel').reinit( this.get('carouselOptions') );
},
owlItemClicked: function(e) {
// ...
}
}
You can insert dynamically created HTML using the triple-stache in your template.
// controller.js
dynamicHtml: Ember.computed({
get() {
return `<div>Hello World!</div>`;
}
})
...
{{! template.hbs }}
{{{dynamicHtml}}}
I have component called display-me. I can add multiple of that same component to thesame template as shown in the jsfiddle by adding multiple calls to that component like this:
<script type="text/x-handlebars" data-template-name="index">
{{display-me action='add'}}
{{display-me action='add'}}
</script>
However, what I desire, is a situation where I can click a button to add the second entry for the component instead of adding it manually as above because I want use to click and add as many as that want.
I have added this action to my index route but it doesn't work:
App.IndexRoute = Ember.Route.extend({
actions: {
add: function(){
var comp = App.__container__.lookup("component:display-me");
//var comp = App.DisplayMeComponent.create();
//comp.appendTo(".test");
//comp.appendTo('#input');
Ember.$(".test").append($('<div> {{display-me action="add"}} </div>'));
}
}
});
Here is the complete jsfiddle
You need an each loop in your template to produce multiple inputs.
For the each loop to iterate through something, create a range: an array with as much elements as the desired number of inputs:
http://emberjs.jsbin.com/defapo/2/edit?html,js,output
But this makes little sense as all your inputs will bind to the same value.
In order to have different values, create and populate an array of values:
names: ['James'],
actions: {
add: function() {
this.get('names').pushObject('John Doe');
}
}
{{#each names key="#index" as |name|}}
<p> {{input value=name}} </p>
{{/each}}
That's better, but in Ember you'll have a hard time manipulating raw strings. One example is that you need this strange key="#index" thingie.
Instead of raw stings, operate objects:
people: [ Ember.Object.create({name: 'James'}) ],
actions: {
add: function(){
this
.get('people')
.pushObject(Ember.Object.create({name: 'John Doe'}));
}
}
{{#each people as |person|}}
<p> {{input value=person.name}} </p>
{{/each}}
Demo: http://emberjs.jsbin.com/defapo/3/edit?html,js,output
The next step for improvement is to use Ember Data. You're probably collecting names to persist the list of names on the backend, and Ember Data is the way to do it.
Disclaimer: I'm quite new to Ember. Very open to any advice anyone may have.
I have a action in a ArrayController that should set an ObjectController property. How I can access the right context to set that property when creating a new Object?
Here is abbreviated app code show my most recent attempt:
ChatApp.ConversationsController = Ember.ArrayController.extend({
itemController: 'conversation',
actions: {
openChat: function(user_id, profile_id){
if(this.existingChat(profile_id)){
new_chat = this.findBy('profile_id', profile_id).get('firstObject');
}else{
new_chat = this.store.createRecord('conversation', {
profile_id: profile_id,
});
new_chat.save();
}
var flashTargets = this.filterBy('profile_id', profile_id);
flashTargets.setEach('isFlashed', true);
}
},
existingChat: function(profile_id){
return this.filterBy('profile_id', profile_id).get('length') > 0;
}
});
ChatApp.ConversationController = Ember.ObjectController.extend({
isFlashed: false
});
The relevant template code:
{{#each conversation in controller itemController="conversation"}}
<li {{bind-attr class="conversation.isFlashed:flashed "}}>
<h3>Profile: {{conversation.profile}} Conversation: {{conversation.id}}</h3>
other stuff
</li>
{{/each}}
I don't see why you need an object that handles setting a property for all the elements in your list. Have each item take care of itself, this means components time.
Controllers and Views will be deprecated anyway, so you would do something like:
App.IndexRoute = Ember.Route.extend({
model: function() {
return [...];
}
});
App.ConversationComponent = Ember.Component.extend({
isFlashed: false,
actions: {
// handle my own events and properties
}
});
and in your template
{{#each item in model}}
{{conversation content=item}}
{{/each}}
So, whenever you add an item to the model a new component is created and you avoid having to perform the existingChat logic.
ArrayController and ItemController are going to be depreciated. As you are new to Ember I think that it would be better for you not to use them and focus on applying to coming changes.
What I can advice you is to create some kind of proxy object that will handle your additional properties (as isFlashed, but also like isChecked or isActive, etc.). This proxy object (actually an array of proxy objects) can look like this (and be a computed property):
proxiedCollection: Ember.computed.map("yourModelArray", function(item) {
return Object.create({
content: item,
isFlashed: false
});
});
And now, your template can look like:
{{#each conversation in yourModelArray}}
<li {{bind-attr class="conversation.isFlashed:flashed "}}>
<h3>Profile: {{conversation.content.profile}} Conversation: {{conversation.content.id}}</h3>
other stuff
</li>
{{/each}}
Last, but not least you get rid of ArrayController. However, you would not use filterBy method (as it allows only one-level deep, and you would have the array of proxy objects, that each of them handles some properties you filtered by - e.g. id). You can still use explicit forEach and provide a function that handles setting:
this.get("proxiedCollection").forEach((function(_this) {
return function(proxiedItem) {
if (proxiedItem.get("content.profile_id") === profile_id) {
return proxiedItem.set("isFlashed", true);
}
};
})(this));
Is there a way to iterate over a view's context's attributes in EmberJS? I am using Ember-Data (https://github.com/emberjs/data) for ORM.
Lets say I use connectOutlets to register a UserView with a user that has attributes such as email, name, etc. In the connected Handlebars template, is there anyway that I can iterate over those attributes?
I basically need to build a generic view that can be reused with different models...
Ryan is right about the attributes, but it takes some doing to actually get where you're going. My examples here are using the latest RC1 Ember.
Here is an editor template that is model agnostic:
<script type="text/x-handlebars" data-template-name="edit_monster">
{{#if clientId}}
<h1>Edit Monster: {{name}}</h1>
<div>
{{#each metadata}}
<span class="edit-label">{{name}}</span>
<span class="edit-field">
{{view App.AutoTextField typeBinding="type" nameBinding="name" }}
</span>
{{/each}}
</div>
{{else}}
No monster selected.
{{/if}}
</script>
To make that work, we need a couple of pieces of magic-magic. This controller is a good start:
App.EditMonsterController = Em.ObjectController.extend({
metadata: function() {
var vals = [];
var attributeMap = this.get('content.constructor.attributes');
attributeMap.forEach(function(name, value) {
vals.push(value);
});
return vals;
}.property('content')
});
That uses that "attributes" property that Ryan mentioned to provide the metadata that we are feeding into our #each up there in the template!
Now, here is a view that we can use to provide the text input. There's an outer container view that is needed to feed the valueBinding in to the actual textfield.
App.AutoTextField = Ember.ContainerView.extend({
type: null,
name: null,
init: function() {
this._super();
this.createChildView();
},
createChildView: function() {
this.set('currentView', Ember.TextField.create({
valueBinding: 'controller.' + this.get('name'),
type: this.get('type')
}));
}.observes('name', 'type')
});
Here is a fiddle demonstrating the whole crazy thing: http://jsfiddle.net/Malkyne/m4bu6/
The Ember Data objects that represent your models have an attributes property that contains all of the attributes for the given model. This is what Ember Data's toJSON uses to convert your models into Javascript objects.
You can use this attributes property to read a models attributes and then pull those specific attributes out of an instance. Here is an example.
http://jsfiddle.net/BdUyU/1/
Just to reiterate what's going on here. We are reading the attributes from App.User and then pulling the values out of App.ryan and App.steve. Hope this makes sense.
Is there a way to iterate over a view's context's attributes in EmberJS? I am using Ember-Data (https://github.com/emberjs/data) for ORM.
Lets say I use connectOutlets to register a UserView with a user that has attributes such as email, name, etc. In the connected Handlebars template, is there anyway that I can iterate over those attributes?
I basically need to build a generic view that can be reused with different models...
Ryan is right about the attributes, but it takes some doing to actually get where you're going. My examples here are using the latest RC1 Ember.
Here is an editor template that is model agnostic:
<script type="text/x-handlebars" data-template-name="edit_monster">
{{#if clientId}}
<h1>Edit Monster: {{name}}</h1>
<div>
{{#each metadata}}
<span class="edit-label">{{name}}</span>
<span class="edit-field">
{{view App.AutoTextField typeBinding="type" nameBinding="name" }}
</span>
{{/each}}
</div>
{{else}}
No monster selected.
{{/if}}
</script>
To make that work, we need a couple of pieces of magic-magic. This controller is a good start:
App.EditMonsterController = Em.ObjectController.extend({
metadata: function() {
var vals = [];
var attributeMap = this.get('content.constructor.attributes');
attributeMap.forEach(function(name, value) {
vals.push(value);
});
return vals;
}.property('content')
});
That uses that "attributes" property that Ryan mentioned to provide the metadata that we are feeding into our #each up there in the template!
Now, here is a view that we can use to provide the text input. There's an outer container view that is needed to feed the valueBinding in to the actual textfield.
App.AutoTextField = Ember.ContainerView.extend({
type: null,
name: null,
init: function() {
this._super();
this.createChildView();
},
createChildView: function() {
this.set('currentView', Ember.TextField.create({
valueBinding: 'controller.' + this.get('name'),
type: this.get('type')
}));
}.observes('name', 'type')
});
Here is a fiddle demonstrating the whole crazy thing: http://jsfiddle.net/Malkyne/m4bu6/
The Ember Data objects that represent your models have an attributes property that contains all of the attributes for the given model. This is what Ember Data's toJSON uses to convert your models into Javascript objects.
You can use this attributes property to read a models attributes and then pull those specific attributes out of an instance. Here is an example.
http://jsfiddle.net/BdUyU/1/
Just to reiterate what's going on here. We are reading the attributes from App.User and then pulling the values out of App.ryan and App.steve. Hope this makes sense.