Web component setting property race condition - google-chrome

After a lot of digging I figured out sometimes if I set a property before the element is fully constructed, it overrides the setter. I found this helpful code on google developers:
A developer might attempt to set a property on your element before its definition has been loaded. This is especially true if the developer is using a framework which handles loading components, inserting them into to the page, and binding their properties to a model.
connectedCallback() {
...
this._upgradeProperty('checked');
}
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
Basically if the setter has been overriden by the property, it deletes the property, and then reapplies it so that the setter picks it up.
It works about 70% of the time. I can get it to work 100% with a nasty hack of setting a timeout:
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
setTimeout( () => this[prop] = value, 1); // <-- Hack here
}
}
This feels really gross, is there any other way to upgrade the property after the element has actually got its getters/setting fully created and ready to go (connectedCallback is not cutting it). Is it a Chrome Bug?

Related

Best way to dim/disable a div in Material-UI?

In my app, I have divs that I want to dim and disable mouse events for, depending on component state - for example, loading. The initial method I came up with is to have a helper function that returns an inline style for dimming an element and disabling pointer events on it, given a boolean value:
const disableOnTrue = (flag) => {
return {
opacity: flag ? 0.15 : 1,
pointerEvents: flag ? "none" : "initial"
}
}
and using it on elements as such:
{loading && {/** render a loading circle */}}
<div style={disableOnTrue(this.state.loading)}>{/** stuff to be dimmed & disabled while loading */}</div>
In the disabled div, there are Material-UI Buttons. However, it turns out that they don't care if pointerEvents are disabled on their parent div, and remain clickable, which is a big problem. So, on the Buttons I had to set disabled={loading}. Then, this dims the Buttons themselves, which unnecessarily compounds with the lowered opacity of disableOnTrue, meaning I would need to add some custom styling to ameliorate that; I want the entire div to be disabled, not for the Button to look especially disabled.
I've also tried using the Backdrop component from Material, but couldn't get it to dim anything but the entire viewport.
Before I implement any sort of hacky solution throughout my entire app, I figured I should ask here to see if there is a clean way to achieve this that I'm missing. I've looked for quite a while, but haven't found anything.
I split the concept of "disabling" into two functions:
const dimOnTrue = (flag) => {
return {
opacity: flag ? 0.15 : 1,
}
}
const disableOnTrue = (flag) => {
return {
pointerEvents: flag ? 'none' : 'initial'
}
}
to be used on divs that should be dimmed and inputs that should be disabled, respectively.

What does the isVisible property mean in intersectionObeserver API?

Before lynching me in the comments hear me out.
The intersectionobserver is supposed to listen for when an element is scrolled into view right?
But when I do so the isVisible property is false, cant figure out why. I think its because Im not understanding isVisible property because the other properties are working.
Here is my code:
import React, { useRef, useEffect, useState, useContext } from 'react'
import ReactGA from 'react-ga';
import Context from '../utils/context'
const Home = props => {
const context = useContext(Context)
const [scrollState, setScroll] = useState(false)
const intersectTarget = useRef(null)
useEffect(() => {
// ReactGA.pageview(props.location.pathname);
if(!context.initialLoadProp) {
context.setInitialLoadProp(true)
}
}, [])
useEffect(() => {
const opts = {
root: null,
rootMargin: '0px',
threshold: 0
}
const callback = entry => {
console.log(entry)
}
const observerScroll = new IntersectionObserver(callback, opts)
observerScroll.observe(intersectTarget.current)
}, [])
return(
<div>
<img height="500px"
width="500px"
src="https://timedotcom.files.wordpress.com/2019/03/kitten-report.jpg" alt=""/>
<div id="heading1" style={{height: '500px'}}>
<h1>First Heading</h1>
</div>
<div style={{height: "500px"}}>
<h1 ref={intersectTarget}
id="heading2">
Second Heading
</h1>
</div>
</div>
)
};
export default Home;
My output even thought isIntersecting is true isVisible is false.
To activate IntersectionObserver v2 isVisible feature, you must add two options as follows:
const options = {
root: null,
rootMargin: '0px',
threshold: 0,
/* required options*/
trackVisibility: true,
delay: 100 // minimum 100
}
Visibility is calculated as follows:
If the observer's trackVisibility attribute is false, then the target is considered visible. This corresponds to the current v1 behavior.
If the target has an effective transformation matrix other than a 2D translation or proportional 2D upscaling, then the target is considered invisible.
If the target, or any element in its containing block chain, has an effective opacity other than 1.0, then the target is considered invisible.
If the target, or any element in its containing block chain, has any filters applied, then the target is considered invisible.
If the implementation cannot guarantee that the target is completely unoccluded by other page content, then the target is considered invisible.
Check IntersectionObserver v2 browser support before using it https://caniuse.com/#feat=intersectionobserver-v2
The "isVisible" property is part of proposed Intersection Observer v2 updates concerning actual visibility of the target element to the user. When passing the options object to the observer you can include a "trackVisibility" boolean to implement it, but a corresponding "delay" property is also required. The value of this delay is in milliseconds and needs to be a minimum of 100. If the appropriate delay is not provided then you'll receive an error in the console and the observer will not have been initiated.
At this point I'm only aware of Chrome supporting this feature. So far I've been unable to actually get the visibility feature to work, the delay seems to work though. It's an experimental feature of an experimental feature.
Intersection Observer v2 article
[note: I wrote the specification for IntersectionObserver and implemented it in Chrome].
As stated in previous answers, isVisible only has meaning if the browser supports IntersectionObserver V2 (currently, only chromium-based browsers, including Chrome and Edge); and if you include the 'trackVisibility' and 'delay' parameters when constructing the observer.
This is not considered to be an experimental feature in Chrome; it is a complete, stable feature used by large production websites. However, it is unclear when it will be supported by other browsers. The most up-to-date information about the browsers supporting this feature is here:
https://chromestatus.com/features/5878481493688320
If you think the feature could be improved, consider filing an issue in the spec repository:
https://github.com/w3c/IntersectionObserver/issues
According to https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry there is no isVisible property as far as I can see.
If your goal is to check when the element is completely in view, you can change the threshold to threshold: 1 and then check if the intersectionRatio is equal to one entry.intersectionRatio == 1.
I might have missed the isVisible somewhere but I cant find it.
Hope this helps!

