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

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.

Related

How to apply css styles to dynamic JavaScript array?

I'm trying to apply CSS styles to input elements that are added to the HTML DOM dynamically via a JSON object.
Essentially an Ajax call receives a JSON payload with an array of data. Some KnockoutJS code then foreach's over the DOM to dynamically add rows.
I'm trying to add styles to inputs where a value is less than a required value. The bottom line is, I know the elements are dynamic to the DOM, and I'm having trouble accessing them so I can apply the style. I've tried both jQuery and pure JavaScript, and I can't access the newly added fields.
How would I do this?
I have a very complex fiddle created that creates the inputs. But I can't figure out how to style those inputs whose values are less than the current year.
I'm trying to add the .k-invalid style to the NextPaymentDate input whose value is less than the current year.
var $incomeWrapper = $("#income-wrapper");
$incomeWrapper.find("input#iNextPaymentDate_" + i).removeClass("k-valid").addClass("k-invalid");
The above doesn't work.
http://jsfiddle.net/kahanu/rdb00n76/9/
You could add a filter function to your selector like this:
$('input[id^="iNextPaymentDate_"]').filter(function(index) {
return parseInt($(this).val().split('/')[2]) < new Date().getFullYear();
}).addClass('k-invalid');
Fiddle: http://jsfiddle.net/rdb00n76/10/
The above code selects all inputs whose ids start with iNextPaymentDate_, then applies a filter that evaluates the current element against the current full year. To do this I split the date string on / and take the 3rd item which should be the year. Then I cast the value to int and compare the the current year.
Your actual filter function should probably be a lot more solid than the one above. For example, you could include moment.js for comparisons.
I think the forEach loop inside ListDateValidation is being executed too soon. If my understanding from your jsfiddle is correct, you're running it as soon as you instantiate the FinancialViewModel, but even though the call comes after everything else, Knockout may not have updated the DOM by this point.
There are several ways you could check this and if correct, guard against this.
But for now, to check if this is the case, I would suggest placing some logic immediately prior to the self.ListDateValidation() method call: in this logic you should just have a quick and dirty way of determining if any of those elements are present - can you temporarily (just for debugging) give these elements id attributes (just increment an int) and then run something like
if (document.getElementById("test-id-1") !== null) {
console.log("element found");
}
This will tell you if you're running the date validation too soon.
If you need a method of determining when the elements have been added then search for "javascript poll dom element added". If you can't be bothered, here's a crude method:
var elmnt,
elmntId = "YOUR ELEMENT'S ID HERE",
tmr = window.setInterval(function() {
elmnt = document.getElementById(elmntId);
if (elmnt) {
window.clearInterval(tmr); // stop the search
// do something
}
}, 15);
This method polls the DOM every 15ms, then stops when it finds that the element with the specified ID is present. 15ms corresponds to the minimum tie increment in which a browser will run - if this has since been lowered then great, but no-one would notice the difference in this context.

Unable to reference dom object

I am trying to set a reference to a DOM Element with the following code. For some reason anything called immediately after the reference is made works perfectly fine but calling it anywhere else in my application nothing happens. I don't get any errors like "unable to set innerHTML of undefined" which is the weirdest part. Immediately after the declaration it works fine later it doesn't do anything, yet other elements referenced in the same manner and in the same function work fine.
var dom = new function() {
this.signInA = document.getElementById("signInPin");
this.orderEntry = document.getElementById("orderEntry");
this.menuGroup = document.getElementById("openGroup");
}
<div id="orderEntry">
<div id="openGroup">
</div>
</div>
then later i am calling
dom.openGroup.innerHTML="TEST";
But nothing is happening. This of course is just a snippet of the application, yet ive already searched through the entire document to check every other refrence to DOM and specifically dom.openGroup. dom.orderEntry works just fine through the entire application and dom.openGroup is only working in the immediate vacinity of this declaration.
The problem I found was that the innerHTML method when changed deleted all nested DOM objects and therefore, any references to the child objects where also deleted.
So in hindsight I should either only use innerHTML on very simple objects that won't cascade down, and use other methods to append new elements into these more complex objects.

Trigger ng-model.$formatters to run programmatically

I'd like a custom control that uses ngModel.$formatters to be able to format data as soon as a server dependency loads in. In my case, it needs to load a lookup table to go from one kind of id to another. $modelValue stores one thing $viewValue displays another. Pretty straight-forward stuff.
The trick is that if my lookup table isn't loaded, I can't do the formatting into a $viewValue.
Once my data loads, I need to do the following:
ngModel.$formatters.push(myFormatter)
Tell ngModel to start the pipeline from $modelValue -> $formatters -> $viewValue
$render() doesn't work, this just moves the value from $viewValue into the UI control.
$rollbackViewValue() looks promising, but that's only in an unstable version (1.3.0-beta.18).
Code Sample:
mappingTable.load().then(function(data){
mappingData = data;
ngModel.$formatters.push(myFormatter); // needs mappingData in order to function
// TODO: Tell ngModel to run the existing $modelValue through $formatters to calculate a new $viewValue and $render it
//ngModel.$render() // doesn't work, only puts the $viewValue in the DOM element.
});
Looking at the code for ngModelController, it appears that what you stumbled upon (setting $modelValue to anything other than the current actual model value) is the accepted way to do this. As you say, the value you set is not used: it just triggers the update. Check its current value first to make sure it actually changes (or use a very unlikely value).
if (ngModel.$modelValue == 'bar')
ngModel.$modelValue = 'foo';
else
ngModel.$modelValue = 'bar';
Here is a related question.
Also, there is an active pull request that looks like an "official" way of doing this is forthcoming.
The reason it works is that ngModelController sets up a $watch that runs every digest cycle that compares $modelValue to the value that ng-model is bound to. If they don't match, it triggers the $formatters pipeline.

Different behavior for object and in explorer 9 and all other browsers and versions in Javascript

I'm trying to user a DOM element that i am saving into a JS var:
lastCheckBoxChecked = this;
"this" represents a input/checkbox DOM element.
Now, trying to get if the checkbox is checked from the element with:
if(lastCheckBoxChecked.getAttribute('checked') == true)
doesn't work.
When I checked what does lastCheckBoxChecked holds, I found out that in IE9 it holds Object and when writing
alert(lastCheckBoxChecked.getAttribute('checked'))
I am getting the correct value but in firefox and chrome it holds
object HTMLInputElement and there is no functionality (getting undefined).
I don't know if this is the reason I am not getting anything, but this is my direction.
Does any one know any thing about this?
Please, just use this:
if(lastCheckBoxChecked.checked)
...
Not:
if(lastCheckBoxChecked.getAttribute('checked') == true)
getAttribute checks for the initial state of the checkbox, or if it was changed with getAttribute only. User operations have no effect on it.
An attribute represents the value it had initially, and which does not change by user interactions. To get the current value, use its property:
lastCheckBoxChecked.checked

JavaScript watch event not working on DOM element objects?

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/

Categories

Resources