JavaScript Setter bug found? - javascript

Hy I wrote a quick and dirty list ui in js and html that can be filtered:
https://jsfiddle.net/maxbit89/2jab4fa4/
So the usage of this looks like this (Fiddle line: 96):
var list = new ui_list(document.body, 200, 300, "Test");
var encoder = function(dom, value) {
console.log("begin encoding");
console.log(value)
dom.innerHTML = value.n;
}
list.add({'n': 1}, function() {
this.value.n++;
console.log(this.value.n);
// this.value = this.value;
}, encoder);
So what this basicaly does is create a List and adds a Element to it that has an Object: {'n': 1} as a value and a onClickHandler(second parameter on list.add) wich should increase the value by 1 (fiddle line: 104)
But it won't do this untill you uncomment the line 106 in the fiddle.
(Tested with FireFox 50.1.0, and Edge Browser)
Has any body an idea why js behaves like this?
In a much simplier example this works just fine:
var myObj= {
'onvalueChange' : function() {
console.log('value changed');
},
'print' : function() {
console.log('value:');
console.log(this.value);
console.log(this.value.n);
}
};
Object.defineProperty(myObj, "value", {
get: function() { return this._value; },
set: function(value) {
this.onvalueChange();
this._value = value;
}
});
myObj.value = {'n' : 1};
myObj.value.n++;
myObj.print();

