Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Extend HTML Elements in JS with a 'Factory'

1.80/5 (3 votes)
27 Mar 2023CPOL3 min read 5.1K  
A simple way to extend an HTML element
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.):

JavaScript
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;
    }

    // Analyzes a 1x1 cube to determine the colors of its faces and the 'position'
    // returns a 3-character string where
    // - the first  character is the color on the x-axes
    // - the second character is the color on the y-axes
    // - the third  character is the color on the z-axes
    #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).

JavaScript
  // Exports the current configuration in the format of JSON like string
  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){
                //let cube1x1    = cubes1x1[index];
                //var id         = cube1x1.id;
                var isFaded    = cube1x1.isFaded();
                var isDisabled = cube1x1.isDisabled();
                configuration[id] = colors;
            }
        }
        return configuration;
    }
    catch(jse){
        jse.alert()
    }
}

This line of code:

JavaScript
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'.

JavaScript
let cube1x1 = this.getCube1x1(id);

and now 'getColors' (gathering the colors of the stickers becomes a 'responsibility' of the object.

JavaScript
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.

Image 1

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.

Image 2

  • "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.

JavaScript
function getColors (id){ ... }

function setSticker (id, face, color){ ... }

function(id) isDisabled { ... }

function isEnabled (id){ ... }

function isPositionOK (){ ... }

Quindi (for example):

Then, the line:

JavaScript
var colors = cube1x1.getColors();

becomes:

JavaScript
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:

  1. It's a criterion for organizing the code.
  2. 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)