Passing variables to handlebars partials in Ember.js - templates

What I want to do should be fairly simple.
I want to pass variables to partials for reusability.
I want to do something like this :
<form {{action login content on="submit"}}>
<fieldset>
{{partial 'components/field-email' label="Email" fieldname="email" size="full"}}
[...]
</fieldset>
</form>
Instead of doing this :
<form {{action login content on="submit"}}>
<fieldset>
<div {{bind-attr class=":field :email size"}}>
<label {{bind-attr for=fieldname}}>{{label}}</label>
{{input type="email" id=fieldname name=fieldname valueBinding="email" placeholder=label}}
</div>
[...]
</fieldset>
</form>
coming from Rails, I expected this to just work, but it seems I can't (don't know how to) pass variables to a partial. I looked at all the ways to "include a template part":
partial
view
render
The thing that worked for me is using a View. But I thinks it's overkill. I just want separate sub-templates for reusability and readability, no context change or specifying a controller needed here.
Edit:
I also tried to use this partial as a component :
{{field-email type="email" id="email" name="email" valueBinding="email" placeholder=label size="full"}}
Which works for everything except the valueBinding.
I guess it's also worth mentioning that I have a route setup with an action that calls login on my AuthController :
App.LoginRoute = Ember.Route.extend
model: -> Ember.Object.create()
setupController: (controller, model) ->
controller.set 'content', model
controller.set "errorMsg", ""
actions:
login: ->
log.info "Logging in..."
#controllerFor("auth").login #
This whole thing works if all the markup is in the login template but fails if I try to break it up with partials, components and such.
There has to be something that I didn't see...

You should use a component in this case.
If you setup your template correctly (components/field-email), you can use on this way:
{{field-email label="Email" fieldname="email" size="full"}}
You could setup the html component properties, if you define the component. Based on your example, it could be:
App.FieldEmailComponent = Ember.Component.extend({
classNames: ['size'],
classNameBindings: ['email', 'field'],
field: null,
email: null
});
Example: http://emberjs.jsbin.com/hisug/1/edit

Got it working, I had to use a component. I had messed up the "value" part.
components/field-email.hbs :
<div {{bind-attr class=":field :email size"}}>
<label {{bind-attr for=fieldname}}>{{label}}</label>
{{input type="email" name=fieldname value=value placeholder=label}}
</div>
login.hbs :
<form {{action login content on="submit"}}>
<fieldset>
{{field-email label="Email" fieldname="email" value=email size="full"}}
[...]
</fieldset>
</form>
What I get from this is that in order for attributes to be used in a component they have to be explicitly set when using the component. Once they are set, they are bound.
In my case, when the input value changes, the associated route property is updated as well which is pretty cool.

Related

Ember and Sails Create/Save Relationship (BelongsTo/HasMany)

I'm having trouble trying to get ember and sails playing nice together when it comes to relationships with belongsTo/hasMany.
I have a simple form:
<form {{action 'addMessage' on='submit'}}>
<div class="form-group">
<label for='name'>Title</label>
{{input value=title class="form-control" required="required"}}
</div>
<div class="form-group">
<label for='location'>User</label>
{{input value=user class="form-control" required="required" value=1}}</div>
<p>
<button class="btn btn-primary btn-block" type="submit">Create Message</button>
</p>
And a controller with the action
actions: {
addMessage: function() {
var newMessage = this.store.createRecord('message', {
title: this.get('title'),
user: this.get('user')
});
newMessage.save().then(function() {
}, function(error) {
console.warn('Save Failed.', error);
});
},
I'm just passing a string, and a value which matches a user id. When I look at what's being passed the title is fine, but the user id is null.
I'm using sails ember blueprints so it should work, but think I might be doing something wrong.
I've uploaded the sample code here if someone can take a look https://github.com/jimmyjamieson/ember-sails-example
On your user input is says value=1 which I think is changing what the controller is writing that property as.
so instead of
{{input value=user class="form-control" required="required" value=1}}
try
{{input value=user class="form-control" required="required"}}
Ok, fixed. I've added a repo for others to look at. It works with ember and ember-data 2.0 https://github.com/jimmyjamieson/ember-sails-relationships-hasmany-belongsto-example

Ember Form Action returns Undefined Input Value

I'm running into a weird issue with Ember.js.
I built out a basic search form like so, with an Ember input field that submits to a form action 'submitSearch':
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
submitSearch: function() {
var searchItem = this.get('searchItem');
this.transitionTo({queryParams: {'q':searchItem}});
}
}
});
<div class="search">
<form {{action "submitSearch" on="submit"}}>
<fieldset>
{{input type="text" class="form-control" value=searchItem}}
<input type="submit" name="submit" id="submit-search" class="btn btn-default" value="Search" />
</fieldset>
</form>
</div>
Any reason why I would be getting a value of 'undefined' when logging out searchItem? I've tried just about everything including creating a model, but I can't get the input to save.
The searchTerm value in your template is referring to your controller's searchTerm property, not a property of your route (by default when referring to a property in your template, it is referring to a property of the corresponding controller).
To get the value in the route, simply do this.get('controller.searchTerm').

