In Ember, why does my template want {{model.key}}, not just {{key}}? - ember.js

I am trying out Ember, and finding a discrepancy with the docs. I used the Ember CLI to ember generate template index and ember generate route index. Then I set up a trivial model in index.js:
model: function () {
return {name: "Joe"};
}
From my reading of the docs and examples, I expected to be able to access this value simply with {{name}} in my index.hbs template, but instead I only get the value with {{model.name}}. Why?

Before Ember 1.11 you could use ObjectController, that works like a proxy to corresponding route model, and you could write {{name}} for model.name.
ObjectController was deprecated in Ember 1.11, details here:
http://emberjs.com/deprecations/v1.x/#toc_objectcontroller. So in last Ember versions you should use Controller class instead ObjectController, that doesn't work as proxy of model. You could think of it as of Ember Object with model property from corresponding route. So {{name}} means property of Controller, {{model.name}} - property of model.
For example:
//route
model: function () {
return {name: "Joe"};
}
//controller
import Ember from 'ember';
export default Ember.Controller.extend({
name: 'Marry'
});
//template
{{name}} //=> Marry
{{model.name}} //=> Joe

I think this might be a thing about explicitness but I'm not 100% sure - you can also have data sent to the template on a property other than model so it might be about allowing that to be more easily understood - model is a poor property name IMO anyway
You could use the with helper if the syntax is too verbose for you:
{{#with story}}
<div class="intro">{{{intro}}}</div>
<div class="body">{{{body}}}</div>
{{/with}}

Related

Ember.JS data model: Filtering a computed property

I have an Ember data model logger defined as below:
import DS from 'ember-data';
import EmberObject, { computed } from '#ember/object';
export default DS.Model.extend({
someAttribute: DS.hasMany('attr'),
test: computed('someAttribute.[]', function(){
return this.get('someAttribute').filterBy('description', 'some value');
})
});
The above model gets passed as logger variable from the controller into my component template. In my template:
{{#if logger.test}}
<h1> Testing </h1>
{{log logger.test.description }}
{{/if}}
It seems like the logger.test in the template is always false. In the same template if I add the following:
{{#each logger.someAttribute as |t|}}
{{t.description}}
{{/each}}
I can see all the values being enumerated. Not sure what I am missing? Any assistance would be greatly appreciated.
Okay, I figured out. Turns out models return promises and the if statement doesn't handle promise well enough. The right way to do this would be to return a DS.promiseArray from the computed property and then everything works like a charm:
return DS.PromiseArray.create({
promise: this.get('someAttribute').then(logs => {return logs.filterBy('description')})
});
Acknowledgements: https://emberigniter.com/guide-promises-computed-properties/
I don't exactly understand what you're trying to achieve, but I would either
Load the DS.hasMany('thing', {async: false}) and make sure they are included in the store. (See https://embermap.github.io/ember-data-storefront/latest/). If the relationship is set to async: false when it's accessed, it's accessed synchronously, so there is no issues with promises.
Using ember-concurrency can help manage the loading of the records and displaying them on the page.

Adding component dynamically in Ember

using
Ember : 1.13.11,
Ember Data : 1.13.8,
ember-cli : 1.13.12
I want to add a component dynamically to webpage - this webpage is template of another component don't think that it will make any difference-. Here is my code snippet in which I try to add a component named LyricsEditorLine to <div> tag, somehow like this
agenda-alpha/components/lyrics-editor.js
import Ember from 'ember';
import LyricsEditorLine from 'agenda-alpha/components/lyrics-editor-line';
export default Ember.Component.extend({
afterRenderEvent:function(){
LyricsEditorLine.create().appendTo($("#in"));
},
init:function(){
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
this._super();
}
});
agenda-alpha/templates/components/lyrics-editor.hbs
<div id='in'> </div>
every time this gives me
'Uncaught Error: Assertion Failed: You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead'
Looked for ContainerViewhere found that it is deprecated
Most of the answers that I found are not using ember-cli and being a beginner makes it harder to understand
I want to be able to add components as much as the user needs
I think you probably want the {{component}} helper which allows to dynamically render a component.
{{component "componentName" param1=paramValue param2=anotherParamValue}}
Which means you can have (made up example)
{{component "lyrics-editor-line" line=line}}
One the best things is that componentName can be a bound property
{{component componentName line=line}}
And in your controller/component:
componentName: Ember.computed('prop1','prop2', function() {
if (this.get('prop1') === 'A') {
return 'my-component-a';
}
return 'default-component';
}),
line: computed('prop3', function() {
return this.get('prop2');
})
Also, you can have the component helper inside an each loop (example taken from the Ember documentation)
{{#each model as |post|}}
{{!-- either foo-component or bar-component --}}
{{component post.componentName post=post}}
{{/each}}

why does using this.store.find to load a single model force me to use content.attributeName in the template

I can't understand this.
In my route I have:
model: function (params) {
return this.store.find('article', params.article_id);
}
And in my template I cannot just output my article's attributes like this: {{title}}
but I have to use {{content.title}}
Likewise when I'm in my create article route and I create a model like this:
model: function () {
return this.store.createRecord('article', {title: '', pageContent: '', urlSegment: ''});
}
I'm having to bind the inputs like this:
{{input type="text" value=content.title id="title" placeholder="Title"}}
But, when I'm loading an index route like this
model: function () {
return this.store.find('article');
}
In my template I am able to just say {{#each}} {{title}} {{/each}} and it is much nicer, I don't want to have to use content. for every item route. Am I doing something wrong?
Thanks.
EDIT
As of the latest versions - ember 1.7 and ember data 1.0000.9 I can substitute content for model, but I still can't address the attributes directly.
And in my template I cannot just output my article's attributes like this: {{title}} but I have to use {{content.title}}
Judging by this, you're not setting up your controller properly. You're likely inheriting from Ember.Controller instead of Ember.ObjectController. Ember.ObjectController will proxy properties to your model, so you can use just {{title}}.
Also, don't use the content property in controllers, use the model property. You'll run into a whole lot of subtle bugs if you use the former.
Here's a JSBin showing that behavior.

Why is Firebase data not displaying properly in my Ember CLI generated output?

I've successfully setup Ember CLI and Firebase and I'm attempting to bring some basic data into my templates. My 'title' and 'subtitle' data are apparent in the Ember Inspector, as well as my Firebase project dashboard. However, {{foo.title}} and {{foo.subtitle}} are coming back empty and undefined in the browser. Why is that? Here's my code:
application.js (adapter)
import DS from 'ember-data';
export default DS.FirebaseAdapter.extend({
firebase: new window.Firebase('https://<firebase-database-name>.firebaseio.com/')
});
foo.js (model)
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
subtitle: DS.attr('string')
});
index.js (controller)
import Ember from 'ember';
export default Ember.Controller.extend({
model: function() {
var titles = this.store.createRecord('foo', {
title: 'Title',
subtitle: 'Subtitle'
});
titles.save();
}
});
index.js (route)
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('foo');
}
});
application.hbs (template)
<h2 id='title'>{{foo.title}}</h2>
{{outlet}}
index.hbs (template)
<h1>{{foo.title}}</h1>
<h3>{{foo.subtitle}}</h3>
The title and subtitle fail to display in the templates.
The Ember Inspector View Tree tab shows 'index' with 'DS.RecordArray:ember368' for the model.
The Ember Inspector Data tab shows Model Type of 'foo' with # Records of 1. When I click on that record, it displays the Firebase ID, title, and subtitle values. When I inspect my Firebase data url, I see the following structure:
firebase-database-name
|— foos
|— JU1Ay8emCNNZBeqYoda
|— subtitle: "Subtitle"
|— title: "Title"
Seems like everything is correct, but the templates do not display the data values. Thanks for any help.
The answer to this question centers on properly retrieving and exposing Ember Data, and not so much to do with Firebase or Ember CLI. There are multiple issues with the code above…
The foo.js code represents a simple model, and is written correctly.
The index.js route is implemented correctly. It is retrieving and returning the ‘foo’ model from the Ember Data store as an array, which, via EmberFire and the Firebase adapter, is ultimately being pulled from the Firebase database. However, this is part 1 of 3 problems. If you want this data displayed once across the application, dispense with the index.js route, and just define an application.js route, like this:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('foo');
}
}
The index.js controller has a number of issues, and is part 2 of 3 problems. Firstly, controllers do not have a ‘model’ method, they only have a ‘model’ property (Ember Routes are the ones that employ a ‘model’ method, and can also set the ‘model’ property of a controller via a Route’s ‘setupController’ method). Secondly, instead of Ember.Controller, it needs to extend Ember.ObjectController for a singular data instance, or, Ember.ArrayController for an array of data, which is the controller needed here, since ‘this.store.findAll(“foo”)’ in the index.js route is going to return an array of objects. Controllers are not used to save or retrieve data from a server, but they can be used to decorate a model. Given that the route is returning the model, the controller, in this simple data exercise, is not even necessary.
The application.hbs handlebars template is part 3 of 3 problems. It is not setup to properly display the model that is being provided to it via the route. It’s necessary to employ the {{#each}} helper, to loop over the data array that is being returned via the route’s model method. Try this:
{{!-- looping over the 'foo' model returned via the route --}}
{{#each foo in model}}
<h2>Application Title = <span style="color: blue;">{{foo.title}}</span></h2>
<h4>Application Tagline = <span style="color: blue;">{{foo.tagline}}</span></h4>
{{/each}}
{{outlet}}
The index.hbs handlebars template is not necessary. The application.hbs template is sufficient to display the data of interest.
This is a very basic exercise, but illustrates fundamental aspects of using Ember Data properly.

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