How valueBinding works in Ember? - javascript

Honestly i didn't find much helpful guides on emberjs.com about valueBindings.
//login_route.js
App.LoginRoute = Ember.Route.extend({
model: function() {
return Ember.Object.create();
}
});
Basically here i'm creating empty JS object to be model for login template.
//login.hbs
...
{{view Em.TextField valueBinding="email" id="email"}}
{{view Ember.TextField valueBinding="password" type="password" id="password"}}
...
So when user start to type user&pass, behind the scenes, Ember will crate 2 properties named "email" and "password" and 'append' them to context(model) of the current template and properties will be constantly updated till user hit login button ?
I can guess that is correct because i can do simple debug inside template:
{{ email }} (which is equal to {{ controller.email }} which again is equal to {{ controller.model.email }})?

valueBinding essentially is telling Ember to bind the property named password to the internal property in Ember.TextField named value.
additionally you can just bind it to the property, like so
{{view Em.TextField value=email id="email"}}
http://emberjs.jsbin.com/xujugayo/1/edit
Now the reason that
{{ controller.email }} is equal to {{ controller.model.email }} because the controller backing your template is an ObjectController. The ObjectController is a proxy object, meaning it will proxy properties from the model that's backing it, as if they were on the controller.

Related

Handlebars Partial Data with Dot Notation

In Handlebars, I need to overwrite partial data that data is scoped within a JS object.
The index.hbs file renders multiple partials with different data, but the module properties need to be scoped within the global data object. Overwriting partial attributes using dot notation fails to compile.
Index.hbs
<body>
{{> User }}
{{> User user.name="laura" }} // fails to compile - how to overwrite?
</body>
User.hbs
<div>
Name is: {{name}}
Location is: {{location}}
</div>
Index.js
import index from "Index.hbs";
import user_partial from "User.hbs";
data = {
user: {
name: "kevin",
location: "bar"
}
}
Handlebars.registerPartial(user, user_partial);
document.innerHTML = Handlebars.compile(index)(data);
The documentation says that contexts are passed to the partial like {{> myPartial myOtherContext }} and that additional parameters are passed like {{> myPartial parameter=favoriteNumber }}.
Therefore, it seems you could achieve your objective by passing user as the context and the overrides as parameters. Your template would become:
{{> User user }}
{{> User user name="laura" }}
I have created a fiddle for reference.

Knockout templates and binding to view models

I am having some trouble trying to get knockout templates to work.
I want to use a select list that allows a person to select a value which in turns shows the template.
The template needs to have its own viewmodel properties which are different between each.
I have created a jsfiddle to show the whole thing
I have 2 very basic templates however when I try running the page I get an error. The code is not production codes its simple throw away stuff so naming conventions are not perfect :)
Error: Unable to process binding "foreach: function (){return contacts }" Message: Unable to process binding "template: function (){return { name:contactTypeId} }" Message: Unknown template type: 1
The template do exist
<script type="text/html" id="1">
<span> Family Template </span>
<input placeholder="From Mum or Dads side"/>
</script>
<script type="text/html" id="2">
<span> Friend Template </span>
<input placeholder="Where did you meet your friend"/>
</script>
I am trying to select the template via a select
<select class="form-control" data-bind="options: $root.contactTypes,
optionsText: 'type',
optionsValue:'id',
value:contactTypeId,
optionsCaption: 'Please Select...'"></select>
2 questions.
Why can it not find the template when I select it from the dropdown?
How would I bind the template to have its own model to allow me to save properties.
Update
Thanks to Georges answer below I have the template binding working. Turns out you can't use an int as an ID for a template without calling to
I have updated my model
self.contactTypeTemplateModel = ko.computed(function () {
return self.contactTypeId === 2 ? someModelWithWhereDidYouMeet : someOtherModel
});
var someModelWithWhereDidYouMeet = {something:ko.observable()};
var someOtherModel = {something:ko.observable()};
It maybe due to no sleep but I can't get this to work. The console is telling me "something is not defined"
Granted my naming is not good. I have also updated the fiddle
The problem for question #1 seems to be that you're passing in a number where it expects a string. For whatever reason, it's not being automatically coerced. This solves it.
template: { name: contactTypeId().toString() }
Even better, create a computed and add a reasonable prefix.
templateName = ko.computed(function() { return "contact-type-" + contactTypeId() })
As for passing in different models. The template binding supports the data property. Your data property can be a computed based on contactTypeId as well.
So you do your template binding with
template: {name: contactTypeTemplateName(), data: contactTypeTemplateModel() }
Where
self.contactTypeTemplateModel = ko.computed(function() {
return self.contactTypeId() === 2 ? someModelWithWhereDidYouMeet
: someOtherModel })
I should also mention, that unless you reuse these templates independently from each other in lots of places, I wouldn't recommend templates for this. I would just use an if binding.

