Ember markup breaks bootstrap CSS - ember.js

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'
});

Related

Stop Ember helper rendering inside div

I have an Ember helper which is literally defined as this:
<span class="glyphicon glyphicon-info-sign" data-toggle="tooltip" data-placement="right" data-title="{{text}}"></span>
It's used to render a Bootstrap tooltip. It renders the following HTML:
<div id="ember572" class="ember-view">
<span class="glyphicon glyphicon-info-sign" data-toggle="tooltip" data-placement="right"
data-title="<script id='metamorph-35-start' type='text/x-placeholder'></script>
When a project is archived, no new items can be created in it.
<script id='metamorph-35-end' type='text/x-placeholder'></script>"
data-original-title="" title="" aria-describedby="tooltip416856">
</span>
</div>
How can I stop the helper rendering the containing div?
Create a Component with tagName: 'span' instead of <span> in it's template:
Component:
App.TooltipElementComponent = Em.Component.extend({
tagName: 'span',
attributeBindings: ['data-toggle', 'data-placement', 'data-title']
});
Now, you can use following:
{{tooltip-element data-toggle='tooltip' data-placement='right' data-title='When a project is archived, no new items can be created in it.'}}
Which produces following HTML:
<span id="ember257" class="ember-view" data-toggle="tooltip" data-placement="right" data-title="When a project is archived, no new items can be created in it."></span>
Notice you can still use bindings(data-title=text):
{{tooltip-element data-toggle='tooltip' data-placement='right' data-title=text}}
Working demo.

