Building modern web applications
posted on 10 Jul 2024 under category application
Date | Language | Author | Description |
---|---|---|---|
10.07.2023 | English | Claus Prüfer (Chief Prüfer) | Developing modern web applications—a completely new approach |
After the Web 2.0 era, what comes next? Marketing strategists and technology consulting firms have yet to identify the next major Internet boom. This essay provides insights into an entirely novel technology approach for modern web application development.
Web application development has remained a trending topic for decades. The “How” has become more important than ever; for example, both ChromeOS and Microsoft now leverage web technologies directly for user interfaces (UI/UX) within their operating systems.
Being a leader in state-of-the-art technology was crucial historically. Video game companies, for instance, modified arcade mainboard firmware or enhanced Nintendo SNES cartridges with assembler-driven 3D functionality—gaining significant recognition.
From the dawn of the Internet serving static content, development has progressed through several pivotal milestones:
Year | Achievement | Description |
---|---|---|
1962 | First Internet | Developed by the US Military; operational deployment |
1991 | Static Web Server | Public access to the Internet; WWW serves static content |
1994 | Web Server C-Language Modules | Dynamic content rendering via server-side C-language interfaces |
1996 | Apache WebServer / CGI | Common Gateway Interface simplifies Web app development |
1998 | CGI / Perl | Perl becomes the standard for CGI programming in Web apps |
2000 | Load Balancing / Redundancy | DNS/WWW usage drives need for load balancing and redundancy |
2010 | Carrier Grade Requirements | Internet user numbers surge; carrier-grade scalability becomes essential |
2011 | Cloud Infrastructure / SDN | Cloud-native infrastructure and Software Defined Networking evolve |
2015 | VoIP / VoD | Digital telephony, TV, and streaming replace analog systems |
2020 | OpenStack / Google Kubernetes | OpenStack, Docker, and Google Kubernetes revolutionize cloud computing |
The OSI Layer 7 Model standardizes network communication, enabling interoperability among network cards, computers, browsers, and web services over the global packet-switched IP infrastructure.
On top of network infrastructure, the classical 3-tier architecture separates concerns to facilitate dynamic multi-user web applications:
Modern infrastructure trends toward multi-tier architectures, adding layers for clearer design and well-defined interfaces.
Note: Lack of standardization can lead to chaotic proliferation in upper-layer infrastructure.
The traditional method—still prevalent in many frameworks—returns fully rendered HTML (entire pages or partial fragments) from server-side scripts.
+----------------------+ +---------------------+
| Browser | -> Request HTML (inc. JS) -> | Backend Application |
| (JavaScript / Logic) | (dynamically rendered) | (PHP/Python/Java) |
| | | |
| | <------ Return HTML <------------ | |
+----------------------+ +---------------------+
A disruptive innovation: Browser-based Single Page Applications (SPA).
Unlike server-rendered content, SPA eliminates server-side HTML rendering. Instead, meta/skeleton data is fetched during initialization, after which the application operates entirely in the browser. Only dynamic data (e.g., search queries, business logic) is exchanged with the backend, and real-time frameworks handle object data updates.
+--------------------------------+ +-----------------------------+
| Browser | | JSON / Data Middleware / AS |
+--------------------------------+ +-----------------------------+
| - Application Logic | 1. <-- Get Base | Return Base.JS scripts |
| (JavaScript OOP) | 2. <-- Get Meta | Return Skeleton.JSON |
| - Templating / Dynamic Styling | | |
| - Object Instance Creation | | |
| - ObjectInstance Realtime | | |
| - ObjectInstance Intercom | | |
| - Service / Data Abstraction | 3. --> OnDemand | Scripts::return JSON-Data |
| ObjectInstance-Mapping | 4. <-- JSON | |
| - Internal Event Handling | | |
| - Interval Event Handling | | |
| - Extensions / Modules | | |
+--------------------------------+ +-----------------------------+
A specialized layer-switching technique can further enhance SPA performance.
All SPA content layers are pre-rendered at initialization. When a menu link is clicked, the corresponding content layer becomes visible, while others are hidden. Dynamic updates for all objects within the visible layer are then triggered.
+---------------------------------------+----------------------------------------+
| Browser | Content Area |
+---------------------------------------+----------------------------------------+
| - MenuLink1 | Current Content: Menu3 |
| - MenuLink2 | |
| - MenuLink3 (OnClick::updateContent) | |
| - MenuLink4 | |
+---------------------------------------+----------------------------------------+
The “Model-View-Controller” pattern aims to separate data, UI, and logic for greater clarity.
This is partly true for server-rendered apps; however, SPA enables seamless MVC integration.
Server rendering introduces Mixed Layers: logic resides both in the browser and backend. When backend code isn’t JavaScript, DOM manipulation is non-native and increases complexity.
SPA solves the Mixed Layers Problem—all logic remains on the client.
SPA significantly reduces complexity and enables a clean MVC architecture. Yet, it’s not the sole innovation of the new approach.
Node.js simplifies server-side rendering by implementing Google’s JavaScript ECMA DOM rendering engine at the application server level.
However, issues persist regarding application logic processing and templating.
CSS (Cascading Style Sheets) has long facilitated separation of layout from HTML, enabling easy theming.
This does not equate to OOP-based object templating—the foundation of the new approach. Basic templating via CSS is standard practice.
JavaScript’s object-oriented capabilities are limited; true instance cloning and native DOM content manipulation aren’t available out-of-the-box.
Binding JS object instances to DOM elements (recursively) is not natively supported.
As the Irish say, “This is shite.” If we pursue OOP, let’s do it properly.
Binding JS object instances to DOM elements—this is the foundation of the new technology approach.
Consider this example:
<div1 class="style1">::cI_BaseClass_div1
<div2>::cI_BaseClass_div2
<div3>content1</div3>::cI_BaseClass_div3
<div4>content2</div4>::cI_BaseClass_div4
</div2>
</div1>
If JavaScript class instances (including subclasses) could be bound to DOM nodes, it would elevate JS OOP programming significantly.
Such capabilities should ideally be part of the ECMA JavaScript engine.
Instead, can the entire DOM be rendered from a custom OOP model’s class hierarchy in JavaScript?
Yes! With this extension, DOM, JS control logic, and templating become highly powerful.
For technical details, see: Chapter 14. Basic Code Model
Node.js with TypeScript and Chrome V8 JS engine remains limited compared to the new approach. Server-side rendering restricts real-time processing; only client-side engines (SPA) enable true real-time interaction.
Does attaching all base and inherited methods to each <div> and pre-rendering all layers hurt performance?
No!
Thanks to JavaScript’s “everything is a pointer” paradigm and modern CPU caching, object duplication is negligible. Similar to GPU processing, copying objects thousands of times has minimal overhead due to caching.
With pointer-based infrastructure, over 50,000 complex, dependent objects can be rendered in less than one second on a typical x86_64 CPU, as verified with x0 Framework v0.98rc.
The new approach enables:
Objects can be cloned or their structure copied, with dynamic properties assigned—requiring zero client-server communication.
The following example, derived from the x0 JavaScript framework (Chapter 15 - x0 Framework), demonstrates how to define classes, member functions, properties, and dependencies to achieve the new technology approach.
The cornerstone is sysBaseObject
. Base properties serve as templates to be overridden in class instance prototypes.
//------------------------------------------------------------------------------
// CONSTRUCTOR "sysBaseObject"
//------------------------------------------------------------------------------
function sysBaseObject() {
this.DOMType = 'div'; // Default DOM element type
this.ObjectID = null; // Object ID
this.ParentObject = null; // Parent object reference
this.DOMObjectID = null; // DOM object ID (recursive)
this.DOMParentID = null; // Parent DOM object ID (recursive)
}
Two member functions establish links between internal objects and DOM elements: addObject(ChildObject)
and renderObject(Prefix)
.
//------------------------------------------------------------------------------
// METHOD "addObject"
//------------------------------------------------------------------------------
sysBaseObject.prototype.addObject = function(ChildObject) {
ChildObject.ParentObject = this;
this.ChildObjects.push(ChildObject);
}
The parent’s object reference is assigned to the child’s ParentObject
property, and the child instance is added to the parent’s ChildObjects
array.
//------------------------------------------------------------------------------
// METHOD "renderObject"
//------------------------------------------------------------------------------
sysBaseObject.prototype.renderObject = function(Prefix) {
this.DOMParentID = Prefix;
this.createDOMElement(this.DOMObjectID);
this.appendDOMParentElement();
this.setDOMElementValue();
for (var i in this.ChildObjects) {
var ChildItem = this.ChildObjects[i];
ChildItem.renderObject(this.DOMObjectID);
}
}
This method generates the DOM element, appends it to its parent, sets its value, and recursively renders all child objects.
//------------------------------------------------------------------------------
// METHOD "createDOMElement"
//------------------------------------------------------------------------------
sysBaseDOMElement.prototype.createDOMElement = function() {
var Element = document.createElement(this.DOMType);
Element.setAttribute('id', this.DOMObjectID);
this.DOMDivElement = Element;
}
Creates the DOM element using the specified type and ID.
//------------------------------------------------------------------------------
// METHOD "appendDOMParentElement"
//------------------------------------------------------------------------------
sysBaseDOMElement.prototype.appendDOMParentElement = function() {
if (this.DOMParentID == null || this.DOMParentID === undefined) {
document.body.appendChild(this.DOMDivElement);
} else {
var parentElement = document.getElementById(this.DOMParentID);
parentElement.appendChild(this.DOMDivElement);
}
}
If no parent ID is set, appends the element to the body; otherwise, to the specified parent element.
//------------------------------------------------------------------------------
// METHOD "setDOMElementValue"
//------------------------------------------------------------------------------
sysBaseDOMElement.prototype.setDOMElementValue = function() {
if (this.DOMValue != null && this.DOMValue !== undefined) {
var divElement = document.getElementById(this.DOMObjectID);
if (divElement != null && divElement !== undefined) {
divElement.innerHTML = this.DOMValue;
}
}
}
Sets the innerHTML of the DOM element with the object’s value.
function sysCustomObject() {
this.ObjectID = 'nam1';
this.ChildObjects = [];
}
// Inherit sysBaseObject
sysCustomObject.prototype = new sysBaseObject();
sysCustomObject.prototype.init = function() {
// Add obj2
var obj2 = new sysBaseObject();
obj2.ObjectID = 'nam2';
this.addObject(obj2);
// Add obj3
var obj3 = new sysBaseObject();
obj3.ObjectID = 'nam3';
obj3.DOMValue = 'val3';
this.addObject(obj3);
// Add obj4
var obj4 = new sysBaseObject();
obj4.ObjectID = 'nam4';
obj4.DOMValue = 'val4';
obj2.addObject(obj4);
// Render recursively
this.renderObject();
}
Inheritance structure:
Object references after adding obj2 and obj3:
Full object hierarchy after sysCustomObject.init()
:
+- obj1 (this)
+- obj2
+- obj4
+- obj3
DOM structure:
<body>
<div id="nam1">
<div id="nam1-nam2">
<div id="nam1-nam4">val4</div>
</div>
<div id="nam1-nam3">val3</div>
</div>
</body>
By introducing multiple abstraction layers, we achieve a robust and clean OOP architecture.
The JavaScript framework “x0 (cross objects)” is engineered with these concepts and provides extensive functionality.
Explore more at https://github.com/WEBcodeX1/x0/ on GitHub.