Ember 2.0 - views for pages - ember.js

I am used to put some jquery code in View files, that I create for pages.
For instance:
I have the route this.route('buildings');
Next create the view file app/views/buildings.js
import Ember from "ember";
export default Ember.View.extend(Ember.TargetActionSupport, {
didInsertElement: function () {
//jquery here
}
});
But now with Ember 2.0 we have no such ability, what should I do?

In Ember 2.0.0 you can still do the following:
App.ApplicationView = Ember.Component.extend({
classNames: ['customClassName'],
didInsertElement: function() {
alert('did insert element')
}
});
App.BuildingsView = Ember.Component.extend({
classNames: ['customClassName2'],
didInsertElement: function() {
alert('did insert element2');
}
});
See this jsbin for a working example.
P.S. Comment by Robert Jackson: "Using a component as ApplicationView will allow customization of classNames and whatnot, but is definitely going to have a number of negative results as well (for example controller is not correct)." https://github.com/emberjs/ember.js/issues/11486#issuecomment-131366332

Put it in a component. In components/my-component.js
import Ember from 'ember';
export default Ember.Component.extend({
didInsertElement: function(){
// jquery here.
}
});
In your template:
{{my-component}}
Or:
{{#my-component}}
Stuff
{{/my-component}}

Related

Ember-cli Component show records to component template

test-component.js
import Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service(),
showLang: Ember.on('init', function() {
var store = this.get('store');
return store.findAll('language');
}),
});
I checked with console and yes they show
XHR finished loading: GET "http://127.0.0.1:8000/languages".
So backend recognize my test-component function.
Now in test-component.hbs I have
{{#each showLang as |lang| }}
<li>{{lang.title}}</li>
{{/each}}
The result was empty. How can I do so data could show up in test-component.hbs?
First of all - you should do it in route and pass data as model to component, but if you really want to do this in component you can do so:
test-component.js:
import Ember from 'ember';
export default Ember.Component.extend({
store: Ember.inject.service(),
showLang: Ember.on('init', function() {
this.get('store').findAll('language').then(languages => this.set('languages', languages));
})
});
test-component.hbs:
{{#each languages as |lang| }}
<li>{{lang.title}}</li>
{{/each}}

Where should I set defaults on components with attributeBindings?

I have a component {{upload-image src=foo.bar}}. When foo.bar is falsy I want to give it a different value. I setting the value in init like so:
import Ember from 'ember';
export default Ember.Component.extend({
attributeBindings: ['src'],
tagName: 'img',
init: function(){
this.set('src', 'http://www.placecage.com/40/40');
this._super();
this.set('src', 'http://www.placecage.com/40/40');
},
click: function(){
console.log(this.get('src'));
}
});
However it doesn't work. The image gets rendered with the value I pass in, not the new one. When I click the image it does log the placecage image. Instead if I try to override it later on say didInsertElement it works as expected:
import Ember from 'ember';
export default Ember.Component.extend({
attributeBindings: ['src'],
tagName: 'img',
didInsertElement: function(){
this.set('src', 'http://www.placecage.com/40/40');
},
click: function(){
console.log(this.get('src'));
}
});
It seems a bit silly to have it render out and only then be able to change the value causing (I presume) a re-render. What's the appropriate place to check if src is falsy and set it to a default and get the expected results?
In Ember 1.13 you can do that in
attributeBindings: ['src:src'],
didInitAttrs() {
this.set('src', <new value>);
}
Or you could do the falsy logic outside of the component and pass it in.
I would create another property defined as follows:
effectiveSrc: function() {
return this.get('src') || 'http://www.placecage.com/40/40';
}.property('src')
then use effectiveSrc in your logic and templates.
Another alternative is to give src a default:
src: 'http://www.placecage.com/40/40'
Then when calling your template, omit src if its value is falsy, something like
{{#if foo.bar}}
{{upload-image src=foo.bar}}
{{else}}
{{upload-image}}
{{/if}}
The following might also work, need to try it:
{{upload-image src=(if foo.bar foo.bar)}}
With ember 2.x (I've tested with 2.4.2, 2.4.3 and 2.5.0), I've achieved this functionality with the mentioned default by torazaburro :
export default Ember.Component.extend({
attributeBindings: ['src'],
tagName: 'img',
src: 'http://www.placecage.com/40/40',
click() {
console.log(this.get('src'));
}
}
And the templates:
{{upload-image src="http://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon.png"}}
{{upload-image}}
The Twiddle to test it
There's an open issue over on Github https://github.com/emberjs/ember.js/issues/11637 where they seem to be saying that I shouldn't set the value using the two-way data binding this way.
I suppose I should make it read from a computed property instead and set my default from that.

ember.js, ember-cli: Outlets not nesting properly

I'm having an issue where I'm unable to get nested outlets to appear properly in my Ember CLI app. The view tree I want is as follows:
application (list of all resources, of which client_availability is one)
- client_availabilities.index (list of client_availabilities)
-- client_availability (individual client_availability)
This is very similar to the "application > posts.index > post" hierarchy in the Ember Starter Kit. My desired behavior is for a list of client_availabilities to appear in "mainoutlet" when I navigate to client_availabilities.index, then persist when I bring up an individual client_availability in "suboutlet".
Easy, right? This is the default behavior & why we all love Ember. However, I can't seem to get it working. When I explicitly target my named suboutlet in client_availabilities.index and click on an individual client_availability, nothing shows up in either outlet:
Scenario 1: Render suboutlet inside client_availabilities
/app/template/application.hbs:
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet 'mainoutlet'}}
/app/template/client-availabilities/index.hbs:
{{outlet 'suboutlet'}}
/app/routes/client-availabilities/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render({
into: "application",
outlet: "mainoutlet"
});
},
model: function() {
return this.store.find('client_availability');
}
});
/app/routes/client-availability.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render('client_availability', {
into: "client_availabilities",
outlet: "suboutlet"
});
},
model: function(params) {
return this.store.find('client_availability', params.client_availability_id);
}
});
Alternately, when I target my mainoutlet in application, client_availability appears in "suboutlet" client_availabilities.index disappears from "mainoutlet":
Scenario 2: Render suboutlet inside application
/app/template/application.hbs:
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet 'mainoutlet'}}
{{outlet 'suboutlet'}}
/app/template/client-availabilities/index.hbs: (empty)
/app/routes/client-availabilities/index.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render({
into: "application",
outlet: "mainoutlet"
});
},
model: function() {
return this.store.find('client_availability');
}
});
/app/routes/client-availability.js:
import Ember from 'ember';
export default Ember.Route.extend({
renderTemplate: function(){
this.render('client_availability', {
into: "application",
outlet: "suboutlet"
});
},
model: function(params) {
return this.store.find('client_availability', params.client_availability_id);
}
});
And here's my router, the same in both cases:
/app/router.js:
import Ember from 'ember';
var Router = Ember.Router.extend({
location: 'auto'
});
Router.map(function() {
this.resource('client_availabilities', function() {
this.resource('client_availability', { path: ':client_availability_id' });
});
});
export default Router;
I'm happy to share more code, but the application is split into several files and unfortunately not something I can post in its entirety. Can anyone see what I'm doing wrong? The rest of the app is working fine, I just can't seem to get this basic behavior to work.
Do you have an /app/templates/client-availibilities.hbs template with only {{outlet}} inside of it? Without this, the app is going to lose its place in the outlet tree. Ember-CLI and the Ember Starter Kit are very, very different from each other in structure, so I can see where the confusion comes from.
How I like to think of Ember's rendering style is that each handlebars file inside the templates folder (i.e. /templates/users.hbs) represents a change the overall state of the application from one subject to another (example: from newsfeed to users).
The corresponding subfolders inside the templates folder change the state of the subject itself.
For example:
Required Templates
Users container OR the only users page you need app-wide is at /templates/users.hbs
Optional Templates
Users Index would be at /templates/users/index.hbs
Users Show would be at /templates/users/show.hbs
Users New would be at /templates/users/new.hbs
You can have [ /templates/users.hbs ] without having [ /templates/users/*.hbs ] and still keep track of your data; however, you cannot have [ templates/users/index.hbs ] without [ /templates/users.hbs ] and still keep track of your data. Why? Imagine if you navigate to somesite.com/users. There is currently no top-level template with an outlet into which Ember can render the [ users/index.hbs ] template. The [ /templates/users.hbs ] template bridges that gap and also serves as a container for all other pages inside the /templates/users folder as well.
For example, in the terms of your app, in order to render [ /app/templates/client-availibilities/index.hbs ] when a user visits http://www.yourwebsite.com/client-availibilities, your app will need these templates defined so that ember can drill down into them.
application.hbs // and in its outlet, it will render...
--client-availibilities.hbs // and in its outlet, it will render by default...
----client-availibilities/index.hbs // then, for the client-availability (singular), you can have ember render it in
----client-availibilities/show.hbs // will render also in the client-availabilites as it is a separate state of the subject. Can also be nested inside the index route within the router so that it renders inside the index template.
As it is, I would structure your app as such...
/app/router.js
... // previous code
Router.map(function() {
this.resource('client_availabilities', function() {
this.route('show', { path: '/:client_availability_id' });
// this.route('new'); ! if needed !
// this.route('edit', { path: '/:client_availability_id/edit' ); ! if needed !
});
});
... // code
/app/templates/application.hbs
{{link-to 'Client Availabilities' 'client_availabilities'}}
{{outlet}}
/app/templates/client-availabilities.hbs
{{outlet}}
/app/templates/client-availabilities/index.hbs
<ul>
{{#each}}
{{#if available}}
<li>
{{#link-to #link-to 'client-availabilities.show' this}}
{{firstName}} {{lastName}}
{{/link-to}}
</li>
{{/if}}
{{else}} <!-- we want this to only render if the each loop returns nothing, which is why it's outside the if statement -->
<li>Nobody is available</li>
{{/each}}
</ul>
<!-- Note: you don't need to put an outlet here because you're at the end of the tree -->
/app/templates/client-availabilities/show.hbs
<!-- Everything you want to show about each availability -->>
<!-- Note: you don't need to put an outlet here because you're at the end of the tree -->
/app/routes/client-availabilities/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('client_availability');
}
});
/app/routes/client-availabilities/show.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return this.store.find('client-availability', params.client_availability_id);
}
});
/app/models/client-availability.js
import DS from 'ember-data';
var client-availability = DS.Model.extend({
firstName: DS.attr('string'),
lastname: DS.attr('string'),
available: DS.attr('boolean'),
available_on: DS.attr('date')
});
export default client-availability;
However, are you sure you want to structure your app by the availability of each client? Wouldn't it make more sense to structure it by each client and then just filter each client to show if they were available or not? Resources are supposed to be nouns, and routes are supposed to be adjectives. Therefore, it would be best to use a client as your model instead of their availability and have a either an isAvailable property on the model (as used in the example above) or a one-to-many association with an additional availability model if you want to show clients who have several availabilities (as shown below).
For example,
/app/models/client.js
import DS from 'ember-data';
var Client = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
availabilities: DS.hasMany('availability')
});
export default Client;
/app/models/availability.js
import DS from 'ember-data';
var Availability = DS.Model.extend({
date: DS.attr('date'),
client: DS.belongsTo('client')
});
export default Availability;
In the long run, this latter approach would set up your app to show all availabilities at once and allow the user to filter by the client, plus it would allow the user to view a client and see all their availabilities. With the original approach (the isAvailable property on the client model), the user can only get the availabilities from the client model itself, but what if the user wants to see all clients who are available on, say, March 3rd at noon? Well, without an availability model associated with the client model, you are going to have to put a lot of code into your client controller that ember would give you by default if you go down the one-to-many path.
If you need more advice on where to go from here, let me know. I'm more than happy to add more examples of the templates, controllers, and routes that you'll need in order to pull this off.

Ember and TinyMCE

I want to use the TinyMCE component in an Ember app. My basic question is: how and where can you init the tinymce ?
Template contains the textarea element:
<textarea class='test' name="content" style="width:100%">
I need to init TinyMce as follows:
tinymce.init({
selector: "textarea"
});
Where do I need to do the init and how ? In think in the controller init, but this does not work as expected ...
App.IndexController = Ember.Controller.extend({
init: function() {
tinymce.init({
selector: "textarea"
});
}
});
See JSFiddle: http://jsfiddle.net/cyclomarc/wtktK/6/
Hope somebody can help ...
Before tinymce.init you must make sure that your textarea tag is in DOM. On the time your controller gets initialized your view (in this case IndexView) is not yet rendered. A valid way to wait for a view be be rendered is to use didInsertElement hook in that view. In your case:
App.IndexView = Ember.View.extend({
didInsertElement: function(){
tinymce.init({
selector: "textarea"
});
}
});

Ember template displayed twice

There is a simple ember.js app with one view displayed in a particular place on the webpage. Have a look at this jsfiddle: http://jsfiddle.net/jkkK3/9/
App = Ember.Application.create({
ready: function(){
this._super();
this.ApplicationView.create().appendTo(".content");
},
ApplicationController: Ember.Controller.extend({
}),
ApplicationView: Ember.View.extend({
templateName: 'application'
}),
Router: Ember.Router.extend({
root: Ember.Route.extend({
})
})
});
My question is: Why is the "some content here" element displayed twice? It works when I remove the router, but that's exactly what I cannot do, since I try to add Router to my Ember app. Could you please help me to display application view only once, inside the red box?
When using router, applicationController/view are used by default. In your ready method you append it explicitly. So 'application' template is appended twice. Remove appending it in ready method and it will be appended only once.
By default it's appended to body but if you want to override use rootElement property of Ember.Application
Ember.Application.create( {
rootElement : '.content',
....
})