Why didInsertElement hook trigger before elements rendered inside {{#each}} block?

I'm new to Ember, I want to add some query DOM manipulation code to the element in the {{#each}} block. So I google it up and found the solution from this guide:
views/products/index.js
import Spinner from 'appkit/utils/someJqueryCode';
Ember.View.reopen({
didInsertElement : function(){
this._super();
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
afterRenderEvent : function(){
// implement this hook in your own subclasses and run your jQuery logic there
}
});
export default Ember.View.extend({
afterRenderEvent: function() {
Spinner();
}
});
templates/products/index.hbs
<div class='panel panel-default products'>
<div class='panel-heading'>
<h2 class='panel-title'>Our Prodcuts</h2>
</div>
<div class='panel-body'>
<ul class='row'>
{{#each}}
<li class='col-md-4'>
<div class='thumbnail'>
<img {{bind-attr src=url alt=alt}} />
</div>
<div class='caption'>
<h3 class='name-me'>{{name}}</h3>
<p>{{description}}</p>
<div class='row no-gutter'>
<div class='col-xs-3'>
<button class='btn btn-primary'>Buy</button>
</div>
</div>
</div>
</li>
{{/each}}
</li>
</div>
</div>
But I seems after the point when afterRenderEvent() is triggered, all the elements in the {{#each}} block hasn't been rendered to the DOM yet, thus, the jQuery code return undefined
What's the right way to do it?
Your view's didInsertElement hook will fire as soon as the application route is rendered, which happens before the index route. You might think that putting it in the index.js file will work, but it's going to just extend the default application view behavior.
You need to create a more focused view that lives within your index.hbs file. One that is only concerned with your spinner jQuery doohickey. That, and an each/else conditional could work nicely here. For example:
{{#each}}
{{#view App.SpinnerDoohickeyView}}
<li class='col-md-4'>
<div class='thumbnail'>
<img {{bind-attr src=url alt=alt}} />
</div>
<div class='caption'>
<h3 class='name-me'>{{name}}</h3>
<p>{{description}}</p>
<div class='row no-gutter'>
<div class='col-xs-3'>
<button class='btn btn-primary'>Buy</button>
</div>
</div>
</div>
</li>
{{/view}}
{{else}}
<li>Empty collection!</li>
{{/each}}
Notice that I've wrapped each list item in its own view. You could wrap the whole ul if you wanted... this is just an example. The idea is that you are only creating views when you have a model.
And now you can define the view, and simply use the didInsertElement hook to work with jQuery:
App.SpinnerDoohickeyView = Ember.View.extend({
didInsertElement: function () {
this.$('li').css('background', 'blue');
}
});
If you have a model to render, jQuery should be able to safely access it this way. Good luck!
Here's some further reading and some code from the Ember folks that looks like what I've shown you here: http://emberjs.com/guides/views/handling-events/

Dynamically set properties on a view from Handlebars

I'm trying to DRY up my templates by creating views for common layout elements. I have the following views defined
App.SplitListView = Em.View.extend({
tagName: 'div',
classNames: [ 'panel', 'panel-default' ]
});
App.SplitListHeaderView = Em.View.extend({
classNames: [ 'panel-heading' ],
templateName: 'split-list-header-view-layout'
});
The template for the SplitListView is a simple {{yield}}
The template for the SplitListHeaderView is
<span class="panel-title">{{view.headerText}}</span>
{{#if view.showCreateButton}}
<span class="pull-right">
<button type="button" class="btn btn-primary btn-sm">
<i class="fa fa-plus fa-lg" style="padding-right: 10px;"></i>{{view.createButtonText}}
</button>
</span>
{{/if}}
Then the template for the submodule:
{{#view App.SplitListView}}
{{view App.SplitListHeaderView headerTextBinding="Sandwiches" showCreateButtonBinding=true createButtonTextBinding="Make me a sandwich!"}}
{{/view}}
The desired end result is that I'd like to use the SplitListView and SplitListHeaderView everywhere in my app that I use that layout and just set the relevant bits of text via the controller. But so far it's just not working. I feel like this should be easy to do and I'm just missing something. I found this question which looks to be along the same lines as my question but the solution did not work for me.
Does anyone have any experience with something like this or am I off my rocker in trying to use views in this manner?
I believe you have three options here:
1) Use a component instead of a view. Components are reusable, self-contained 'modules' that allow you to bind properties from the component's context in exactly the same manner as you are trying with your view.
2) If the only thing you're reusing between the current non-DRY templates is the html/handlebars you should use a {{partial}} instead of a view because that doesn't create any scope within the view hierarchy and will allow you to bind the handlebars in the partial directly to route's view/controller properties without specifying additional property bindings or scope.
3) Use a Em.Handlebars.helper to accept three arguments (headerText, buttonText, and showCreateButton) and return a new Handlebars.SafeString('some html string'); or something along those lines.
A solution
If I were you, I would utilize methods 2 and 3 together as follows:
First, use a helper (I'm using a helper with a globally accessible method) instead of App.SplitListView to wrap some html around the buffer (i.e. content) inside of the opening and closing handlebars:
// Handy method you can use across many different areas of the app
// to wrap content in simple html
Utils.wrapBuffer = function(open, close, options, context) {
options.data.buffer.push('\n' + open);
if (options.fn(context)) {
options.data.buffer.push('\n' + options.fn(context));
}
options.data.buffer.push('\n' + close);
}
Then the actual helper
App.Handlebars.helper('split-list', function(options) {
var open = '<div class="panel panel-default">;
var close = '</div>';
Utils.wrapBuffer(open, close, options, this);
});
Now, your template will look like:
{{#split-list}}
// This is where our content will go
{{/split-list}}
This has the advantage of wrapping what is inbetween the handlebars opening and closing tags with html without adding or changing scope. Thus, property bindings will work seamlessly.
Now, replace App.SplitListheaderView with a component set up in a similar manner:
App.SplitListHeaderComponent = Em.Component.extend({
classNames: ['panel-heading'],
headerText: null,
createButtonText: null,
showCreateButton: false,
});
You layout (components use layouts, not templates) will be located at templates/components/split-list-header.hbs. it will look as follows:
<span class="panel-title">{{headerText}}</span>
{{#if showCreateButton}}
<span class="pull-right">
<button type="button" class="btn btn-primary btn-sm">
<i class="fa fa-plus fa-lg" style="padding-right: 10px;"></i>{{createButtonText}}
</button>
</span>
{{/if}}
Note, that properties are something not view.something and each property is declared allowing it to be overwritten in the handlebars helper. Now your submodule's template will look like:
{{#split-list}}
{{split-list-header
headerText='Sandwiches'
showCreateButton=true
createButtonText='Make me a sandwich!'
}}
{{/split-list}}
If you wanted, you could bing these properties to a property on your controller or view instead of writing them in the template every time.
Another Improvement
You could go one step further and scrap the wrapping helper all together because it's not doing anything except adding HTML. In this case, the component would look like {{split-list headerText=blah...}} and would have the following template:
<div class="panel-heading">
<span class="panel-title">{{view.headerText}}</span>
{{#if view.showCreateButton}}
<span class="pull-right">
<button type="button" class="btn btn-primary btn-sm">
<i class="fa fa-plus fa-lg" style="padding-right: 10px;"></i>{{view.createButtonText}}
</button>
</span>
{{/if}}
</div>

EmberJS multiple yield helper

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')

Ember.js: Toggle Nested Views

I have a header with some login/signup forms that popup when you click the respective buttons.
While it was working fine using just jQuery, I've now started to integrate Ember into the application and I'm running into some trouble with some simple toggle functionality.
Here's the basic HTML markup:
<header>
<h1>Page Title<h1>
<nav>
<a id="toggles-login" class="button {{active_class_binding}}">Login</a>
<a id="toggles-signup" class="button {{active_class_binding}}">Signup</a>
</nav>
<div id="popup-forms">
<div id="login-form"></div>
<div id="signup-form"></div>
</div>
<header>
I'm completely new to Ember and I really have no idea how to set this up. The only thing I want is to be able to set the popup forms up as Ember.View objects and toggle them with some action helpers.
I really am lost on this one.
A simple solution would be to trigger simple actions to show the respective forms:
<a id="toggles-login" class="button {{active_class_binding}}" {{action showLoginForm target="view"}}>Login</a>
<a id="toggles-signup" class="button {{active_class_binding}}" {{action showSignupForm target="view"}}>Signup</a>
The corresponding view would have to implement both actions:
App.YourView = Ember.View.extend({
showLoginForm : function(){
this.$("#login-form").toggle();
},
showSignupForm : function(){
this.$("#signup-form").toggle();
}
});