JavaScript watch event not working on DOM element objects? - javascript

I know I can use watch to bind a callback that will be triggered when object property changes. And this does work on generic objects like:
{'a':1, 'b':'7'}
So, I thought that I can simply do this to bind a callback that will trigger when input field value changes:
var inputDomElement = document.getElementById('someInputElement');
inputDomElement.watch('value',function (id, oldval, newval) {
alert(oldval);
alert(newval);
});
But this doesn't work. Simply doesn't trigger. No alert boxes. I've tried it in Firefox 5 and Google Chrome (latest).
Is this not how watch works? Is watch simply doesn't work on DOM elements? I thought that they're simply objects - aren't they?
UPDATE 1:
Here's MDN info about what watch is:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/watch
UPDATE 2:
I cannot use change event. because change only triggers when text element catches blur. Meaning that it'll only trigger when user switches from this textfield to another one. It's not in any way dynamic for when for example checking if this username or email address already taken which I'd like to happen on each distinct change.

The DOM is written in C/C++ where the concept of getting and setting a Javascript variable doesn't exist as you or I would often imagine it. You probably imagined the code to be implemented similar to what is below. Unfortunately Object.watch is never initiated because the DOM isn't constantly updating the Javascipt value, but Javascript is requesting an update from the DOM.
input.onuserchangevalue = function(){
input.value = 'new user input'
}
Thinking how the DOM commonly works, each element has dozens of potential properties.
innerHTML,value,style.cssText,name,id,style.background,style.backgroundColor
Imagine if the DOM underlining code had to constantly update every DOM elements Javascript properties %) Memory and CPU cycles would go through the roof having to ensure the properties matched the display value. The DOM's internals would also have to check if the Javascript value has potentially changed.
Reality - Red Pill
Basically the DOM isn't giving info to javascript, but Javascript is requesting the info from the DOM. This is a decent Javascript interpretation of what is going on underneath.
Object.defineProperty(input, "value", {
get : function(){ /* get C/C++ DOM value */ },
set : function(){ /* change C/C++ DOM value */ }
});
This explains why the DOM is often the bottleneck for Javascript. Javascript has to request/set the internal DOM values every time you interface with the DOM.

You need to use jQuery Objects, not DOM Objects. There is a difference.
document.getElementById("someID") //returns DOM Object
$('#someId') //returns jQuery Object
Note: you could doe something strange like this:
$(document.getElementById("someID")) //returns a jQuery Object
this would work
$('#someInputElement').watch('value',function (id, oldval, newval) {
alert(oldval);
alert(newval);
});

if you want to track changes on a text element, why not just use the .change() method?
http://jsfiddle.net/rkw79/qTTsH/
$('input').change(function(e) {
$('div').html('old value: ' + e.target.defaultValue + '<br/>'
+ 'new value: ' + e.target.value);
})
For instant change, use .keyup(): http://jsfiddle.net/rkw79/qTTsH/1/

Related

Why isn's dom updates detected by subsequent dom reads?

I'm coding a small Vue app. I've got an element which has a data-range property written like this:
:data-range="form.appearence.height_min + '/7'"
form.appearence.height_min will change based on a select element values, selected by the user.
After every select change, I'll read again the data-range and do things based on it.
// from the vue app, a watcher
'form.appearence.xps':function(val, oldval){
// this will properly change the model and the dom as well
this.$set(this.form.appearence, 'height_min', xps_map[val]);
this.$emit('xps-updated');
}
// then from another script
this.options.vue.$on('xps-updated', function(){
this.options.vue.$nextTick(function(){
console.log($('#test5').data('range')) // issue: this value doesn't change
}.bind(this))
}.bind(this));
My issue is that the range value does change on dom, I can see it from console, but javascript will always read the initial value... For example, at start was 3/7, then it gets changed to 5/7, but $('#test5').data('range') will still read 3/7. Why?
Ok, I'll answer by myself. I found out that jquery objects do not follow Vue's dom updates, at least in this case. Therefore, even if dom gets updated by Vue, $('#test5').data('range') will always give the initial value.
Instead, by getting the 'real' element with vanilla js, like
let range = document.querySelector('#test5').dataset.range;
Will always return the updated value.

