Simple TIP to extend an HTML element by adding functionality to it from an object-oriented perspective. In JavaScript, you can dynamically add attributes to an instance of an object that will only belong to that object. If a 'factory' function is defined that instantiates an object, adds (dynamically) the attributes we want, and returns the newly modified object, we have a quick way to implement some sort of specialization.
Description
Custom elements are a formal way to extend DOM elements. However, although they are very powerful, they are not fully supported by all major browsers and can be challenging to implement.
In contrast, it is effortless to "extend" a JavaScript object by dynamically adding attributes that implement new functionality. Once a function is written that instantiates the object, adds the necessary attributes, and returns it as a parameter, that function becomes a "factory" function, and its return parameter is an "extension" of the original object.
Finally, the constructor of a class in JavaScript allows the return parameter to be overridden. If the implementation of the 'factory' is written within the constructor, the return parameter becomes the object that was just extended and has effectively become an extension of the original HTML element.
Although this may sound complicated, it is in fact a straightforward approach to implement, which can effectively enhance the clarity and expressiveness of the code, as illustrated in the below example.
(This example is just a portion of the architecture of a cubie: one of the 26 small 1x1 cubes that forms a 3x3 Rubik's cube.):
window.Cube1x1 = class {
constructor(context, id) {
var _this = this;
var cube1x1 = context.querySelector(`#${id}`);
cube1x1.getColors = function(){
return _this.#getColors(this);
}
cube1x1.setSticker = function (face, color){
}
cube1x1.isDisabled = function(){
}
cube1x1.isEnabled = function(){
return !this.isDisabled();
}
cube1x1.isPositionOK = function (){
}
return cube1x1;
}
#getColors(cube1x1){
}
static getHTML(id, colors) {
}
}
The Cube1x1
class manipulates HTML elements that are also accessible from other functions outside the class itself. This violates the principle of encapsulation, making the class a bit more fragile.
However, this is an inherent limitation when working with web pages that require the developer to work with discipline and methods!
This is a function inside the Cube3x3
(The container of all the 26 Cubes1x1
).
export(){
try{
var configuration = {};
var cubes1x1 = this.scene.querySelectorAll(`div.cube1x1`);
for (let index = 0; index < cubes1x1.length; index++){
let id = cubes1x1[index].id;
let cube1x1 = this.getCube1x1(id);
var colors = cube1x1.getColors();
if(colors.length > 1){
var isFaded = cube1x1.isFaded();
var isDisabled = cube1x1.isDisabled();
configuration[id] = colors;
}
}
return configuration;
}
catch(jse){
jse.alert()
}
}
This line of code:
var cubes1x1 = this.scene.querySelectorAll(`div.cube1x1`);
shows that the 'object' 'cube1x1
' is still an HTML element, accessible with the standard functions of the DOM. (This is an example of how the 'incapsulation' principle is violated.)
This line performs a kind of "cast" of the tag
element '<div>
into the 'object 'Cube1x1
'.
let cube1x1 = this.getCube1x1(id);
and now 'getColors
' (gathering the colors of the stickers becomes a 'responsibility' of the object.
var colors = cube1x1.getColors();
Demo
The code shown here is an excerpt of the 'engine' that implements a new method to solve the Rubik's cube.
The JS code is massive to be treated here. But if you want, it is accessible via the F12 key
These are the implementations of the 4 "objects" defined in the project.
- "algorithms.js" implements the field that shows the running algorithm (the field at the right of the button "Scramble".
- Cube.js & Cube3x3.engine.js implement the Cube 3x3.
- Cube1x1js implements the cubie.
Background
No particular background is required. This technique can be easily mastered even by a beginner.
Points of Interest
As a final consideration, it should be said that defining an object (the Cube1x1
in this example) and equipping it with the functions described above is just one way of organizing the code.
Its functions could have simply been written as a standalone set of functions.
function getColors (id){ ... }
function setSticker (id, face, color){ ... }
function(id) isDisabled { ... }
function isEnabled (id){ ... }
function isPositionOK (){ ... }
Quindi (for example):
Then, the line:
var colors = cube1x1.getColors();
becomes:
var colors = getColors(id);
The implementation of the functions would have been the same. The only thing that changed was the 'syntax' with which they were called. Within certain limits, it's just a matter of style!
However, when a project becomes "bulky," organizing functions into "containers" becomes a necessity for at least two reasons:
- It's a criterion for organizing the code.
- Object-oriented programming, when based on the concept of an object's responsibilities, provides the organization criterion (for point 1) and allows the main problem to be broken down into subproblems.
I hope to have provided an additional alternative. I thank those who have dedicated their time to me. If you have suggestions or even criticisms that can improve the article, they are welcome.
Cheers!
History
- 27th March, 2023: Initial version