Detecting when a variable changes in javascript - javascript

I have a chrome app that I want to correctly resize (dimensions proportional to the screen width) on resolution change. I have written a function that redraws the app with the correct dimensions, now I want to execute it only when it needs to.
A resolution change causes screen.width to change, but (probably unsurprisingly since they relate to different things) the "resize" event is not fired, and as far as I can tell no event is fired.
I know about the Proxy object (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) so I wrote some code which detects when a variable is set and executes my callback, this seemed to work but not in the instance of resolution change.
So I searched online and tried this https://gist.github.com/eligrey/384583 (which is essentially the answer provided in several stackoverflow questions on a similar topic, and indeed what I initially produced, albeit lacking the abstraction that the newish Proxy object offers).
This also seems to work (in the sense if I say manually set screen.width after having done screen.watch("width", () => console.log("hello")); and then do screen.width = 100; my callback is executed). But not in the instance of resolution change (in fact, perhaps most importantly, assigning this watcher seems to prevent screen.width getting assigned).
I have three questions
1) What is going on when I assign a watcher/proxy that's messing things up.
2) How could I find out what the browser is doing at this level of detail (what sends the trigger to change screen.width? I guess it is the OS, what does this look like)
3) Is there a better way to achieve what I was initially going for (the chrome app resizing).
Mostly I am interested in question 1) and don't care much about 3) any more.
To replicate my issue,
open a new tab in firefox or chrome
go to the developer console.
check screen.width, change resolution, observe that screen.width changes
Copy and paste the code from https://gist.github.com/eligrey/384583
Do screen.watch("width", (id, oldval, newval) => {console.log("hello"); return newval;});
Do screen.width = 100; and observe that hello is logged and screen.width is set to 100
Change resolution, observe that screen.width is not set.
Edit - As revealed after Bertrand's answer, the resize event may actually fire on resolution change, but this seems to be as a response to the resolution change forcing the boundary of the the window to get smaller, if the window is small then screen.width can change without firing a resize event.

What is going on when I assign a watcher/proxy that's messing things up.
The issue is that the width property is actually a getter, on Screen.prototype. It's not an ordinary value on window.screen:
console.log(window.screen.width);
console.log(window.screen.hasOwnProperty('width'));
// The property exists on the prototype instead, and is a getter:
const descriptor = Object.getOwnPropertyDescriptor(Screen.prototype, 'width');
console.log(descriptor);
If the width property were an ordinary property, composed of just a plain value, which was set by the browser via evaling
window.screen.width = 1500
then a proxy or the Object.watch polyfill would work, because you would be able to intercept the assignment to the .width property. This is why you see that, after assigning your own setter to window.screen.width:
Do screen.width = 100; and observe that hello is logged and screen.width is set to 100
It shows you hello - you're invoking the setter that you previously assigned to the screen property of window. In contrast, because the native built-in property is not a plain value which gets assigned to by the browser, your hello does not get logged when the screen changes. The situation is a bit similar to what's going on in this example snippet:
const obj = (() => {
let privateVal = 'propOriginal';
// privateVal changes after 500ms:
setTimeout(() => {
console.log('Changing privateVal to propChanged');
privateVal = 'propChanged';
}, 500);
// Return an object which has a getter, which returns privateProp:
return {
get privateProp() {
return privateVal;
}
};
})();
// At this point, if one only has a reference to `obj`,
// there is no real way to watch for changes to privateVal:
console.log(obj.privateProp);
// Assigning a custom setter to obj.privateProp
// will only result in observing attempted assignments to obj.privateProp
// but will not be able to observe the change to privateVal:
Object.defineProperty(obj, 'privateProp', { set(newVal) {
console.log('Observed change: new value is ' + newVal);
}});
setTimeout(() => {
obj.privateProp = 1000;
}, 2500);
As you can see, the setter function added to obj in the outer scope cannot capture the change to privateVal. This isn't exactly what's happening with window.screen, but it's similar; you do not have access to the underlying code structure that results in changes to the value returned by calling the built-in getter.
The built-in getter function on screen is composed of native code - this means that it's not an ordinary Javascript function. Functions composed of native code are always functions provided by the browser (or whatever environment the code is running in); they often cannot be emulated by any Javascript functions you write yourself. For example, only the window.history object can perform history actions like history.back(); if window.history gets overwritten, and you don't have a saved reference to it or any other history objects, there's no way to write your own function that can do history.back(), because .back() invokes privileged native code that requires an interface between the visible Javascript and the browser engine.
Similarly, the built-in window.screen getter, when called, returns a value not directly observable by plain Javascript. Without polling, the only other way to watch for a change is by listening for the resize event, as covered in the other answer. (This resize event, similar to the underlying value returned by window.screen, is managed by browser internals not observable otherwise.)
If the resize event does not get fired in response to a change in resolution - for example, if the browser window is small enough already that a change is not required for the smaller resolution - then the resolution change does not result in any event being fired, which means there's unfortunately no way to listen for it, aside from polling. (You can try it for yourself, it doesn't look like any other events are triggered on resolution change)
One could probably write (or tweak) a browser engine which listens for a resolution change from the OS and dispatches a Javascript event when found, but doing so would require knowledge far beyond Javascript - feel free to browse Chromium's source code for details, but it's pretty complicated, and probably opaque to one without experience in the languages used.