Polymer - How do I attach an observer to an array?

How do I attach an observer to a polymer attribute that is an array? To be clear, I want callbacks when items in the array change. For simplicity, let's say my array is:
[
{ text: 'foo' },
{ text: 'bar' }
]
I want something like:
observe : {
'items.text' : 'itemsChanged'
}
The following works, but is obviously un-sustainable:
observe : {
'items[0].text' : 'itemsChanged',
'items[1].text' : 'itemsChanged'
}
Note that in my case, the changes are coming from another polymer element that I have control over. So if I could somehow trigger a change from the element that has control over { text: 'foo' }, that would work as well.
To be clear, Polymer will automatically observe Arrays, but an 'ArrayObserver' will only tell you if (a) the Array itself is replaced or (b) items are added, removed, or rearranged. Just like observed objects, Polymer does not automatically observe properties of sub-objects.
the changes are coming from another polymer element that I have control over
This is usually the case and we typically have the element doing the changing fire an event for communication.
Kinda late, but because the question ranks quite high on google and none of the above is really helping, here is my solution:
Polymer ships with observe.js and this helps solving your problem.
The basic version would be to use the ArrayObserver but this only fires when the array gets altered, meaning when elements are added, removed and i guess also when you replace an entry. But this won't help if you wanna observe changes to object properties in an array - such as in your case.
Here it's better to use the CompoundObserver and add all paths via addPath or addObserver(new PathObserver(obj, '...')). But you have to iterate over the whole array and add all observed properties in a loop for instance.
You can use Object.observe() to accomplish this. e.g.:
ready: function() {
Object.observe(this.items, this.myObserver);
}
myObserver: function (changes){
changes.forEach(function(change, i){
console.log('what property changed? ' + change.name);
console.log('how did it change? ' + change.type);
console.log('whats the current value? ' + change.object[change.name]);
console.log(change); // all changes
});
},
More here.
There is a documentation for this
Observe array mutations

What happens if a JavaScript event-listener is called and target element is missing?

