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
: stringattributeName
: 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));
}
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