i have nested components. Every component has button to show its nested component.
From controller I pass selectbox options (query on store) through all components. But query is done before final component with selectbox is even shown/drawn. Real query its done at moment of when second component with this property is drawn.
Is there way to not do query till its really needed for selectbox to be drawn ? I did not wanted to have store property straight in nested component itself.
EDITED
As suggested by kumkanillam, his solution worked pretty well, here is code to get the idea.
In controler:
reactionTimesForOptions: null,
allReactionTimes: function() {
return this.get("store").findAll("reaction-time");
}.property("store"),
actions:{
initialiseRatingOptionsData(){
if(Ember.isEmpty(this.get("reactionTimesForOptions"))) {
this.set("reactionTimesForOptions", this.get("allReactionTimes"));
}
}
In controler hbs
{{#task-list
allReactionTimes=reactionTimesForOptions
initialiseRatingOptionsData=(action "initialiseRatingOptionsData")}
{{/task-list}}
Then in next 2 nested components
allReactionTimes=allReactionTimes
initialiseRatingOptionsData=initialiseRatingOptionsData
And finnaly at point of clicking and showing my component which need to have data from database, in component deciding on showing final component
toggleRatingScreen(){
this.initialiseRatingOptionsData(); /* at this point query are done since needed only at this point*/
....
You can pass empty selectbox options property from controller along with function updateSelectboxOptions which will update selectbox options, and in nested component when you need to data you can just call updateSelectboxOptions.
Related
I need to generate a random string from two arrays with a bunch of names of people and devices. I have an Ember computed property that does it perfectly well. Except computed properties only happen once. So I end up with a list of like 5 rows with the SAME TEXT.
The reason I'm doing this is because I am getting a list of devices from a REST web service, but they don't have the ability to return the name of the device, only id and a bunch of other info. So I am supposed to use dummy names for now, and so I figure. no problem. I'll just generate a random name for each row. Well as I already said, I end up with this:
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
When it needs to be the name of a random person and random device (which I have in arrays and the code works perfectly, but only executes once, and returns the same value for each consecutive time it's called.
So then I read on here and in an Ember book I have to do this:
property1: function() {
bunch of code
}.property('property2');
So this is supposed to run every time property2 changes, except. Back to square one. How do I change property2? I can't do it from the hbs template, from the code within the {{#each}} .. Then I read somewhere to use a custom helper, but in my experience ANY helper ecapsulates the returned value in a div which breaks the layout and would result in
text
helper response
remaining text
when what I want is:
text helper response remaining text
I mean yeah I could just make an array of text and then pass the index but then when I add new data I have to manually add items to the array because it's not dynamically generated for every row of data.
With my method I have like a ton of names and device names chosen randomly so no matter how much data is returned it can populate the name field until they fix it on the back end to return names.
Would really love to know how not just to solve this problem but how to run ANY CODE that I want from ANY PLACE I want in the page/template/etc. not just static properties or computed once properties..
sometimes you want to be able to have templates that use variables in them that are completely dynamic ran EVERY TIME that component is called.
Sometimes you want an actual helper that doesn't encapsulate the response in a div, so you can do stuff like The answer is {{answer-helper}}. and not have the output be this:
The answer is
5
.
Okay, first there are helpers if you just won't to output data. And the helper does not encapsulate anything in a div. And a helper is not a component. A component usually produces a div. But if you want a component that doesn't produce a div you can just set tagName to ''.
And thats what you should do: use two components:
device-list/template.hbs
{{#each devices as |device|}}
{{device-data device=device}}
{{/each}}
device-item/template.hbs
{{myprop}}
device-item/component.js
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
myprop: Ember.computed('device.foo', {
get() {
return this.get('device.foo') + 'bla';
}
}),
});
now you have the property myprop once for every item in the array.
Another option is to have an array as computed property:
devicesWithNames: Ember.computed('devices.#each.foo', {
get() {
return this.get('devices').map(device => ({
foo: device.foo,
name: device.foo + 'bla', // anything you want to do
}));
}
}),
Another way to solve your problem is an ArrayProxy.
Would really love to know how not just to solve this problem but how to run ANY CODE that I want from ANY PLACE I want in the page/template/etc. not just static properties or computed once properties..
You can't, and thats good. This forces you to follow clean standards. For example you can't just call code on the component from the template. You can invoke helpers, or use data provided by the template. But this is a one-way data flow, and this helps a lot in understanding how a component works. If you need to do this use a computed property, and if you need this inside an {{#each}} loop write another component to be used inside the loop.
sometimes you want to be able to have templates that use variables in them that are completely dynamic ran EVERY TIME that component is called.
It seems you don't understand this. But this works as expected. A component that is called twice will compute all properties twice. However you need to understand that if you use an {{#each}} loop you are still in the same component, so where would you want to declare the property that should run for every instance in the array? Thats why you need another component for this. The other option is to have a computed property that does provide you with a new different array, with all the required data.
I made a very simple table component. It takes a model, and builds a table from it.
http://emberjs.jsbin.com/raqomebeqi/1
Since its very primitive and has hard-coded property names, I decided to make an in-component representation of the model, to sort, filter the content of the table. It also allows to show just certain columns from the bounded model. (columns property)
http://emberjs.jsbin.com/raqomebeqi/2
The problem: It doesn't react to the changes of the model anymore. In the first example If I hit the 'change' button, it takes the first record, and set a new name. One can see the change in the table. In the second case Ember inspector shows the change of the name, but the table shows the old value.
I know I could do
data: Ember.computed('model.#each.name', function(){
//...
})
to monitor the changes on the name, but it is not very dynamic. How could I make my data react to each and every change on the model?
If you want your data to react on every change, you could do:
data: Ember.computed('model', function(){
//...
})
UPDATE:
This is not working
In my app I have a route where I'm using queryParams to filter an array.. When I add a new item to the array that matches the filter criteria the template does not update with the new item.
Super simple example bin at http://emberjs.jsbin.com/qetami/1#/colors?type=secondary
In that example, while filtered to Secondary colors click the Add Color button and add a new color with Color Type set to secondary. The color does not immediately appear. If you change the filter then go back to Secondary it appears. It automatically appears when on the unfiltered/default route.
I've tried with and without the queryParams hook in the Colors route with no luck.
This seems like it should be straight forward but I've run into a wall.
I couldn't really get something working with .observes() however I came up with a working version of your example if you leverage actions bubbling up through the routes so that you have a good spot to call this.refresh() in order to reload the filtered model.
http://jsbin.com/qomiba/3/edit
Side-note, I found it confusing that you had references to 'colors' in different places that meant different things.
From emberjs.com/guides
This will offload searching all of the possible records to the server,
while still creating a live updating list that includes records
created and modified on the client.
App.PostsFavoritedRoute = Ember.Route.extend({
model: function() {
var store = this.store;
// Create a filter for all favorited posts that will be displayed in
// the template. Any favorited posts that are already in the store
// will be displayed immediately;
// Kick off a query to the server for all posts that
// the user has favorited. As results from the query are
// returned from the server, they will also begin to appear.
return store.filter('post', { favorited: true }, function(post) {
return post.get('isFavorited');
});
}
});
I seem to be having a bit of an issue with computed properties on ember 1.7.0 inside of a component. Let's say I have 2 models, A & B. A has a "belongsTo" relationships to B.
On my component template I have an Ember.Select control which will allow one to select a B entry for A.
{{view Ember.Select class="form-control" content=bList selection=objectA.objectB optionValuePath="content.id" optionLabelPath="content.name"}}
In my component I have a computed property which watches for changes on objectA.objectB like so:
isSomething: function() {
return this.get("objectA.objectB.id") === "some id";
}.property("objectA.objectB"),
Then in my component template I conditionally display something based on the value of isSomething:
{{#if isSomething}}
Something :D
{{/if}}
If I place a breakpoint in the isSomething computed property, and I select a new value on the select control, it hits the breakpoint as expected. It will hit again after I select a new value. But if I then select the original value again, the breakpoint will not hit (and the component will not rerender). It seems to only be hitting the first time I select any given value in the select control. At first I thought this may be an issue with caching, but adding volatile() to the property didn't seem to make a difference.
I can get around this by binding the select control selection to a property on the component like tempObjectB, and changing the property being monitored by isSomething to tempObjectB as follows:
isSomething: function() {
return this.get("tempObjectB") === "some id";
}.property("tempObjectB"),
Then to keep objectA's reference updated I can use a method which observes all changes to tempObjectB and updates objectA.objectB as follows:
updateObjectA: function() {
this.set("objectA.objectB", this.get("tempObjectB"));
}.observes("tempObjectB"),
These changes will allow the breakpoint to be hit every single time I change the value in the select control.
Does anyone know what could be causing this behaviour? I'd rather not resort to creating a temporary variable.
http://jsbin.com/qeyite/1/edit?html,js,output
http://jsbin.com/kugino/3/edit?html,js,output
The only difference between the two is that the first bin is storing and watching on objectA.objectB, while the second bin is storing and watching on tempObjectB.
For anyone who stumbles accross this, it appears to be fixed as of latest Ember.js release (1.8.1).
https://github.com/emberjs/ember.js/issues/5578
I have a model property called players which is a record array returned by an ember-data store. I would like to access one of its items in the view without the need of iterating it whole. I know that if I need the first one Ember.Enumerable has a firstObject method that I can use like this:
{{ players.firstObject.name }}
but for other indices, the thing gets tricky:
{{ players.[1].name }} // does not work for Ember records
as the handlebars notation players.[1].name does not work for record array. But it works for native JS arrays. So one solution is to override the property on the controller like this:
players: (function() {
return this.get('model.players').toArray();
}).property('players'),
Another would be to make a Handlebars helper maybe. But my question is (finally): why isn't it working by default? Is there a reason why I shouldn't be converting it into an array? Are there methods that I can mixin into the record array so that it will work as expected in handlebars?
rarely do you need access to an item in the array that isn't the first or last, and if you do then you generally know something magical about item at index n. If so, create a computed property to return that object. It makes for more maintainability as well.
coolPlayer: function(){
return this.get('players').objectAt(1);
}.property('players.[]')
Then just use coolPlayer in your template
{{coolPlayer.name}}
You should usually get the index of the item somehow through the route, if not by association to another model or parameter then by filtering based on parameters coming from the route. Otherwise the necessary data to select an item should come from some controller (or even less likely a view etc.).
After that your either displaying a list (with each) or a single item, both of which are computed properties, based on variables in your app.
Handlebars is meant to be logic-less.
However to select an item at a certain index use .objectAt(index). Just not in the template.