According to Mozilla, screen resize event is only fired on window object. Therefore, you should add listener to window not to screen :
window.addEventListener('resize', yourfunc)
Tested under Windows 10, it works like a charm with Chrome. Worth to say that the actual screen size computed by the browser use either the screen resolution and the zoom.
For instance, if you're on 1920x1080, zoom 100% :
Passing to 3840x2160, zoom 200% will output a browser window of 1920x1080 and resize seems not triggered
Passing to zoom 150% will output a browser window of 1280x720 and resize is triggered
Nevertheless, there's a pitfall. Screen resolution update will only be detected if it actually triggers a browser window resize. That is not so obvious given the windowed/fullscreen initial state of the browser window and the smaller/bigger screen size evolution.

Related

Is there any way to create TouchEvents or something that works as one in Firefox?

I'm trying to write a browser add-on that can create artificial TouchEvents. So far I have it working in Chrome but Firefox does not seem to support TouchEvents, as trying to create one with new TouchEvent(...) produces:
ReferenceError: TouchEvent is not defined
I'm wondering if there's some way I can either implement TouchEvent myself in the add-on or otherwise dispatch an event that has all the necessary properties to be usable like a TouchEvent.
However if I create a CustomEvent and assign it the extra properties necessary for it to effectively be a TouchEvent (i.e. touches, targetTouches, and changedTouches) those properties don't persist by the time the webpage receives the event.
Is there any way to accomplish what I'm after, or am I out of luck trying to get this to work in Firefox?
In case anyone else has this question, here is the solution I found. You can create your own class that extends UIEvent like so:
class TouchEvent extends UIEvent {
constructor(name, initDict) {
super(name, initDict);
this.touches = initDict.touches;
this.targetTouches = initDict.targetTouches;
this.changedTouches = initDict.changedTouches;
}
}
The one caveat is that these events have to be constructed by a script running in the same domain as the handler that will receive them, otherwise you'll get a Permission denied to access property error. My solution for that was to use my extension's content script to add a script element in the current document's body and set its textContent property to my javascript code that creates and dispatches the touch events.
I couldn't get the accepted answer to work because I couldn't figure out how to construct the touches that need to be placed inside initDict. Here is what worked for me:
function dispatchTouchEvent(eventName, details) {
const uiEvent = document.createEvent('UIEvent');
uiEvent.initUIEvent(eventName, true, true, window, 1);
const event = Object.assign(uiEvent, {touches: [details]});
details.target.dispatchEvent(event);
}
And here's how to invoke it with a touchstart event on the body element at coordinates (50, 100):
dispatchTouchEvent('touchstart', {identifier: 0, target: document.body, pageX: 50, pageY: 100});
For my purposes I only needed to set the pageX and pageY properties, but depending on what you're doing it may be necessary to set clientX, clientY, screenX, or screenY.

Is a repaint required to get correct/updated element sizes?

Suppose some piece of code changed a size of an element and called my callback:
$element.css("width", 500);
elementsWidthChanged();
For the demonstration purposes suppose there is no way the new size can be passed to the callback. So inside the callback I'm trying to get new size:
function elementsWidthChanged() {
var width = $element.outerWidth();
}
Do I always get the new dimensions? Or do I need to wait for the repaint to occur?
The answer is that a reflow happens when you query the DOM for the new width. So yes, $element.outerWidth(); will return the new value. I found this article helps explain how the browsers handle reflows/repaints. But a brief summary:
The browser has a queue of DOM changes. When you set $element.css("width", 500); the change is added to the queue. Since DOM changes are expensive, the browser doesn't do them immediately. However, when you query the DOM for the new width it flushes the queue, updating the DOM, doing a reflow, and returning the new value. You can see this by doing a javascript profile. You'll notice that most of the time is spent in the .outerWidth() call doing the reflow, not the .css call.

Debugging: Is it possible to see value of JS variable in real time?

