Getting element by ID in Ember - ember.js

I am running two ember applications. One has the following component:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'a',
click: function() {
Ember.$('#wrapper').toggleClass('toggled');
}
});
and the other one, has this one:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'a',
click: function() {
this.$('#wrapper').toggleClass('toggled');
}
});
What I can't understand here is why in one application I select an element by ID using Ember.$('#wrapper') and in the other using this.$('#wrapper').
What is this about? Ember version?
UPDATE
I'm very puzzled, since both components are the same:
{{#show-menu}}
<i class="fa fa-bars"></i>`
{{/show-menu}}`
They are both hamburger menus used to hide a sidebar div, and the #wrapper is an external element.
Since in both cases the #wrapper are external elements, shouldn't just the first case work #Gaurav and #Kevin Jhangiani?

The difference is in the context of the jquery selector.
Ember.$()
is scoped to the entire document, ie, you can access any element on the page.
In contrast,
this.$()
is scoped to the current component or view, and thus you can only access dom elements that are children.
Generally, you should be using this.$ as it will be more performant (since the search space is only child elements). Ember.$ should be reserved for times when you absolutely need to access an element outside of the current context.

Ember.$('#wrapper') will find an element in the page with the id of wrapper.
this.$('#wrapper') will find an elment within the component with the id of wrapper.
If there is any chance that the component you are defining will ever occur more than once in the page, then you should use neither. Edit the appropriate template so that wrapper is a class, not an id. Then use:
this.$('.wrapper')

Since you are essentially just toggling a class, the more "Ember" way of doing this is having a conditional class on your wrapper and toggle a property on your component:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'a',
classToggle: false,
click: function() {
this.toggleProperty('classToggle');
}
});
Then, on your DOM element you can have a conditional class:
<div id="wrapper" class="{{if toggleClass "toggled"}}">...</div>
or, if you are using an older version of Ember:
<div id="wrapper" {{bind-attr class="toggleClass:toggled"}}>...</div>
This is a bit more reusable as your component doesn't rely on a DOM element (which can get messy if you want to reuse this component ever).

Related

Multiple layouts in Ember 2