how to force a Polymer.Element extended class to execute its lifecycle without attaching it to the dom?

Consider this element (minimal for the purpose of the question) :
class MyCountDown extends Polymer.Element
{
static get is () { return 'my-count-down'; }
static get properties ()
{
return {
time: { /* time in seconds */
type: Number,
observer: '_startCountDown'
},
remains: Number
}
}
_startCountDown ()
{
this.remains = this.time;
this.tickInterval = window.setInterval(() => {
this.remains--;
if (this.remains == 0) {
console.log('countdown!');
this._stopCountDown();
}
}, 1000);
}
_stopCountDown () {
if (this.tickInterval) {
window.clearInterval(this.tickInterval);
}
}
}
customElements.define(MyCountDown.is, MyCountDown);
If I get one instance and set the property time,
let MyCountDown = customElements.get('my-count-down');
let cd = new MyCountDown();
cd.time = 5;
the property time changes but the observer and the _startCountDown() function is not called. I believe Polymer is waiting for the Instance to be attached to the DOM because in fact when I appendChild() this element to the document the count down starts and after 5 seconds the console logs 'countdown!' as expected.
My goal is to execute this lifecycle without attaching anything to the document because the instances of MyCountDown are not always attached to the view but/and they need to be live-code between the different components of my web application.
One solution is to attach the new MyCountDown instances to an hidden element of the dom to force the Polymer lifecycle but I think this is not so intuitive.
I don't know the exact place to call, but the problem you have is that the property assessors are not in place.
I think you might get a clue from this talk https://www.youtube.com/watch?v=assSM3rlvZ8 at google i/o
call this._enableProperties() in a constructor callback?

React upgrade: "this" visibility in getDefaultProps

I am upgrading some older react component I inherited (v0.10.0) to work with the latest version of react (v0.14.8).
The following scenario stopped working:
// within a react component
onClick: function() {
// DO SOMETHING
}
getDefaultProps: function () {
return {
someProp: 'prop',
onClick: this.onClick
}
}
This is easily resolved moving the code into an anonymous function, like the following:
getDefaultProps: function () {
return {
someProp: 'prop',
onClick: function() {
//DO SOMETHING
}
}
}
My question is: why has the visibility of 'this' changed at that level and what's the best way to refactor this code? And what if I had-to/wanted-to use 'this' at that level?
Any help appreciated, as a disclaimer I am a react super-beginner!
The result of getDefaultProps() is shared across all instances of a component. That means that the result can't rely on any particular instance of the component. The reason it changed is likely because of the performance benefit from caching, although I can't say for sure.
As for refactoring the code, I'm not sure there's a silver-bullet here. From my perspective what you currently have seems like an anti-pattern. Props are meant to be passed in by consumers that have no knowledge of the inner workings of the component, so it seems odd that a default value for a prop would depend on the inner workings. Without knowing exactly what you're doing, I would say your best bet is to just use null as the default value for the prop, then check the value at runtime when you do have access to the this context.
handleSomeAction() {
if (!this.props.onClick) {
// DO SOMETHING
}
}

Is there a way to detect reflows instantly in Angular, without using $timeout?

I'm working on a site with a scrollable list of canvases for plotting data, and I need to resize the canvases whenever the width of the div they're in changes.
I have it working in most cases, but if I delete a plot such that the scroll bar goes away, it doesn't work. I tried the following, where plotsScroller is the div with the scroll bar and plotsList is what's inside of it:
$scope.isScrollingPlotsList = function() {
return plotsList.offsetHeight > plotsScroller.offsetHeight;
}
$scope.$watch('isScrollingPlotsList()', $scope.$apply);
This code would work except that no $digest happens after the reflow that removes the scroll bar; $digest is called when I delete a plot but I guess the reflow happens later.
Does anyone know how I can detect the reflow without using $timeout?
You can use Mutation Observers to detect changes in the DOM. When a change occur you will be notified and can traverse to see what changed.
An example of usage (source):
// select the target node
var target = document.querySelector('#some-id');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
// later, you can stop observing
observer.disconnect();
Also see the source link for details.
It should be supported in all major browsers incl. IE11. For [IE9, IE11> there exists a polyfill that can be used.
The specification