How to display html button based on index of a {{each}} loop? - ember.js

How to display html button based loops index?
Hi
I have a this template:
<ul>
{{#each answers as |answer key|~}}
<li>
<label>Answer text: <input type="text" name="answer-definition-label[]"
value={{answer}}/></label>
<input type="radio"/> {{view.showDelete}}
</li>
{{~else~}}
<li>
<label>Answer text: <input type="text" name="answer-definition-label[]"/></label>
<input type="radio" checked/>
</li>
<li>
<label>Answer text: <input type="text" name="answer-definition-label[]"/></label>
<input type="radio"/>
</li>
{{~/each}}
</ul>
<button {{action "addAnswerRow"}} type="button">Dodaj</button>
Class app/views/forms/elements/ix-selectable-fields.js
import Ember from "ember";
var ShowDelete = Ember.Component.extend({
template: Ember.HTMLBars.compile('<button style="color:red;">X</button>'),
init :function(){
console.log(this);
console.log(arguments);
}
});
export default Ember.View.extend({
content : undefined,
template: function() {
if (this.get("content") instanceof Object) {
return this.get("content");
} else {
return Ember.HTMLBars.compile('Please choose answer');
}
}.property(),
showDelete : ShowDelete
});
In the {{each}}{{else}} part I want to display X to allow user to delete rows (li's).
When rows come out of store I would like to make delete button appear at the key>1 (for key==0 and key==1 there is no button).
I tried {{if key>1 'test'}} but it throws "Build error",
then I tried {{view.showDelete key}} but I get
"Uncaught Error: Assertion Failed: A helper named 'view.showDelete' could not be found".
Resolution for Ember-cli (ember.js 1.12.1) based on #Kit-Sunde answer:
//app/helpers/boolean/ix-gt.js
import Ember from "ember";
export default Ember.Handlebars.makeBoundHelper(function (a, b) {
return a > b;
});

Create helpers/gt.js with:
export default (a, b) => a > b;
Then in your template:
{{#if (gt key 1)}}
{{view.showDelete}}
{{/if}}

Related

How to share settings across an application?

I have a main page listing some categories / subcategories. Whenever a subcategory is clicked, the action openSubcategory is triggered:
// routes/application.js
import Ember from 'ember';
export default Ember.Route.extend({
userSelections: Ember.inject.service('user-selections'),
actions: {
openSubcategory: function(categoryId, subcategoryId) {
var userSelections = this.get('userSelections');
userSelections.set('category', categoryId);
userSelections.set('subcategory', subcategoryId);
this.transitionTo('filter-categories');
},
}
});
To pass the selections to the corresponding controller, I am using a service:
// services/user-selections.js
import Ember from 'ember';
export default Ember.Service.extend({
category: null,
subcategory: null,
init() {
this._super(...arguments);
this.set('category', null);
this.set('subcategory', null);
},
});
Which is evaluated in:
// controllers/filter-categories.js
import Ember from 'ember';
export default Ember.Controller.extend({
userSelections: Ember.inject.service('user-selections'),
init() {
this._super(...arguments);
this.get('userSelections'); // We need to get it so that we can observe it?
// We can not declare the observers, because we need to make sure userSelections is first read
this.addObserver('userSelections.category', function() {
Ember.run.once(this, 'refreshProducts');
});
this.addObserver('userSelections.subcategory', function() {
Ember.run.once(this, 'refreshProducts');
});
},
actions: {
changedCategory: function(selectedCategory) {
this.set('selectedCategory', selectedCategory);
this.get('userSelections').set('category', selectedCategory.value);
},
changedSubcategory: function(selectedSubcategory) {
this.set('selectedSubcategory', selectedSubcategory);
this.get('userSelections').set('subcategory', selectedSubcategory.value);
},
},
refreshProducts: function() {
var userSelections = this.get('userSelections'),
category = userSelections.get('category'),
subcategory = userSelections.get('subcategory');
var products = this.store.filter('product', function(product) {
var catId = parseInt(product.get('category').get('id')),
subcatId = parseInt(product.get('subcategory').get('id'));
if (category && catId !== category) {
return false;
}
if (subcategory && subcatId !== subcategory) {
return false;
}
return true;
});
this.set('model', products);
},
});
Observing the userSelections (after some hacking, as seen in the comments) works: the actions are properly triggering the refreshProducts method. But it seems the method is not triggered when coming from the application route, probably because the controllers/filter-categories is not yet initialized.
(*) According to the documentation there are lots "issues" observing services.
Observers and asynchrony
Observers and object initialization
Unconsumed Computed Properties Do Not Trigger Observers
"The injected property is lazy; the service will not be instantiated until the property is explicitly called" (link)
As a result, code needs to be written in a difficult to understand way.
Is there a better pattern to share data between routes / controllers than using a service?
EDIT
These are my templates:
// partials/categories.hbs (used on the application.hbs template)
{{#each model.categories as |category| }}
<div class="categories-list row">
<div class="container">
<h3 class="category-name centered">
<span class="bg-left"></span>
<span class="bg-center uppercase">{{category.name}}</span>
<span class="bg-right"></span></h3>
</div>
<div class="category owl-carousel">
{{#each category.subcategories as |subcategory| }}
<div class="category-item">
<a href="{{subcategory.link}}">
<div class="category-icon">
<img src="{{subcategory.image}}">
</div>
<h4 class="capitalize" {{action "openSubcategory" category.id subcategory.id}}>{{subcategory.name}}</h4>
</a>
</div>
{{/each}}
</div>
</div>
{{/each}}
And:
// filter-categories.hbs
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>Our Vault</h2>
<legend>Filter products by category / subcategory</legend>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form class="form-inline portfolio-form">
<div class="form-group col-md-2">
{{#power-select options=optionsCategory selected=selectedCategory onchange=(action "changedCategory") as |category|}}
{{category.text}}
{{/power-select}}
</div>
<div class="form-group col-md-2">
{{#power-select options=optionsSubcategory selected=selectedSubcategory onchange=(action "changedSubcategory") as |subcategory|}}
{{subcategory.text}}
{{/power-select}}
</div>
<div class="form-group col-md-2">
<button type="button" class="btn btn-default" {{action "clearSelections" id}}><i class="fa fa-remove"></i> Clear Filters</button>
</div>
</form>
</div>
</div>
<div class="row">
{{partial "products"}}
</div>
</div>
Is there a reason you're avoiding the use of dynamic segments? If not you can pass the category and subcategory as dynamic segments when using transitionTo('filter-categories'), an added benefit would be that this route will become linkable :)
So for eg. you should define your filter-categories route like this:
// router.js
...
this.route('filter-categories', { path: 'filter-categories/:category_id/:subcategory_id' });
...
Now in your routes/filter-categories.js router you could do:
// routes/filter-categories
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
// you can put your `refreshProducts` logic in here
// (I mean in this router not literally in this method)
// and return the products model or whatever you want
// I will just return an object with the ids for simplicity
return {
categoryId: params.category_id,
subcategoryId: params.subcategory_id
};
}
...
And now in your application route:
// routes/application.js
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
openSubcategory: function(categoryId, subcategoryId) {
this.transitionTo('filter-categories', categoryId, subcategoryId);
}
}
});
edit:
If you don't always have an categoryId and subcategoryId you can use query parameters instead of dynamic segments.

On submit can't get data from checkbox selection

Hi in my apps I have a form with checkbox group, and I can't retrived selected at submit.
Here is some code
The form content from declare.handlebars:
<form class="declare">
<div class="hidden-fields" style="display:none">
{{view Ember.TextField valueBinding="declaration_type" class="form-control half" type="text"}}
</div>
<fieldset>
...
</fieldset>
<fieldset>
<div class="form-group">
<label>Type de support</label>
<p>
{{render 'publication/declaration_support_types' Sampick.supportTypes}}
</p>
</div>
...
</fieldset>
<div class="actions-bottom">
<button {{action "sendDeclaration" content}} class="button button-select"><i class="icon-download"></i> Confirm</button>
</div>
</form>
The handlebars code for the render of publication/declaration_support_types:
{#each }}
<label class="checkbox-inline">
{{input type="checkbox" name="publication_declaration_support_type" checked=isChecked}} {{ description }}
</label>
{{/each}}
Then I have the following controller for the render 'publication/declaration_support_types':
Sampick.PublicationDeclarationSupportTypesController = Ember.ArrayController.extend({
sortProperties: ['description'],
sortAscending: false,
itemController: 'publicationDeclarationSupportType',
selected: Ember.computed.filterBy('[]', 'isChecked', true),
selectedItems: Ember.computed.mapBy('selected', 'description')
});
Sampick.PublicationDeclarationSupportTypeController = Ember.ObjectController.extend({
isChecked: false,
toggle: function() {
this.toggleProperty('isChecked');
}
});
and finaly the route for the previous html
Sampick.PublicationDeclareRoute = Ember.Route.extend({
actions: {
sendDeclaration: function(content) {
var self = this;
if (content.get("prints") == 1) {
self.validateRecipient(content);
} else {
self.submitDeclaration(content);
}
}
}
});
My issue is that in my sendDeclaration action I can't get the selected checkbox from declarationSupportTypes using the selectedItems propertie define in the controller.
Thanks for your helps
Working fiddle: http://emberjs.jsbin.com/legozucega/1/
There was a typo in IndexRoute, action=>actions.

Why do I see an empty product in products.index?

This is how my products.index looks:
When I click on "Create product" link. It sends me to /products/new. I see a form there, but I dont submit it, instead I click on the "Cancel" button. I have an action in my controller that redirects me to the products.index page.
actions: {
cancel: function() {
this.transitionToRoute('products.index');
return false;
}
}
In /products, I see:
Which is an empty product... The DB in the API has no products. I refresh the page, and the empty product goes away. Whats going on here?
The full code:
// app/routes/products/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('product');
}
});
// app/routes/products/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('product');
},
});
// app/controllers/select-addresses/new.js
export default Ember.ObjectController.extend({
actions: {
cancel: function() {
this.transitionToRoute('products.index');
return false;
}
}
});
// app/templates/products/index.hbs
<h1>Products index</h1>
<p>{{#link-to 'products.new'}}Create product{{/link-to}}</p>
<ul>
{{#each}}
<li>
{{#link-to 'products.show' this}}<strong>{{name}}</strong>{{/link-to}}
<br />Description: {{description}}
<br />Amount in cents: {{amountInCents}}
<br />{{link-to 'Edit' 'products.edit' this}} ยท <a href="#" {{action "delete" this}}>Delete</a>
<br /><br />
</li>
{{/each}}
</ul>
// app/templates/products/new.hbs
<h1>Add a new friend</h1>
<form {{action "save" on="submit"}}>
<p>
<label>Name:
{{input value=name}}
</label>
{{#each error in errors.name}}
<br />{{error.message}}
{{/each}}
</p>
<p>
<label>Description:
{{input value=description}}
</label>
{{#each error in errors.description}}
<br />{{error.message}}
{{/each}}
</p>
<p>
<label>Amount in cents:
{{input value=amountInCents}}
</label>
{{#each error in errors.amountInCents}}
<br />{{error.message}}
{{/each}}
</p>
<p>
<label>Status:
{{input value=status}}
</label>
{{#each error in errors.status}}
<br />{{error.message}}
{{/each}}
</p>
<input type="submit" value="Save"/>
<button {{action "cancel"}}>Cancel</button>
</form>
{{outlet}}
My guess (as a non-Ember-specialist):
In your Ember.Route.extend, for the model, you issue a call to store.createRecord. By this you add an empty product to the store. When you cancel the form, you do not remove the dummy product from the store, so it is still there when you load the index view.
Use ember-data-route and then you should do
{{#each product in model}}
{{#unless product.isNew}}
{{product.name}}
{{/unless}}
{{/each}}
If you don't want to use ember-data-route, you can always use resetController and do model.deleteRecord().
// app/routes/products/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('product');
},
resetController: function (controller, isExiting) {
var model = controller.get('model');
if (isExiting && model.get('isNew')) {
model.deleteRecord()
}
}
});
For a more in-depth version see what ember-data-route is doing.
So in the product index routes, I changed return this.store.find('product'); to return this.store.find('product', {});
And the problem went away! I am not sure if this is the right approach, but so far no issues!
// app/routes/products/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
// Before
// return this.store.find('product');
// After
return this.store.find('product', {});
}
});
UPDATE: Doing this.store.unloadAll('product'); before the transition also seems to work. This seems to be a more natural solution. As per this pull request https://github.com/emberjs/data/pull/1714
However, clicking the back button on the browser, still renders the empty product.
So basically:
// app/routes/products/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('product');
}
});
// app/controllers/select-addresses/new.js
export default Ember.ObjectController.extend({
actions: {
cancel: function() {
this.store.unloadAll('product');
this.transitionToRoute('products.index');
return false;
}
}
});
You can use the deactivate route hook to delete the new (not-persisted) record from the store.
// app/routes/products/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('product');
},
deactivate: function () {
var model = this.modelFor('products.new');
if (model.get('isNew')) {
model.destroyRecord();
}
}
});
When you transition away from the route without persisting the record with .save(), it will destroy the record in the store, and therefore won't be rendered in the template.

Using {{render}} to display a form and save a model instance from anywhere in the application

I have a resource (Trip) and its routes - trips.index, trips.edit, trips.new. I would like to put a copy of the form in trips/new into application.hbs template so it appears on every page.
The form in trips/new route works but the one in application.hbs doesn't. I get the following errors when I submit the form:
Uncaught Error: Assertion Failed: Cannot delegate set('name', a) to
the 'content' property of object proxy
: its 'content' is
undefined.
Uncaught Error: Assertion Failed: Cannot delegate set('errorMessage',
You have to fill all the fields) to the 'content' property of object
proxy : its 'content' is
undefined.
I have the following code.
application.hbs:
...
{{render "trips/new"}}
...
{{outlet}}
...
templates/trips/new.hbs:
<form {{action "save" on="submit"}} role="form">
<p class="text-danger">{{errorMessage}}</p>
<div class="form-group">
<label for="">Name</label>
{{input class="form-control" value=name}}
</div>
<div class="form-group">
<label for="">Country</label>
{{input class="form-control" value=country}}
</div>
<div class="form-group">
<label for="">Start Date</label>
{{input class="form-control" value=startDate placeholder="YYYY-MM-DD"}}
</div>
<div class="form-group">
<label for="">End Date</label>
{{input class="form-control" value=endDate placeholder="YYYY-MM-DD"}}
</div>
<input type="submit" value="Save" class="btn btn-primary">
<button {{action "cancel"}} class="btn btn-default">Cancel</button>
</form>
controllers/trips/base.js:
import Ember from 'ember';
export default Ember.ObjectController.extend({
isValid: Ember.computed(
'name',
'country',
function() {
return !Ember.isEmpty(this.get('name')) &&
!Ember.isEmpty(this.get('country'));
}
),
actions: {
save: function() {
if (this.get('isValid')) {
var _this = this;
this.get('model').save().then(function(trip) {
_this.transitionToRoute('trips.show', trip);
});
} else {
this.set('errorMessage', 'You have to fill all the fields');
}
},
cancel: function() {
return true;
}
}
});
controllers/trips/new.js:
import TripsBaseController from './base';
export default TripsBaseController.extend({
actions: {
cancel: function() {
this.transitionToRoute('trips.index');
return false;
}
}
});
routes/trips/new.js:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('trip');
},
actions: {
save: function() {
return true;
},
cancel: function() {
return true;
}
}
});
Any idea how I can solve this? I'm about to try converting it into a View or component but I'm wondering if I can still use {{render}} and I'm missing something simple here.
I'm using ember-cli with ember 1.7.0 and ember-data 1.0.0-beta.10.
I've also added a JS Bin here:
http://jsbin.com/zofive/edit
Here's the answer - I needed to set the model using the Application Route, setupController and controllerFor.
import Ember from 'ember';
export default Ember.Route.extend({
setupController: function() {
this.controllerFor('trips/new').set('model', this.store.createRecord('trip'));
}
});
JS Bin (non-ES6): http://jsbin.com/zofive/13
Thanks to #locks, #abuiles and #xymbol.

Ember - displaying model value in form field

I have a user settings form like so:
<script type="text/x-handlebars" data-template-name="settings">
<form class="form-horizontal user-form" {{action "update" on="submit"}}>
<div>
<label>First Name</label>
{{input type="text" value=firstName placeholder="First Name"}}
{{error.firstName}}
</div>
<div>
<label>Last Name</label>
{{input type="text" value=lastName placeholder="Last Name"}}
{{error.lastName}}
</div>
<div>
<label>Email Address *</label>
{{input type="text" value=email placeholder="Email Address"}}
{{error.email}}
</div>
</form>
</script>
In my route for this page, I define the model:
App.SettingsRoute = Ember.Route.extend({
model: function() {
return this.store.find('user', 1);
}
});
If things are left like this, the form will automatically populate with the values retrieved from the model. However, if I add a controller:
App.SettingsController = Ember.Controller.extend({
actions: {
update: function() {
// Do something
}
}
});
...They won't. So how do I use my model in conjunction with this controller to set the properties?
The way you defined your controller was as a regular Ember.Controller and not an Ember.ObjectController so the controller is not proxying the model. If you modify it to be like this:
App.SettingsController = Ember.ObjectController.extend({
actions: {
update: function() {
// Do something
}
}
});
Then it should still automatically populate with the values from the model.