I am building a web page with HTML, CSS, and TypeScript. I had a Figma template to follow, and there were many parts of it that were boilerplate. To make future work easier, my plan was to register an HTML template for each class involved in the Figma design on localStorage using decorators. This way, I could dynamically build any structural pattern on the page. For example, if I have a product card in the Figma design, I then create a Product class below
import { h1, image, p } from "../cardAnotations/CardContentAnnotations.js";
export class Product {
@h1
private _name: string;
@p
private _description: string;
@p
private _price: number;
@image
private _imageSource:string;
private _discountedPrice: number;
private _imageDescription:string;
constructor(name: string, description: string, price: number,imageSource:string
, discountPercent: number) {
this._name = name;
this._description = description;
this._price = price;
this._imageSource = imageSource;
this._imageDescription = ''
this._discountedPrice = this.calculateDiscountedPrice(discountPercent);
}
//Getters and Setters
.
.
.
Those decorators in the properties correspond to the respective HTML tag representations in the card.
<li>
<img src="/src/assets/images/product01.png" alt=""> \ @image private _imageSource:string;
<h1>peanut butter</h1> \@h1 private _name: string;
<p>the best peanut butter in america with extra protein</p> \@p private _description: string;
<p>6.48<span class="greenText">6.22</span></p> \ @p private _price: number;
</li>
I tried using a singleton pattern to store the data initially, but it didn’t work inside a decorator. However, I still wanted a single instance to receive this data, so I created a type object to store the HTML data for each necessary class and then put it in the LocalStorage.
export type ObjectHTMLTemplate = {
[prop: string]: string | imageTemplateObj;
};
export type TamplateObserver = {
[prop:string]:ObjectHTMLTemplate
}
export type imageTemplateObj = {
src:string;
alt:string;
}
the TamplateObserver final should look like this
{
"Blog": {
"_description": "p",
"_date": "p",
"_commentsNumber": "p",
"_title": "h1"
},
"Product": {
"_description": "p",
"_price": "p",
"_imageSource": "img",
"_name": "h1"
},
"SectionCard": {
"_shortMensage": "p",
"_sectionName": "p",
"_imageSource": "img"
}
}
i put the pieces together like this
import { ObjectHTMLTemplate, TamplateObserver } from "../types/types.js";
export function image(target: any, key: string){
let className:string = target.constructor.name;
const tamplete = getFromLocalStorage("ClassesTamplate")
if(tamplete){
if (!tamplete[className]) {
tamplete[className] = {} as ObjectHTMLTemplate;
}
tamplete[className][key] = 'img'
saveToLocalStorage("ClassesTamplate",tamplete)
}
}
export function h1(target: any, key: string){
let className:string = target.constructor.name;
const tamplete = getFromLocalStorage("ClassesTamplate")
if(tamplete){
if (!tamplete[className]) {
tamplete[className] = {} as ObjectHTMLTemplate;
}
tamplete[className][key] = 'h1'
saveToLocalStorage("ClassesTamplate",tamplete)
}
}
export function p(target: any, key: string){
let className:string = target.constructor.name;
const tamplete = getFromLocalStorage("ClassesTamplate")
if(tamplete){
console.log(tamplete)
if (!tamplete[className]) {
tamplete[className] = {} as ObjectHTMLTemplate;
}
tamplete[className][key] = 'p'
saveToLocalStorage("ClassesTamplate",tamplete)
}
}
export function h2(target: any, key: string){
let className:string = target.constructor.name;
const tamplete = getFromLocalStorage("ClassesTamplate")
if(tamplete){
if (!tamplete[className]) {
tamplete[className] = {} as ObjectHTMLTemplate;
}
tamplete[className][key] = 'h2'
saveToLocalStorage("ClassesTamplate",tamplete)
}
}
async function saveToLocalStorage(key: string, value: TamplateObserver) {
try {
// Convert the object to a JSON string
const jsonString = JSON.stringify(value);
// Save to localStorage
localStorage.setItem(key,jsonString);
//console.log(`Object saved to localStorage with key: ${key}`);
} catch (error) {
console.error("Error saving object to localStorage:", error);
}
}
function getFromLocalStorage(key: string): TamplateObserver | null {
try {
const jsonString = localStorage.getItem(key);
if (jsonString) {
// Parse the JSON string to an object
return JSON.parse(jsonString) as TamplateObserver;
}
} catch (error) {
console.error("Error retrieving object from localStorage:", error);
}
return null;
}
export default class ObjectTemplateRepo {
private ObjectsTemplates:ObjectHTMLTemplate[];
private _observe!:TamplateObserver;
private static _instance:ObjectTemplateRepo;
private constructor() {
this.ObjectsTemplates = []
}
static getInstance():ObjectTemplateRepo{
if (!ObjectTemplateRepo._instance) {
ObjectTemplateRepo._instance = new ObjectTemplateRepo();
}
return ObjectTemplateRepo._instance;
}
public getObjectTemplate(key:string):ObjectHTMLTemplate | void{
const template = getFromLocalStorage("ClassesTamplate");
if (template && template[key]) {
return template[key];
}
}
}
i used it to build a list of products like this
import ObjectTemplateRepo from "../cardAnotations/CardContentAnnotations.js";
import { Product } from "../models/Product.js";
import { SectionCard } from "../models/SectionCard.js";
import { ObjectHTMLTemplate, productTypeSectionAmount } from "../types/types.js";
export class ProductListInjection{
constructor(){
}
injectList(targetListId: string, products?: Product[]): void {
const htmlElementList = document.getElementById(targetListId) as HTMLElement;
if (!htmlElementList) {
throw new Error("tag no found");
}
if (Array.isArray(products)){
products.forEach((product =>{
htmlElementList.appendChild(this.ProductPatternTamplete(product))
}))
}
}
ProductPatternTamplete(product:Product):HTMLLIElement{
const objectsTemplate = ObjectTemplateRepo.getInstance();
const tamplete = objectsTemplate.getObjectTemplate('Product') as ObjectHTMLTemplate;
const divElement = document.createElement('li');
const imageElement = document.createElement(tamplete._imageSource as string) as HTMLImageElement;
const nameElement = document.createElement(tamplete._name as string);
const descriptionElement= document.createElement(tamplete._description as string)
const priceElement = document.createElement(tamplete._description as string)
const discoutPriceElement = document.createElement('span');
discoutPriceElement.className ="greenText";
discoutPriceElement.innerHTML = " " + product.discountedPrice.toString();
imageElement.src = product.imageSource;
imageElement.alt = product.imageDescription;
nameElement.innerText = product.name
descriptionElement.innerText = product.description
priceElement.innerText = product.price.toString();
priceElement.appendChild(discoutPriceElement);
divElement.appendChild(imageElement);
divElement.appendChild(nameElement);
divElement.appendChild(descriptionElement);
divElement.appendChild(priceElement);
return divElement;
}
}
Then, my problem is if I deploy it on GitHub Pages, it’s kind of like the decorator is not even called, but it works fine on my localhost. In this question, it suggests changing the localStorage to sessionStorage, but nothing changes on GitHub Pages, and on my localhost, the decorator is not called as in LocalStorage in the gitHub pages.
I tried to write a GitHub Action to build it on the GitHub machine, but it also did not work. Where can I find content about how decorators work internally and the real reason for this behavior with local and sessionStorage? I would like to understand more deeply how they work. There is no way to save local data with decorators ?