EmberJS multiple yield helper - templates

I have a custom view that I've created in Ember. I really love the {{yield}} helper to allow me to control the 'bread' of the sandwich. However, what I'd like to do now, is create a 'double decker' sandwich, and have a view with more than 1 yield in it, or at the very least be able to parameterize which template to use in the 2nd yield.
so for example:
layout.hbs
<div>
<div class="header">Header Content</div>
<div class="tab1">
Tab 1 Controls.
<input type="text" id="common1" />
{{yield}}
</div>
<div class="tab2">
Tab 2 Controls.
<input type="text" id="common2" />
{{yield second-template}} or {{template second-template}}
</div>
</div>
app.js
App.MyDoubleDeckerView = Ember.View.extend({
layoutName:"layout',
templateName:"defaultTemplate",
"second-template":"defaultSecond"
});
App.MyExtendedDoubleDecker = App.MyDoubleDeckerView({
templateName:"myTemplate",
"second-template":"mySecondTemplate"
});
is there any way of doing something like this? What I love about the views in ember is the ability to centralize & extend views which allows me to keep the things that are common among all the views in one place...

As of Ember 3.25 you can use so called "named blocks" (see the Passing multiple blocks subsection of https://api.emberjs.com/ember/release/modules/#glimmer%2Fcomponent).
Example component:
<h1>{{yield to="title"}}</h1>
{{yield}}
and then use it like this:
<PersonProfile #person={{this.currentUser}}>
<:title>{{this.currentUser.name}}</:title>
<:default>{{this.currentUser.siganture}}</:default>
</PersonProfile>

I think you should use named outlets for this
http://emberjs.com/guides/routing/rendering-a-template/

Something like this should work:
layout.hbs
<div>
<div class="header">Header Content</div>
<div class="tab1">
Tab 1 Controls.
<input type="text" id="common1" />
{{yield}}
</div>
<div class="tab2">
Tab 2 Controls.
<input type="text" id="common2" />
{{view "view.secondView"}}
</div>
</div>
app.js
App.MyDoubleDeckerView = Ember.View.extend({
layoutName:"layout',
templateName:"defaultTemplate",
secondView: Ember.view.extend({
templateName: "defaultSecond"
})
});
App.MyExtendedDoubleDecker = App.MyDoubleDeckerView({
templateName:"myTemplate",
secondView: Ember.view.extend({
templateName: "mySecondTemplate"
});
});
In other words, invoke a view given by view.secondView from within your template. Then, set the secondView property in your class or subclass.
You could add a bit of syntactic sugar with
App.viewForTemplateName = function(templateName) {
return Ember.View.extend({
templateName: templateName
});
};
Then, in your view definitions above, do
secondView: App.viewForTemplateName('mySecondTemplate')

Related

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}}>

Ember markup breaks bootstrap CSS