If I set something in the controller, how do I access it in the view?

I created this in a the company controller.
$scope.$watch('companyName', function () {
console.log($scope.company.name);
});
I wanted to get the company name, how do I call it where the ng-controller matches the controller name in the view?
Would it be something along the lines of this?
<span ng-init="companyName"> {{ companyName() }} </span>
I'm not sure I understand how to get the information from that function in the view. I think I've confused myself.
Yes, your sample seems a bit strange.
Anyway, to bind a $scope property in the view, you use its name.
So in the controller:
$scope.company = { name: "Acme" };
And in the view:
<span> {{ company.name }} </span>
There is no need to create a $watch manually. Angular will create a watch for any property used in the view (for you).
You also, don't have to use ng-init, unless you want to initialize something in the view.
To see the watch in action, simple add a text input next to the span:
<input type="text" ng-model="company.name"/>
If you change the value in the text box, you will see the changes reflected in the span.
Here is a plunker to play with that demonstrates.
I think you have confused yourself!
In the company controller all you need to do is:
$scope.companyName = "ACME";
To access it in the view:
{{ companyName }}
When binding to values, it is assumed to be relative to $scope. In this case (assuming the value you want is the same as what you specify in your $watch), you want to init an item to an object, and it should look something like this:
<span ng-init="company = {name: 'whatever' }">{{company.name}}</span>
For various reasons, it's not recommended to use ng-init except in things like ng-repeat. If it works, it works, but I'd recommend getting used to initializing things in your controller (just add the statement, such as $scope.company = {name: 'whatever'}; somewhere before your controller function ends). One reason is to make it easier to test your controller in unit tests, because it's annoying to make your tests dependent on a particular view).
The simplest was to do it:
controller:
$scope.companyName = "My company";
view:
<span>{{companyName}}</span>
$scope.$watch is just a function to monitor changes in the value of CompanyName, I don't think you need it.

Ember.Select two way binding for edit form

