I have a base class in ES6 like this:
class BasePlot {
props = {
'data': []
}
constructor() {
// do things but don't setup data
}
draw(data){
if (data && data.length )
this.props.data = data;
// Q: how to exit here if height and width are not yet available?
this.setScale()
this.setAxis()
}
setDimensions(height, width) {
this.props.height = height;
this.props.width = width;
}
}
The class will never be instantiated directly but will be used only for inheritance.
Apart the constructor, all the other methods might be called in unpredictable order, that is why in the draw method I don't want to proceed if height and width are not yet defined for the instance.
I could simply add an if condition and exit but that's not what I had in mind.
In a child class I call the parent draw like this:
class RectPlot extends BasePlot{
draw(data){
super.draw(data);
// DON'T EXECUTE if height and width are not set
// rest of the code
}
}
In this case when I call the child draw I first call the parent method, and in case the height and width are not set yet I'd like to exit (return) from the parent method but ALSO from the child one.
What I mean, is something like this:
// Parent
draw(data){
if (data && data.length )
this.props.data = data;
if(this.props.height && this.props.width)
this.setScale()
this.setAxis()
return true
}
else return false
}
}
// Child
draw(data){
if(super.draw(data)){
// proceed w rest of the code
}
else return false
}
This is exactly what I'd like to do, except I don't want to check with an if in all the subclasses if the parent draw completed successfully.
Q: Is there a way to 'early exit' a parent AND a child method besides the aforementioned repetition of the if-else block in all the children classes?
You cannot return from a caller method from within a called method. However, you could avoid the problem altogether...
An alternative design could be to create another method, say drawCanvas for example, as follows...
class BasePlot {
props = {
data: [],
};
draw(data) {
if (data && data.length) this.props.data = data;
if (this.props.height && this.props.width) this.drawCanvas(data);
}
drawCanvas(data) {
this.setScale();
this.setAxis();
}
}
class RectPlot extends BasePlot {
drawCanvas(data) {
super.drawCanvas(data);
// other stuff
}
}
You would then override drawCanvas instead of draw, knowing that drawCanvas is only called if width and height exist.
I agree that checking the returned value of super.draw(data), as in your example, is not ideal. It would be easy to forget this check, leading to potential errors. It is also not obvious to the reader of the child class implementation why this check is required without looking at the base class implementation.
Related
I ran into an issue in my current project where I needed to access the array that held an object, as the event listener that an object inside that object needed to access all of the elements of the array. So, I did a little test and discovered for myself that you can, in fact, store the container array that contains an object inside that object and it will actually reference the original array, not a copy.
There are some fairly obvious problems with this in terms of being maintainable code, but I'm curious as to what the potential harm of doing this is. Is there anything I should know regarding this recursive property scenario before I put this in my project? Is there a better way to do what I'm looking to do?
For clarity: the way I'm planning on setting it up is as follows:
The array (linesArray) contains a series of objects of class GraphLine. Each Graphline object (lineObject) contains a Raphael canvas element object (line) for the purposes of formatting that element post drawing. It would also contain a reference property to linesArray (container)
I am planning on having the lineObject.line.mousedown() [which fires on clicking the line] event run a for loop through lineObject.container, aka linesArray, to transform each of the lineObject.line's within it based off of which lineObject.line fired the lineObject.line.mousedown() event.
It would look something like this:
class GraphLine {
id;
line;
lineStr;
container;
constructor(container, /* Bunch of Inputs */) {
this.container = container;
//...
}
Draw() {
this.line = canvas.path(this.lineStr);
this.line.mousedown( function() {
for(let i = 0; i < this.container.length;i i++) {
if(this.container[i].id != this.id) {
//Do A Thing
}
else {
//Do A Different Thing
}
}
});
}
}
var canvas;
$(document).ready(function() {
function Run() {
var container = [];
canvas = new Raphael($('#canvas'), 100, 100);
container.push(new GraphLine(container, /* Bunch of Inputs */));
//...
container[0].Draw();
}
Run();
});
How can I iterate over instances of one custom element within the shadow dom of another custom element? HTMLCollections don't seem to behave as expected. (I'm a jQuerian and a novice when it comes to vanilla js, so I'm sure I'm making an obvious error somewhere).
HTML
<spk-root>
<spk-input></spk-input>
<spk-input></spk-input>
</spk-root>
Custom Element Definitions
For spk-input:
class SpektacularInput extends HTMLElement {
constructor() {
super();
}
}
window.customElements.define('spk-input', SpektacularInput);
For spk-root:
let template = document.createElement('template');
template.innerHTML = `
<canvas id='spektacular'></canvas>
<slot></slot>
`;
class SpektacularRoot extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(template.content.cloneNode(true));
}
update() {
let inputs = this.getElementsByTagName('spk-input')
}
connectedCallback() {
this.update();
}
}
window.customElements.define('spk-root', SpektacularRoot);
Here's the part I don't understand. Inside the update() method:
console.log(inputs) returns an HTMLCollection:
console.log(inputs)
// output
HTMLCollection []
0: spk-input
1: spk-input
length: 2
__proto__: HTMLCollection
However, the HTMLCollection is not iterable using a for loop, because it has no length.
console.log(inputs.length)
// output
0
Searching SO revealed that HTMLCollections are array-like, but not arrays. Trying to make it an array using Array.from(inputs) or the spread operator results in an empty array.
What's going on here? How can I iterate over the spk-input elements within spk-root from the update() method?
I'm using gulp-babel and gulp-concat and using Chrome. Let me know if more info is needed. Thanks in advance.
Edit: To clarify, calling console.log(inputs.length) from within the update() outputs 0 instead of 2.
The reason will be that connectedCallback() of a custom element in certain cases will be called as soon as the browser meets the opening tag of the custom element, with children not being parsed, and thus, unavailable. This does e.g. happen in Chrome if you define the elements up front and the browser then parses the HTML.
That is why let inputs = this.getElementsByTagName('spk-input') in your update() method of the outer <spk-root> cannot find any elements. Don't let yourself be fooled by misleading console.log output there.
I've just recently taken a deep dive into this topic, and suggested a solution using a HTMLBaseElement class:
https://gist.github.com/franktopel/5d760330a936e32644660774ccba58a7
Andrea Giammarchi (the author of document-register-element polyfill for custom elements in non-supporting browsers) has taken on that solution suggestion and created an npm package from it:
https://github.com/WebReflection/html-parsed-element
As long as you don't need dynamic creation of your custom elements, the easiest and most reliable fix is to create the upgrade scenario by putting your element defining scripts at the end of the body.
If you're interested in the discussion on the topic (long read!):
https://github.com/w3c/webcomponents/issues/551
Here's the full gist:
HTMLBaseElement class solving the problem of connectedCallback being called before children are parsed
There is a huge practical problem with web components spec v1:
In certain cases connectedCallback is being called when the element's child nodes are not yet available.
This makes web components dysfunctional in those cases where they rely on their children for setup.
See https://github.com/w3c/webcomponents/issues/551 for reference.
To solve this, we have created a HTMLBaseElement class in our team which serves as the new class to extend autonomous custom elements from.
HTMLBaseElement in turn inherits from HTMLElement (which autonomous custom elements must derive from at some point in their prototype chain).
HTMLBaseElement adds two things:
a setup method that takes care of the correct timing (that is, makes sure child nodes are accessible) and then calls childrenAvailableCallback() on the component instance.
a parsed Boolean property which defaults to false and is meant to be set to true when the components initial setup is done. This is meant to serve as a guard to make sure e.g. child event listeners are never attached more than once.
HTMLBaseElement
class HTMLBaseElement extends HTMLElement {
constructor(...args) {
const self = super(...args)
self.parsed = false // guard to make it easy to do certain stuff only once
self.parentNodes = []
return self
}
setup() {
// collect the parentNodes
let el = this;
while (el.parentNode) {
el = el.parentNode
this.parentNodes.push(el)
}
// check if the parser has already passed the end tag of the component
// in which case this element, or one of its parents, should have a nextSibling
// if not (no whitespace at all between tags and no nextElementSiblings either)
// resort to DOMContentLoaded or load having triggered
if ([this, ...this.parentNodes].some(el=> el.nextSibling) || document.readyState !== 'loading') {
this.childrenAvailableCallback();
} else {
this.mutationObserver = new MutationObserver(() => {
if ([this, ...this.parentNodes].some(el=> el.nextSibling) || document.readyState !== 'loading') {
this.childrenAvailableCallback()
this.mutationObserver.disconnect()
}
});
this.mutationObserver.observe(this, {childList: true});
}
}
}
Example component extending the above:
class MyComponent extends HTMLBaseElement {
constructor(...args) {
const self = super(...args)
return self
}
connectedCallback() {
// when connectedCallback has fired, call super.setup()
// which will determine when it is safe to call childrenAvailableCallback()
super.setup()
}
childrenAvailableCallback() {
// this is where you do your setup that relies on child access
console.log(this.innerHTML)
// when setup is done, make this information accessible to the element
this.parsed = true
// this is useful e.g. to only ever attach event listeners once
// to child element nodes using this as a guard
}
}
The HTMLCollection inputs does have a length property, and if you log it inside the update function you will see it's value is 2. You can also iterate through the inputs collection in a for loop so long as it's inside the update() function.
If you want to access the values in a loop outside of the update function, you can store the HTMLCollection in a variable declared outside of the scope of the SpektacularInput class.
I suppose there are other ways to store the values depending on what you're trying to accomplish, but hopefully this answers your initial question "How can I iterate over the spk-input elements within spk-root from the update() method?"
class SpektacularInput extends HTMLElement {
constructor() {
super();
}
}
window.customElements.define('spk-input', SpektacularInput);
let template = document.createElement('template');
template.innerHTML = `
<canvas id='spektacular'></canvas>
<slot></slot>
`;
// declare outside variable
let inputsObj = {};
class SpektacularRoot extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(template.content.cloneNode(true));
}
update() {
// store on outside variable
inputsObj = this.getElementsByTagName('spk-input');
// use in the function
let inputs = this.getElementsByTagName('spk-input');
console.log("inside length: " + inputs.length)
for(let i = 0; i < inputs.length; i++){
console.log("inside input " + i + ": " + inputs[i]);
}
}
connectedCallback() {
this.update();
}
}
window.customElements.define('spk-root', SpektacularRoot);
console.log("outside length: " + inputsObj.length);
for(let i = 0; i < inputsObj.length; i++){
console.log("outside input " + i + ": " + inputsObj[i]);
}
<spk-root>
<spk-input></spk-input>
<spk-input></spk-input>
</spk-root>
Hope it helps,
Cheers!
I need to use componentWillReceiveProps() to call a method in my component once three conditions are met. Two of these conditions compare current props to next props, and those two receive their values via an Ajax request. The problem is not all conditions will be true at the same time.
For example.
export class Styles extends Component {
componentWillReceiveProps(nextProps) {
if (
!_.isEmpty(nextProps.one) && // set via ajax request
!isEqual(this.props.two, nextProps.two) &&
!isEqual(this.props.three, nextProps.three) // set via ajax request
) {
this.myMethod();
}
}
myMethod() {
… do something
}
render() {
return (
<div />
)
}
}
Because two of the props are being set with an Ajax response, I can’t be sure when those values are set and when they’ll fulfill the condition. I obviously need to achieve three true values in order to call this.myMethod(), but instead I get various combinations of true/false at any given time. It’s a bit of a lottery scenario.
Do I ultimately need to manage each of these conditions temporarily in state, then clear them out when they’re met?
You could do this without the deprecated componentWillReceiveProps with something like this:
export class Styles extends Component {
isDirtyTwo = false;
isDirtyThree = false;
..
componentDidUpdate(prevProps) {
this.isDirtyTwo = this.isDirtyTwo || !isEqual(prevProps.two, this.props.two);
this.isDirtyThree = this.isDirtyThree || !isEqual(prevProps.three, this.props.three);
if (!_.isEmpty(this.props.one) && this.isDirtyTwo && this.isDirtyThree) {
this.isDirtyTwo = false;
this.isDirtyThree = false;
this.myMethod();
}
}
..
}
This will call myMethod when one is empty and both other props have changed at some point. (I'm assuming that once one is empty it stays that way, but if not, you could add another property for it.)
I have a couple of d3 charts that share quite a lot of functionality. However they do diverge in a several areas. I'm considering moving them to separate classes based off a parent class because the singular buildChart function's length has grown unmaintainable.
The problem I'm facing right now is that even though it seems pretty easy to override certain methods from the parent in some instances, it would be nice to have the parent build most of one setup object, which contains properties like tick intervals, display formats, etc, but have each child class add a few properties to that object.
I thought to do this in the following way:
class Chart {
constructor({ svg, data, range }) {
this.svg = svg;
this.range = range;
this.data = data;
this.setDetails();
}
setDetails() {
this.details = {
sharedProp: "this property is shared"
};
}
scaffoldChart() {
/* initial d3 stuff */
}
}
export class SimilarChartUno extends Chart {
constructor({ svg, data, range }) {
super({ svg, data, range });
this.decorateDetails()
super.scaffoldChart()
}
decorateDetails() {
this.details = Object.assign(this.details, {
someUniqueProp: 'prop for UNO a'
})
}
}
// instance details :
{
"sharedProp": "this property is shared",
"someUniqueProp": "prop for UNO A"
}
This seems to work, but I have not seen an example like this anywhere: Is there is a better pattern than this?
Code Sandbox
There's nothing wrong with doing it the way you are currently, but if you'd like to avoid adding methods you could alter your SimilarChartUno to be something like this:
class SimilarChartUno extends Chart {
constructor({ svg, data, range }) {
super({ svg, data, range });
super.scaffoldChart()
}
setDetails() {
super.setDetails();
this.details.someUniqueProp = 'prop for UNO a';
}
}
In this way, you've maintained inheritance. The setDetails() is invoked in the super constructor with the child implementation. Alternatively, you can still perform an this.details = Object.assign(this.details, { complexObject: {} }); after the super.setDetails(); call for more complex assignments.
I currently have the following working code:
Function.prototype.GetLastCallerName = function () {
if (!this.arguments || !this.arguments.callee || !this.arguments.callee.caller) return null;
var result = /^function\s+([\w\$]+)\s*\(/.exec(this.arguments.callee.caller.toString());
this.LastCaller = result ? result[1] : 'Anonymous';
return this.LastCaller;
};
I picked up that code from another thread. As you can see, it extends the Function.prototype in order to add a method called GetLastCallerName, which picks the last calling function name and (1) sets it to LastCaller on Function.LastCaller and (2) returns it.
In order to make it work:
function MyFunction1() {
MyFunction1.GetLastCallerName();
console.log(MyFunction.LastCaller);
}
function MyFunction2() {
MyFunction1();
}
MyFunction2();
What I'd like to be able to do: Eliminate the need to use GetLastCallerName() every time and extend Function in order to perform that get every time any function is called.
I'm struggling to follow what you have tried so far with your example, but I think I get the idea of what you'd like to do. Why not leverage classes, and extend on them for your use case. Check out the following example...
class Base {
baseFn() {
console.log('from base');
}
}
class Thing extends Base {
fn1() {
this.baseFn();
}
}
let thingee = new Thing();
thingee.fn1();
So baseFn is now always called when fn1 is called.
JSFiddle Link - class demo
In some of your comments it looks like you are wanting to get the "last calling function's name." How about passing back the instance of the caller itself to the parent? This would surely give you even more flexibility because now you can sculpt your caller however you wish. Check out the following...
class Base {
baseFn(caller) {
console.log(caller.id); // 1
}
}
class Thing extends Base {
constructor(id) {
super();
this.id = id;
}
fn1() {
this.baseFn(this);
}
}
let thingee = new Thing('1');
thingee.fn1();
Now you can add whatever you'd like to your Thing instance, in this case, an object with an id of 1 which can be inspected when fn1 propagates up to baseFn
JSFiddle Link - caller demo