emberjs-2.0 dynamically add more component on click - ember.js

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.

Related

How to dynamically apply classes to objects in ember-cli?

I'm creating an app that has a listing of items and a series of filter buttons at the top. As the user applies different filters, I want the buttons to change style using CSS classes to show them as enabled/disabled.
I want to be able to write something like the code below, but it doesn't work.
{{#each category in category_options}}
<button {{action "filterCategory" category}} {{bind-attr class=":btn-small isFiltered(category):btn-active:btn-inactive"}}>{{category}}</button>
{{/each}}
In this example, isFiltered is a computed property on the controller, and it looks at the query parameters to determine whether the specified category has been applied as a filter.
From the reading I've done, it sounds like you can't pass parameters to computed properties. I've come across answers mentioning helpers, bound helpers, and components, but I haven't been able to sort out which one I need, or how I would apply it in this situation.
EDIT:
To clarify the example, imagine I have a series of buttons that filter on various tags:
Filter for: <Cats> <Dogs> <Rabbits> ... # imagine an arbitrary number of these. dozens, maybe
When a user clicks Cats, it triggers filterCategory, which sets the model.category query parameter to ['Cats']. If he then clicks Dogs, model.category becomes ['Cats','Dogs']
Following the latter case, I want the Cats and Dogs buttons to have the class btn-active.
I would like to define isFiltered like so:
isFiltered: function(buttonname) {
if (this.get('model.categories').containsObject(buttonname)) { # pseudocode
return true;
}
else { return false; }
}
Passing buttonname into the function makes it easy to do the comparison for every button and determine if it's in the filter.
If this overall approach is the wrong way to go about things, what's the right way to do it?
1)As component you can do something like below:
in template
{{#each category in category_options}}
{{category-button category=category selectedCategoies=selectedCategories action="filterCategory"}}
{{/each}}
component template
{{category}}
component
export default Ember.Component.extend({
tagName: 'button',
classNames: 'btn-small',
classNameBindings: 'isFiltered:btn-active:btn-inactive',
isFiltered: Ember.computed('category', 'selectedCategories', function(){
return this.get('selectedCategories').contains(this.get('category'));
}),
click: function(){
this.sendAction('action', this.get('category'));
}
})
2)Or you can make your categories as array of objects like so
[
{name: 'category1', isActive: false},
{name: 'category2', isActive: true},
...
]
And then change isActive flag as you need.
In controller:
categoryObjects: Ember.computed('category_options', function(){
return this.get('category_options').map(function(category){
Ember.Object.create({name: category, isActive: false});
})
}),
actions: {
filterCategory: function(category){
category.toggleProperty('isActive');
return
}
}
And in template:
{{#each category in categoryObjects}}
<button {{action "filterCategory" category}} {{bind-attr class=":btn-small category.isActive:btn-active:btn-inactive"}}>{{category.name}}</button>
{{/each}}
I'm not sure how the rest of your code looks like but in general you would use model hook in your route to get query parameter, process it, if needed, and return with your model, let's say you would return model.category, then in your controller you would have something like this:
isFiltered: function() {
var category = this.get('model.category');
// do whatever you want here with category to return true or false
}.property('model.category')
then in .hbs you would be able to write this:
{{#each category in category_options}}
<button {{action "filterCategory" category}} {{bind-attr class=":btn-small isFiltered:btn-active:btn-inactive"}}>{{category}}</button>
{{/each}}
If you were to do this by your approach, you can get it working by making a Computed Property Macro and then looping over the category_options and creating computed properties as isCategory ( isRed, isBlue etc..)
But this won't be the right way to do it, You need to make those button components, which will accept the category_options and model.category and internally decide whether it should be active or not.

Creating a map property and enumerating its properties in a template?

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.

Using Ember.js, how do I get a template to show dynamically all of the properties of a model? [duplicate]

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.

Ember.js arraycontroller call from view

I might be using this all wrong, but:
I've got an ArrayController representing a collection of products. Each product gets rendered and there are several actions a user could take, for example edit the product title or copy the description from a different product.
Question is: how do you interact with the controller for the specific product you're working with? How would the controller know which product was being edited?
I also tried to create an Ember.Select with selectionBinding set to "controller.somevar" but that also failed.
I think the most important thing you need to do, is first move as much logic as you can away from the views, and into controllers.
Another thing that would be useful in your case, is to have an itemController for each product in the list. That way, you can handle item specific logic in this item controller.
I don't have enough information to understand your architecture, so I will make a few assumptions.
Given you have the following ProductsController:
App.ProductsController = Ember.ArrayController.extend();
You need to create a ProductController that will be created to wrap every single product on its own.
App.ProductController = Ember.ObjectController.extend();
You need to modify your template as follows:
{{#each controller itemController="product"}}
<li>name</li>
{{/each}}
Now every product in your list will have its own ProductController, which can handle one product's events and will act as the context for every list item.
Another option:
If you will only be handling one product at a time, you can use routes to describe which product you are working with:
App.Router.map(function() {
this.resource('products', { path: '/products' }, function() {
this.resource('product', { path: '/:product_id' }, function() {
this.route('edit');
});
});
});
And create a controller for editing a product:
App.ProductEditController = Ember.ObjectController.extend();
And your list items would link to that product route:
{{#each controller}}
<li>{{#linkTo "product.edit" this}}name{{/linkTo}}</li>
{{/each}}
If you define itemController on your ProductsController you don't need to specify that detail in your template:
App.ProductsController = Em.ArrayController.extend({
itemController: 'product',
needs: ['suppliers'],
actions: {
add: function() {
// do something to add an item to content collection
}
}
});
App.ProductController = Em.ObjectController.extend({
actions: {
remove: function() {
// do something to remove the item
}
}
});
Use a collection template like this:
<button {{action="add"}}>Add Item</button>
<ul>
{{#each controller}}
<li>{{name}} <button {{action="remove"}}>x</button></li>
{{/each}}
</ul>
The Ember documentation describesitemController here:
You can even define a function lookupItemController which can dynamically decide the item controller (eg based on model type perhaps).
The thing I found when rendering a collection wrapped in an ArrayController within another template/view is the way #each is used. Make sure you use {{#each controller}} as Teddy Zeeny shows otherwise you end up using the content model items and NOT the item controller wrapped items. You may not notice this until you try using actions which are intended to be handled by the item controller or other controller based content decoration.
When I need to nest an entire collection in another view I use the view helper as follows to set the context correctly so that any collection level actions (eg an add item button action) get handled by the array controller and not by the main controller setup by the route.
So in my products template I would do something like this to list the nested suppliers (assuming your route for 'product' has properly the 'suppliers' controller):
{{view controller=controllers.suppliers templateName="products/suppliers"}}
The suppliers template just follows the same pattern as the template I show above.

Iterating over a model's attributes in EmberJS handlebars template

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.