I have a parent component with a state holding the data and a handler to update it upon any input element change.
Now I want to improve performance as this handler is doing quite a lot of validations, so I would like to wait until the last keystroke before starting the validations but I still want the input to react to users typing.
The issue is when I debounce my handler, it works but in the time of the debounce delay if I switch to a different input element fast and start typing it will delete the old event so I'm stuck with the result inside the element but with validation only on the second element.
I tried to create a new function that will call that handler to make each a diffrerent instance so they wont block each other but it didnt work.
Tried to copy the function and then run it, no luck.
I also tried setting timeout by myself, which did work but then I didnt manage to find a way to stop it on new event :(
Im stuck with debouncing the handler on the constructor level:
this.onAdvancedChange = debounce(this.onAdvancedChange, 500);
which is exactly what I described before.
There is two handlers one which is not debounced to react to input typing:
onSimpleChange({ target }) {
const newData = ...; // Im copying target data to a new object here to pass instead.
setState(..., () => this.onAdvancedChange({ target: newData }));
}
onAdvancedChange({ target }) {
validate(target);
if needed -> setState(...);
}
It does work, but again when I switch fast to a different input and start typing while the prev debounce didnt hit yet, it wont hit at all and only the second will validate.
How Do I make my advanced handler private for each input call, or maybe the other option I tried to use timeout but then how to stop it on every new event for each input individualy?
Thank you a LOT in advance.
Related
I have the following buttons:
<button id="abcd" onclick="something()">click</button>
and the following functions are attached to this button apart from the one in its html definition.
$('#abcd').on('click',function(){alert("abcd");});
$('#abcd').on('click',function(){
someAjaxCallWithCallback;
});
Now I want a new function with another ajax call to execute on this button's click, before the above mentioned functions. This new function determines whether the remaining functions would be called or not based on what data is recieved by the ajax call. That is, this pre function should complete its execution before giving control over to the rest of the functions and also determine whether they would run or not.
As an example, without changing the existing validation logics and button code, I have to add a new pre-validation function and similarly and post validation function.
I have a bindFirst method using which I can at least bring my new function to the beginning of the call stack but I have not been able to contain its execution and control further delegation because of callbacks.
If I understand correctly, you are looking for the way to do this, without modifying html and already existing js, only by adding new js-code.
First of all, if onclick handler is set and you want to control it, you should disable it on page load (maybe, saving it to some variable):
$(document).ready(function() {
var onclick = $("#abcd").attr("onclick").split("(")[0];
//to run it in future: window[onclick]();
$("#abcd").attr("onclick", "");
});
Edit: I changed my answer a little, previous approach didn't work.
Now you need to remove all already existing handlers. If number of handlers you want to control is limited, constant and known to you, you can simply call them in if-else after pre-validation inside your pre-function. If you want something more flexible, you are able to get all the handlers before removing, save them and then call them in a loop.
For that "flexible" solution in the end of $(document).ready(); you save all already existing handlers to an array and disable them. Then you write your pre-function and leave it as the only handler.
var handlers = ($._data($("#abcd")[0], "events")["click"]).slice();
$("#abcd").off("click");
$("#abcd").click(function() {
//this is your pre-func
//some code
handlers[1].handler.call();
});
Try console.log($._data($("#abcd")[0], "events")) to see, what it is.
Finally just run your post-function and do whatever you need, using conditions.
So, the general algorithm is as follows:
Disable onclick
Save all handlers
Disable all handlers
Run pre-func first
Run handlers you want to be executed
Run post-func
In fact, you just make your pre-func the only handler, which can run all other handlers you may need.
Although Alex was spot on, I just wanted to add more details to cover certain cases that were left open.
class preClass{
constructor(name,id){
if($(id) && $(id)[0] && $(id)[0]['on'+name])
{
var existing = $(id)[0]['on'+name]
$(id).bindFirst(name,existing);
$(id).removeAttr('on'+name)
alert("here");
}
if($._data($(id)[0],"events")){
this.handlers = $._data($(id)[0],"events")[name].slice();
}
else
{
this.handlers = null;
}
this.id = id;
this.name = name;
}
generatePreMethod(fn,data)
{
$(this.id).off(this.name);
$(this.id).bindFirst(this.name,function(){
$.when(fn()).then(execAll(data));
});
}
}
function exec(item,index){
item.handler.call()
}
function execAll(handlers){
return function(){ handlers.forEach(exec);}
}
This more or less takes care of all the cases.
Please let me know if there is something I missed!
The onChange event for a field on my opportunity form is being called twice and I'm trying to track down the source of the second call. I've already passed the execution context into the onChange function but don't know any way to see the source of the call from there. Now I'm wondering if there's a way to see the pending events for an XRM page, does anyone know where that information is?
My code is basically this, tied to onLoad of the opportunity form. There could be a plugin or 3rd party library or something updating the field, but nothing jumps out.
function onLoad() {
Xrm.Page.data.process.addOnStageChange(handleStageChange);
}
function handleStageChange() {
var dateFieldName = "new_enteredstage1"
var dateFieldAttr = Xrm.Page.getAttribute(dateFieldName);
if (dateFieldAttr) {
dateFieldAttr.setValue(new Date());
}
}
If new_enteredstage1 is null when the stage change occurs then 2 calls occur and the value is set and immediately reset to null. If new_enteredstage1 has a value the value is updated as expected. Again, there could be some third party code that I'm missing but I have no idea how to track it down.
UPDATE:
This only happens on date fields, and it happens on all date fields. If I replace the code with a number field the value is not reset to null.
There's a know issue (example) related to the "new" form rendering engine (Turbo Forms) that can cause this issue.
You can try using the legacy form rendering engine and see if it solves the problem:
I am trying to programmatically update a currency field to run the value changed event which holds a numeric calculation. I want the value to set to zero using something like.
$('.tester').igCurrencyEditor("setFocus");
$('.tester').igCurrencyEditor('option','value', 0);
Then when I blur out, or not sure what to do here, the valueChanged event should trigger as per the API docs (It can be raised on lost focus or on spin events).
But I can't seem to trigger the value changed event, it only works when I manually click into the input and change the number.
The valueChanging and valueChanged events would trigger when a user interaction changes the displayInput value of the editor, and the corresponding valueInput value is different from the display input one. The editors have adopted the same approach as all other Ignite UI controls where events do not trigger on API calls, because when an API call is performed, the developer can choose whether to invoke their event handler after the API call, or not.
There's two things that you can do to invoke your event handler. First one is to cache the event handler method and invoke it manually:
$('.tester').igCurrencyEditor({
...
valueChanged: valueChanged,
...
});
function valueChanged(event, ui) {
// event handler
};
$('.tester').igCurrencyEditor("setFocus");
$('.tester').igCurrencyEditor('option','value', 0);
valueChanged(null, { /* if you need arguments */ });
The second one is to extend the currency editor and override the method that performs the check whether these events should be triggered, and make it always trigger the events:
$.widget("ui.igCurrencyEditorExtension", $.ui.igCurrencyEditor, {
_processValueChanging: function (value) {
this._triggerInternalValueChange(value);
}
}
The second approach requires you to switch to using the igCurrencyEditorExtension and may cause side effects, as the method performs other checks as well.
Anyways, what Alex Marinov has suggested should work, but it depends on your editor configuration, depending on whether you've set nullValue, allow null values in the editor, etc.
you need a function like this:
function clearValue() {
$('.tester').igCurrencyEditor('option','value', "");
$('.tester').igCurrencyEditor('field').blur();
}
The result will be that the displayed value inside the currency editor is "$0.00" and the valueChanged event is fired.
I've made a small script using some of the HTML5 files features, which allows you to select one or more files, and each time it will write the name of the file(s). Everything works as it should, only the event to detect the value change of the files input fire only once, so how can I make it fire every change and not only on the first change?
By the way, here is what I made:
http://tamir.netspot.co.il/html5/files/
If you want to upload twice, clear file input value
$('input[type="file"]').val(null);
jsfiddle test
It appears that the change event listener is being removed because you're using innerHTML to update the same element (wrapper) that the input itself is inside. So the contents of the wrapper element – including the file input – is being re-rendered, and along the way, the event listener is removed (or, rather, it's connected to an element that's no longer there).
Here's a simple jsfiddle that does exactly the same as your code, except that it prints the selected file names in a different element than the element the input is in. And it works (in WebKit, anyway)
Here's further proof (I basically copied your code, and only added a line to re-register the event listener after the modification of wrapper.innerHTML)
So, the change event does fire for each change, but the input that's being observed is removed by the use of innerHTML on the input's parent element.
I honestly don't know whether this is a legitimate browser bug or not. It makes sense for innerHTML to "overwrite" the existing input element, yet the browser is smart enough to not not reset the input's value, so you'd think listeners would stick around too… so… well, it's confusing
I'm not sure why but none of the answers to this old question are all that simple. Here's the way to do this easily today...
with jquery...
$('#myfileinputfieldid')[0].onchange = function(e) {
//do something with e. Like write an image to a canvas or make a yummy cup of coffee
e.target.value = '';
};
that's it after you have changed the value to something other than the file that was selected the next time the file input is clicked the onchange
event will fire.
Basically, if you still have a value for your input, no extra event would be fired. I'm working with react and i Had to clear the value of the input for the next event to be triggered.
Using a ref, you can do something like this.
buttonRef.current.value = null;
Instead of using onchange use oninput event
$scope.ShowIcon = function (input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#iAIcon')
.attr('src', e.target.result)
};
reader.readAsDataURL(input.files[0]);
}
}
None of the above worked for me, I actually had to create a new "dummy" file input field each time it was changed - allowing me to capture the change event again.
The process for me was:
OnChange
- move file input to another element
- create a new file input to capture the change event again
addEventListener wont work for IE8(Not sure about IE9 onwards). We need to use attachEvent listiner. If you need cross browser support then use this
if (!inputfile.addEventListener) {
inputfile.attachEvent("onclick", setCheckedValues); //IE8
}else {
inputfile.addEventListener("click", setCheckedValues, false); //Other browser
}
ok well according to #Flambino the input is being re-rendered. For whatever reason this may be, for me its irrelevant.
The $.on('change', callback) functionality is lost.
Try using .delegate function which I absolutely love!
http://api.jquery.com/delegate/
Ok so delegate is exactly the same, it just tells jquery if there is an element rendered on screen with a particular handle, attach a functionality to it.
So even if the element is re-rendered, it will still keep to function.
$(document).delegate('.file_upload_btn', 'change', function(){});
You may think this is a throw away function & say whats the difference but this has saved me a lot of time on projects.
I got the .change callback to fire on every new file by reassigning the .change function at the end of its own callback:
$('#myfileinputfieldid').change(function (event) {
scope.processFile(event.target.files[0]);
});
scope.processFile = function(fileStruct) {
doStuff;
// Reassign the onchange callback.
$('#myfileinputfieldid').change(function (event) {
scope.processFile(event.target.files[0]);
});
};
In my case i use ajax to upload file.
I just clear the value of input with onclick event handler.
$('#myFile').click(function(e) {e.target.value = '';});
$('#myFile').change(function(e) {
var file = e.target.value;
var formdata = new FormData();
formdata.append('file', file, 'somefile');
$.ajax({
type: 'POST',
url: './uploadFile',
data: formdata,
processData: false,
contentType: false,
success: function(data){
}
});
});
I have a Dojo SubmitButton with jsId="saveParamButtonWidget". I overrided its onClick method by putting:
saveParamButtonWidget.onClick = editParam
I defined the editParam() function like this:
function editParam(eventObj) {
dojo.stopEvent(eventObj);
// ...
}
dojo.stopEvent() is supposed to stop event bubbling and default processing. However, the browser will submit the form anyway. I also tried with the following:
function editParam(eventObj) {
eventObj.stopPropagation();
eventObj.preventDefault();
// ...
}
Same thing. The only way I've managed to prevent form submission is by returning "false" from the event handler:
function editParam(eventObj) {
// ...
return false;
}
Can someone tell me why the first two ways did not work? Thanks.
Okay, after doing some digging through the source, I believe I can answer your question definitively.
The reason dojo.stopEvent() doesn't work, but return false does, is entirely due to how dijit.form.Button is coded. If you're interested, it's time for a little field trip. Keep your hard hats on.
When a dijit.form.Button is clicked...
The button's _onButtonClick method is invoked. (This is hooked up in the template, to the special ondijitclick event which captures not only mouse click but also certain keypresses, for a11y purposes.)
The _onButtonClick method first invokes the _onClick method, which, presuming the button is not disabled (which it's not in this case), invokes and returns the result of the onClick method. This is of particular interest since it's the method you're overriding!
Coming back to _onButtonClick, if _onClick returned precisely false (e.g. if your onClick handler returned false), _onButtonClick immediately bails out. This is why returning false makes your code work as desired. But what happens if it doesn't bail out there? Let's follow the trail further...
Next, _onButtonClick checks whether this button not a descendant of an actual HTML form, but is a descendant of a widget with an _onSubmit method (duck-typing). I'm assuming that in your case it is inside a real form (dijit.form.Form counts), so we'll skip over this. (I am under the impression that this code path wouldn't actually end up submitting, whereas yours apparently does.)
One final condition is checked: if the button has a valueNode defined (it does), the click method of this node is invoked. Unfortunately, this produces an entirely new event object on an invisible input type="submit" node under your form, and thus anything you tried to tell the original event is rendered immaterial, and the form goes on to submit! This is why dojo.stopEvent did not work - this code in dijit.form.Button pays it absolutely no heed.
I cooked this up as a somewhat-limited proof of concept (be sure to open firebug/etc. to get the logs): http://jsfiddle.net/Bf5H8/
Perhaps this is something that should be logged as a bug, but I suppose the initial thought may have been that supporting the well-known return false mechanism would be enough.
All this being said, it's quite possible that overriding onSubmit of the form is more in-line with your interests than overriding the button's onClick anyway (as S.Jones suggested), but at least this should solve the mystery.
Interesting question. +1
I believe you have to use dojo.connect to connect your function to a DOM event to get access to those methods with an event object.
See: The Event Object (DojoTollkit.org Reference Guide)
The Event Object
When you connect a function to a DOM
event with dojo.connect,
Dojo passes your function a normalized
event object. This means that,
regardless of the client's browser,
you can count on a set of standard
attributes about the event and a set
of methods to manipulate the event.
Assume that your function has been
called by dojo.connect and takes an
argument named event, like:
dojo.connect(dojo.byId("node"), "onclick", function(event){
// the var 'event' is available, and is the normalized object
});
...
Dojo normalizes the following methods with an event object:
event.preventDefault — prevent an event's default behavior (e.g., a link from loading a new page)
event.stopPropagation — prevent an event from triggering a parent node's event
Additionally, dojo.stopEvent(event)
will prevent both default behavior any
any propagation (bubbling) of an
event.
That said, placing a function like the one below in your form to perform some logic before submitting it, is a fairly clean, easily understood & maintainable approach.
<script type="dojo/method" event="onSubmit">
if (!this.validate()) { // or whatever else you'd like to evaluate
// insert calls here...
return false;
}
return true;
<script>
Cheers.
I had the same issue for using dojo.stopEvent
This issue is solved the form submission issue like this - here it is a simple form used to connect through dojo:
this.formId = dojo.byId("formId");
dojo.connect(this.formId, 'onsubmit', function(evt) {
var val_main = validate_this_form(0);
if(val_main == false)
dojo.stopEvent(evt);
});