I need implement next application structure with 3 routes:
localhost/users
localhost/posts
localhost/settings
'users' and 'posts' routes should have basic layout1 with main navbar.
'settings' route should have another layout2 with second navbar.
How can I implement multiple layouts approach with Ember > 2.12?
Can I set a layout name/path for each route or group of routes?
I can think of two possible recommended approaches to this problem. I've used both in different scenarios. You can either:
Use a component to encapsulate each navbar and then present them accordingly in each template
Set the templateName attribute of each route to use the correct template.
The component approach seems to be the easiest/most used in my experience. It also allows you to have differences within your base route template. e.g.:
users.hbs:
{{layout1}}
<h1>Users<h1>
...
posts.hbs:
{{layout1}}
<h1>Posts</h1>
...
While if you use the templateName approach, you are locked into using the same exact template. So, if you need any customization between any page that uses the same layout, you must use a subroute. e.g.:
routes/users.js:
import Ember from 'ember';
export default Ember.Route.extend({
templateName: 'layout1'
});
routes/posts.js:
import Ember from 'ember';
export default Ember.Route.extend({
templateName: 'layout1'
});
templates/layout1.hbs:
<nav>...</nav>
{{outlet}}
A third possible approach, though I don't necessarily recommend it, is to dynamically replace the navbars in the application.hbs template. The application controller/template have access to a special attribute called currentPath, which you can used to create two computed properties (isLayout1 and isLayout2).
With that, this becomes a viable, though like I said, not necessarily recommended solution:
application.hbs:
{{#if isLayout1}}
<nav>layout 1</nav>
{{else}}
<nav>layout 2</nav>
{{/if}}
{{outlet}}

How do I set a class on a parent element when a route is the first one loaded?

I have an Ember demo app that works fine if the first route loaded is 'index', 'list' or 'list/index', but not if the first route loaded is 'list/show'. Code is at https://github.com/DougReeder/beta-list , demo is running at https://ember-demo.surge.sh To see the problem, set your window narrower than 640px and surf to https://ember-demo.surge.sh/list/5 You'll see the list panel, rather than the detail panel.
The underlying problem is that, when the route is 'list/show', the divs with class 'panelList' and 'panelDetail' should also have the class 'right'.
I can't set this in the template, because panelList and panelDetail are created by the parent 'list' template. If I move panelList and panelDetail to the child templates 'list/index' and 'list/show', then the list gets re-rendered when going from 'list/index' to 'list/show' which would be a terrible performance hit.
Currently, I use the 'didTransition' action to toggle the class 'right'. This is called both then transitioning from 'list/index' to 'list/show', and when 'list/show' is the initial route. Unfortunately, if 'list/show' is the first route, none of the DOM elements exist when 'didTransition' is called.
I can envision two routes to a solution, but don't know how to implement either:
Toggle the class 'right' on some action which happens after DOM elements exist.
Insert conditional code in the 'list' template, which sets the class 'right' on 'panelList' and 'panelDetail' if the actual route is 'list/show'.
Suggestions?
Answer current as of Ember v2.12.0
You can use the link-to helper to render elements other than links, with styles that change based on the route. Utilizing the activeClass, current-when, and tagName properties, you can basically have that element be styled however you want depending on which route you are on. For example, to render your panelList div:
{{#link-to tagName='div' classNames='panelList' activeClass='right' current-when='list/show'}}
More markup
{{/link-to}}
I love a trick with empty component. In didInsertElement and willDestroyElement hooks you can add and remove a css class from parent element or (I like it better) body. Here is a code:
import Ember from 'ember';
export default Ember.Component.extend({
bodyClass: '',
didInsertElement() {
const bodyClass = this.get('bodyClass');
if (bodyClass) {
Ember.$('body').addClass(bodyClass);
}
},
willDestroyElement() {
const bodyClass = this.get('bodyClass');
if (bodyClass) {
Ember.$('body').removeClass(bodyClass);
}
}
});
I use it in template (in my example it's a template of player route) like this
{{body-class bodyClass='player-page-active'}}
To apply classes to parent element, you can use this.$().parent(), but using body is more reliable. Note that this component will create an empty div, but it shouldn't be a problem (can be in rare cases, fix it with classNames and css if needed).
Sukima suggested looking at currentRouteName, and I thus found hschillig's solution, which I simplified for my case. In the controller, I created an isShow function:
export default Ember.Controller.extend({
routing: Ember.inject.service('-routing'),
isShow: function() {
var currentRouteName = this.get('routing').get('currentRouteName');
return currentRouteName === 'list.show';
}.property('routing.currentRouteName'),
In the template, I now use the if helper:
<div class="panelList {{if isShow 'right'}}">
RustyToms's answer eliminates the need for adding a function to the Controller, at the expense of being less semantic.

Creating alias to a dynamic property now that Ember.Binding is deprecated

How can I add alias or observer to a property that's name I only know when the component is initialized?
I need this for a generic use component where the property name depends on what data was passed to the component. In previous Ember versions I could just create the binding on init:
binding = Ember.Binding.from("model.settings." + this.get('type')).to("setting");
binding.connect(this);
Then use the "setting" wherever it is needed and everything gets correctly updated when or if the property changes. The "type" property is passed to the component from the outside and is different for every instance of the component so I can't hard code the property name in the component itself.
Now Ember.Binding was deprecated in Ember 2.7 and will be removed in Ember 3.0.
I can't figure out how to achieve this without Ember.Binding. And no, there isn't a good way to pass the value from elsewhere or manage this without a binding as far as I can tell. The actual component is a bit more complicated than what I described above but the problem remains same.
You need to use defineProperty to create computed property for dynamic dependant key.. ember-twiddle
controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
actions:{
changeType(){
this.set('model.settings.admin','changed-adminsettings');
}
}
});
templates/application.hbs
{{my-component model=model type='admin' }}
<button {{action 'changeType'}}> ChangeType</button>
my-component.hbs
{{setting}}
{{yield}}
my-component.js
import Ember from 'ember';
export default Ember.Component.extend({
init(){
this._super(...arguments);
var type= this.get('type');
Ember.defineProperty(this,'setting',Ember.computed('model.settings.' + type,function(){
return this.get('model.settings.'+type);
}));
}
});
Reference: https://github.com/emberjs/ember.js/issues/13912

ember application template classname

I'm having a similar problem to this one:
Setting tagName on application template in ember js
While I agree that falling back to a legacy view addon can't be the way to go, here too my bootstrap-based CSS is broken by the enclosing div (the height being not set, to be precise).
Now a different way to achieve what I need is to set the enclosing div's classNames property (if it exists), like it can be done with a component:
export default Ember.Component.extend({
classNames: ['container']
});
Thus I could apply height:100%, and everything would be fine.
Update:
The problem is not the styling of the enclosing div of a component, but the way the main application template behaves. Let me clarify:
application.hbs:
{{outlet}}
Therein is rendered a route's template, e.g. map.hbs:
{{#tab-navigation-container}}
{{top-nav}}
{{tab-contentpane model=model}}
{{tab-navigation map=true}}
{{/tab-navigation-container}}
Now, components/tab-navigation-container.js transforms the enclosing div to include the container CSS class:
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['container']
});
However, the rendered HTML looks like this:
So, seemingly application.hbs puts another div around the component, and I'm looking for a way to either
remove it (which can only be achieved by a legacy view addon, as explained in the link above) or
apply a className to it.
Can it be done? Thanks!
Okay, this is more of an ugly hack than an actual solution, but for the moment it does the trick. I just fell back to ordinary jQuery DOM manipulation while overwriting the component's didInsertElement event:
export default Ember.Component.extend({
setupFunc: function () {
$('#tab-navigation-container').unwrap();
}.on('didInsertElement')
});
where tab-navigation-container is the component's ID.
You should set tagName and classNames to be your outer most element--this will keep consistent styling. For example, say originally you had:
<section class="container col-md-6">
<div class="col-sm-12 test">
<p>some content...</p>
...
</div>
</section>
You would create your component like this:
export default Ember.Component.extend({
tagName: 'section',
classNames: ['container', 'col-md-6']
});
Then your template can exclude the outer section wrapper
<div class="col-sm-12 test">
<p>some content...</p>
...
</div>
There's really no reason why your CSS should be broken by using components. You can set all the same classes and tags that you were using before and CSS should render just the same.
Let me know if I misunderstood your question.
In Ember 2.7 and below, you can control the top-level tag (even not rendering it), and its CSS classes by modifying your Ember Application as follows:
// app/app.js
App = Ember.Application.extend({
ApplicationView: Ember.Component.extend({
classNames: ['my-custom-classname'],
// Or to omit the tag entirely:
tagName: '',
}),
// ...
});
As seen in this discussion.
This does not require any hacks or a legacy plugin.

How do I add classNames to the first element of the page without using a view?

I want to add a wrapper class to the first div element in my page. I used to do this with a view. So, it seems that Ember 2.0 won't support Views anymore. So how can I do that now?
view/application.js:
import Ember from 'ember';
export default Ember.View.extend({
classNames: ['wrapper'],
});
Resulting in the following page:
<body class="ember-application">
<div id="ember573" class="ember-view wrapper">
the rest of my page in this div
</div>
</body>
How is this done now that views are deprecated?
I used css to solve this problem:
body > .ember-view {
padding-left: 240px; //styles for container goes here
}
I don't have a neat solution, but subclassing Ember.Component from inside applications/view.js works.
https://ember-twiddle.com/b15411266f996191605c
Like the others said, the only way to add a class using Ember 2.0 is to use a component on your page. The component has the same properties that the view had. Your page will have a component-only call in the template, like the following:
your-page.hbs
{{your-page-component}}
If you really don't want to have a component on your page, my advice to you would be to add manually a class name in your template:
your-page.hbs
<div class="your-page">
{{outlet}}
</div>
Views are deprecated in ember 2.0. The way to do things from now on is using component and route. You can specify which class name is applied to your component by doing:
export default Ember.Component.extend({
/* Wrap your component in primary class*/
classNames: ['primary'],
/*defined class binding*/
classNameBindings: ['isUrgent'],
isUrgent: true
});
All information regarding on how to customize your component can be found in the ember documentation(click here to find out more)
You're not supposed to use Views now since they're deprecated. Use components instead, example:
import Ember from 'ember';
export default Ember.Component.extend({
classNameBindings: ['functionName'],
functionName: Ember.computed(function() {
// function logic
})
});