Is there any tool (preferably extension/add-on to any browser) that allows you to see all the value changes of the desired JS variable in real time?
Previously I did something like this (in pure JS):
var someVariable;
var previousValueOfSomeVariable;
var f = function() {
if (previousValueOfSomeVariable != someVariable) {
console.log(Date.now(), someVariable);
previousValueOfSomeVariable = someVariable;
}
}
var TO = setInterval(f, 100);
It did the trick, but was, of course, inefficient (in reality the function was bigger, while it required object-copy function if variable was an object and further checks)...
UPDATE
I'm aware of console tools, but I'd like to see the history of changes, like:
someVariable
0ms: undefined;
10ms: 5;
40ms: 'someothervalue';
150ms: null
etc.
(Milliseconds are given for example purposes, not necessarily required). Maybe it can be done within the DevTools console, but I don't know how.
Chrome dev tools has functionality for this.
insert the line
debugger;
right after the variable you're interested in. When your page executes and dev tools is open it will pause there and you can inspect the console.log with the value it had at that moment.
For example - say you have an onClick handler and want to see what information is passed in the event:
html:
<input onClicked={myFunc} />
JS
function myFunc(event){
console.log(event)
}
This will log the event to the console, but if you try to drill down chrome evaluates the obj when you click on it and since its long gone, its mostly null:
However if you use debugger, chrome pauses execution when it hits that and you can dig into the real event:
JS:
function myFunc(event){
console.log(event);
debugger;
}
Lets you drill down into the object as it was at the time you hit the debugger line
More info in the chrome dev tools site:
https://developers.google.com/web/tools/chrome-devtools/javascript/breakpoints
The different DevTools (tested in Chrome DevTools, Firefox DevTools and Firebug) don't offer a way to see the value changes in real time. You always have to refresh their display manually.
Firefox offers an Object.prototype.watch() function (for other browsers there is a shim), which does what you want.
Example:
test = 0;
setInterval(() => test++, 1000);
window.watch("test", (id, oldValue, newValue) => {
console.log(new Date().toLocaleTimeString(), newValue);
return newValue;
});
This will output something like this:
09:51:03 1
09:51:04 2
09:51:05 3
09:51:06 4
09:51:07 5
Note: This function only allows to watch single properties of an object, so, in order to watch all object properties you need to loop over them and call watch() for each one.
Ah yes object.watch . It isn't used very often though! Here is a more detailed post of what I think you're looking for Listening for variable changes in JavaScript or jQuery

save current window to use it at resume

i am trying to save the current window in focus event to app properties like this
$.profileWin.addEventListener('focus', function(e) {
Ti.App.Properties.setObject("curwin", $.profileWin);
});
i am doing this for more than 1 window
but at focus of window i get this error
-[TiUIWindowProxy encodeWithCoder:]: unrecognized selector sent to instance 0x1ea19c00";
how do i save the the current window and have it accessed when the iPhone resumes from its suspended state
When you try to save a value in Ti.App.Properties it will simply be converted to at string of text. I'm not sure setObject will accept anything else but JSON-objects (and a Ti.UI.Window is not a JSON-object).
That being said, saving the actual Window-object might not be a good idea, as the different dependencies might have been removed from memory, when you try to reload the window.
A better approach would be to save the relevant properties of the Window (and other values that you might need to restore the current state of the Window) and the re-layout the Window once it gains focus.

How do I bind a function to a change in an object's data member in Javascript?

I'm working on a project in JavaScript where we're building a Greasemonkey plugin to an organizational site we're using in our office. We're having trouble getting our changes to stay rendered, since we can't simply inject our changes into the existing render function.
As a result, we need to find every event where rendering happens and inject our own render function there. However, there are some events that we can see happening, but we can't hook into them. What I'd like to know is how to bind a function to an object's data member, so that the function is called whenever that member changes. One of our team members seemed to think it was possible, but the method he told us to use didn't seem to work.
What we tried was something along the lines of
window.Controller.bind("change:idBoardCurrent", OMGITWORKED);
where idBoardCurrent is a member of window.Controller and OMGITWORKED is the function we'd like to be called when window.Controller.idBoardCurrent is changed.
I'm not very familiar with JavaScript or data binding, so I have no idea if this is right or wrong, or what is correct or incorrect about it. If someone could point out what to change in this snippet, or if they could suggest another way to go about this, I would be very appreciative.
You can use Object.defineProperty to define a setter and getter for the Objects property
Object.defineProperty(window.Controller,"idBoardCurrent",{
get : function() { return this.val; },
set : function(value) {this.val = value;OMGITWORKED(value); }
});
function OMGITWORKED(param) {
console.log("idBoardCurrent has been Changed to " + param);
}
window.Controller.idBoardCurrent = "Test";
window.Controller.idBoardCurrent = "Test2";
console.log(window.Controller.idBoardCurrent)
Edit: changed the code according to the contexts object
JSBin
As this is specifically Firefox, you can use the mutation events it provides. But note the caveats on them from that page:
The W3C specification for them was never widely implemented and is now deprecated
Using DOM mutation events "significantly degrades" the performance of DOM modifications
If you're able to restrict yourselves to Firefox 14 and higher, you can use the new mutation observers stuff instead.
This is, when I am not totally wrong, more a question of javascript.
I found some information about that topic
Listening for variable changes in JavaScript or jQuery
jQuery trigger on variable change
Javascript Track Variable Change
Sorry when I didn't understand the topic.
All the best

Categories

Resources