I have a service which I call state that just handles loading states and I can expand it to whatever I may want in the future. I needed this service so I could have different components talk to each other in a way so they would know if another component is loading or doing something.
Anyway I have this button that I use in different places that just sends an action, and then turns into a spinner until that action is complete.
They way the loading animation works currently is classNameBindings: ['state.working'],
However if I now have two of these buttons on screen at the same time, and I call this.state.set('working',true); all the buttons are now spinning.
I would rather pass in a property name to the button component that tells it what property to watch on the state service to determine if it should add the working class or not
I'm just having some trouble figuring out how to make this work in the component.
How can I have the class binding watch for a dynamic property name that will be passed to the component as something like loadingPropertyName so each button component can watch a different property for it's working class binding.
You can do something like this:
init() {
this.set('classNameBindings', 'state.'+this.get('stateProp'));
this._super();
}
Related
I'm playing with ideas for making a drag and drop interface. I have multiple components that can be dragged into position over a grid. I will have a button that will allow the user to select different arrangements.
I want a function on the component that sets its position (quite simply with left and top). I want to be able to call this function when the component is inserted, and when a different arrangement is selected.
What I want to do is create an event in the route's controller. I then want each component to be able to listen for the event, and react as necessary. I'm afraid I don't understand enough to make this work. I've read the API, and a couple of questions on here (1, 2), but no luck.
Here's what I have...
The route's controller:
import Ember from 'ember';
export default Ember.Controller.extend(Ember.Evented, {
actions: {
callPosition: function(){
this.trigger('position');
console.log('Trigger set...');
},
})
And the component:
import Ember from 'ember';
export default Ember.Component.extend(Ember.Evented, {
didInsertElement : function(){
this.get('controller').on('position', this, this.position);
},
position: function(){
console.log('event heard by the component');
},
A few things I don't understand:
Have I added the Ember.Evented mixin correctly?
I've added the listener in the didInsetElement event as per the example. Is this the correct way to ensure the component will listen for the event throughout its lifetime?
What's the scope of the event? Can it only be listened for so long at we 'get' the controller that set it?
Your advice is a great help to this budding amateur!
You should not try to fire an event on components from the controller! What you should do is to pass the variables (left, top) and then register an observer on them on the component
sizeChanged: Ember.on('didInsertElement', Ember.observer('top', 'left' function () {
// arrange it
}))
Now you pass down these attributes when you call the component:
{{my-component top=myCoolDnDElement.top left=myCoolDnDElement.left}}
And the sizeChanged will automatically be called whenever the top or left attribute changes as well as on didInsertElement.
This question is not about components, but about controller/view pairs which are not longer part of ember.
Back then every controller had an associated view, which often lead to confusion when to place things on the controller and when on the view.
Back then the separation was between controllers and views, where controllers managed global and local state and views basically managed the rendering.
With the introduction of components this has changed. Components handle local state as well as the rendering for that. Basically every component is a view. But its not anymore magically coupled to a controller, but explicit called from the template. For global state services were introduced.
Today you should follow the DDAU (Data Down Actions Up) principle and pass all actions and attributes explicit to the components in the template.
Components call actions on the parent component and pass updatable attributes and callable actions to child components. Thats how you should handle it.
I have a component working pretty well and now I need to call it inside a controller.
Scenario: I have an ember application, and I have an update button controller, I did a component that just display a toast(Materializecss) with some message I pass as parameter to the component and both the button and the toast are working well separately. I need to call inside the button controller this component to display to the user if the update was successfully or not using this component I did. Any sugestion of how can I call this component inside the controller? Thanks
have a look at the ember-twiddle I created and see if it fits the bill, in regards to what you want to do ?
You should instead of thinking "calling the component", rather, how can I push updated attributes/data to the component.
Ember relies on the "Data Dow Actions Up" pattern. This implies that you cannot make an explicit call to a component action from a controller. (see https://dockyard.com/blog/2015/10/14/best-practices-data-down-actions-up for example)
Instead, a better design should be to define a service to manage data : messages to be "toasted". Then make this service available by injecting in in your controller. You will be able to call methods to register a new messages and generate new data.
Provide also a component template (to be included in your own templates) that will be in charge to display the new message, etc. Each change in the data managed by the service will lead to a component template update.
You should definitely take a look to https://www.npmjs.com/package/ember-toastr
I'm using Ember 2.3 and have built a controller around Ember Highcharts. When a chart point is clicked I send that event back to the controller via the following nasty bit of code added to the highcharts configuration object:
Graph.__container__.lookup('controller:accounts.account.outlets.outlet.ratings').send('pointClick', event);
(I know this is fundamentally wrong and should be using getOwner.lookup for the container reference)
This works without issue, but I'm now refactoring this controller to a component and can't think how to pass the graph event back to the component as components are not registered in the app container.
Any pointers to how to achieve this would be very much appreciated!
Thanks.
UPDATE
I've just discovered that I can send an action to a component from the Highcharts event function:
Graph.__container__.lookup('component:ratingsGraph').send('pointClick', event)
But this seems to create a new instance of the component, rather than send to the existing instance. I guess that makes sense as components aren't singletons.
Very frustrating!
Some days you can't see the wood for the trees!
Instead of defining the highcharts event function in the highcharts configuration parameters, I simply defined it within the component and passed it to highcharts. This enabled me to use a closure to hold reference to the defining component.
Doh!
I am wondering what is the best way to control behaviour on a component.
In my case I have a {{stop-watch}} component.
I want to start, stop and reset the component via the routes using the {{stop-watch}} in their template. The start and reset function should allow me to somehow pass the number of seconds to run for.
How can this be done when a component only really supports bindings and not the ability to execute behaviour?
This is the only way I can think of doing it. In this case; isStarted,isStopped and isReset would be boolean variables and I would toggle them to control the component.
{{stop-watch start=isStarted stop=isStopped reset=isReset timeout=timoutSeconds}}
Toggle like this for each property binding in the controller
this.set('isStarted', !this.get('isStarted'));
Observe like this for each property in the component
startUpdated : function() {
//start the timer
}.property('start')
In my opinion the above solution is very inelegant and verbose and there must be a better way to achieve this.
Are the any best practices for this scenario?
You should have a model that possesses a state and methods to control the state.
You set up an instance of the model in the route, then you'll be able to control it both in the controller and the stop-watch component.
The component will automatically update its looks based on the properties of the model, and will be able to call methods on the model via actions on the component.
Quick context:
Application view has 2 outlets. One for a toolbar. The other for the routable "main" view hierarchy.
app -- main
\-- toolbar
I need some buttons in the toolbar to trigger events in the "main" view. Not update any data in any model. I simply instruct it to trigger some changes to a drawing library that the view is presenting. Clearing the canvas, resetting zoom value and such.
In 1.0 pre2 and earlier I have used actions and router.get('someController.view') to get access to the view I want and trigger the action/method/event. Hardly the pinnacle of application design but it worked fine.
This option is now gone and I am at a loss for a good alternative. What mechanism should I use when communicating between views that are not in a child/parent hierarchy? Everything I have come up with is clunky and triggers my sense that "Ember has a better way".
In short I want:
A toolbar-button to trigger an event
The main view to react to this and perform some updates on parts of itself.
The main view to NOT re-render in the Ember sense of the word as would through routing. It uses a drawing library and integrating all it's properties and behavior into Ember models and controllers would not be a lot of fun.
The toolbar and the main view share a parent view but are on different "branches".
Poor Options I am considering:
The toolbar is very much an application level concern but it does have some buttons that need to instruct specific views. One option I see within Ember is to nest the toolbar under the "main" view. This seems wrong for some of it's other functions.
Communication could be handled by a controller (and possibly even a model) that would hold properties that the toolbar sets and the "listening" view reacts to and resets the value of. This sounds like abuse of the controller and model purposes and like a pretty poor event listener setup.
I could make the drawing library an application global as App.Drawing or something but that does also seems bad. It would also mean that the actions still would not be able to make use of any data in the view to update the drawing library with.
Any suggestions?
What mechanism should I use when communicating between views that are not in a child/parent hierarchy?
In a typical ember application this communication should happen between controllers. Otherwise "Poor Option 2" is on the right track:
Communication could be handled by a controller (and possibly even a model) that would hold properties that the toolbar sets and the "listening" view reacts to and resets the value of.
Consider using two controllers. Toolbar actions will target the ToolbarController, which is responsible for maintaining the toolbar's state and for changing main in response to user action. ToolbarController should declare a dependency on MainController via the needs property. For example:
App.ToolbarController = Ember.Controller.extend({
needs: ['main'],
buttonOneGotAllPressed: function() {
main = this.get('controllers.main');
main.turnOffAnOption();
main.makeSomeOtherChange();
}
});
Now MainController can be focused on the state of MainView. It should not be aware of ToolbarController or it's buttons.
This sounds like abuse of the controller and model purposes and like a pretty poor event listener setup.
Agreed it would likely be an abuse of model purposes but it is exactly what controllers are for.
This is definitely not an event-listener setup, but that does not seem a good fit in your case. The toolbar you describe seems to exist only to interact with the main view, so it makes sense to have a toolbar controller that depends on main and interacts with it directly.
If the components are truly decoupled then an observer (Pub/Sub) pattern might be more appropriate. See How to fire an event to Ember from another framework if that's of interest.