I have the following problem:
I have an HTML textbox (<input type="text">) whose contents are modified by a script I cannot touch (it is my page, but i'm using external components).
I want to be notified in my script every time the value of that textbox changes, so I can react to it.
I've tried this:
txtStartDate.observe('change', function() { alert('change' + txtStartDate.value) });
which (predictably) doesn't work. It only gets executed if I myself change the textbox value with the keyboard and then move the focus elsewhere, but it doesn't get executed if the script changes the value.
Is there another event I can listen to, that i'm not aware of?
I'm using the Prototype library, and in case it's relevant, the external component modifying the textbox value is Basic Date Picker (www.basicdatepicker.com)
As you've implied, change (and other events) only fire when the user takes some action. A script modifying things won't fire any events. Your only solution is to find some hook into the control that you can hook up to your listener.
Here is how I would do it:
basicDatePicker.selectDate = basicDatePicker.selectDate.wrap(function(orig,year,month,day,hide) {
myListener(year,month,day);
return orig(year,month,day,hide);
});
That's based on a cursory look with Firebug (I'm not familiar with the component). If there are other ways of selecting a date, then you'll need to wrap those methods as well.
addEventListener("DOMControlValueChanged" will fire when a control's value changes, even if it's by a script.
addEventListener("input" is a direct-user-initiated filtered version of DOMControlValueChanged.
Unfortunately, DOMControlValueChanged is only supported by Opera currently and input event support is broken in webkit. The input event also has various bugs in Firefox and Opera.
This stuff will probably be cleared up in HTML5 pretty soon, fwiw.
Update:
As of 9/8/2012, DOMControlValueChanged support has been dropped from Opera (because it was removed from HTML5) and 'input' event support is much better in browsers (including less bugs) now.
IE has an onpropertychange event which could be used for this purpose.
For real web browsers (;)), there's a DOMAttrModified mutation event, but in a couple of minutes worth of experimentation in Firefox, I haven't been able to get it to fire on a text input when the value is changed programatically (or by regular keyboard input), yet it will fire if I change the input's name programatically. Curiouser and curiouser...
If you can't get that working reliably, you could always just poll the input's value regularly:
var value = someInput.value;
setInterval(function()
{
if (someInput.value != value)
{
alert("Changed from " + value + " to " + someInput.value);
value = someInput.value;
}
}, 250);
Depending on how the external javascript was written, you could always re-write the relevant parts of the external script in your script and have it overwrite the external definition so that the change event is triggered.
I've had to do that before with scripts that were out of my control.
You just need to find the external function, copy it in its entirety as a new function with the same name, and re-write the script to do what you want it to.
Of course if the script was written correctly using closures, you won't be able to change it too easily...
Aside from getting around the problem like how noah explained, you could also just create a timer that checks the value every few hundred milliseconds.
I had to modify the YUI datable paginator control once in the manner advised by Dan. It's brute force, but it worked in solving my problem. That is, locate the method writing to the field, copy its code and add a statement firing the change event and in your code just handle that change event. You just have to override the original function with that new version of it. Polling, while working fine seems to me a much more resource consuming solution.
Related
TLDR Below
JS Fiddle To Demo
I've been really involved in recreating the tools that are foundations of premiere JS Libraries to better improve my skills. Currently I'm working on functional data-binding a la Angular.
The idea of data-binding is to take data and bind it to elements so that if manipulated all elements subscribed will change accordingly. I've gotten it to work but one thing I hadn't considered going into it was the issue with innerHTML vs value. Depending on the element you need to change one or the other( in the demo above you'll see that I needed to specifically single out the button element in a conditional statement because it has both, but that's kind of a fringe case )
The issue is that in order to capture a SPAN tag update I needed to trigger an event to happen, and the easiest one to manipulate for Text Boxes/Textareas was 'keyup'.
In my function then, if you pass in an element with no value property we assume you're going to be updating innerHTML, and we setup an observer to determine if the element ever mutates, and if it ever does, the observer will emit a 'keyup' event.
if (watchee.value == void(0)) {
var keyUpEvent = new Event('keyup');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
watchee.dispatchEvent(keyUpEvent);
});
});
observer.observe(watchee, {
childList: true
});
}
Now it may just be my paranoia, but it seems like I might be tunneling into a can of worms by faking 'keyup' on an element that doesn't natively have that support.
TLDR:
I'm curious if there's an alternative way to make, a.e. a span tag reactive other than faking a 'keyup'/'keydown'/'change' event? For instance, is there a way that I can make my own pure event(by pure I mean not reliant on other events) that checks if innerHTML or value has changed and then performs a function? I know that this is probably possible with a timer, but I feel like that might hinder performance.
EDIT: just an aside. In the demo the function called hookFrom works by taking a DOM node and returning a function that will take the receiving dom node and continues to return a function that will take additional receiving dom nodes. :
hookFrom(sender)(receiver);
hookFrom(sender)(receiver)(receiver2);
hookFrom(sender)(receiver)(receiver2)(receiver3)(receiver4)...(receiver999)...etc
JS Fiddle To Demo (same as above)
There is nothing inherently wrong with creating a similar event on a DOM node that doesn't natively have that functionality. In fact this happens in a lot of cases when trying to polyfill functionality for separate browsers and platforms.
The only issue with doing this sort of DOM magic is that it can cause redundancy in other events. For instance the example given in this article: https://davidwalsh.name/dont-trigger-real-event-names shows how a newly minted event using the same event name can cause problems.
The advice is useful, but negligible in this specific case. The code adds the same functionality between text boxes, divs, spans, etc... and they are all intentionally handled the same way, and if the event would bubble up to another event, it would be intentional and planned.
In short: There is a can of worms that one can tunnel into while faking already explicitly defined event names, but in this case, the code is fine!
I have a TEXTAREA that is created through an external JavaScript. I am writing new script to detect when the contents are changed. DOM events like "change" and "blur" do not work, since the change is initiated by the other script. I do not have the ability to read/modify the external script.
Any ideas?
If you want full cross-browser support, you will simply have to set up a polling interval and compare the contents each time to what you saw the last time. DOM mutation events aren't going to cut it.
Have fun.
I have an application that is using KnockoutJS and I'm attempting to write some tests that test a form. If you don't know KnockoutJS, the short story for it is that it provides bindings from my view to my data model. This means that when I type a value in the an input field, my underlying object automatically gets updated with that input fields value. This is done through a change event by default.
The problem I am having is that when my WebDriver test is typing into the field, the change event is not firing so my underlying data model does not have the appropriate values. This causes my form validation to fail when it should not.
I've done everything I could find on the internet to make this work. I've:
sent the tab key
clicked away from the form field
send JavaScript code to fire focus and blur events (validation occurs on blur)
clicked the form field before typing
set waits just incase it was a timing issue
changed KnockoutJS to update input field on afterkeydown
None of these have worked for me.
In addition, I have verified that this is not an event bubbling issue as I removed all other events explicitly, leaving just the KnockoutJS change event.
for the solution i'm looking for is one that works for all browser drivers (... at least the main ones e.g. IE, FF, Chrome, Safari) and does not require the use of jQuery.
How do I solve the problem?
Here is the relevant code I'm using to type values into the field:
// find element
WebElement input = this.element.findElement(By.className("controls"))
.findElement(By.tagName("input"));
// to set focus?
input.click();
// erase any existing value (because clear does not send any events
for (int i = 0; i < input.getAttribute("value").length(); i++) {
input.sendKeys(Keys.BACK_SPACE);
}
// type in value
input.sendKeys(text);
// to fire change & blur? (doesnt fire change)
//input.sendKeys(Keys.TAB);
// to fire change & blur? (doesnt fire change)
driver.findElement(By.tagName("body")).click();
So I have found a way to work around this issue for now, but by far do I believe this is the correct solution. This does break my rule about not using jQuery, however I feel this is okay for me as KnockoutJS requires jQuery be loaded. There's probably a plain ol' JavaScript way of doing this. I have tested this with FireFox, Safari, and PhantomJS. I assume it will work just as well in Chrome. I give no promises for Internet Explorer.
I am NOT going to mark this answer as the correct answer. The correct solution should be through WebDriver browsers firing the proper events. Only when I believe this is not possible through WebDriver will I mark this as the correct answer.
// find element in question
WebElement input = this.element.findElement(By.className("controls"))
.findElement(By.tagName("input"));
// click it to fire focus event
input.click();
// erase any existing value
for (int i = 0; i < input.getAttribute("value").length(); i++) {
input.sendKeys(Keys.BACK_SPACE);
}
// enter in desired text
input.sendKeys(text);
// fire on change event (WebDriver SHOULD DO THIS)
((JavascriptExecutor) driver).executeScript(
"$(arguments[0]).change(); return true;"
, input);
// click away to fire blur event
driver.findElement(By.tagName("body")).click();
So I believe I have found out my problem. I will fully admit this was PEBKAC. I had forgotten that WebDriver has issues if the browser window is not in active focus on the machine (which is still weird to me). When I was debugging the problem, I was using my editor to step through the code. By running the code normally and without removing focus from the browser, the events fire as expected using all three solutions (tab, click-away, and javascript).
I have an example project showing all three methods, however I am a major noob with git and github and am having problems delivering the project. Once i figure that out, I will share the project with you all.
EDIT: Got the example code up on GitHub (https://github.com/loesak/knockout.webdriver.change.event.test). You can either start the project as a webapp in a server and run the test manually or you can run the tests via maven (mvn clean install). I didn't put a whole lot of effort into getting this to work robustly so it is assuming you have Firefox installed and port 0808 8080 is open.
EDIT: Fixed stated port number
My question is about using Back and Next buttons (of the browser) on an AJAX (dynamical) webpage.
The solution I self came up with:
setInterval(function(){
if (location.hash != hash)
{
hash = location.hash;
app.url = window.location.href.toString().replace('http://xxxxx.nl/xxxx/#!/','')
app.handleURL();
}
}, 500);
this function reads the url(hash) and compares it with the last stored url(hash), every 0.5 second. If url has changed (back/next is pushed) it runs handleUrl() which runs more functions to dynamically build my page.
the problem is, this sort of works BUT when I click an html [A] element or when I change the url in an other way (javascript), that content will be loaded TWICE because of the setInterval()... functionality.
How can I build my HTML/Javascript in such way that my content will always be loaded once,
once when I push back/next
once when I click on an HTML element/use Javascript functions on
runtime
I searched the sh*t out of google for a solution, plz help!
You don't need a timer to check it. Just use the onhashchange event, and fire your AJAX calls when the event is called. This event isn't supported in IE versions below 8, though, so your method seems fine if you need IE support.
Also, it doesn't make sense that they're being called twice for a elements, since there's no reason for the interval to call your AJAX loader twice just because the hash was changed using an a element. You probably have an event listener attached to the a element which causes it to load the AJAX content, which wouldn't be needed since you're detecting any change in the hash, no matter how it was changed.
I suggest using a library for that. It will be tricky to make your own solution. Take a look at these:
http://www.asual.com/jquery/address/docs/#sample-usage
http://benalman.com/projects/jquery-bbq-plugin/
I need to reliably detect the state change of radio buttons/checkboxes on my page in order to watch if the form was modified or not. Now, this is a completely separate script, I cannot modify anything that controls the form.
Right now, I can see only two ways of doing this:
onchange event handler, which helps with textboxes, textareas and selects, but is not fired for checkboxes/radiobuttons
onclick event handler, which is not reliable, because users often use hotkeys to change the values of these elements.
What am I missing here? Is there a way to reliably detect that checkbox was checked/unchecked?
UPDATE: As you guys pointed out, change event is really fired on checkboxes/radiobuttons, despite the fact that w3schools says it is only for text inputs
However, my problem turned out to be that the values of checkboxes/radiobuttons are set via setAttribute in scripts and in that case the event is not fired.
Is there anything I can do in this case?
See: http://www.quirksmode.org/dom/events/change.html.
It says that all major browsers support change event but the IE's implementation is buggy.
IE fires the event when the checkbox or radio is blurred, and not when it is activated. This is a serious bug that requires the user to take another action and prevents a consistent cross-browser interface based on the change event on checkboxes and radios.
I think you can overcome IE's bug with this trick. blur() elements when they focued! (Use something like $('input[type=radio]').focus(function(){$(this).blur();}); in jQuery or use pure javascript)
Ok, after some digging, here is what I found out. Note, this is applicable to Firefox, and, probably to Firefox only. Since in this case I was dealing with internal application, this was enough for me.
So, basically, in order to reliably detect changes in checkbox/radiobutton state in Firefox, you need to do two things:
Set up custom Firefox's event handlers CheckboxStateChange and RadioStateChange for checkbox and radiobutton respectively. These events will be fired when the user changes the inputs or when it is modified via script, using setAttribute, however, these events are not fired, when the state is changed in the script, using checked or selected properties of these elements, this is why we need ...
Watch the changes of the checked property using Object.watch
Standard onchange event is no good, since it only fired when user changes the value directly.
Damn, this thing is broken...
If people get interested, I'll post some code.