Ember.js: binding to dynamic form inputs - ember.js

I'm trying to figure out the proper way to collect "variable" personalized data on a product. The product has these personalized fields defined:
"personalization": [
{
"id": 234,
"maxlength": "128",
"prompt": "Text Line 1 (12 character limit)",
"required": "1"
},
{
"id": 235,
"maxlength": "128",
"prompt": "Text Line 2 (12 character limit)",
"required": "1"
}
],
Building a small form to collect that input would be fairly straightforward, except that personalized data can be different for each quantity. So if I order 2 of this item, it can have personalization:
FIRST ITEM
Text Line 1: Yarr
Text line 2: Matey
SECOND ITEM
Text Line 1: Swab
Text line 2: The poop deck
So basically the set of personalized fields needs to repeat for each quantity.
I've got the form built using a computed property:
personalizedForm: computed('quantity', function() {
let q = get(this, 'quantity');
let persform = [];
for (let i = 0; i < q; i++) {
persForm.push(get(this, 'model.personalization'));
}
return persForm;
}),
with this template:
{{#each personalizedForm as |quantity index|}}
Item {{add index 1}}
<ul>
{{#each quantity as |set|}}
<li class="label">{{set.prompt}}</li>
<li class="field">{{input value=????}}</li>
{{/each}}
</ul>
{{/each}}
And that shows a form like the image below. So that's great. But I just can't figure out exactly what to bind each form field to, and how. I imagine the "mut" and "get" helpers are the ticket, but I don't even know how to set up the object to save the data to.
Any help is appreciated!

I think I have this somewhat figured out. On the controller there's a property and a computed property that updates it if/when the quantity changes:
persModel: [], // will contain dynamic model
persModelUpdated: computed('quantity', function() {
let persModel = this.get('persModel');
let persPrompts = get(this, 'model.personalization');
// loop thru quantities
for (let i = 0; i < get(this, 'quantity'); i++) {
// if persModel does not have this set of prompts, push in a copy
if (!persModel.objectAt(i)) {
let copySet = Ember.copy(persPrompts, { 'deep': true });
persModel.pushObject(copySet);
}
}
return persModel;
})
In the template the fields are written out dynamically based on the computed property. I'm using the mut and get helpers to update a 'value' field within each set.
{{#each persModelUpdated as |thisQty index|}}
Item {{add index 1}}
<ul>
{{#each thisQty as |persSet|}}
<li>{{persSet.prompt}}</li>
<li>{{input value=(mut (get persSet 'value'))}}</li>
{{/each}}
</ul>
{{/each}}

I'm not sur to understand you want to find a way to get all the value from the dynamic form ?
You can try to serialize your form.
You have to put the balise form before and after your each like so
<form id="my_form">
{{#each personalizedForm as |quantity|}}
Item {{add index 1}}
<ul>
{{#each quantity as |set|}}
<li class="label">{{set.prompt}}</li>
<li class="field">{{input value=????}}</li>
{{/each}}
</ul>
{{/each}}
<form>
and then in your component .js you can get them by doing
const formArray = Ember.$('#my_form').serializeArray();
to get a array or
const form = Ember.$('#my_form').serialize(); to get a object

Related

Dynamically setting select key in Ember within select field

So I have an API that generates forms from a database. The API for the fields returns:
{
"formFields": [
{
"module": 1,
"fieldName": "Global Title",
"fieldPosition": "1",
"fieldType": "select",
"fieldEditable": true,
"dataTable": "message",
"dataKey": "calling_gt",
"dataValue": "id",
"id": 1,
"createdAt": "2015-10-15T13:59:30.764Z",
"updatedAt": "2015-10-15T13:59:30.764Z"
}
]
}
I have multiple Ember components. Essentially an Add Parameter component that loops through the fields related to the component. Below is loading the select component passing in the Model from the database table and the fields to use for the Key->Value:
{{#each model.fields as |field|}}
{{#if (eq field.fieldType "select")}}
{{form/select-field model=field.dataTable label=field.fieldName key=field.dataKey value=field.dataValue selected=1}}
{{/if}}
{{/each}}
The select-field component then generates a select that brings out the Model data from the values provided in the Add Parameter component, like so:
<div class="form-group">
<label for="{{key}}">{{label}}</label>
{{#x-select value=optionValue action="selectOption" class="form-control" id=key}}
{{#each componentModel as |option|}}
{{#x-option value=option.calling_gt}}{{option.calling_gt}}{{/x-option}}
{{/each}}
{{/x-select}}
</div>
But as you can see, in x-option, I'm currently hard-coding the values, where they should be using the key=field.dataKey. But as I'm looping through and my componentModel -> Options don't hold that variable, I'm not sure how to pass it into the x-option value.
My component.js for select-field looks like this, if it helps:
import Ember from 'ember';
export default Ember.Component.extend({
componentModel: function() {
return this.store.findAll(this.get('model'));
}.property(),
actions: {
selectOption(value, component) {
Ember.Logger.debug("Option " + component + " with value " + value + " selected");
this.set('optionValue', value);
}
},
});
Does anyone know how to bring out the data using the key and value passed into the select-field component?
Sounds like you might be able to use the get helper. http://emberjs.com/api/classes/Ember.Templates.helpers.html#method_get
{{#x-option value=(get option key)}}
{{get option key}}
{{/x-option}}

Generating a dynamic sentence from an array with link-to

Say I have an array like:
var values = [
Ember.Object.create({id: 1, name: 'One'}),
Ember.Object.create({id: 2, name: 'Two'}),
Ember.Object.create({id: 3, name: 'Three'})
];
I would like to create a sentence that links to each of the different objects. The output would generate something like:
{{link-to 'One' 'my-route' 1}}, {{link-to 'Two' 'my-route' 2}}, and {{link-to 'Three' 'my-route' 3}}
which would output a sentence, where each of the words links to a different route:
One, Two, and Three
Is it possible to do this dynamically? I know how to create a dynamic list of links if I'm just iterating over the array (see example):
{{#each item in values}}
<li>{{link-to item.name 'my-route' item.id}}</li>
{{/each}}
But this doesn't give me the flexibility to create a comma-separated list with 'and' at the end.
I'd create a computed property to give a bit of extra information. Assuming that values is a property on a controller:
listValues: function() {
return this.get('values').map(function(item, index, array) {
return {
item: item,
isLastItem: index === array.length - 1
};
});
}.property('values.[]')
Then, in your template:
{{#each listValues}}
{{#if isLastItem}}
and <li>{{link-to item 'my-route' item.id}}</li>
{{else}}
{{! Notice the trailing comma }}
<li>{{link-to item 'my-route' item.id}}</li>,
{{/if}}
{{/each}}
It's a bit of a hack, but you can create a computed property which is all your items except for the last item, and another computed property to return the last item.
Then, in your code you #each through all items but the last one, and then do and {{lastItem}}. For example:
{{#each item in allButLast}}
{{link-to item.name 'test' item.id}},
{{/each}}
and {{link-to lastItem.name 'test' lastItem.id}}
Here is a jsbin as an example.

How can I store an array in Ember Data & update each array item in the template?

I have a Todo-App and now I want to add a list of checkboxes after each task.
When I click a checkbox the correct array item is changed but it's not updated in the template. Furthermore the checkboxes are always filled, even though the value when created is set to false.
I created an array attribute in the DS.Model with DS.Transform for that.
Todos.Todo = DS.Model.extend({
title: DS.attr('string'),
quarters: DS.attr('array')
});
Template:
{{#each item in quarters}}
<input type="checkbox" checked=item {{action "checkIndex" _view.contentIndex on="click"}}>
{{/each}}
_
Todos.TodoController = Ember.ObjectController.extend({
actions: {
checkIndex: function (index) {
var quarters = this.get('model.quarters');
var value = quarters[index];
value = value ? false : true;
var strIndex = '' + index;
quarters.set(strIndex, value);
console.log(this.get('model.quarters')[index]); // --> returns updated value
this.get('model').save();
}
....
It seems like I cannot iterate over the array attribute and render each item seperatly while keeping them updated. I searched a lot for solutions but still didn't figure it out. Or is there a better way to do this?
Iterating over array --> item gets not updated in template:
{{#each item in quarters}}
{{item}}
{{/each}}
Array --> get's updated in template:
{{quarters}}
JsFiddle: http://jsfiddle.net/Ld7gf/5/

Where does view logic go in ember.js?

In one of my templates I would like to take an integer from the model and create html from it. I have a Customer model property called starRating with the value 3. From that value I would like to inject this html into the Customer template:
<i style="color: #dddddd" class="icon-star"></i>
<i style="color: #dddddd" class="icon-star"></i>
<i style="color: #dddddd" class="icon-star"></i>
<i style="color: #dddddd" class="icon-star-empty"></i>
<i style="color: #dddddd" class="icon-star-empty"></i>
Where do I put the logic which creates that html? I tried adding a computed property to the view but the whole function definition gets injected in the page as plaintext. Creating a helper/component seems excessive for this tiny snippet that will only be used once in the page.
There might be many solutions to do something like a dynamic rating bar, so here is my try.
StarRatingComponent
Define a new component (see here for more info on components) that holds the logic for the star rating, and the best of all it's reusable. Note also that it's dynamic in terms of how many stars the rating bar will show, the number is defined in the customer model (as you will see below) for simplicity, but could come from everywhere:
App.StarRatingComponent = Ember.Component.extend({
maxStars: 0,
starRating: 0,
stars: [],
didInsertElement: function() {
this.initStars();
this.setStars();
},
initStars: function() {
var stars = [], i = 0;
for(i = 0; i < this.get('maxStars'); i++){
stars.pushObject(Em.Object.create({empty:true}));
}
this.set('stars', stars);
},
setStars: function() {
var counts = [], i = 0;
for(i = 0; i < this.get('starRating'); i++){
this.get('stars').objectAt(i).set('empty', counts[i]);
}
}
});
Pseudo Customer model
I've just defined a pseudo model since I don't know how your's looks like, that holds the information:
App.Customer = DS.Model.extend({
starRating: DS.attr('number'),
maxStarRating: DS.attr('number', {defaultValue: 5})
});
star-rating component template
Now let's backup our rating bar with a template that will render based on how the component is parametrized (more on this below)
<script type="text/x-handlebars" id="components/star-rating">
{{#each star in stars}}
<i style="color: #AA2567" {{bindAttr class=":glyphicon star.empty:glyphicon-star-empty:glyphicon-star"}}></i>
{{/each}}
</script>
Implementation
Now having everything setup, the actual implementation is fairly simple, with this line:
{{star-rating starRating=customer.starRating maxStars=customer.maxStarRating}}
we render out component providing the rating value starRating and how many stars the dynamic bar should render with maxStars, as you will see in the demo we use the information randomly generated (for simplicity) in our models:
...
{{#each customer in model}}
<li>Rating: {{customer.starRating}}
{{star-rating starRating=customer.starRating maxStars=customer.maxStarRating}}</li>
{{/each}}
...
Maybe this is not the solution you where after, but I guess it get's you in the right direction on how you could do it.
See here for a working demo.
Hope it helps.

How to remove an object from the controller when an Ember.Checkbox is checked

Currently I'm playing with the latest ember.js release and I'm building a simple "add username / remove username" hello world app. So far I can add a user (with the controller method below). I also have a checkbox in the html that when clicked should remove the user but ... right now I can only get the bool value of the checkbox to pass off. Instead I need the username to look it up and remove it from the controller content.
How can I re-do the html / view code below so that I can pass off the actual username instead of the bool value?
Thank you in advance!
PersonApp.ModifyPersonCheckbox = Em.Checkbox.extend({
change: function(event) {
PersonApp.personController.removePerson(this.$().val());
},
});
PersonApp.personController = Em.ArrayProxy.create({
content: [],
createPerson: function(username) {
var person = PersonApp.Person.create({ username: username });
this.pushObject(person);
},
removePerson: function(username) {
person = this.content.findProperty('username', username);
this.removeObject(person);
}
});
the basic html below shows how my checkedBinding is wired up
<ul>
{{#each PersonApp.personController}}
<li {{bindAttr class="isActive"}}>
<label>
{{view PersonApp.ModifyPersonCheckbox checkedBinding="isActive"}}
{{username}}
</label>
</li>
{{/each}}
</ul>
You will need to set the content on the view showing the checkbox so that when the event is triggered, the context is passed. I believe this will work:
{{view PersonApp.ModifyPersonCheckbox contentBinding="parentView.content" checkedBinding="isActive"}}
Then, the event variable in the change function will have a context variable containing the record associated with that checkbox. Then you won't even need to search for it in the controller. You can also just bind the username, but this way is cleaner.
The final solution looks like the below (notice the contentBinding="this" addition to the markup)
PersonApp.ModifyPersonCheckbox = Em.Checkbox.extend({
content: null,
change: function(event) {
PersonApp.personController.removePerson(this.content);
},
});
PersonApp.personController = Em.ArrayProxy.create({
content: [],
createPerson: function(username) {
var person = PersonApp.Person.create({ username: username });
this.pushObject(person);
},
removePerson: function(person) {
this.removeObject(person);
}
});
<ul>
{{#each PersonApp.personController}}
<li {{bindAttr class="isActive"}}>
<label>
{{view PersonApp.ModifyPersonCheckbox contentBinding="this" checkedBinding="isActive"}}
{{username}}
</label>
</li>
{{/each}}
</ul>