For the moment, we're loading site-wide event-listeners from a single common.js file for a Rails project. We're aware of (most of) the trade-offs involved there, and are just trying to mitigate them. Once our basic architecture takes hold, we may move them off to separate files by controller or by view.
For the moment, the quick question is how we can activate them only when necessary, which begs the mangled, pseudo-zen question:
if an event-listener is declared in a forest when nobody is around to hear it, does it still make a sound?
In other words, if one declares a basic listener (i.e., nothing persistent like .live() or .delegate()) in the JavaScript for a given page, and the target element is not actually present on that given page, does anything really happen, other than the few cycles devoted to evaluating it and checking the DOM for the element? Is it active in memory, looking for that element? Something else? It never seems to throw an error, which is interesting, given that in other contexts a call like that would generate a null/nil/invalid type of error.
For instance:
$(document).ready(function () {
$('#element').bind('blur keyup', function);
}
Assume that #element isn't present. Does anything really happen? Moreover is it any better to wrap it in a pre-filter like:
$(document).ready(function () {
if ($('#element')) {
$('#element').bind('blur keyup', function);
}
Or, are the .js interpreters in the browsers smart enough to simply ignore a basic listener declared on an element that's not present at $(document).ready? Should we just declare the initial, simple form above, and leave it at that, or will checking for the element first somehow save us a few precious resources and/or avoid some hidden errors we're not seeing? Or is there another angle I'm missing?
JQuery was designed to work with 0+ selected elements.
If no elements were selected, nothing will happen.
Note that you will never get null when using jQuery selector. For example:
$('#IDontExist') // != null
$('#IDontExist').length === 0 // true (it's ajQuery object with
// zero selected elements).
The docs says:
If no elements match the provided selector, the new jQuery object is "empty"; that is, it contains no elements and has .length property of 0.
$('#element') if results into empty set then jQuery will not do anything.
Since jQuery always returns an object we can can call the methods on an empty set also but internally it will do the checking before applying it's logic.
Even if you want to check if the element exists before attaching the event handler you can use length property of jQuery object.
if ($('#element').length > 0) {
$('#element').bind('blur keyup', function);
}

Where are attached event-handlers 'meta-data' stored? On the "DOM," object, or...?

I've always wondered... so you have a code like this:
$('#click-me');
and you attach it with this:
$('#click-me').click(someFunction);
where is the 'meta-data' that says:
"Hey "jQuery-object #click-me," I will point you to 'someFunction' when you are clicked!"
I know that event-handlers can get destroyed such as my situation with Backbone.js where my events stopped firing due to me re-rendering the entire page, destroying some background functions/objects/Views along the way.. (this is the context as to why I'm asking this question)
NOW, MY QUESTION IS:
where are events 'meta-data' stored and how are they destroyed?
Are they stored within the function that bound it to a function? Are they within the DOM 'meta-data' (if there is one) itself?
I'm trying to learn the intricacies of JavaScript because I'm tired of bugs. In addition to that, I'm wondering if I should watch out for garbage collection that might detach my events and such. Coming from C#, I would say JavaScript with the DOM is really something...
(also, as a side note, how can I access these events and 'debug' them? firefox? chrome?)
UPDATE
To say it in different words, where is the information that connects a DOM element to a certain event stored? DOM? Objects? (or.. does jQuery map it? does JavaScript have a 'meta-data'? it's around that context..
Update : So I misunderstood the question, you wanted to know how events are bound in the context of just javascript and html. My original answer below describes how jquery creates and manages events. It boils down to a call to element.addEventListener.
From the MDN docs you see the eventtarget can be an element, the document, window or an XMLHttpRequest. From the w3 specifications on DOM Events an event target adds, removes and dispatches an event. So even information is probably stored in whatever is encapsulating things like elements, this will be implemented at the browser level.
From the issue you mentioned about copying and then replacing the html from the body erases the events, I'm thinking the browser just gives you the markup (without the event metadata) and then when you replace it, the metadata is gone. ( http://jsfiddle.net/qu9bF/1/)
Original answer: How jquery event handlers work.
Ok so I started digging this, for JQuery 1.4.2 (because I had to use a couple tools, all of which aren't updated)
Take a look first at this:
http://james.padolsey.com/jquery/#v=1.4.2&fn=click
function (fn) {
return fn ? this.bind(name, fn) : this.trigger(name);
}
That is how click is defined, it isn't actually defined in code. JQuery defines this function for all events/handler functions like below, yes! they are created/defined dynamically :
jQuery.each( ("blur focus focusin focusout load resize scroll unload click
dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error").split(" "),
function( i, name ) {
// the magic happens here..
//each string is added as a function to the prototype
jQuery.fn[ name ] = function( fn ) {
return fn ? this.bind( name, fn ) : this.trigger( name );
};//if a handler is NOT specified then attach an event OR call/trigger it
if ( jQuery.attrFn ) {
jQuery.attrFn[ name ] = true;
}
});
From here on we need to look at bind, now bind() and one() are also defined like this. Search for "Code : bind and one events" here
From here I used chrome with this fiddle http://jsfiddle.net/qu9bF/ to step into the code. The block from c.each(["bind" is how the bind function is defined. The source is minified, but chrome can format it.
From here on the code calls JQuery.events.add, you can find this under the Events section here. This is not the add() that is documented I think
Toward the bottom, this piece of code is what does the magic. It accordingly calls element.addEventListener or attachEvent. See how it adds the on for attachEvent.
// Check for a special event handler
// Only use addEventListener/attachEvent if the special
// events handler returns false
if ( !special.setup ||
special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
// Bind the global event handler to the element
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false );
} else if ( elem.attachEvent ) {
elem.attachEvent( "on" + type, eventHandle );
}
}
And there you have it! :) I hope it answered both your questions. You can link up to the non-minified versions of jquery source and step through it to figure things out. IMO sometimes IE9's debugger is more intuitive (that's the only thing I use it for), and use the pages I've mentioned to browse through the source sanely.
jQuery stores all the event binding and data cache on the jQuery.cache object. All the DOM nodes which were wrapped with jQuery and had events bound to them or data set will get automatically cleared when you are using jQuery html, empty, remove, replace etc.
That's why it's very important to never use innerHTML or other native DOM methods to insert/replace content that was altered before by jQuery. It will lead to leaks that you won't be able to cleanup unless you reset the jQuery.cache object manually.
There is also an undocumented method jQuery.cleanData which takes a collection of DOM nodes as an argument and it iterates over them and cleans up all their event bindings, data and removes references to these elements from the cache. This one can be useful if you have DOM fragments which were detached from the main DOM tree and there is a risk that they won't get cleaned up properly.
Regular events like click or submit (when not bound by jQuery) are actually just properties ('onclick', 'onsubmit') of the DOM elements themselves.
For jQuery events, the library keeps it's own record when you bind them and looks at it when you trigger them. jQuery puts all data about elements in a standard place, which you can access with $(e).data(). For events, it's just $(e).data('events').
You can unbind jQuery events with $().unbind(), and regular events using the delete keyword to delete the object's property which corresponds to the given event.
jQuery keeps its own map of element event handlers. There's rarely, very rarely, cause to worry about this unless you're abusing the library somehow.

Rendering suggested values from an ext Combobox to an element in the DOM

I have an ext combobox which uses a store to suggest values to a user as they type.
An example of which can be found here: combobox example
Is there a way of making it so the suggested text list is rendered to an element in the DOM. Please note I do not mean the "applyTo" config option, as this would render the whole control, including the textbox to the DOM element.
You can use plugin for this, since you can call or even override private methods from within the plugin:
var suggested_text_plugin = {
init: function(o) {
o.onTypeAhead = function() {
// Original code from the sources goes here:
if(this.store.getCount() > 0){
var r = this.store.getAt(0);
var newValue = r.data[this.displayField];
var len = newValue.length;
var selStart = this.getRawValue().length;
if(selStart != len){
this.setRawValue(newValue);
this.selectText(selStart, newValue.length);
}
}
// Your code to display newValue in DOM
......myDom.getEl().update(newValue);
};
}
};
// in combobox code:
var cb = new Ext.form.ComboBox({
....
plugins: suggested_text_plugin,
....
});
I think it's even possible to create a whole chain of methods, calling original method before or after yours, but I haven't tried this yet.
Also, please don't push me hard for using non-standard plugin definition and invocation methodics (undocumented). It's just my way of seeing things.
EDIT:
I think the method chain could be implemented something like that (untested):
....
o.origTypeAhead = new Function(this.onTypeAhead.toSource());
// or just
o.origTypeAhead = this.onTypeAhead;
....
o.onTypeAhead = function() {
// Call original
this.origTypeAhead();
// Display value into your DOM element
...myDom....
};
#qui
Another thing to consider is that initList is not part of the API. That method could disappear or the behavior could change significantly in future releases of Ext. If you never plan on upgrading, then you don't need to worry.
So clarify, you want the selected text to render somewhere besides directly below the text input. Correct?
ComboBox is just a composite of Ext.DataView, a text input, and an optional trigger button. There isn't an official option for what you want and hacking it to make it do what you want would be really painful. So, the easiest course of action (other than finding and using some other library with a component that does exactly what you want) is to build your own with the components above:
Create a text box. You can use an Ext.form.TextField if you want, and observe the keyup event.
Create a DataView bound to your store, rendering to whatever DOM element you want. Depending on what you want, listen to the 'selectionchange' event and take whatever action you need to in response to the selection. e.g., setValue on an Ext.form.Hidden (or plain HTML input type="hidden" element).
In your keyup event listener, call the store's filter method (see doc), passing the field name and the value from the text field. e.g., store.filter('name',new RegEx(value+'.*'))
It's a little more work, but it's a lot shorter than writing your own component from scratch or hacking the ComboBox to behave like you want.
#Thevs
I think you were on the right track.
What I did was override the initList method of Combobox.
Ext.override(Ext.form.ComboBox, {
initList : function(){
If you look at the code you can see the bit where it renders the list of suggestions to a dataview. So just set the apply to the dom element you want:
this.view = new Ext.DataView({
//applyTo: this.innerList,
applyTo: "contentbox",
#qui
Ok. I thought you want an extra DOM field (in addition to existing combo field).
But your solution would override a method in the ComboBox class, isn't it? That would lead to all your combo-boxes would render to the same DOM. Using a plugin would override only one particular instance.

Categories

Resources