I am generating a button group with a template:
<div class="btn-group">
{{#if allowNew}}
{{#link-to newRoute type="button" class="btn btn-primary"}}<i class="fa fa-plus"></i> {{t generic.add}} {{capitalize singularHuman}}{{/link-to}}
{{/if}}
{{#if allowDeleteAll}}
<button type="button" {{action "destroyAllRecords"}} class="btn btn-danger"><i class="fa fa-trash-o fa-lg"></i> {{t generic.delete_all}} {{capitalize pluralHuman}}</button>
{{/if}}
</div>
Ember is placing <script> nodes inside the button group, I imagine to handle binding, view updates or whatever.
The problem is that bootstrap is relying on CSS rules like
.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle)
to finetune the styles. Since the <script> tag is placed before the first <button> or <a> tag, and after the last one, those CSS rules are getting applied where they should not, and instead of having something like this:
I get something like this:
As you can see the stock bootstrap style has rounded corners for the first and last button (the external corners), but my implementation has no such rounded corners.
Is it possible to somehow overcome this problem?
Use an ember view. See https://coderwall.com/p/f1npbg
App.MyView = Ember.CollectionView.extend({
classNames: ['btn-group'],
itemViewClass: Ember.View.extend({
template: Ember.Handlebars.compile("{{view.content.name}}"),
tagName: 'button',
classNames: ['btn']
}),
attributeBindings: ['data-toggle'],
'data-toggle': 'buttons-radio'
});

Ember view with dynamic class names

Considering the following:
Parent template:
{{view App.SomeView id="42" panelClass="default"}}
View template:
<div class="col-md-3 col-sm-6">
<div class="panel panel-{{panelClass}}">
<div class="panel-heading">
<h3 class="panel-title">
{{name}}
</h3>
</div>
<div class="panel-body">
{{description}}
</div>
</div>
</div>
View JS:
App.SomeView = Ember.View.extend({
templateName: 'views/some-view'
});
How can I achieve output HTML where the panel class gets set properly? At the moment it doesn't work because it wants to bind, so it inserts the ember metamorph script tags, instead of just plain text for the panel class.
Also, the template is wrapped in an extra div. How would I modify it so that the ember-view wrapping div is actually the first div in the template (the one with col-md-3 col-sm-6)?
The bind-attr helper exists for that reason. Here's the guide entry.
<div {{bind-attr class=":panel panelClass"}}></div>
Also, not sure if you can use a prefix on panelClass in the template. If might be easier just to use a computed property to add the panel- beforehand.
I'm sorry, I didn't see your second question about the extra div. The guide explains here how to extend the element.
App.SomeView = Ember.View.extend({
classNames: ['col-md-3', 'col-sm-6']
});

Set Handlebar class on the fly

I'd like to update the class of a div according to the user input. A simple input text that need to be validated.
I have to go with a helper but I can't figure it out.
<div class="{{validationClass}}">
<p>{{input type="text" id="titleInput" value=title placeholder="Title"}}</p>
</div>
When there's nothing written in the text field I'd like to surround the box with the red colour, after the used typed a single character I want it to go default.
So, according to bootstrap 2.x I'd need to set the div class to control-group error or control-group success etc.
I've never created a helper so I'm struggling, I don't know how to call it and how to return the desired string to be replaced in {{validationClass}}
Thanks.
You can use the bind-attr helper .
This is a sample:
<script type="text/x-handlebars" data-template-name="index">
<div {{bind-attr class=":control-group validationClass"}}>
<p>{{input type="text" id="titleInput" value=title placeholder="Title"}}</p>
</div>
</script>
App.IndexController = Ember.ObjectController.extend({
title: null,
validationClass: function() {
var title = this.get('title');
return title ? 'success' : 'error';
}.property('title')
});
http://jsfiddle.net/marciojunior/6Kgty/
Use {{bind-attr}} helper
{{!hbs}}
<div {{bind-attr class=":control-group isError:error"}}>
{{input type="text" class="form-control" value=testVal}}
</div>
//Controller
App.ApplicationController = Em.Controller.extend({
testVal: '',
isError: Em.computed.empty('testVal')
});
Sameple Demo

Ember.js - CollectionView, add an attribute to the div wrapper of the child view

I'm trying to combine two ebmer.js examples: Integrating with jQuery UI and the todos example from emberjs.com. I want to have a todo list that is sortable.
Everything went smooth until I got to a point where I wanted to serialize the sortable. For that to work, I need to be able to add an attribute to the sortable items.
this is the template:
{{#collection Todos.TodosListView}}
{{#view Todos.TodoView contentBinding="content" checkedBinding="content.isDone"}}
<label>{{content.title}}</label>
{{/view}}
{{/collection}}
Todos.TodosListView is a CollectionView, similar to the menu in the jQuery UI example. Todos.TodoView is a Checkbox.
This generates the following html:
<div class="ember-view todo-list ui-sortable" id="ember267">
<div class="ember-view" id="ember284">
<input type="checkbox" class="ember-view ember-checkbox todo" id="ember297">
<label>
<script type="text/x-placeholder" id="metamorph-1-start"></script>
something to do
<script type="text/x-placeholder" id="metamorph-1-end"></script>
</label>
</div>
</div>
What I need to be able to do is edit the <div> that wraps the <input>. Assuming the todo's id is 1, I want to add serial=todos_1. I tried to add didInsertElement to TodoView and add an attribute to the parent view, but I didn't have access to the content of the view (the todo itself).
Is this possible?
Thanks for your help.
EDIT:
I found a workaround - adding the ID to the DOM as a hidden element.
The updated template:
{{#collection Todos.TodosListView}}
{{#view Todos.TodoView contentBinding="content" checkedBinding="content.isDone" serial="content.serial"}}
<label>{{content.title}}</label>
<span style="display: none;" class="todo-id">{{content.id}}</span>
{{/view}}
{{/collection}}
Todos.TodoView.didInsertElement:
didInsertElement: function() {
var $div = this.get('parentView').$();
var id = $div.children('.todo-id').text();
$div.attr('serial', 'todos_' + id);
}
Generated html:
<div class="ember-view todo-list ui-sortable" id="ember267">
<div class="ember-view" id="ember284" serial="todos_100">
<input type="checkbox" class="ember-view ember-checkbox todo" id="ember297">
<label>
<script type="text/x-placeholder" id="metamorph-1-start"></script>
something to do
<script type="text/x-placeholder" id="metamorph-1-end"></script>
</label>
<span class="todo-id" style="display: none;">
<script type="text/x-placeholder" id="metamorph-2-start"></script>
100
<script type="text/x-placeholder" id="metamorph-2-end"></script>
</span>
</div>
</div>
I would still like to know if there's a more elegant way of achieving this.
You can create a computed property serial and add this property to the attributeBindings (documented here) of your itemViewClass of Todos.TodosListView, see http://jsfiddle.net/pangratz666/6X4QU/:
Todos.TodosListView = Ember.CollectionView.extend({
itemViewClass: Ember.View.extend({
attributeBindings: ['serial'],
serial: function() {
return 'todos_' + Ember.getPath(this, 'content.id');
}.property('content.id').cacheable()
})
});