I have DECLARED an emberjs component in template as:
<script type="text/x-handlebars" id="components/custom-component">
<h4>The name of the object passed is : {{object.name}}</h4>
{{#if show_details}}
<p>The details of the object passed are : {{object.details}}</p>
{{/if}}
</script>
Now I am USING this component in my html template as :
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each object in model}}
<li>{{custom-component object=object}}</li>
{{/each}}
</ul>
</script>
My component class for custom-component is as shown below :
App.CustomComponentComponent = Ember.Component.extend({
show_details: function() {
// return true or false based on the OBJECT's property (e.g. "true" if object.type is 'AUTHORIZED')
},
});
Update
The way I achieved it is as follows :
App.CustomComponentComponent = Ember.Component.extend({
show_details: function() {
var object = this.get('object');
if (object.type == "AUTHORIZED")
return true;
else
return false;
}
});
The parameters passed to the component class are available using it's get methods.
It should work this way:
{{custom-component object_name=object}}
(you just used the wrong property name).
This should work if object is the object name. If name is a property of object then use object.name.
UPDATE
This should be straightforward. show_details should be defined as computed property depending on the object type:
App.CustomComponentComponent = Ember.Component.extend({
object: null,
show_details: function() {
var object = this.get('object');
if (object.get('type') === "AUTHORIZED")
return true;
else
return false;
}.property('object.type')
});
or simpler:
App.CustomComponentComponent = Ember.Component.extend({
show_details: function() {
return this.get('object').get('type') === "AUTHORIZED";
}.property('object.type')
});
Don't forget to use get when accessing the properties of an Ember object.
Updated for Glimmer Components
In the newer Ember components (Glimmer), you can access the values passed to the components inside the component class from this.args. The guide here is very helpful.
Simple example from the guide
Before:
import Component from '#ember/component';
import { computed } from '#ember/object';
export default Component.extend({
width: 0,
height: 0,
aspectRatio: computed('width', 'height', function() {
return this.width / this.height;
})
});
{{!-- Usage --}}
<Image #width="1920" #height="1080" />
After:
import Component from '#glimmer/component';
export default class ImageComponent extends Component {
get aspectRatio() {
return this.args.width / this.args.height;
}
}
{{!-- Usage --}}
<Image #width="1920" #height="1080" />
Related
I want to bind parent (arrayController) property with children (itemController). The binding works only when I'm setting the properites in childController like this.set('foo', property), instead using computed properties.
App.IndexController = Ember.ArrayController.extend({
numberOfAllLetters: (function() {
return this.getEach('lettersNo').reduce(function(prev, curr){
return prev + curr
});
}).property('#each.lettersNo'),
numberOfAllLetters2: (function() {
return this.getEach('length2').reduce(function(prev, curr) {
return prev + curr;
})
}).property('#each.length2')
});
// child
App.WordController = Ember.ObjectController.extend({
lettersNo: (function() {
length = this.get('name').length;
// why only through 'set' the arrayController binds properly?
// if in parent controller I bind to '#each.length2', it works good. but why?
this.set('length2', length);
return this.get('name').length;
}).property('name')
});
handlebars:
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each word in model itemController="word"}}
<li>{{input value=word.name}}</li> letters:
{{word.lettersNo}}
{{/each}}
</ul>
Total letters:{{numberOfAllLetters}} <br/> <!-- doesnt work -->
Total letters2:{{numberOfAllLetters2}} <br/> <!-- works -->
</script>
I am wondering if I am missing something and why i cannot use the same property in child's template, child's controller and binded to parent's controller.
Here is the fiddle: http://jsfiddle.net/licancabur/de1k7jcb/
Both Ember.ObjectController and Ember.ArrayController will be deprecated in future so it's not recommended to use them if you want upgrade your Ember version. So I came up with solution that gets rid of Ember.ObjectController and is working:
App.Word = Ember.Object.extend({
lettersNo: (function() {
console.log('lettersNo invoked')
length = this.get('name').length;
console.log('length change', length);
return length
}).property('name')
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
App.Word.create({
id: 1,
name: 'one'
}),
App.Word.create({
id: 2,
name: 'two'
}),
App.Word.create({
id: 3,
name: 'three'
}),
];
}
});
App.IndexController = Ember.ArrayController.extend({
numberOfAllLetters: (function() {
return this.getEach('lettersNo').reduce(function(prev, curr){
return prev + curr
});
}).property('#each.lettersNo'),
});
Template:
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each word in model}}
<li>{{input value=word.name}}</li> letters:
{{word.lettersNo}}
{{/each}}
</ul>
Total letters: {{numberOfAllLetters}} <br/>
</script>
Working JSFiddle.
How I managed to solve it? You were mentioning that you use WordController only in your template - when iterating over words collection(model). Your IndexController didn't know that model has any additional behaviour, so #each.lettersNo simply didn't exist for controller. However, when Ember.Object - App.Word was created - both template and IndexController knew it has computed property lettersNo.
How to iterate over each model value and based on the value update the handlebar UI.
I am using ArrayController. Basically for a particular value in the model I want to change how I display it.
I am not sure what is wrong in the above code. But it does not function as required.
App.SomeStat = Ember.Object.extend({
target: null,
starts: null
}
{{#each stat in controller}}
{{#if isRestricted}} Do something..
{{/if}}
{{/each}}
App.SomestatController = Ember.ArrayController.extend({
isRestricted: function () {
this.forEach(function(target) {
var t= target.get('target');
return t >= MAGIC_NUMBER;
});
}.property('model.#each.target'),
});
You should setup the ArrayController itemController property to an ObjectController which extends the content for each array content.
App.ExtendIndexController = Ember.ObjectController.extend({
isRestricted: Em.computed(function () {
return this.get('name') === 'red';
}).property('name')
});
App.IndexController = Ember.ArrayController.extend({
itemController: 'extendIndex'
});
Then, you could access the added properties in your template when iterating the controller:
{{#each controller}}
<li>{{name}} ({{isRestricted}})</li>
{{/each}}
http://emberjs.jsbin.com/gexos/1/edit
This case is documented in the Ember guide but I think, this specific case should documented as well.
Try this:
App.CensusStat = Ember.Object.extend({
targetPc: null,
starts: null,
isRestricted: function () {
var offTarget = this.get('targetPc');
return (offTarget &&
(Math.abs(offTarget) >=
Ember.I18n.t('ps.label.census.offtarget.restricted.percentage')));
}.property('targetPc')
});
I would like to be able to define the model for a component template inside the Ember.Component js instead of inside the route where the component is sitting. I have not seen any examples which are doing this...
Here I have my component template:
<script type="text/x-handlebars" id="components/info-box">
<div class="infoBox box">
<p>
<label>
{{preUnits}}
</label>
<span>
{{value}}
</span>
</p>
</div>
</script>
And here is how I am placing it inside one route template:
{{info-box title='Total Area' dataDef='buddhaData:DataGet/site/areaNum'}}
What I would like to do is use my relevant Ember.Component to do some stuff with the parameters of the info-box and then return a model for it.
App.InfoBoxComponent = Ember.Component.extend({
buildIt: function(){
var container = $('#' + this.get('elementId') );
var title = this.get('title');
var preUnits = this.get('preUnits') || '';
var dataDef = this.get('dataDef');
// Do stuff with dataDef.
var model = {
preUnits: '$',
value: 5000
}
// Hopefully return model somehow.
},
didInsertElement: function(){
this.buildIt();
}
});
I want to be able to use this component inside a bunch of different routes, and I do not want to have to refer to the route that a particular info-box is inside of in order to give the info-box its model, is this possible, or should I use some other feature, like a regular template and the render helper?
Once you have the model object, just set properties on the component itself:
App.InfoBoxComponent = Ember.Component.extend({
buildIt: function(){
var container = $('#' + this.get('elementId') );
var title = this.get('title');
var preUnits = this.get('preUnits') || '';
var dataDef = this.get('dataDef');
// Do stuff with dataDef.
var model = {
preUnits: '$',
value: 5000
}
// Set component's preUnits and value properties directly
this.setProperty('preUnits', model.preUnits);
this.setProperty('value', model.value);
// or
this.setProperties(model);
// Hopefully return model somehow.
},
didInsertElement: function(){
this.buildIt();
}
});
You should use render if you'd like to define which model you want to use (if the model is different than the current context). If it's the same context, you should just use partials. You could also generate helper and pass in the model to that.
Ember.Handlebars.helper('autocomplete', Ember.View.extend({
templateName: 'controls/autocomplete',
filteredList: function() {
var list = this.get('list'),
filter = this.get('filter');
if (!filter) { return list; }
return list.filter(function(item) {
return item.name.indexOf(filter) !== -1;
});
}.property('list.[]', 'filter')
}));
Usage:
<script type="text/x-handlebars" data-template-name="application">
{{autocomplete list=list1}}
{{autocomplete list=list2}}
</script>
<script type="text/x-handlebars" data-template-name="controls/autocomplete">
<p>{{input type="text" value=view.filter}}</p>
<ul>
{{#each view.filteredList}}
<li >{{name}}</li>
{{/each}}
</ul>
</script>
Full example
I have a list of items I'm trying to display with Ember. For each of these items, I'd like to be able to dynamically select the view type to use to display it based on a "message_type" field in each model.
I currently have something like this, which totally sucks and is not scalable:
{{#each message in controller}}
{{#if message.isImage}}
{{view App.ImageMessageView}}
{{/if}}
....
{{#if message.isVideo}}
{{view App.VideoMessageView}}
{{/if}}
{{/each}}
How can you dynamically select a view based on a model's field in Ember?
Here is a similar question that showed 2 ways to do this: Collection of objects of multiple models as the iterable content in a template in Ember.js
rendering items based on a property or their type
I know of two ways to do this:
add a boolean property to each object and use a handlebars {{#if}} to check that property and render the correct view
extend Ember.View and use a computed property to switch which template is rendered based on which type of object is being rendered (based on Select view template by model type/object value using Ember.js)
Method 1
JS:
App.Post = Ember.Object.extend({
isPost: true
});
App.Bookmark = Ember.Object.extend({
isBookmark: true
});
App.Photo = Ember.Object.extend({
isPhoto: true
});
template:
<ul>
{{#each item in controller.stream}}
{{#if item.isPost}}
<li>post: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isBookmark}}
<li>bookmark: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isPhoto}}
<li>photo: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{/each}}
</ul>
Method 2
JS:
App.StreamItemView = Ember.View.extend({
tagName: "li",
templateName: function() {
var content = this.get('content');
if (content instanceof App.Post) {
return "StreamItemPost";
} else if (content instanceof App.Bookmark) {
return "StreamItemBookmark";
} else if (content instanceof App.Photo) {
return "StreamItemPhoto";
}
}.property(),
_templateChanged: function() {
this.rerender();
}.observes('templateName')
})
template:
<ul>
{{#each item in controller.streamSorted}}
{{view App.StreamItemView contentBinding=item}}
{{/each}}
</ul>
JSBin example - the unsorted list is rendered with method 1, and the sorted list is rendered with method 2
It would probably need some more thought, but here is what I have come up with quickly:
var get = Ember.get,
isGlobalPath = Ember.isGlobalPath,
normalizePath = Ember.Handlebars.normalizePath;
var getProp = function (context, property, options) {
if (isGlobalPath(property)) {
return get(property);
} else {
var path = normalizePath(context, property, options.data);
return get(path.root, path.path);
}
};
Ember.Handlebars.registerHelper('detect', function (definition, instance, options) {
Ember.assert("You must pass exactly two argument to the detect helper", arguments.length === 3);
Ember.assert("You must pass a block to the detect helper", options.fn && options.fn !== Handlebars.VM.noop);
var path = '_detect_' + definition.replace('.', '_').toLowerCase();
context = (options.contexts && options.contexts[0]) || this;
definition = getProp(context, definition, options);
instance = getProp(context, instance, options);
context.set(path, definition.detectInstance(instance));
return Ember.Handlebars.helpers.boundIf.call(options.contexts[0], path, options);
});
Then you can use a helper like this:
{{#detect App.Definition instance}}
DETECTED
{{else}}
NOT DETECTED
{{/detect}}
I have a list of items I'm trying to display with Ember. For each of these items, I'd like to be able to dynamically select the view type to use to display it based on a "message_type" field in each model.
I currently have something like this, which totally sucks and is not scalable:
{{#each message in controller}}
{{#if message.isImage}}
{{view App.ImageMessageView}}
{{/if}}
....
{{#if message.isVideo}}
{{view App.VideoMessageView}}
{{/if}}
{{/each}}
How can you dynamically select a view based on a model's field in Ember?
Here is a similar question that showed 2 ways to do this: Collection of objects of multiple models as the iterable content in a template in Ember.js
rendering items based on a property or their type
I know of two ways to do this:
add a boolean property to each object and use a handlebars {{#if}} to check that property and render the correct view
extend Ember.View and use a computed property to switch which template is rendered based on which type of object is being rendered (based on Select view template by model type/object value using Ember.js)
Method 1
JS:
App.Post = Ember.Object.extend({
isPost: true
});
App.Bookmark = Ember.Object.extend({
isBookmark: true
});
App.Photo = Ember.Object.extend({
isPhoto: true
});
template:
<ul>
{{#each item in controller.stream}}
{{#if item.isPost}}
<li>post: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isBookmark}}
<li>bookmark: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isPhoto}}
<li>photo: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{/each}}
</ul>
Method 2
JS:
App.StreamItemView = Ember.View.extend({
tagName: "li",
templateName: function() {
var content = this.get('content');
if (content instanceof App.Post) {
return "StreamItemPost";
} else if (content instanceof App.Bookmark) {
return "StreamItemBookmark";
} else if (content instanceof App.Photo) {
return "StreamItemPhoto";
}
}.property(),
_templateChanged: function() {
this.rerender();
}.observes('templateName')
})
template:
<ul>
{{#each item in controller.streamSorted}}
{{view App.StreamItemView contentBinding=item}}
{{/each}}
</ul>
JSBin example - the unsorted list is rendered with method 1, and the sorted list is rendered with method 2
It would probably need some more thought, but here is what I have come up with quickly:
var get = Ember.get,
isGlobalPath = Ember.isGlobalPath,
normalizePath = Ember.Handlebars.normalizePath;
var getProp = function (context, property, options) {
if (isGlobalPath(property)) {
return get(property);
} else {
var path = normalizePath(context, property, options.data);
return get(path.root, path.path);
}
};
Ember.Handlebars.registerHelper('detect', function (definition, instance, options) {
Ember.assert("You must pass exactly two argument to the detect helper", arguments.length === 3);
Ember.assert("You must pass a block to the detect helper", options.fn && options.fn !== Handlebars.VM.noop);
var path = '_detect_' + definition.replace('.', '_').toLowerCase();
context = (options.contexts && options.contexts[0]) || this;
definition = getProp(context, definition, options);
instance = getProp(context, instance, options);
context.set(path, definition.detectInstance(instance));
return Ember.Handlebars.helpers.boundIf.call(options.contexts[0], path, options);
});
Then you can use a helper like this:
{{#detect App.Definition instance}}
DETECTED
{{else}}
NOT DETECTED
{{/detect}}