In my ember application I have defined a select element like this in my template:
{{view Ember.Select
content=greetings
optionValuePath="content.id"
optionLabelPath="content.code"
value=selectedGreeting
selection=selectedGreeting
prompt="Please choose"}}
The controller for this page (shared form and controller for create and edit form) looks like this:
greetings: [
{code: "Mr.", id: 1},
{code: "Mrs.", id: 2}
],
selectedGreeting: null,
actions: {
save: function(){
var person = this.get('model');
if (person == null || person.id == undefined)
{
// create mode
var greeting = this.selectedGreeting.id;
// ....
var newPerson = this.get('store').createRecord('person',{
greeting: greeting,
// ..
});
newPerson.save();
this.transitionToRoute('index');
}
else
{
// edit mode
person.set('greeting', this.selectedGreeting.id);
person.save();
}
}
}
This works perfect when creating a new person, but when opening an existing one the select box shows the promt instead of the saved value (do I really have to do this with jQuery?).
Besides I suppose there must be an easier way to update the existing model with the selected values in the else branch, as the other properties get modified automagically.
Any suggestions would be appreciated, the documentation for Ember unfortunately does not help a lot in this case.
The Ember.Select view has quite a few quirks. In fact, if I remember correctly, I saw an issue on Github to just have the whole thing rewritten. For now, I'm assuming that the prompt attribute takes precedence over the selection attribute. To get around that, I would just use a conditional in the template.
{{#if model}}
{{view Ember.Select ... value=selectedGreeting}}
{{else}}
{{view Ember.Select ... prompt='Please Choose'}}
{{/if}}
Also, your second question, if I understand you correctly, you should be able to just use selection=model.greeting to bind the selection value to the model property. And if you're using the template above, you can make it so it only applies to the former and not the latter.
I got it fixed using:
{{view Ember.Select
content=greetings
optionValuePath="content.id"
optionLabelPath="content.code"
value=model.greeting
prompt="Please select"
}}
This works in both ways - loading the data from model and saving the data to the model. The prompt does not interfere with the value from what I experienced.

Passing variables through handlebars partial

I'm currently dealing with handlebars.js in an express.js application. To keep things modular, I split all my templates in partials.
My problem: I couldn't find a way to pass variables through an partial invocation. Let's say I have a partial which looks like this:
<div id=myPartial>
<h1>Headline<h1>
<p>Lorem ipsum</p>
</div>
Let's assume I registered this partial with the name 'myPartial'. In another template I can then say something like:
<section>
{{> myPartial}}
</section>
This works fine, the partial will be rendered as expected and I'm a happy developer. But what I now need, is a way to pass different variables throught this invocation, to check within a partial for example, if a headline is given or not. Something like:
<div id=myPartial>
{{#if headline}}
<h1>{{headline}}</h1>
{{/if}}
<p>Lorem Ipsum</p>
</div>
And the invokation should look something like this:
<section>
{{> myPartial|'headline':'Headline'}}
</section>
or so.
I know, that I'm able to define all the data I need, before I render a template. But I need a way to do it like just explained. Is there a possible way?
Handlebars partials take a second parameter which becomes the context for the partial:
{{> person this}}
In versions v2.0.0 alpha and later, you can also pass a hash of named parameters:
{{> person headline='Headline'}}
You can see the tests for these scenarios: https://github.com/wycats/handlebars.js/blob/ce74c36118ffed1779889d97e6a2a1028ae61510/spec/qunit_spec.js#L456-L462
https://github.com/wycats/handlebars.js/blob/e290ec24f131f89ddf2c6aeb707a4884d41c3c6d/spec/partials.js#L26-L32
Just in case, here is what I did to get partial arguments, kind of. I’ve created a little helper that takes a partial name and a hash of parameters that will be passed to the partial:
Handlebars.registerHelper('render', function(partialId, options) {
var selector = 'script[type="text/x-handlebars-template"]#' + partialId,
source = $(selector).html(),
html = Handlebars.compile(source)(options.hash);
return new Handlebars.SafeString(html);
});
The key thing here is that Handlebars helpers accept a Ruby-like hash of arguments. In the helper code they come as part of the function’s last argument—options— in its hash member. This way you can receive the first argument—the partial name—and get the data after that.
Then, you probably want to return a Handlebars.SafeString from the helper or use “triple‑stash”—{{{— to prevent it from double escaping.
Here is a more or less complete usage scenario:
<script id="text-field" type="text/x-handlebars-template">
<label for="{{id}}">{{label}}</label>
<input type="text" id="{{id}}"/>
</script>
<script id="checkbox-field" type="text/x-handlebars-template">
<label for="{{id}}">{{label}}</label>
<input type="checkbox" id="{{id}}"/>
</script>
<script id="form-template" type="text/x-handlebars-template">
<form>
<h1>{{title}}</h1>
{{ render 'text-field' label="First name" id="author-first-name" }}
{{ render 'text-field' label="Last name" id="author-last-name" }}
{{ render 'text-field' label="Email" id="author-email" }}
{{ render 'checkbox-field' label="Private?" id="private-question" }}
</form>
</script>
Hope this helps …someone. :)
This can also be done in later versions of handlebars using the key=value notation:
{{> mypartial foo='bar' }}
Allowing you to pass specific values to your partial context.
Reference: Context different for partial #182
This is very possible if you write your own helper. We are using a custom $ helper to accomplish this type of interaction (and more):
/*///////////////////////
Adds support for passing arguments to partials. Arguments are merged with
the context for rendering only (non destructive). Use `:token` syntax to
replace parts of the template path. Tokens are replace in order.
USAGE: {{$ 'path.to.partial' context=newContext foo='bar' }}
USAGE: {{$ 'path.:1.:2' replaceOne replaceTwo foo='bar' }}
///////////////////////////////*/
Handlebars.registerHelper('$', function(partial) {
var values, opts, done, value, context;
if (!partial) {
console.error('No partial name given.');
}
values = Array.prototype.slice.call(arguments, 1);
opts = values.pop();
while (!done) {
value = values.pop();
if (value) {
partial = partial.replace(/:[^\.]+/, value);
}
else {
done = true;
}
}
partial = Handlebars.partials[partial];
if (!partial) {
return '';
}
context = _.extend({}, opts.context||this, _.omit(opts, 'context', 'fn', 'inverse'));
return new Handlebars.SafeString( partial(context) );
});
Sounds like you want to do something like this:
{{> person {another: 'attribute'} }}
Yehuda already gave you a way of doing that:
{{> person this}}
But to clarify:
To give your partial its own data, just give it its own model inside the existing model, like so:
{{> person this.childContext}}
In other words, if this is the model you're giving to your template:
var model = {
some : 'attribute'
}
Then add a new object to be given to the partial:
var model = {
some : 'attribute',
childContext : {
'another' : 'attribute' // this goes to the child partial
}
}
childContext becomes the context of the partial like Yehuda said -- in that, it only sees the field another, but it doesn't see (or care about the field some). If you had id in the top level model, and repeat id again in the childContext, that'll work just fine as the partial only sees what's inside childContext.
The accepted answer works great if you just want to use a different context in your partial. However, it doesn't let you reference any of the parent context. To pass in multiple arguments, you need to write your own helper. Here's a working helper for Handlebars 2.0.0 (the other answer works for versions <2.0.0):
Handlebars.registerHelper('renderPartial', function(partialName, options) {
if (!partialName) {
console.error('No partial name given.');
return '';
}
var partial = Handlebars.partials[partialName];
if (!partial) {
console.error('Couldnt find the compiled partial: ' + partialName);
return '';
}
return new Handlebars.SafeString( partial(options.hash) );
});
Then in your template, you can do something like:
{{renderPartial 'myPartialName' foo=this bar=../bar}}
And in your partial, you'll be able to access those values as context like:
<div id={{bar.id}}>{{foo}}</div>
Yes, I was late, but I can add for Assemble users: you can use buil-in "parseJSON" helper http://assemble.io/helpers/helpers-data.html. (Discovered in https://github.com/assemble/assemble/issues/416).
Not sure if this is helpful but here's an example of Handlebars template with dynamic parameters passed to an inline RadioButtons partial and the client(browser) rendering the radio buttons in the container.
For my use it's rendered with Handlebars on the server and lets the client finish it up.
With it a forms tool can provide inline data within Handlebars without helpers.
Note : This example requires jQuery
{{#*inline "RadioButtons"}}
{{name}} Buttons<hr>
<div id="key-{{{name}}}"></div>
<script>
{{{buttons}}}.map((o)=>{
$("#key-{{name}}").append($(''
+'<button class="checkbox">'
+'<input name="{{{name}}}" type="radio" value="'+o.value+'" />'+o.text
+'</button>'
));
});
// A little test script
$("#key-{{{name}}} .checkbox").on("click",function(){
alert($("input",this).val());
});
</script>
{{/inline}}
{{>RadioButtons name="Radio" buttons='[
{value:1,text:"One"},
{value:2,text:"Two"},
{value:3,text:"Three"}]'
}}

Categories