Ember.js: conditional input attribute

In Ember's input helper, how can I show/hide attributes based on a condition? For example, let's say I want to show required="required" if isEditable is true and disabled="disabled" otherwise. Currently I have something like this:
{{#if isEditable}}
{{input value=model.name required="required"}}
{{else}}
{{input value=model.name disabled="disabled"}}
{{/if}}
...but it would be nice if I bind the attributes somehow instead.
{{ input type='text' required=required disabled=disabled }} works just fine
Working example here
There are a whole bunch of attributes that you can bind directly and required and disabled are among the pack. See here
Note #blackmind is correct that if you were to do this from scratch, you would need to do some work. Fortunately though, TextSupport already does the work for you... :) See here
From the EmberJS site
By default, view helpers do not accept data attributes. For example
{{#link-to "photos" data-toggle="dropdown"}}Photos{{/link-to}}
{{input type="text" data-toggle="tooltip" data-placement="bottom" title="Name"}}
renders the following HTML:
<a id="ember239" class="ember-view" href="#/photos">Photos</a>
<input id="ember257" class="ember-view ember-text-field" type="text" title="Name">
There are two ways to enable support for data attributes. One way would be to add an attribute binding on the view, e.g. Ember.LinkView or Ember.TextField for the specific attribute:
Ember.LinkView.reopen({
attributeBindings: ['data-toggle']
});
Ember.TextField.reopen({
attributeBindings: ['data-toggle', 'data-placement']
});
Now the same handlebars code above renders the following HTML:
<a id="ember240" class="ember-view" href="#/photos" data-toggle="dropdown">Photos</a>
<input id="ember259" class="ember-view ember-text-field"
type="text" data-toggle="tooltip" data-placement="bottom" title="Name">
Or you can reopen the view
Ember.View.reopen({
init: function() {
this._super();
var self = this;
// bind attributes beginning with 'data-'
Em.keys(this).forEach(function(key) {
if (key.substr(0, 5) === 'data-') {
self.get('attributeBindings').pushObject(key);
}
});
}
});
I typically do the following
<input type="checkbox" {{bind-attr disabled=isAdministrator}}>

How should I be loading a related model into a template with Ember?

I've only just started using Ember and am struggling with a couple of concepts. I used Knockout for a previous application which was great, and I can already see how Ember will help me be more structured and write less code. Although I could go back to Knockout and get this up and running pretty quickly, I really want to persevere with Ember.
The bit I'm struggling with is the right way and place to load related records. My app has a list of systems, and a list of suppliers related to each system. Each system may have one or more suppliers, and vice-versa.
So far I have a route called Systems with a corresponding controller and template. The model hook on the route gets the list of systems from an API (I'm using Ember Data). The systems are displayed as a list via the template, and when the user selects one (via a radio button) I need to load and display the related suppliers. That's the bit I'm not sure about.
I'm not changing the URL here, just displaying an additional list of suppliers underneath the list of systems so adding a new route doesn't sound quite right. Conceptually what would be the best way to do this?
Should I use an action in the controller to load and display the data? That doesn't sound quite right either as the model hooks are all in the routes.
Should I just load the data all up front and filter it (and toggle display) using the controller?
Is there a right, or better, way?
Here's the current code.
System Model
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
suppliers: DS.hasMany('supplier')
});
Supplier Model
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
systems: DS.hasMany('system')
});
Systems Route
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('system');
}
});
Systems Controller
import Ember from 'ember';
export default Ember.ObjectController.extend({
systemSelected: false,
actions: {
selectSystem: function(id) {
this.set('systemSelected', true);
console.log(id);
},
selectSupplier: function(supplier) {
console.log(supplier);
}
}
});
Systems Template
<h1>systems</h1>
<form id="systems">
<fieldset>
<div class="form-group">
<label for="system" class="control-label">Select the system</label>
{{#each}}
<div class="radio">
<label>
<input type="radio" name="system" value="{{id}}" {{action 'selectSystem' id}}>
<span>{{name}}</span>
</label>
</div>
{{/each}}
</div>
{{#if systemSelected}}
<div class="form-group">
<label for="supplier" class="control-label">Select the supplier</label>
{{#each suppliers}}
<div class="radio">
<label>
<input type="radio" name="supplier" value="{{id}}" {{action 'selectSupplier' supplier}}>
<span>{{name}}</span>
</label>
</div>
{{/each}}
</div>
{{/if}}
</fieldset>
</form>
The way to handle this is pretty simple. You just load both related models in your route. You do that using RSVP.
First of all, we'll need a Suppliers Controller, but just a really basic one to store all the Suppliers:
App.SuppliersController = Ember.ArrayController.extend();
In your Systems Route, fetch the models like this and set them to the correct model properties:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
systems: store.find('system'),
suppliers: store.find('suppliers')
});
},
setupController(controller,models) {
this.controllerFor('systems').set('model',models.systems);
this.controllerFor('suppliers').set('model',models.suppliers);
}
});
Then in your Systems Controller, add the Suppliers Controller to the needs:
import Ember from 'ember';
export default Ember.ObjectController.extend({
needs:['systems'],
systemSelected: false,
actions: {
selectSystem: function(id) {
this.set('systemSelected', true);
console.log(id);
},
selectSupplier: function(supplier) {
console.log(supplier);
}
}
});
You might want to use an ArrayController instead of an ObjectController for Systems Controller since it stores more than one object.
Then in your Systems Template, you can access the Systems Controller as a property of your main controller, like this:
<h1>systems</h1>
<form id="systems">
<fieldset>
<div class="form-group">
<label for="system" class="control-label">Select the system</label>
{{#each system in controllers.systems}}
<div class="radio">
<label>
<input type="radio" name="system" value="{{id}}" {{action 'selectSystem' id}}>
<span>{{name}}</span>
</label>
</div>
{{/each}}
</div>
{{#if systemSelected}}
<div class="form-group">
<label for="supplier" class="control-label">Select the supplier</label>
{{#each suppliers}}
<div class="radio">
<label>
<input type="radio" name="supplier" value="{{id}}" {{action 'selectSupplier' supplier}}>
<span>{{name}}</span>
</label>
</div>
{{/each}}
</div>
{{/if}}
</fieldset>
</form>
There ya go. Hope that helps. There are other ways to do this, but this is the most Emberific way to do it that I know.

Ember.js View not properly passing context to router even

I am having a bit of trouble with ember.js.
I have the following code which properly calls the event in the router to create the notebook. However, it will not pass the notebook context, it it undefined. I have been searching for hours to try and find a solution for this.
I found this and this, which are helpful but I'm not completely sure I'm on the right track. Am I missing something?
Route
App.NotebooksNewRoute = Ember.Route.extend
model: ->
App.Notebook.createRecord()
events:
create: (notebook) ->
#persist notebook
Form
{{#with content}}
<form {{action create content on="submit" }} >
<div>
{{view Ember.TextField placeholder="Enter a title" valueBinding="title" }}
</div>
<div>
{{view Ember.TextArea placeholder="Notes" valueBinding="description" }}
</div>
<button type="submit">Create</button>
</form>
{{/with}}
Am I missing something?
Change {{action create content on="submit" }} to {{action create this on="submit" }}
Why?
When you use the handlebars helper {{#with}}, the enclosed block will be rendered in the context of the specified variable. So after {{#with content}}, this is whatever content was and you can access properties like title and description directly instead of content.title and content.description