G'day all,
I'm trying to wrap a component with another to provide a simplified editing wrapper.
The component is to conditionally show either a label or a select component that allows the user to pick the right value.
I want to wrap the power-select component, and pass it's values through to the sub-component, so the page template component reference looks like this:
{{cm-select
title="Country ##"
options=countries
selected=selectedCountry
searchField="name"
action="selectCountry"
}}
"countries" is an array of country objects, and selectedCountry is one of those country objects.
The component template has the following:
<td>{{title}}</td>
<td>
{{#if edit}}
{{#power-select
options=options
selected=selected
searchField=searchField
as |object|}}
{{object.name}}
{{/power-select}}
{{else}}
<small>{{modelElement}}</small>
{{/if}}
</td>
Unfortunately the power-select component renders with an empty list of options.
I thought wrapping those parameters in handlebars might do the trick, but it seems that handlebars in handlebars isn't a valid syntax.
Does anyone have any ideas?
Thanks,
Andy
That should work, I created a twiddle for you, demonstrating your use case. You'll see I updated the your cm-select template to this:
{{title}} |
<button {{action 'toggleEdit'}}>Toggle Edit</button>
<br/>
{{#if edit}}
Search for a Item via {{searchField}}
<br/>
{{power-select
options=options
selected=selected
searchField=searchField
onSelect=(action "itemSelected")
}}
{{else}}
{{search-list
options=options
searchField=searchField
onSelect=(action "itemSelected")
}}
{{/if}}
Where you iterated over options for power-select in the cm-select component, I moved that down into the power-select template. It's better to try and encapsulate some functionality there, instead of having everything in cm-select. I wasn't sure what you had in mind with {{modelElement}} so I just demonstrate what it would look like, using two different components in cm-select.
Related
I've got a Template which defines an <div id="sidemenu"><ul>...</ul></div>.It should has some <li>-Elements which represents general available-Menu-Items. On Sub-routes I want to add some additional menu-entries which only make sense on that routes. But since the div where to put the li's into is defined in the parent-route-template, I don't know how I should implement this.
My first theoretical approach was to call a controller function which returns the necessary li's. I could override this function in each sub-controller but I don't know if this is good practice.
However I want to do it in a ember-vanilla way if possible (someone told me to use a plugin called "wormhole" or something)
I would make the dependent portions of the menu a named outlet, then render into the outlet from each route's renderTemplate hook.
{{! components/sidebar/template.hbs }}
<div id="sidemenu">
<ul>
<li>Generally available menu item 1</li>
{{outlet name='nav'}}
^^^^^^^^^^^^^^^^^^^^^
</ul>
</div>
// thing1/route.js
renderTemplate() {
this.render('thing1-nav-template', {into: 'nav'});
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
this._super(...arguments);
}
You could use {{ember-wormhole}} I suppose--I don't know that module. It certainly does not fall into the category of "ember vanilla" if that's one of your criteria. Essentially, it would allow you to write the thing1-nav-template contents directly in your route template instead of another template. I have no idea how stable it is, so personally I'd do it the old-fashioned way using outlets; that's what they're there for.
You can use https://github.com/yapplabs/ember-wormhole to render content from one template into an HTML element identified by an ID.
Menu Template:
<ul>
<li>General stuff</li>
</ul>
<ul id='foobar' />
Subroute Template:
{{#ember-wormhole to="foobar"}}
<li>Subroute stuff</li>
{{/ember-wormhole}}
Is it possible to dynamically render content in ember directly from the template?
i.e., using 4 links that are bound to 4 different template names:
v1
v2
v3
v4
{{render view.view_to_render generic_controller}}
or are there more efficient ways to achieve this?
From the original post, I guess that you would like to render dynamically an actual view, rather than a partial template.
The snippet
{{render view.view_to_render generic_controller}}
does not work (in my experience), because ember tries to look for a view named 'view.view_to_render', rather than interpreting it as a variable to read the view from.
The solution I use is to have a custom helper:
Ember.Handlebars.registerBoundHelper( 'renderBoundView', function ( panel ) {
var args = Array.prototype.slice.call(arguments, 1)
Array.prototype.splice.call(args, 0,0,panel.view, 'panel.model')
// Call the render helper
return Ember.Handlebars.helpers.render.apply(this, args)
})
This helper extracts the view name from the variable 'view' in the passed object, and then passes that name to the standard render helper. Then using
{{renderBoundView panel}}
where panel has properties 'view' with the name of the view and 'model' containing the (resolved) model does the trick.
Of course you could also interpret the object passed as a variable name to get from the current context (which is also one of the arguments passed to the helper).
The entire purpose of Ember is to dynamically render content.
If you want to render particular "views" that are driven from data, that's pretty easy in Ember. Ember calls these partial templates "partials", appropriately-enough :)
Say you have an attribute called partialToRender set in your controller for the template that you're doing your "generic rendering" on. Say it's bound to a set of buttons which are bound to actions which each change the value of partialToRender. Something like this:
<button {{action changePartialToRender 'hello'}}>Change to Hello</button>
<button {{action changePartialToRender 'goodbye'}}>Change to Goodbye</button>
<button {{action changePartialToRender 'yes'}}>Change to Yes</button>
<button {{action changePartialToRender 'no'}}>Change to No</button>
and then in your controller you'd have an action something like this:
App.IndexController = Em.ObjectController.extend({
partialToRender: null,
actions: [
changePartialToRender: function(newValue) {
this.set('partialToRender', newValue);
}
]
});
That'd mean whenever the user clicked on one of your buttons, the value of partialToRender would be changing. Sweet, right? :)
So now all that we need to do is hook up our bit of template code that renders the partial. A partial is just another template, but it's part of a page rather than a full one... some bit of different content in each case, to render into our initial template...
So, we revisit the template like this:
<button {{action changePartialToRender 'hello'}}>Change to Hello</button>
<button {{action changePartialToRender 'goodbye'}}>Change to Goodbye</button>
<button {{action changePartialToRender 'yes'}}>Change to Yes</button>
<button {{action changePartialToRender 'no'}}>Change to No</button>
{{#if partialToRender}}
{{partial partialToRender}}
{{/if}}
Note I'm just wrapping the partial in an if statement to make sure it doesn't render if it's not set.
Also note that I haven't specified the partials here for you. I've just kind of whet your appetite. If you're really interested in this, I suggest watching the Ember video on the getting started guide in the ember site. It's a bit rambling, but it shows off some of Ember's powerful features, or possibly go through the guides / tutorial over at the Ember main site.
Hope that answers your question :)
I see that ember has a very nice mechanism for wrapping content in a component using the {{yield}} mechanism documented here.
So, to use the example in the documentation, I can have a blog-post component template defined like so:
<script type="text/x-handlebars" id="components/blog-post">
<h1>{{title}}</h1>
<div class="body">{{yield}}</div>
</script>
I can then embed blog-post into any other template using the form:
{{#blog-post title=title}}
<p class="author">by {{author}}</p>
{{body}}
{{/blog-post}}
My question is, can I specify two different {{yield}} outlets in the components template?
Something like this is possible via Named Outlets in Ember.Route#renderTemplate like so:
Handlebars:
<div class="toolbar">{{outlet toolbar}}</div>
<div class="sidebar">{{outlet sidebar}}</div>
JavaScript:
App.PostsRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({ outlet: 'sidebar' });
}
});
I'm not sure I can take this path for a component which will not know what route's template would be rendering it.
EDIT 1:
For the sake of clarity, I'm trying to implement the Android Swipe for Action Pattern as an Ember component.
So, I'd like users of this component to be able to specify two different templates:
A template for the normal list item, and
A template for the actions that are revealed when a swipe on (1) is detected.
I want to make this into a component, because quite a lot of javascript goes into handling the touch(start/move/end) events, while still managing smooth touch based scrolling of the list. Users would supply the two templates and this component would manage handling of touch events and necessary animations.
I've managed to get the component working in the block form, where the block's contents are treated like (1). The second template (2) is specified through a parameter (actionPartial below) which is the name of a partial template for the actions:
Component Handlebars Template: sfa-item.handlebars
<div {{bind-attr class=":sfa-item-actions shouldRevealActions:show" }}>
{{partial actionPartial}}
</div>
<div {{bind-attr class=":sfa-item-details isDragging:dragging shouldRevealActions:moveout"}}>
{{yield}}
</div>
Calling Handlebars Template:
{{#each response in controller}}
<div class="list-group-item sf-mr-item">
{{#sfa-item actionPartial="mr-item-action"}}
<h5>{{response.name}}</h5>
{{/sfa-item}}
</div>
{{/each}}
Where the mr-item-action handlebars is defined like so:
mr-item-action.handlebars:
<div class="sf-mr-item-action">
<button class="btn btn-lg btn-primary" {{action 'sfaClickedAction'}}>Edit</button>
<button class="btn btn-lg btn-primary">Delete</button>
</div>
Problem is, actions from the user supplied partial, sfaClickedAction above, are not bubbled up from the component. A fact which is mentioned in the docs in para 4.
So, now I do not know how a user could capture actions that he defined in the supplied actions template. A component cannot catch those actions because it doesn't know about them either.
EDIT 2
I sprung a follow up question here
This blog post describes the most elegant solution for Ember 1.10+: https://coderwall.com/p/qkk2zq/components-with-structured-markup-in-ember-js-v1-10
In your component you pass yield names into {{yield}}s:
<header>
{{yield "header"}}
</header>
<div class="body">
{{yield "body"}}
</div>
<footer>
{{yield "footer"}}
</footer>
When you invoke your component, you accept the yield name as a block param... and use an esleif chain!
{{#my-comp as |section|}}
{{#if (eq section "header")}}
My header
{{else if (eq section "body")}}
My body
{{else if (eq section "footer")}}
My footer
{{/if}}
{{/my-comp}}
PS eq is a subexpression helper from the must-have ember-truth-helpers addon.
PPS Relevant RFC: proposal, discussion.
Since it is not possible to have two {{yield}} helpers within one component (how would the component know where one {{yield}}'s markup stops and the next one begins?) you may be able to approach this problem from a different direction.
Consider the pattern of nested components. Browsers do this already with great success. Take, for example, the <ul> and <li> components. A <ul> wants to take many bits of markup and render each one like a member of a list. In order to accomplish this, it forces you to separate your itemized markup into <li> tags. There are many other examples of this. <table>, <tbody>, <tr>, <td> is another good case.
I think you may have stumbled upon a case where you can implement this pattern. For example:
{{#sfa-item}}
{{#first-thing}}
... some markup
{{/first-thing}}
{{#second-thing}}
... some other markup
{{/second-thing}}
{{/sfa-item}}
Obviously first-thing and second-thing are terrible names for your specialized components that represent the things you'd want to wrap with your first and second templates. You get the idea.
Do be careful since the nested components won't have access to properties within the outer component. You'll have to bind values with both outer and inner components if they are needed in both.
# here is CreditCards controller context
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
{{#is property.symbol 'your_savings'}}
<td class="td">{{yourSavings}}</td>
{{else}}
<td class="td">{{cardProperty this property.symbol}}</td>
{{/is}}
{{/each}}
{{/with}}
I create the table dynamically. All content comes from ArrayController except one that is a computed property that comes from the controller. symbol' field is underscored likeannual_fee' and belongs to CreditCardProperty. Each card category has different set of card properties. Only two categories have properties (category has many card properties) and one record has computed field set to true. That means that the template should look up the corresponding controller.
As symbol (e.g age_to_apply) relates to one of the CreditCard fields (ageToApply) all I could to figure out was to use the cardProperty helper which takes current card in the context (this) and resolves property.symbol, e.g:
camelizedProperty = categoryProperty.camelize()
card.get 'camelizedProperty'
Ideally I'd like to do without the helper and use it somehow like this:
# *** {{property.symbol}} should look up this context
# and become e.g {{annual_fee}} or {{annualFee}} - is it possible?
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
<td class="td">{{property.symbol}}***</td>
{{/each}}
{{/with}}
But the problem is that I don't know how can I render that '{{yourSavings}}' part. The helper you can see comes from swag collection of Handlebars helpers. The helper, unfortunately does not resolve properties so that property.symbol becomes a string.
Here it is:
Handlebars.registerHelper 'is', (value, test, options) ->
if value is test then options.fn(#) else options.inverse(#)
I think it is possible but with the right helper - don't know which one, though.
What I do would like to avoid is to resort to computed property like if isYourSavings.
I am not certain about the context of your code, but it seems like you are looking for registerBoundHelper with a block helper. This isn't supported. You will run into this warning,
Assertion failed: registerBoundHelper-generated helpers do not support use with Handlebars blocks.
An alternative way to do what you are doing is to use a view helper instead. A view helper is like a helper but can render with a custom template.
For instance a CardItemView would be,
App.CardItemView = Em.View.extend({
templateName: 'cardItemTemplate'
});
Em.Handlebars.registerHelper('cardItem', App.CardItemView);
Where the cardItemTemplate is,
<script type='text/x-handlebars' data-template-name='cardItemTemplate'>
{{#if view.property.symbol}}
<td class="td">{{yourSavings}}</td>
{{else}}
<td class="td">{{cardProperty view.property.symbol}}</td>
{{/if}}
</script>
And you could use the helper like so,
{{#with controllers.currentCardCategory}}
{{#each property in cardProperties}}
{{cardItem property=property etc}}
{{/each}}
{{/with}}
You can pass in any number of properties as attributes. These will be bound to the CardItemView. And since it's a view anything a view does, like custom computed properties, can be done in CardItemView.
I'm trying to link a label to an input field using the {{bindAttr}} and the input field's [viewName].elementId. It works on a single entry view, but not when there are several records being displayed: it just links the label to the last input field in the collection. (This used to work in a previous iteration using an older ember library but now it doesnt.) I've created a fiddle but the gist of it is:
{{#each controller}}
<fieldset>
<label {{bindAttr for="view.tbFullName.elementId"}}>Full Name</label>
{{view App.DetailTextField viewName="tbFullName" placeholder="Full Name" valueBinding="fullName" readonly="readonly"}}
</fieldset>
{{/each}}
I thought maybe I could create a collectionView and create a calculated property for viewName which would generate a unique ID for each item in the collection, sort of mentioned in answer to another problem here. But that is getting WAY too complicated - just so that I can have the input field highlight itself if the user clicks on the corresponding label.
Any help appreciated.
Create a wrapper Ember.View around the label and input field. Let's call it App.FieldView:
App.FieldView = Ember.View.extend({
tagName: 'fieldset'
});
Then in your template:
{{#each controller}}
{{#view App.FieldView}}
<label {{bindAttr for="view.tbFullName.elementId"}}>Full Name</label>
{{view App.DetailTextField viewName="tbFullName" placeholder="Full Name" valueBinding="fullName" readonly="readonly"}}
{{/view}}
{{/each}}
Fiddle: http://jsfiddle.net/NQKvy/26/
Panagiotis Panagi, has answered the question correctly. I'll just add why this is happening, ie:- linking to the incorrect view.
The view property inside a template refers to the Ember View wrapping the html markup. This property however has different value depending on the context it is in.
This value is dependent on the view block it placed in. By default the template itself corresponds to a view in this case, ListOfPeopleTemplateView.
So when you are binding to view.tbFullName.elementId, you are actually binding to an {instance of ListOfPeopleTemplateView}.tbFullName.elementId. And when the loop finishes the only tbFullName visible is the last one.
Panagiotis Panagi's solution is to wrap the label inside another view, so the value of view changes to within that block, and hence points to the correct tbFullName
Finally an even easier way to achieve the same result is to wrap the textfield inside the label. Then you do not need the label for binding at all.
<label>Full Name
{{view App.DetailTextField viewName="tbFullName" placeholder="Full Name" valueBinding="fullName" readonly="readonly"}}
</label>
See this jsfiddle
Forms are somewhat tricky I must admit if you want to do things right. But there are is an ember add-on that comes to the rescue, for example easyForm.
Have a look it might helps you solving exact the problems you are facing, like the ones on having unique labels for your form fields etc.
Hope it helps.