📜 ⬆️ ⬇️

Using inheritance when generating web pages in pure javascript

Hi, Habr!

I am not a front-end developer, but sometimes there are tasks of rapid prototyping of the WEB interface in relation to business applications. The specifics of the industry are many similar entities (and therefore interactive forms), where the use of OOP, and specifically inheritance, makes life very easy. I heard that in the world of WEB, in order to deal with complexity, they mostly use composition, but I wanted to use inheritance - it gives a more rigid, coherent application structure (as opposed to a weakly-connected component), and reflects the subject area well. The task sounded like this - on the server there are data structures connected by the inheritance hierarchy, you need to create in the browser a similar hierarchy of interactive forms (pages) where markups, styles and behavior are inherited - naturally, with the ability to re-define any of the entities.

Restrictions I set myself following:


I understand that for the professional “front end” all of the above sounds trite, but I, like June, was very pleased with the beauty and brevity of the resulting solution, and decided to leave this article here — maybe some other June would come in handy.

As a simple example - an application of 3 pages. The first page is home, the second user loads the file with data, and the third one - enters the formula and gets the result of the calculation over the second page data. Further, as they say, "talk is cheap, show me the code".

The entry point to the application is index.html . We import the homepage class, instantiate and display it. We also import the global navigation function, which is used in the markup like this: <button onclick = '' nav ('Page_Home') ''>

<!-- index.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <script type="module"> import Page_Home from './Page_Home.js' (new Page_Home()).show() import {nav} from './Nav.js' window.nav = nav </script> </body> </html> 

The base ancestor of all pages contains methods that return various markup blocks, handler functions (if any), the initial initialization method load (), and the view () mapping method, which, in fact, is responsible for saving / restoring DOM on entry / exit.

 // module Page_.js export default class Page_ { //   HEAD head() { return ` <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JS OOP</title> <style></style> `} //   ,   style() { return ` .menubar {background-color: silver; font-weight: bold} .a {color: darkblue} .a:hover {color: darkred; cursor: pointer} .acurr {color: darkred; background-color: white} ` } //   BODY    body() { return ` <div class="menubar"> <span class="a" onclick="nav('Page_Home')"> Home </span> <span class="a" onclick="nav('Page_Upload')"> Upoad data </span> <span class="a" onclick="nav('Page_Calculate')"> Calculate </span> </div> <div id="content"></div> `} //    ,   content() { return ` `} //     DOM ( HEAD  BODY) constructor() { this.headsave = undefined this.bodysave = undefined } //     ,   load() { document.head.innerHTML = this.head() document.querySelector('head > style').innerHTML = this.style() document.body.innerHTML = this.body() document.querySelector('body > #content').innerHTML = this.content() } //       //  DOM  ,  DOM  //       window // window.page       //      show() { if (window.page !== undefined) { window.page.headsave = document.head.innerHTML window.page.bodysave = document.body.cloneNode(true) } window.page = this if (window[this.constructor.name] === undefined) { window[this.constructor.name] = this this.load() } else { document.head.innerHTML = this.headsave document.body = this.bodysave } let a = document.querySelector('[onclick = "nav(\'' + this.constructor.name + '\')"]'); if (a !== null) { a.className = 'acurr' } } } 

Home Page - override only the method that returns the content.

 // module Page_Home.js import Page_ from './Page_.js' export default class Page_Home extends Page_ { content() { return ` <h3>Hi, geek !</h3> `} } 

File download page - override content, add one style, enter new fselect () handler. Notice how the handler is assigned in the markup — via the global page variable, which always contains a link to the current page.

 // module Page_Upload.js import Page_ from './Page_.js' export default class Page_Upload extends Page_ { content() { return ` <br> <input type="file" onchange="page.fselect(this)"/> <br><br> <textarea id="fcontent"></textarea> `} style() { return super.style() + ` textarea {width: 90vw; height: 15em} `} fselect(elem) { let fr = new FileReader() fr.readAsText(elem.files[0]) fr.onload = (ev) => { document.querySelector('#fcontent').value = ev.target.result } } } 

Calculation page - we redefine the content, change the page title, add a handler.

 // module Page_Calculate.js import Page_ from './Page_.js' export default class Page_Calculate extends Page_ { content() { return ` <br> <label for="formula">Formula:</label><br> <textarea id="formula" style="width:90vw; height:5em">data.length</textarea> <br><br> <button onclick="page.calc()">Calculate...</button> <br><br> <div id = "result"></div> `} load() { super.load() let t = document.querySelector('head > title') t.innerHTML = 'Calculation result - ' + t.innerHTML } calc() { let formula = document.querySelector('#formula').value if (!formula) { return alert('Formula is empty !') } let datapage = window.Page_Upload; if (datapage === undefined) { return nodata() } let data = datapage.bodysave.querySelector('#fcontent').value if (!data) { return nodata() } document.querySelector('#result').innerHTML = 'Result: ' + eval(formula) function nodata() { alert('Data is not loaded !') } } } 

Global navigation function . If the requested page is not in memory, create it from the class, otherwise activate it. Unfortunately, the navigation module has to statically import the classes of all pages. When dynamic import is finally implemented in browsers, only the function will remain.

 // module Nav.js import Page_Home from './Page_Home.js' import Page_Upload from './Page_Upload.js' import Page_Calculate from './Page_Calculate.js' export function nav(pagename) { if (window[pagename] === undefined) { eval('new ' + pagename + '()').show() } else { window[pagename].show() } } 

Actually, this is all I wanted to show - the pages turned out to be quite compact (the deeper the inheritance hierarchy is the more compact the descendants), the styles and functions are strictly encapsulated, the markup and code are located side by side, in the same file. Saving context allows you to build multipage hierarchical forms, master assistants, etc.

Disadvantages:


A working example is here.

PS: I will be grateful for the information - whether inheritance is used in WEB frameworks, and generally in front-end development.

Thank.

Source: https://habr.com/ru/post/443762/


All Articles