Exploring Open Source: Hacktoberfest 2023

During the month of October, I extensively worked on a dom-manipulation library called "DOM Wizard".

This library is designed to significantly enhance the ease of use, readability, and maintainability of JavaScript DOM manipulation. By simplifying the process, it aims to make the manipulation of the DOM structure more accessible and intuitive for developers.

Contribution 1: Creating read() functionality

I contributed to the read functionality in the library which reads the properties and attributes of a single or multiple HTML elements and returns the value of the property to the user.

read()

The read() function is responsible for retrieving the data from a specific element.

Parameters/Inputs:

  • selector: string

  • attributeName: string (optional)

  • all: boolean (optional)

The function accepts three parameters: selector, attributeName (optional), and all (optional).

The function utilizes querySelector(selector) to retrieve the element. If this returns undefined, the function throws an error. Subsequently, it returns the value of the requested attribute. If the attributeName is not a qualified name, an error is thrown. If attributeName is not specified, the function returns the element as is. By default, all is set to false, but if the user sets all to true, the function uses querySelectorAll(selector) to retrieve every HTML element which matches the selector instead. If all is true and attributeName is specified, the function returns an array containing the values of that attributeName. For example, if the user invokes domManager.read(".content", "dataset", true), the function returns an array of datasets for elements with a class name of .content. However, if attributeName is not specified and all is set to true, the function returns a NodeList or an array containing all elements with the specified selector.

const readContent = () => {

    const _error = () => {
      console.error("Please check the attribute either you went wrong in the parameter or please add it in the specified HTML Element");
      throw new Error("The attribute name mentioned is not qualified");
    }

    const read = (selector, attributeName="", all=false) => {

        /* to make attribute parameter optional the user gives 
        either nothing (empty string) or bool if attribute name not specified*/

        if (typeof(attributeName) === "boolean") {
          all = attributeName;
          attributeName = "";
        }

        // conditional selection
        const el = (!all ? document.querySelector(selector) : 
        document.querySelectorAll(selector));

        // invalid selector
        if (!el || el.length === 0) {
          console.error("Retrieving of the element was not possible. Please check your selector.");
          throw new Error("Element was not selected from the DOM");
        }

        // when all is false and attribute is valid
        if (!all && el[attributeName]) {
          return el[attributeName];
        }

        // when all is true and attributeName is specified
        if (all && attributeName !== "") {

          //check if property or attribute is valid for each element selected 
          el.forEach((element) => {
            if(!element[attributeName]) {
              _error();
            }
          })

          const _attributes = [];

          el.forEach((element) => {
              _attributes.push(element[attributeName]);
          })

          return _attributes;
        }

        // undefined attributes or invalid property
        if (attributeName !== "" && !el[attributeName]) {
          _error();
        }

        /* returns the element(s) when attribute not specified */
        return el;
    }

    return { read };
  }

  export { readContent };
## read() Usage

To read the property of the HTML DOM Element, use the `read()` function. For example, to read the innerHTML of the element with a class selector ".para", ( selector parameter is the must and the other two are optional ) you may use the following code:

```javascript
import { readContent } from "./index.js";

const domManager = readContent();

// single element property value (innerHTML) returned 
const data = domManager.read(".para","innerHTML");

console.log(data);
```

To read the property of the multiple HTML DOM Elements, use the `read()` function along with a third parameter `all` set to true default being false. `all` takes a boolean value `true` or `false` where true indicates selection of `all` the elements matching the selector else the first element with the selector is retrieved. For example, to read the innerHTML of the multiple elements with the same class selector ".para", you may use the following code:

```javascript
import { readContent } from "./index.js";

const domManager = readContent();

// multiple elements property values (innerHTML) returned in an array
const data = domManager.read(".para","innerHTML",true);

console.log(data);
```

The second parameter is optional, if not specified then based the value of `all` given, the elements are retrieved and returned instead of the property values:

```javascript
import { readContent } from "./index.js";

const domManager = readContent();

// the html DOM element itself is returned
const data = domManager.read(".para",false);

console.log(data);
```

```javascript
import { readContent } from "./index.js";

const domManager = readContent();

// the html DOM elements itself is returned in an array
const data = domManager.read(".para",true);

console.log(data);
```

Contribution 2: Creating a mini-redux-like store

I contributed to a mini-redux-like implementation where I created a store object which holds all the variables that can be accessed from anywhere within the app.

store

The store will hold variables that are intended to be accessible throughout the app. Users will be able to retrieve and modify these variables from anywhere within the app.

createStore()

The createStore() function takes an object and stores it in localStorage within the app. The function throws an error if invoked for the second time.

const create = () => {

    const _error = () => {
        console.error("Store already exists.")
        throw new Error("Cannot invoke createStore more than once.");
    }

    const createStore = (storeObject ) => {

        /* Global store creation for entire access */
        const _store = storeObject;

        /* handling refresh */
        window.onbeforeunload = () => {
            localStorage.setItem("isLoading","true");
        }

        /* in case of store does not exist */
        if (!localStorage.getItem("store")) {
            localStorage.setItem("store", JSON.stringify(_store));
        }

        /* if store exists and it is not reloading */
        if (localStorage.getItem("store") &&
            !JSON.parse(localStorage.getItem("isLoading"))) {
            _error();
        }

        /* indicates reloading is complete */
        localStorage.setItem("isLoading","false");
    }

    return { createStore };

}

