My goal is to create an Inventory App in pure typescript. The "App" class is the starter class. Specifically, I make two instances to initialize ProductView and CategoryView. I do this because I need to generate tables(show/hide by CSS style) based on clicked menus. For example, if a user clicks on the Products menu, I should display the table's and the modal's products. When I clicks on each menu, I got this kind of error:
Uncaught TypeError: Cannot read properties of undefined (reading 'initUI')
AND
Uncaught TypeError: Cannot read properties of undefined (reading 'initCategoryUI')
Github Repository
import { selectedMenu, productMenu, categoryMenu } from "./dom";
import { ProductView } from "./productview";
import { CategoryView } from "./categoryview";
import { Types } from "./entity";
import "./assets/scss/style.scss";
import Entity from "./entity";
class App {
private _productView: ProductView;
private _categoryView: CategoryView;
constructor() {
this._productView = new ProductView(new Entity<IProduct>(Types.IProduct));
this._categoryView = new CategoryView(
new Entity<ICategory>(Types.ICategory)
);
console.log("check ::", this._categoryView);
selectedMenu.forEach((el: HTMLAnchorElement) =>
el.addEventListener("click", this._selectedMenuHandler)
);
this._init();
}
private _selectedMenuHandler(): void {
if (this instanceof Element) {
if (this.classList.contains("menu__products")) {
this.classList.add("menu__selected");
categoryMenu?.classList.remove("menu__selected");
console.log("what happend in ::", this._productView); //undefined
this._productView.initUI();
} else if (this.classList.contains("menu__categories")) {
this.classList.add("menu__selected");
productMenu?.classList.remove("menu__selected");
console.log("what happend in ::", this._categoryView); //undefined
this._categoryView.initCategoryUI();
}
}
}
private _init() {
this._productView.initUI();
}
}
const app = new App();
import { modalPopup } from "./dom";
export class View {
private _span: HTMLElement;
constructor() {
this._span = document.querySelector<HTMLElement>(".close")!;
this._span.addEventListener("click", this._closeModal);
document.addEventListener("click", (e: Event) =>
this._closeModalWindowClicked(e)
);
}
protected _closeModalWindowClicked(e: Event) {
if (e.target === modalPopup) modalPopup.style.display = "none";
}
protected _openModal() {
modalPopup.style.display = "block";
}
protected _closeModal() {
modalPopup.style.display = "none";
}
}
import {
btn,
tableThead,
tableBody,
btnSubmit,
inputTitle,
categoryElement,
inputQuantity,
modalContentCategory,
modalContentProduct,
modalHeader,
} from "./dom";
import { Product } from "./product";
import Entity from "./entity";
import { View } from "./view";
btn?.classList.add("btn-product");
export class ProductView extends View {
private _categoryValue: string = "";
private _productInventory: Entity<IProduct>;
constructor(inventory: Entity<IProduct>) {
super();
console.log("create Product::", this);
this._productInventory = inventory;
btn?.addEventListener("click", this._openModal);
btnSubmit?.addEventListener("click", this._addButtonHandler.bind(this));
categoryElement?.addEventListener("change", this._selectCategoryHandler);
}
public initUI() {
this._createModal();
this._createHeaderTable();
this._renderTable();
}
private _createModal() {
modalContentCategory?.classList.add("hidden");
modalContentProduct?.classList.remove("hidden");
modalHeader!.innerHTML = "Add Product";
}
private _createHeaderTable(): void {
if (tableThead) {
tableThead.innerHTML = `<th>#</th>
<th>Title</th>
<th>Quantity</th>
<th>Category</th>
<th></th>`;
}
}
private _tableUIBody(item: Product, id: number) {
return `<tr odd" : ""}">
<td>${id}</td>
<td>${item.title}</td>
<td>${item.quantity}</td>
<td>${item.category}</td>
<td><button data-id="${item.id}" >
<i ></i>
<span>Delete</span>
</button> <button data-id="${item.id}" >
<i ></i>
<span>Edit</span>
</button></td>
</tr>`;
}
private _selectCategoryHandler(e: Event) {
const select = document.querySelector<HTMLSelectElement>(
".form__select-category"
);
this._categoryValue = select?.options[select?.selectedIndex].value!;
}
private _addButtonHandler() {
const newProduct = new Product(
inputTitle?.value!,
this._categoryValue,
Number.parseInt(inputQuantity?.value!)
);
this._productInventory?.add(newProduct);
this._renderTable();
this._closeModal();
}
private _renderTable(): void {
tableBody!.innerText = "";
const categories = this._productInventory.storage;
const allCategory = categories.map((category: ICategory, index) => {
return this._tableUIBody(<Product>category, index);
});
tableBody!.innerHTML = allCategory.join("");
}
}
import {
tableBody,
tableThead,
inputTitle,
btn,
btnSubmit,
modalContentCategory,
modalContentProduct,
modalHeader,
} from "./dom";
import Entity, { Types } from "./entity";
import { Category } from "./category";
import { View } from "./view";
export class CategoryView extends View {
private _categoryInventory: Entity<ICategory>;
constructor(categoryInventory: Entity<ICategory>) {
super();
console.log("create Category::", this);
this._categoryInventory = categoryInventory;
btn?.addEventListener("click", this._openModal);
btnSubmit?.addEventListener("click", this._addButtonHandler);
}
initCategoryUI() {
this._createModal();
this._createHeaderTable();
this._renderTable();
}
private _createModal() {
modalContentProduct?.classList.add("hidden");
modalContentCategory?.classList.remove("hidden");
modalHeader!.innerHTML = "Add Category";
}
private _createHeaderTable(): void {
if (tableThead) {
tableThead.innerHTML = `<th>#</th>
<th>Title</th>
<th></th>`;
}
}
_addButtonHandler() {
const newCategory = new Category(inputTitle?.value!);
this._categoryInventory.add(newCategory);
this._renderTable();
this._closeModal();
}
private _renderTable(): void {
tableBody!.innerText = "";
const categories = this._categoryInventory.storage;
const allCategory = categories.map((category: ICategory, index) => {
return this._tableUIBody(<Category>category, index);
});
tableBody!.innerHTML = allCategory.join("");
}
private _tableUIBody(item: Category, id: number) {
return `<tr odd" : ""}">
<td>${id}</td>
<td>${item.title}</td>
<td><button data-id="${item.id}" >
<i ></i>
</button> <button data-id="${item.id}" >
<i ></i>
</button></td>
</tr>`;
}
}
CodePudding user response:
el.addEventListener("click", () => this._selectedMenuHandler())
try to use arrow function which can retains the this
value of the enclosing lexical context