I'm working on an inheritance chain of 3 extendable classes.
Renderer (base) -> Mailbox (child) -> MailboxInbox (final child)
Each of them has its own render
method. Children just override the Renderer (Base) render
method and finally they should anyway use the render method of the Renderer
(Base< /strong>) class . My problem is that when I execute certain methods in the Renderer class (createElementWithTextInside), it does not fully execute the Renderer's render method (Renderer . render()), but it starts at the end of the extensible class chain. For example, it starts traversing:
1 - MailboxInbox.render
2 - Mailbox.render
3 - Renderer.render
I understand how js classes/inheritance work behind the scenes. Javascript just extends the object prototypes and the script will iterate over them etc. But my question is: How can I avoid this behavior and directly call the Renderer.render method in its method when needed?
I can't save __self in the renderer because it would point to this
of the current instance (MailboxInbox) anyway. Also, I can't use .bind/lambda (arrow function) because I need to save the context
violin
class Renderer { get noResultContent() { const p = this.createElement('p', 'no-result-msg'); const textNode = document.createTextNode('No result'); p.appendChild(textNode); return p; }; createElement(tag, className) { if (!tag) throw new Error('Tag must be passed!'); const res = document.createElement(tag); res.classList.add(className); return res; } createTextNode(text) { const res = document.createTextNode(text); return res; } /** * automatically creates an el based on tag, text node and insert the text node into the el */ createElementWithTextInside(tag, className, content) { const el = this.createElement(tag, className); const text = this.createTextNode(content); this.render(el, text); return el; } checkIfDomEl = el => el instanceof Element; render(el, content) { console.log('3: RENDERER RENDER') if (!this.checkIfDomEl(el)) throw new Error(`Please, pass the valid DOM el. Received: ${el}, ${typeof el} `); const finalContent = content || this.noResultContent; el.appendChild(finalContent); } } -------- class Mailbox extends Renderer { rootEl; constructor(selector) { super(); this.rootEl = document.querySelector(selector); } renderTitle(content) { if (!content) return null; const titleEl = super.createElementWithTextInside('h3', 'mailbox-title', content) super.render(this.rootEl, titleEl); } render() { console.log('2: MAILBOX RENDER') super.render(this.rootEl); } } -------- class MailboxInbox extends Mailbox { title = "Inbox" constructor(params) { const { selector } = params; super(selector); } renderTitle() { super.renderTitle(this.title); } render() { console.log('1: INBOX RENDER') super.render(); } } -------- const inbox = new MailboxInbox({selector: '#container'}); inbox.renderTitle()
Here it just renders in the console:
1: Inbox Rendering
2: Mailbox rendering
3: Renderer rendering
thanks for your help! kind regards!
renew
Basically I just want to have a basic render
class that can accept parameters: (el,content)
(content is optional) and Childreni Wanted to override it with some predefined el
and content
etc in their own .render()
method, but if I try to execute renderer The .render()
method comes from inside Renderer and it goes through the whole chain and my parameters are missing because in MailboxInbox the render method currently doesn't accept any parameters, So I should either have it accept parameters and pass them throughout the chain, or just define some dedicated class like baseRender in Renderer
and call it directly< /p>
Technically you can replace
this.render(el, text);
withIt bypasses inherited property lookups. However, this is generally not a good practice and will lose the advantages of
class
inheritance.Fortunately, you've identified the actual problem in your update:
This does incorrectly override a method with an incompatible signature, violating the Liskov Substitution Principle .
You have also identified potential solutions:
Both are good. In the latter, I recommend not naming the methods
render
andbaseRender
, but instead recommend something likerenderContent(el, content)
andrenderDefault (el)
, their signatures are actually different - and both can be overridden. However, looking at the implementation ofrender
when called with two parameters, it seems to me that it doesn't actually do anything useful other than callingel.appendChild(content)
thing, so I'd rather abandon it entirely and just callappendChild
(unless you need the ability to override specific behavior, such as by doingel.prepend(content)
instead).