export default create().createStore;
## createStore Usage

To create the store, use the `createStore()` function. For example, to create a store and add all the variables which you want to access anywhere from the application, you would use the following code:

```javascript

const storeObject = {
    name: "John Doe",
    age: 35,
    univerity: "Stanford",
    isGraduated: false
}

// creates a "store" and stores the object
createStore(storeObject);

// to retrieve the "store object" use getStore()
```

Note: `createStore()` cannot be invoked or called more than once even in different script files of the same application.

Highlight Feature: No second invoke handling the refresh edge case

window.onbeforeunload = () => {
            localStorage.setItem("isLoading","true");
        }

The code uses the window object along with onbeforeunload property to set a flag of the refresh edge case to make sure the function is called on refresh but not in the second invoke since the store can be created only once.

Contribution 3: Retrieving the Store Object

The store object created and stored in the localStorage must be retrieved by the user to update the store using updateState() method as well as getting the state of the store using getState() method.

getStore()

Highlight Feature:

The store object retrieved by the user using getStore() receives the entire store object along with additional methods getState() and updateState() that can be used to manipulate the store object.

import getState from "./getState.js";
import updateState from "./updateState.js";

const getStore = () => {

    /* Store exists */
    if (localStorage.getItem("store")) {

        const store = JSON.parse(localStorage.getItem("store"));

        return {...store, getState, updateState}
    }
    else {
        console.error("Store doesn't exist create one using createStore()");
        throw new Error("Store not found");
    }

}

export default getStore;
## getStore Usage

To retrieve the store, use the `getStore()` function with no parameters needed. For example, to retrieve a store and access functionalities like `updateState()` and `getState()` anywhere from the application, you would use the following code:

```javascript

const store = getStore(); // retrieve store

const title = store.getState("title"); // get the state of store

console.log(title); // prints the title

// updates the state of the store
const newStore = store.updateState("title", "javascript"); 

console.log(newStore); // prints the object returned by updateState

console.log(store); // store is updated with new title

```

Note: `getStore()` retrieves the store and functionalities like `updateState()` and `getState()` can be used on the store object.

Warning: `getStore()` is the only way to access the store.

Note: this pointer present in getState() and updateState() methods refers to the store object retrieved using getStore() on which the methods are called.

Contribution 4: Registering routes

I contributed to improving the router.register() feature with single invoke functionality and id uniqueness in the "routes" array of objects.

router

The router module is responsible for registering routes, linking pages, and configuring URLs.

register()

The router module maintains an array to store all the routes and the register() function will simply push the provided routes into this array.

Parameters/Inputs:

  • routes: Array

The register() function accepts an array of objects containing information about a route. The array provided by the user should have this structure:

[
    { id: "home", route: home },
    { id: "about", route: about },
    { id: "contact", route: contact }
]

In this array, route should be an object, and id must be unique. If these constraints are not met, the function throws an error.

It's important to note that register() should be invoked only once throughout the app. If it is invoked more than once, the function throws an error.

const register = (routes) => {

    // sets 
    const _idSet = new Set();

    /* Invoke only once */
    window.onbeforeunload = () => {
      localStorage.setItem("route_flag", JSON.stringify(true));
    }

    /* checking id uniqueness using set*/
    for (const route of routes) {
      if(_idSet.has(route.id)) {
        console.error(`Id ${route.id} is not unique`);
        throw new Error("Unique Id must be assigned");
      }
      else {
        _idSet.add(route.id);
      }
    }

    /* Error invoking for more than once */
    if (!JSON.parse(localStorage.getItem("route_flag"))) {
      console.error("Invalid to register routes more than once");
      throw new Error("Cannot invoke register() more than once")
    }

    /* id is unique */
    for (const route of routes) {

      if (!route.id || !route.route) {
        console.error("Please enter valid id and route");
        throw new Error("Please make sure that a valid id and route is passed");
      }

      if (typeof(route.route) !== "object") {
        console.error(`${route.route} is not an object`);
        throw new Error("Object is the valid parameter");
      }

      pages.push(route);
    }

    localStorage.setItem("route_flag", JSON.stringify(false));
  };

  return { configRouter, register, pages };
})();
export { router };
for (const route of routes) {
      if(_idSet.has(route.id)) {
        console.error(`Id ${route.id} is not unique`);
        throw new Error("Unique Id must be assigned");
      }
      else {
        _idSet.add(route.id);
      }
    }

The code checks the uniqueness of id using sets and the below code allows the function to be invoked after refresh handling the edge case.

window.onbeforeunload = () => {
      localStorage.setItem("route_flag", JSON.stringify(true));
    }
0
Subscribe to my newsletter

Read articles from Sindhur V Shabaraya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sindhur V Shabaraya
Sindhur V Shabaraya