First you have the setter defined like this:
set: function (value) {
this.encoder(this, value);
this._value = value;
}
that means that every time the value is set, the encoder will be called with the new value to update the equivalent DOM element.
But then inside the event listener function you have:
function() {
this.value.n++;
console.log(this.value.n);
//this.value = this.value;
}
where you think that this.value.n++ is setting the value (means it calls the setter which means the encoder will be called to update the DOM element). But it's not true. this.value.n++ is actually calling the getter. To explain more this:
this.value.n++;
is the same as:
var t = this.value; // call the getter
t.n++; // neither call the getter nor the setter. It just uses the reference (to the object) returned by the getter to set n
So, when you uncomment the line this.value = this.value;, the setter gets called, and the encoder gets called to update the DOM element.
So to fix the issue you have to either:
Make a call inside the getter to the encoder as you did for the setter (but this solution is very hacky as it will update the DOM element on every getter call even if nothing is being set).
Change this this.value.n++; to actually call the setter like: this.value = {n: this.value.n + 1}; (but this is hacky too as if value has a lot of key-value pairs then you have to enlist them all here just to set n).
Call the encoder inside the event listener which will be the best way to do it (or if you don't want to pass the parameters to it make another function (for example this.callEncoder()) that will call it and [you] call the new function instead inside the event listener).

Related

fire event when property of a variable is changed

I'm creating a re-usable object that would contain multiple properties. I would like to fire an event within the object that will update some of its own properties when a specific property is changed through assignment. I have read something similar to this but what they used was an object. How can I achieve this?
My apologies if this is something basic but I don't really have a formal training is JavaScript or in-dept knowledge how JavaScript works.
also I would like to add that this is something that should work in IE11 and up.
I have tested the method from this but unfortunately I don't really understand how can I implement it on my case.
Listening for variable changes in JavaScript
var test;
function myObject(){
this.dataSource = null;
this.changeEvent = function(val){
//do something inside
}
}
test = new myObject();
test.dataSource = 'dataSource'; //trigger changeEvent() inside myObject
When creating the object, wrap it in a Proxy.
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
Implement a set trap which performs the needed internal changes.
OK, so I'll start from the method you've suggested and it seems you like that approach.
Sorry, if this seems obvious but better to explain!
Basically watch watches properties within your object (say datasource) and when a new value is assigned - fires the callback function. So, watch takes two params - what to watch, and what to do (when the property you are watching changes).
The caveat with this approach is it's a a non-standard and was not implemented by other browsers. Although, we can get a polyfill (which declares it if it does not exist).
https://gist.github.com/eligrey/384583
Warning from Mozilla's own docs:
Deprecation warning: Do not use watch() and unwatch()! These two
methods were implemented only in Firefox prior to version 58, they're
deprecated and removed in Firefox 58+. In addition, using watchpoints
has a serious negative impact on performance, which is especially true
when used on global objects, such as window. You can usually use
setters and getters or proxies instead.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch
So, to implement using a polyfil of watch (thank Eli Grey for the Polyfill)
First, register the polyfill (put it somewhere which will run before anything else in JS, or put it in a polyfill.js file and import it first on your HTML page!)
/*
* object.watch polyfill
*
* 2012-04-03
*
* By Eli Grey, http://eligrey.com
* Public Domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*/
// object.watch
if (!Object.prototype.watch) {
Object.defineProperty(Object.prototype, "watch", {
enumerable: false
, configurable: true
, writable: false
, value: function (prop, handler) {
var
oldval = this[prop]
, newval = oldval
, getter = function () {
return newval;
}
, setter = function (val) {
oldval = newval;
return newval = handler.call(this, prop, oldval, val);
}
;
if (delete this[prop]) { // can't watch constants
Object.defineProperty(this, prop, {
get: getter
, set: setter
, enumerable: true
, configurable: true
});
}
}
});
}
// object.unwatch
if (!Object.prototype.unwatch) {
Object.defineProperty(Object.prototype, "unwatch", {
enumerable: false
, configurable: true
, writable: false
, value: function (prop) {
var val = this[prop];
delete this[prop]; // remove accessors
this[prop] = val;
}
});
}
Then, to use (using your example);
var test;
**function myObject(){
this.dataSource = null;
this.changeEvent = function (id, oldval, newval) {
console.log(id + ' changed from ' + oldval + ' to ' + newval);
return newval;
}
this.watch('datasource', this.changeEvent);
}**
test = new myObject();
test.dataSource = 'dataSource'; //trigger changeEvent() inside myObject
However, I would look into Event Listeners and trigger events when objects change - but that solution should work for you, especially with watch
Listening for variable changes in JavaScript
You can store the value of dataSource in local storage and then compare and check if it has been changed.
var test;
function myObject()
{
this.dataSource = null;
this.changeEvent = function(val)
{
console.log("Value has been changed.");
}
}
test = new myObject();
test.dataSource = 'dataSource';
console.log("Before change" + test.dataSource);
localStorage.setItem("dataSource", test.dataSource);
var newVal = "dtSrc";
test.dataSource = newVal;
var originalVal = localStorage.getItem("dataSource");
console.log("After change" + test.dataSource);
if(newVal == originalVal)
console.log("Value has not changed.");
else
test.changeEvent();
Let me know if it works for you. If not, let me know the expected output.
You can use like :
var myObject = {
_dataSource: null,
changeEvent : function(val){
//do something inside
alert(val);
}
};
Object.defineProperty(myObject, "dataSource", {
get: function(){
return this._dataSource;
},
set: function(newValue){
this._dataSource=newValue;
this.changeEvent(this.dataSource);
}
});
myObject.dataSource= "dataSource";

understanding the code from transit.js

I was just going through the source of transit.js and came across the following fucntion ::
$.cssHooks['transit:transform'] = {
// The getter returns a `Transform` object.
get: function(elem) {
return $(elem).data('transform') || new Transform();
},
// The setter accepts a `Transform` object or a string.
set: function(elem, v) {
var value = v;
if (!(value instanceof Transform)) {
value = new Transform(value);
}
// We've seen the 3D version of Scale() not work in Chrome when the
// element being scaled extends outside of the viewport. Thus, we're
// forcing Chrome to not use the 3d transforms as well. Not sure if
// translate is affectede, but not risking it. Detection code from
// http://davidwalsh.name/detecting-google-chrome-javascript
if (support.transform === 'WebkitTransform' && !isChrome) {
elem.style[support.transform] = value.toString(true);
} else {
elem.style[support.transform] = value.toString();
}
$(elem).data('transform', value);
}
};
I understand the latter part of the function, but its really hard to understand the initial part of the function, the function can be found on git too , HERE .
Initially I see this, $.cssHooks['transit:transform'] what is that line really saying?
After that we have the below line of code I.E. the getter and setter method,
set: function(elem, v) {
But who is passing the elem and v inside the function, I don't see anything being passed?
Read about cssHooks at jQuery cssHooks
Look at the source code (search for hooks.get and hooks.set)
.cssHooks is an array of objects that contains getter and setters tha will be executed by .css(). Thats all.
$.cssHooks['transit:transform'] = {set: function(elem,value){}, get: function(elem){}}
equal:
$.cssHooks['transit:transform'] = {};
$.cssHooks['transit:transform'].set = function(elem, value){};
$.cssHooks['transit:transform'].get = function(elem){};
$(element).css('transit:transform',value)
comes to:
$.cssHooks['transit:transform'].set(element,value)
$(element).css('transit:transform')
comes to:
$.cssHooks['transit:transform'].get(element)
$.cssHooks['transit:transform'] = {set:function(){}, get: function(){} }
{...} is an object creation.get and set not executed at this moment.
They created {set:function(){}, get: function(){} }
So. Simply: .css() will execute set and get functions for hooked property.
If you want to know how real getters and setters works:
Object.defineProperty()
In Javascript, you can add/access to a property with this syntax :
myObject.myProperty
or with this syntax :
myObject['myProperty']
This is the same result
So your line
$.cssHooks['transit:transform']
just mean that we want to store an object (code between {} in your original post) inside the 'transit:transform' property which is inside the cssHooks property which is inside the $ object
This is the same things :
$['cssHooks']['transit:transform']
The reason why they use the [''] syntax is that transit:transform contains the ':' char which is not allowed if you want to access it this way :
$.cssHooks.transit:transform //doesn't work
EDIT:
To answer to your second question, i don't know...the code you are showing is just the 'description' of the "transit:transform' property

How to invoke model events forcefully

I written a function, it will trigger whenever model attribute change just like in the following way.
modelEvents:{
"change:Name":"callback1"
},
callback1:function(){
console.log("This is testing");
}
Initially I set model.set("Name","hi") so automatically callback1 was invoked. Again If I set the same value into model, callback1 not triggering because model attribute not modified. so For this every time I am doing in the following.
model.set({"Name":""},{silent:true});
model.set({"Name":"hi"});
If I do like above it's working fine, but I want to know is there any option(like silent) to forcefully invoke callback.
Thanks.
If you want to go the route of passing an option then the only way to accomplish this would be to override the set method with something like this in your Model, although i haven't done testing on this to make sure it would not produce unexpected results.
set: function(key, val, options) {
//call the origonal set so everything works as normal
Backbone.Model.prototype.set.call(this, key, val, options);
current = this.attributes, prev = this._previousAttributes;
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options || (options = {});
//new option to always trigger the change on an attribute
if (options.loud) {
for (var key in attrs) {
//if the value is the same as before trigger the change
//otherwise backbone will have already triggered the chage
if (_.isEqual(prev[key] , attrs[key])) {
this.trigger('change:' + key, this, current[key], options);
}
}
}
}
then to make use of it call the normal set but pass loud: true
this.model.set({
name: "Hi"
}, {
loud: true
});
here a fiddle that makes use of it http://jsfiddle.net/leighking2/ktvj0kgp/
To show that the event is triggered even when the attribute is the same i have added an attribute called renders to show how many times it has been called.
Why don't you use Model.hasChanged for this? basically it will listen for changes in an attribute.
Take a look here.
http://backbonejs.org/#Model-hasChanged
Hope it helps

Retrieving the value of a variable through a closure

I have the following module pattern in the javascript for a webpage:
var swf_debugger = swf_debugger || {};
(function($, swf_debugger) {
swf_debugger.pageSetup = (function() {
var
swfType = null,
formData = {},
// ... unimportant code continues ...
initChangeEvents = function() {
$.each(formElements, function(index, $el) {
if ($el.hasClass("swfToLoad")) {
// THIS EVENT IS WHERE I MAKE THE ASSIGNMENT TO 'swfType'
$el.change(function() {
swfType = $("option:selected", this).val();
console.log("swfToLoad has triggered");
console.log(swfType);
});
return;
}
// NO ISSUES HERE WITH THESE EVENTS...
switch($el.prop("tagName")) {
case "SELECT":
$el.change(function() {
formData[$el.attr('id')] = $("option:selected", this).val();
});
break;
case "INPUT":
switch ($el.attr('type')) {
case "text" :
$el.change(function() {
formData[$el.attr('id')] = $(this).val();
});
break;
case "checkbox" :
$el.change(function() {
formData[$el.attr('id')] = $(this).prop("checked");
});
break;
default:
}
break;
default:
}
});
},
init = function() {
$(function() {
addFormComponents();
populateDropdowns();
initCachedData();
initChangeEvents();
});
};
init();
return {
swfType: swfType,
formData: formData
};
}());
}($, swf_debugger));
Essentially I am attaching an event to a series of jquery selected elements, with the callback simply storing the contents of a particular form element (specifically select, text, and checkbox elements) in a variable or an object.
I know my events are attaching properly because when I add console.log statements to them I can see them firing. Also, whenever I call swf_debugger.pageSetup.formData in the console I see valid contents of the object that each of those events are populating, so those events are doing what they're supposed to.
My troubles are happening whenever I try to access swf_debugger.pageSetup.swfType it always returns null and I am not understanding why. I know that the particular event feeding this value, is firing and I know that at least within the function scope of the callback, swfType is valid because of what is returned in my console.log statements. However, whenever I try to access the contents of swfType through the closure, (i.e. typing swf_debugger.pageSetup.swfType in the console), It always returns null.
I am guessing that I am running into the difference between an objects reference being passed and a variables value being passed, but I am not sure. Can someone please help me along here and explain why swfType is always returning null through the closure.
why is swfType always returning null
Because that's the value which you assigned to the property (gotten from the swfType variable which had that value at the time of the assignment). A property is not a reference to the variable assigned to it - you can only assign a value.
What you can do:
make the object property a getter method which returns the value of the local swfType variable whenever it is called
don't use a variable but assign to the property of the swf_debugger.pageSetup object each time

Is it possible to capture property access in JavaScript?

I want to be able to provide a JavaScript function that will be called whenever any property of a specified object is being queried or updated. Is that possible, if so, how?
To give a simple example, if I have obj = { a:3 }, I want to have some function called whenever any code queries obj.a, and be able to return whatever I want instead of its current value, e.g. 4 instead of 3.
It's easy, you can use Object.prototype.defineProperty. more on it here.
To answer your question:
var obj = {};
Object.defineProperty(obj, "a", {
get: function() {console.log("I've been accessed"); return 5;//or whatever value}
});
console.log(obj.a)
Working fiddle
Update
The above can be shorthanded.
Object.prototype.addMonitoredGetter = function(property, value, callback) {
Object.defineProperty(this, property, {
writable: false,
get: function() {callback(); return this[property]
};
};
And the callback here could be the monitoring function. Of course, needs default params and checks, but it should do the trick.
There's a watch method available for Firefox...
o.watch("p", function(...)
http://jsfiddle.net/NTc52/

Categories