I have code in action that works fine if the person clicks only a single time on the button however if they click multiple time fast on the button, that code get run many times and creating weird issues. Anyone know how to prevent this from happening?
First solution is disabling the button after clicked by setting the disabled property of the button.
Although setting disabled property of the button can completely solve the solution; spreading the re-enable logic to the code is not nice, when some async operation is being executed.
Further more, re-enabling the button can be more complex when you have some other conditions to disable the button. (e.g. If a field on the form has an invalid value, you may again want to disable the button.)
We mostly prefer ember-concurrency addon for such needs. Its task structure provides a more readable code for most cases.
You want to set the disabled attribute of your <button>/<a> to prevent users from triggering it again while you are processing the first action. To do so, I typically use a flag to determine if that action is currently processing. If the flag is set, set the disabled attribute. It's also a good idea to have a guard statement in your action as well. As some browsers may not respect the disabled attribute.
Controller/Component:
import Ember from 'ember';
import moment from 'moment';
export default Ember.Controller.extend({
isLoading: false,
actions: {
yourAction() {
// It's a good idea to use a guard statement here as well. Just incase the browser doesn't respect the `disabled` attribute.
if(this.get('isLoading')) {
return;
}
this.set('isLoading', true);
// Do your work here. If it's a promise, use `finally` to unset the flag to avoid forever loading on errors.
this.set('isLoading', false);
}
}
}
Template:
<button {{action 'yourAction'}}
disabled={{isLoading}}>
Your Action
</button>
Using this method, you can also add some loading styling on your action as well. Perhaps a spinner as pseudo element:
Template:
<button class="{{if isLoading 'loading'}}"
{{action 'yourAction'}}
disabled={{isLoading}}>
Your Action
</button>
Edit
As ykaragol mentioned, if you want/can add a library to your Ember app, ember-concurrency will greatly simplify the native Ember example above. They have a guide here which refactors the example above into a more readable example using ember-concurrency.
Add this to your button.
onclick="this.disabled=true;"
Example:
<button onclick="this.disabled=true;">Click</button>
When you click the button, the onclick runs immediately and disables the button to prevent additional clicks.
Related
Browser native confirm dialog doesn't look good.
I have ember route wherein taking user confirmation to proceed if there are any unsaved changes.
I wanted to change the dialog with bootbox confirm dialog but that doesn't stop the execution as bootbox.confirm() method is async.
willTransition: function(transition){
// model dirty checking logic to set flag value
if(flag){
if(!confirm("Do you want to leave this page without save?")){
transition.abort();
}
}
}
Note: ES6 is not used in project
Since it doesn't block, the only way to do this is to:
Act as if the user said "No" then
If they click "Yes": programmatically restart whatever it was that was cancelled at step 1.
In ember, I have a form which ask the user their username and password for our remote server and then I want to verify the credentials by making an ajax call. Currently, I am using a custom made component which allows me to bind any action on clicking next or submit where I can verify the credentials and throw errors if needed. But once an error will occur, user cant click the next button (the way component is implemented) and hence second time validation is not happening. So, I thought to make a computed property which will look for username and password (and hence make ajax call when entered). But the problem with this approach is: every time something is entered in these boxes, computed property gets triggered which makes an ajax call (i dont want to make so many network calls for no purpose). Is there a way in ember where we can wait for user to finish the input and then invoke any kind of action?
Better approach is to use oninput with action:
<input oninput={{action "onInput" value="target.value"}}>
and then from action onInput debounce call to ajax function using debounceTask from ember-lifeline addon(this example), or using ember-concurrency:
actions: {
onInput(val){
debounceTask(this, 'sendAjax', val, 500);
}
}
I am currently learning Ember and I have come across a problem that I would like to solve.
I have a component that presents X amounts of buttons that are linked to actions on the component.
When one of these buttons are pressed the proper action should execute and in executing it should add a newly instantiated component to another component.
I realize that the proper way would not be to let component X know about component Y, but what does the proper solution look like?
I solved my issue by daisy chaining an action from the source component to the destination component(the one I wanted to handle the action).
Component JavaScript:
export default Ember.Component.extend({
actions: {
elementSelected: function(){
this.sendAction('elementSelected', this.model);
}
}
});
Defining an action is not strictly required, it could be any code in javascript that actually calls sendAction. It is the sendAction that exposes an event to the world outside of the component.
Component template:
<div {{action 'elementSelected'}}>
</div>
Any subsequent component in the chain needs to bind the action to a local action. The implementation of that action is almost the same as the first component (note that we now pass whatever object/s from the original action through).
The Binding:
{{component-some-component type="text" elementSelected="elementSelected"}}
The Implementation:
export default Ember.Component.extend({
actions: {
elementSelected: function(model){
this.sendAction('elementSelected', model);
}
}
});
This solution really still leaves some issues:
1) Every component in the chain needs to know the action explicitly.
2) The verbosity re-sending the action at each level.
Sadly this has a lot to do with how actions currently bubble in Ember 2.3<.
By default, the {{action}} helper triggers a method on the template's
controller, as illustrated above.
If the controller does not implement a method with the same name as
the action in its actions object, the action will be sent to the
router, where the currently active leaf route will be given a chance
to handle the action.
Source: https://guides.emberjs.com/v1.10.0/templates/actions/
(this particular link is for 1.10, but this is still valid in 2.3)
As stated above, actions do not bubble through the component hierarchy, so what my verbose daisy-chain solution actually does is to explicitly bubble a specific event through the component hierarchy.
This might also be solved by a service or a helper class, but in my opinion component actions exposed to the outside world should bubble through the component chain before they land in the route.
I am new to Ember and I am not sure how to do things the Ember way so I turn to you.
My problem:
I have a sidebar, I created a View for it. I have two buttons on the sidebar for the moment. I added an action for each button on it. I am not sure if I should handle it on controller or on view. I want on clicking one of these button, a new view to be inserted that would open a pop up menu and also the button that called the action to remain in a selected state.
I am not very sure how to do this. I tried to target the view with the action but I can't have access to the target element or at least I don't know how to access it (tried this.$()).
What way do you suggest to follow?
User 'actions' are handled with methods on a Controller or a Route. You should put them in an actions hash:
App.MyController = Ember.ObjectController.extend({
actions: {
doSomething: function() {
// do it here
}
}
});
Ember manipulates the DOM and inserts views automatically based on resources and routes. If you don't want to use the router, you can manually control the view hierarchy, but I'd suggest getting more familiar with Ember routing before you try manual views.
If I were you, I'd create a Component that handled the button. You will have a reference to the DOM element in the didInsertElement callback: http://emberjs.com/api/classes/Ember.Component.html#event_didInsertElement
I use Ember data with the REST Adapter. I want to make sure that in case of slow server responses, the application does not fail.
I have simulated this bij adding at server side a sleep method of 5 seconds before returning the JSON response.
If you have a form with a SAVE button, and you click this button while a previous save is still is progress, you receive a inFlight error and the whole Ember app freezes (only thing you can do is reload app). So, you can easily disable the save button by checking the isSaving state:
<button {{action 'save'}} {{bindAttr disabled="isSaving"}}>Save</button>
Now it also seems that when changing a form field while a previous save is still is progress, you receive a inFlight error. This would thus indicate that I also need to disable the complete form.
Uncaught Error: Attempted to handle event `willSetProperty` on
<App.Author:ember477:5203e34599808d1c6c000001> while in state
rootState.loaded.updated.inFlight. Called with {reference: [object Object], store:
<App.Store:ember541>, name: name}
Is there a known good practice to handle these cases ... I want to prevent that I need to add a lot of logic (disable buttons, set fields readonly, etc.) for these edge cases.
It may not be within the scope of what you are trying to do, but the Ember Persistence Foundation is designed to allow updating your models while a save is still in flight.
It is relatively trivial to migrate your models to EPF, but there are some changes required in the controller code, see "Migrating from Ember Data".