קורס Front End למתכנתים שיעור תרגול: חיבור קומפוננטות


זהו נושא דיון מלווה לערך המקורי שב־https://www.tocode.co.il/bundles/frontend/lessons/components-lab

בתרגילים, לכאורה צריך ליצור קומפוננטה שתכיל את הקומפוננטות האחרות, ותכניס כל פעם אחת מהן לאלמנט שעל המסך, בצורה כזו כל פעם שעוברים בין הדפים (או בין התויות בשאלה 1) הדף/התוית הקודמת נמחקת ובפעם הבאה תיוצר תוית חדשה (הפרטים נשמרים בשעת הצורך בקומפוננטה המפעילה).
זה תקין או שאמורים לעשות את זה בדרך אחרת?

כן בדיוק - קומפוננטה אחת היא ״המיכל״ וכל פעם היא מאתחלת לתוכה קומפוננטה אחרת

מצרפת את התרגיל הראשון, כמה שאלות אם אפשר:
1.) את הקוד הכפול ב-2 הפאנלים שיתפתי באמצעות ירושה (אותו למדתי מהקורס הישן ,בקורס החדש לא ראיתי התייחסות לירושה - האם זה במכוון?).
2.) בעת לחיצה על הפאנל ביטלתי בכל פעם את האירועים של הפאנל הנוכחי כיון שאין צורך בהם אם הפאנל נמחק, עשיתי זאת באמצעות באמצעות unsubscribe… האם נכון לעשות כך או שיש דרך לעשות זאת מהמחלקה של ניהול האירועים?
3.) יצרתי בלולאה 5 תיבות טקסט כאלה כאשר המקסימום אורך שונה בכל אחד מהם, ואז נתקלתי בבעיה שכל אירוע מתבצע עבור כל תיבות הטקסט, את הבעיה פתרתי באמצעות תנאי בתוך הפונקציה שבודק האם שם ה-class של האובייקט שיצר את האירוע מתאים ל-class הנוכחי.
הדרך הזו היתה נראית לי לא מספיק יעילה - כיון שהפונקציה בכל מקרה מתבצעת , האם יש דרך למנוע את הקריאה לפונקציה דרך מנהל האירועים? בכלל האם יש אפשרות לזהות את האוביקט עבורו שמורה הפונקציה במערך ולהגיע דרכו ל- parent וכו’?
תודה מראש ומאד אשמח להערות על הפתרון.

---------------------------------------------------------- HTML ------------------------------------------------------

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF8"/>
        <link rel="stylesheet" href="index.css"/>
        <title>Traning 1</title>
    </head>
    <body>
        <script src="eventBus.js"></script>
        <script src="panel.js"></script>
        <script src="remaningCharactersPanel.js"></script>
        <script src="currentLengthPanel.js"></script>
        <script src="textArea.js"></script>
        <script src="main.js"></script>
    </body>
</html>

---------------------------------------------------------- CSS------------------------------------------------------

body ,html {
    margin: 0;
    padding: 0;
    width: 100vw;
    height: 100vh;
    background-color: beige;
}

div {
    display: flex;
    flex-direction: column;
    width: calc(100vw - 10rem);
    margin-left: calc(50vw - ((100vw - 10rem)/2));
}

div p {
    font-size: 3rem;
    margin: 1rem 0;
}

div textarea {
    width: calc(100vw - 10rem);
    height: calc(33.333333vh - 6rem);
    box-sizing: border-box;
    padding: 1rem;
    font-size: 3rem;
}

JS
------------------------------------------------------------------ main --------------------------------------------------

for(let i=0;i<5;i++) {
    const divForPanelAndTextArea = document.createElement('div');
    divForPanelAndTextArea.classList.add(`divForPanelAndTextArea${i}`);
    const textArea = new TextArea(divForPanelAndTextArea , bus , (i+1)*10);
    document.body.appendChild(divForPanelAndTextArea);
}

---------------------------------------------------------- EventBus ------------------------------------------------

"use strict";
class EventBus {
    constructor() {
        this.listeners = {};
        this.number = 1;
    }

    subscribe(eventName, handler) {
        if (!this.listeners[eventName]) {
            this.listeners[eventName] = [];
        }
        this.listeners[eventName].push(handler);
        return ()=> {this.listeners[eventName] = this.listeners[eventName].filter(fn=> fn!==handler)};
    }

    emit(eventName, ...arg) {
        const handlers = this.listeners[eventName] || [];
        for(let fn of handlers) {
            fn(...arg);
        }
    }
}

const bus = new EventBus();

---------------------------------------------------------- TextArea------------------------------------------------

"use strict";
class TextArea {
    constructor(divForPanelAndTextArea , bus , maxLength ) {
        this.divForPanelAndTextArea = divForPanelAndTextArea;
        this.bus = bus;
        this.maxLength = maxLength;
        this.setupUi();
        this.currentPanel = new CurrentLengthPanel(this.divForPanel, this.bus);
        this.bus.subscribe('panelClick',this.replacePanel);
    }

    setupUi() {
        this.divForPanelAndTextArea.innerHTML += `
            <div class = "divForPanel"></div>
            <textarea maxlength="${this.maxLength}" class="textArea"></textarea>
        `;
        this.divForPanel = this.divForPanelAndTextArea.querySelector(`.divForPanel`);
        this.textArea = this.divForPanelAndTextArea.querySelector(`.textArea`);
        this.textArea.addEventListener('input', ()=>{
        this.bus.emit('input', this.divForPanelAndTextArea.className);});
    }

    replacePanel = ()=> {
       if(event.path[2].className === this.divForPanelAndTextArea.className) {
            if(this.currentPanel.constructor.name === "CurrentLengthPanel") {
                this.currentPanel = new RemaningCharactersPanel(this.divForPanel, this.bus);
                this.bus.emit('onload', this.divForPanelAndTextArea.className);
        }
        else {
            this.currentPanel = new CurrentLengthPanel(this.divForPanel, this.bus);
            this.bus.emit('onload', this.divForPanelAndTextArea.className);
        }
       }
    }
}

----------------------------------------------- CurrentLengthPanel----------------------------------------------

"use strict";
class CurrentLengthPanel extends Panel {
    constructor(divForPanel , bus) {
        super(divForPanel , bus);
        this.setUpUi();
    }

    setUpUi() {
        super.setUpUi(`<p class="panel">Current length panel : <span class="value">0</span></p>`);
    }

    getValue() {
        return this.divForPanel.nextElementSibling.value.length;
    }
}

------------------------------------------ RemaningCharactersPanel ---------------------------------------

"use strict";
class RemaningCharactersPanel extends Panel {
    constructor(divForPanel , bus) {
        super(divForPanel , bus);
        this.setUpUi();
    }

    setUpUi() {
        super.setUpUi(`<p class="panel">Remaning characters panel : <span class="value"></span></p>`);
    }
    
    getValue () {
        return Number(this.divForPanel.nextElementSibling.getAttribute('maxlength')) - this.divForPanel.nextElementSibling.value.length;
    }
}

-------------------------------------------------------- Panel ------------------------------------------------

class Panel  {
    constructor (divForPanel , bus) {
        this.divForPanel = divForPanel;
        this.bus = bus;
        this.unsubscribeOnLoad = this.bus.subscribe('onload',this.updateValue);
        this.unsubscribeInput = this.bus.subscribe('input',this.updateValue);
    }
    
    setUpUi (innerHTML) {
        this.divForPanel.innerHTML = innerHTML;
        this.value = this.divForPanel.querySelector('.value');
        this.divForPanel.querySelector('.panel').addEventListener('click', this.emitPanelClick);
    }

    updateValue = (divContainerClassName) => {
        if(this.divForPanel.parentElement.className === divContainerClassName) {
               this.value.textContent = this.getValue();
        } 
     }

    emitPanelClick = ()=> {
        this.unsubscribeOnLoad();
        this.unsubscribeInput();
        this.bus.emit('panelClick');
    }
}

מצרפת את התרגיל השני אשמח אם אפשר בבקשה להערות גם על הפתרון הקודם שצירפתי - תודה רבה!!!
index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Traning 2</title>
        <meta charset="UTF8"/>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
        <link rel="stylesheet" href="style.css"/>
    </head>
    <body>
        <script src="page3.js"></script>
        <script src="page2.js"></script>
        <script src="page1.js"></script>
        <script src="form.js"></script>
        <script src="eventBus.js"></script>
        <script src="main.js"></script>
    </body>
</html>

style.css

form {
    padding: 3rem;
    width: 50vw;
    height: 70vh;
    margin-top: 10rem;
    margin-left: 25vw;
    border: 1px solid #ced4da;
    border-radius: 1rem;
}

form h1 {
    text-align: center;
}

button {
    margin-top: 1rem;
}

main.js

const bus = new EventBus();

for(let i=0; i<5; i++) {
    const formElement = document.createElement('form');
    formElement.id = `form${i+1}`;
    const form = new Form(bus , formElement);
    document.body.appendChild(formElement);
}

eventBus.js

class EventBus {
    constructor() {
        this.listeners = {};
    }

    subscribe(eventName, handler) {
        if(!this.listeners[eventName]) {
            this.listeners[eventName] = [];
        }
        this.listeners[eventName].push(handler);
        return ()=> {this.listeners[eventName] = this.listeners[eventName].filter(fn=> fn!==handler)};
    }

    emit(eventName, ...arg) {
        const handlers = this.listeners[eventName] || [];
        for(let fn of handlers) {
            fn(...arg);
        }
    }
}

form.js

class Form {
    constructor(bus, formElement) {
        this.bus = bus;
        this.formElement = formElement;
        this.summaryObj = {};
        this.setupUi();
        this.currentPage = new Page1(this.CurrentPageContainer, this.bus);
        this.submit.addEventListener('click',this.submitFormClick);
    }

    setupUi() {
        this.formElement.innerHTML = `
            <h1>Form</h1>
            <div class="currentPageContainer"></div>
            </div>
            <button type="submit" class="btn btn-primary">Next</button>
        `;
        this.CurrentPageContainer = this.formElement.querySelector('.currentPageContainer');
        this.submit = this.formElement.querySelector('button');
    }

    submitFormClick = () => {
        this.bus.emit('submitFormClick', this.summaryObj);
        this.replaceToTheNextPage();
        event.preventDefault();
    }

    replaceToTheNextPage() {
        const pageName = this.currentPage.constructor.name;
        if(pageName === 'Page1') {
            this.currentPage = new Page2(this.CurrentPageContainer, this.bus);
        } else if(pageName == 'Page2') {
                    this.currentPage = new Page3(this.CurrentPageContainer, this.bus);
                    this.submit.innerHTML = 'Submit';
                    this.bus.emit('page3IsLoaded', this.summaryObj);
                } else {
                    this.CurrentPageContainer.innerHTML = "The form has been submitted successfully!!!";
                    this.formElement.style.border = "none";
                    this.formElement.style.fontSize = "5rem";
                    this.submit.style.display = "none";
                    this.formElement.querySelector('h1').style.display = "none";
                }
    }
}

page1.js

class Page1 {
    constructor(currentPageContainer, bus) {
        this.bus = bus;
        this.currentPageContainer = currentPageContainer;
        this.setupUi();
        this.unsubscribeSave = this.bus.subscribe('submitFormClick', this.saveDataBeforeLeave);
    }

    setupUi() {
        this.currentPageContainer.innerHTML = `
            <div class="form-group">
                <label for="name">Name</label>
                <input type="name" class="form-control" id="name" placeholder="Enter name">
            </div>
            <div class="form-group">
                <label for="email">Email address</label>
                <input type="email" class="form-control" id="email" placeholder="Enter email">
            </div>
        `;
        
    }

    saveDataBeforeLeave = (summaryObj) => {
        if(this.currentPageContainer.parentElement.id === event.target.parentElement.id) {
            const inputs = this.currentPageContainer.querySelectorAll('input');
            inputs.forEach(input=> {
                    summaryObj[input.id] = input.value;
                }
            );
            this.unsubscribeSave();
        }
    }
}

page2.js

class Page2 {
    constructor(currentPageContainer, bus) {
        this.bus = bus;
        this.currentPageContainer = currentPageContainer;
        this.setupUi();
        this.unsubscribeSave = this.bus.subscribe('submitFormClick',this.saveDataBeforeLeave);
    }

    setupUi() {
        this.currentPageContainer.innerHTML = `
        <select name="select" class="custom-select" multiple>
            <option value="1">One</option>
            <option value="2">Two</option>
            <option value="3">Three</option>
            <option value="4">Four</option>
            <option value="5">Five</option>
        </select>
        `;
    }

    saveDataBeforeLeave = (summaryObj)=> {
        if(this.currentPageContainer.parentElement.id === event.target.parentElement.id) {
            summaryObj['selected'] = [];
            const options =  this.currentPageContainer.querySelector('[name="select"]').children;
            for(let option of options) {
                if(option.selected === true) {
                    summaryObj['selected'].push(option.textContent);
                }
            }
            this.unsubscribeSave();
        }
    }
}

page3.js

class Page3 {
    constructor(currentPageContainer, bus) {
        this.bus = bus;
        this.currentPageContainer = currentPageContainer;
        this.setupUi();
        this.bus.subscribe('page3IsLoaded',this.fillPage);
    }

    setupUi() {
        this.currentPageContainer.innerHTML = `
        <div>
            <label>Name: &nbsp <span class="name"></span></label>
        </div>
        <div>
            <label>Email: &nbsp <span class="email"></span></label>
        </div>
        <div>
            <label>Your choices:</label>
        </div>
        <ul class="list-group">
           
        </ul>
        `;
        this.name = this.currentPageContainer.querySelector('.name');
        this.email = this.currentPageContainer.querySelector('.email');
        this.listGroup = this.currentPageContainer.querySelector('.list-group');
    }

    fillPage = (summaryObj)=> {
        if(this.currentPageContainer.parentElement.id === event.target.parentElement.id) {
            this.name.textContent = summaryObj['name'];
            this.email.textContent = summaryObj['email'];
            for(let obj of summaryObj['selected']) {
                const li = document.createElement('li');
                li.classList.add('list-group-item');
                li.textContent = obj;
                this.listGroup.appendChild(li);
            }
        }
    }
}

הי,

הקוד נראה אחלה. שתי נקודות שהייתי חושב עליהן-

  1. חבל שאי אפשר להעביר מערך של Page Objects לטופס, ושהטופס יתאים את עצמו לדפים שנמצאים במערך. כך היינו יכולים לכתוב קומפוננטת טופס-מרובה-דפים כללית יותר (או בניסוח אחר - יש לך רעיון איך לתמוך בהתנהגות כזאת?)

  2. אני רואה ששמירת המידע מתבצעת ממש לפני שעוברים עמוד באמצעות הפונקציה saveDataBeforeLeave. יכול להיות שהיה יותר מועיל לשמור את השינוי כל פעם שיש אירוע input, כלומר כל פעם שיש שינוי.

תודה על ההיערות!

  • תיקנתי אצלי את 2 הנקודות למרות שלגבי ההערה השניה לא ממש הבנתי מדוע יותר יעיל לשמור את השינוי בכל אירוע, במקום לקרוא לפונקציה פעם אחת?
  • דבר נוסף אם תוכל בבקשה לענות לי על השאלות של התרגיל שצירפתי קודם, תודה רבה!!

הי,

במחשבה שניה אני לא חושב שזה משנה במיוחד מתי מעדכנים את המידע. לגבי השאלות:

את הקוד הכפול ב-2 הפאנלים שיתפתי באמצעות ירושה (אותו למדתי מהקורס הישן ,בקורס החדש לא ראיתי התייחסות לירושה - האם זה במכוון?).

לא יותר מדי - בעיקר כי הקורס היה מספיק עמוס וכבר נגמר לי המקום. השארתי את נושא ירושה לקורס JavaScript ES6. אבל שמח שהשתמשת בזה התוצאה נראית טוב.

בעת לחיצה על הפאנל ביטלתי בכל פעם את האירועים של הפאנל הנוכחי כיון שאין צורך בהם אם הפאנל נמחק, עשיתי זאת באמצעות באמצעות unsubscribe… האם נכון לעשות כך או שיש דרך לעשות זאת מהמחלקה של ניהול האירועים?

כן זה מצוין. אירועי DOM מתבטלים אוטומטית כשמוחקים את האלמנטים מה DOM, אבל כשאנחנו בונים מנגנון אירועים עצמאי צריך לטפל לבד בכל ההסרות.

יצרתי בלולאה 5 תיבות טקסט כאלה כאשר המקסימום אורך שונה בכל אחד מהם, ואז נתקלתי בבעיה שכל אירוע מתבצע עבור כל תיבות הטקסט, את הבעיה פתרתי באמצעות תנאי בתוך הפונקציה שבודק האם שם ה-class של האובייקט שיצר את האירוע מתאים ל-class הנוכחי.
הדרך הזו היתה נראית לי לא מספיק יעילה - כיון שהפונקציה בכל מקרה מתבצעת , האם יש דרך למנוע את הקריאה לפונקציה דרך מנהל האירועים? בכלל האם יש אפשרות לזהות את האוביקט עבורו שמורה הפונקציה במערך ולהגיע דרכו ל- parent וכו’?

במצב כזה נדמה לי שיהיה יותר קל ליצור מספר מופעים של מחלקת ה Event Bus, אחד לכל TextArea. כך ה TextArea יעביר לילדים שלו את אוביקט ה EventBus שהוא יצר והם יוכלו לתקשר ביניהם על bus פרטי

תודה מראש ומאד אשמח להערות על הפתרון.

תודה על התשובות.
לגבי הקורס החדש גם לי נראה שהוא עמוס הרבה יותר מהישן, אבל זה טוב כיון שמכיל יותר חומר כולל את כל העדכונים החדשים.
בכלל הקורס החדש הרבה יותר מפורט, עם הרבה תרגול, ויותר מסודר גם מבחינת החלוקה לנושאים.
מאד נהניתי ממנו , תודה רבה!

לייק 1

מה קורה ינון ,
כשאני עושה eventListener במחלקה TavitTopCountWrite או במחלקה TavitTopHowMuchLeft בצורה הרגילה

 this.howMuchDiv.addEventListener('click',this.show);

או

    this.countWriteDiv.addEventListener('click',this.show);

זה לא קולט אפילו את הלחיצה
וכשאני עושה eventListener על root שהוא בעצם הDiv שעוטף את כל התוכנית אז הוא כן מזהה אבל לא מבצע שינוים כמו שצריך

    this.root.addEventListener('click', (event) => { 
        if (event.target.id === 'howMuchDiv' ||'howMuchP') {
            this.show();
        }
    });

ובכל אופן בצורה השנייה שאני פותח event על הroot גם אם זה משתנה אין שינוי בהצגה בממשק המשתמש
אשמח לעזרתך מצרף את הקוד שלי :

 class FatherComponent{
 constructor(el,taskMang){
     this.root = el;
     this.taskMang = taskMang;
     this.setupUI();
 }
 setupUI(){
    this.tavitTop = new TavitTopCountWrite(this.root,taskMang);
    this.tavitTop2 = new TavitTopHowMuchLeft(this.root,taskMang);
    this.textArea = new TextArea(this.root,taskMang);
 }
 class TaskManager{
constructor(){
    this.handlers ={};
}

subscribe=(eventName , handlerFunc)=>{
    if(!this.handlers[eventName]){
        this.handlers[eventName]=[];
    }
    this.handlers[eventName].push(handlerFunc);
    
    return () =>{
        this.handlers[eventName] = this.handlers[eventName].filter((x)=> x !== handlerFunc);
    }
}

emit(eventName,...info){
    const handlers = (this.handlers[eventName]||[]);
    for(let handler of handlers){
        handler(...info);
    }
}
  }

 class TavitTopCountWrite{
constructor(el,taskMang){
    this.val = 0;
    this.root = el;
    this.taskMang = taskMang;
    this.setupUI();
    this.countWriteDiv = this.root.querySelector('#countWriteDiv');

    //subscribes here 
    this.taskMang.subscribe('inputChar',this.update)
    this.taskMang.subscribe('countWrtieOn',this.disOn);
    this.taskMang.subscribe('countWrtieOff',this.disNone);

    //eventListener
    this.countWriteDiv.addEventListener('click',this.show);
    // this.root.addEventListener('click', (event) => {
    //     if (event.target.id === 'countWriteDiv' ||'countWriteP') {
    //         this.show();
    //     }
    // });

}
setupUI=()=>{
    this.root.innerHTML+=`
    <div id = 'countWriteDiv'>
       <p id = 'countWriteP'></p>
    </div>`;
    this.updateUI(this.val);
}
updateUI=(valueToUpdate)=>{ //משנה את הטקסט בתווית לערך שהתקבל
    this.root.querySelector('#countWriteDiv').children[0].textContent = valueToUpdate;
}
update=(value)=>{ 
    this.val = value;
    this.updateUI(this.val);
}
show=()=>{
    this.taskMang.emit('countWrtieOff');
}
disNone=()=>{
    this.countWriteDiv.style.display = 'none';
}
disOn=()=>{
    this.countWriteDiv.style.display = '';

}
       }

 class TavitTopHowMuchLeft{
constructor(el,taskMang){
    this.taskMang = taskMang;
    this.root = el;
    this.val = 280;
    this.setupUI();//הקנת העיצוב 

    this.howMuchDiv = this.root.querySelector('#howMuchDiv');
    this.disNone = this.disNone;
    this.disOn = this.disOn;
    //subscribes here
    taskMang.subscribe('inputChar',this.update);
    this.taskMang.subscribe('countWrtieOn',this.disNone);
    this.taskMang.subscribe('countWrtieOff',this.disOn);

    //Event listener
    this.howMuchDiv.addEventListener('click',this.show);//הוא לא מזהה לי את הלחציה בצורה הזאת
    this.root.addEventListener('click', (event) => { 
        if (event.target.id === 'howMuchDiv' ||'howMuchP') {
            this.show();
        }
    });
    // this.howMuchDiv.addEventListener('click',()=>{
    //     console.log("event work");
    // });
}
setupUI(){
    this.root.innerHTML+=`
    <div id ='howMuchDiv'>
       <p id ='howMuchP'></p>
    </div>
    `;
   this.updateUI(this.val);
}
updateUI(valueToUpdate){
   this.root.querySelector('#howMuchDiv').children[0].textContent = valueToUpdate;
}
update=(value)=>{
    const result = this.val - value;
    this.updateUI(result);
}
//function for taskManger to flip the divs
show=()=>{
    this.taskMang.emit('countWrtieOn');
}
disNone=()=>{
    this.howMuchDiv.style.display = 'none';
}
disOn=()=>{
    this.howMuchDiv.style.display = '';
}
  }

 class TextArea{
constructor(el,taskMang){
    this.root = el;
    this.setupUI();//התקנת העיצוב
    this.taskMang = taskMang;
    this.teAr = el.querySelector('.teAr');//גישה לטקסט ארה
    this.val = this.teAr.value.length;//אורך הקלט

    this.teAr.addEventListener('input',this.updateUI);
}
setupUI(){
    this.root.innerHTML+=`
    <textarea class="teAr" maxlength="280"></textarea> 
     `;
}
updateUI=()=>{
    this.val = this.teAr.value.length;
    this.taskMang.emit('inputChar',this.val);
}
 }

  const t = document.createElement('div');
 t.id = 'fatherDiv';
 const taskMang = new TaskManager();//יצירת מנהל משימות
 const father = new FatherComponent(t,taskMang);

  document.body.appendChild(t);

HTML

 <!DOCTYPE html>
 <html lang="en">
 <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Document</title>
</head>
<body>
<script src="TaskManager.js"></script>
<script src="FatherCompon.js"></script>
<script src="tavitTop.js"></script>
<script src="TavitTopHowMuchLeft.js"></script>
<script src="textArea.js"></script>
<script src="main.js"></script>
</body>
</html>

CSS

 body{
display: flex;
width: 100vw;
height: 100vh;
background-color: bisque;
align-items: center;
justify-content: center;
}
#fatherDiv{
background-color: aqua;
width: 60%;
height: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#countWriteDiv{
width: 100px;
height: 100px;
background-color: blue;
 }
#howMuchDiv{
width: 100px;
height: 100px;
background-color: red;
/* display: none; */
border: 3px solid black;
 }
.teAr{
width: 80%;
height: 60%;
margin-top: 50px;
}

הי

כמה דברים לבדוק וכמה בעיות ברורות בקוד-

דבר ראשון זה הנושא של bind. כתבת:

 this.howMuchDiv.addEventListener('click',this.show);

אבל זה כמעט אף פעם לא מה שצריך. עלינו להשתמש ב bind כי בתוך הפונקציה אנחנו נשתמש במשתנה this ונרצה שהוא יצביע לאותו this שעשה את ה addEventListener, כלומר יש לכתוב:

 this.howMuchDiv.addEventListener('click',this.show.bind(this));

או אופציה שניה שגם תעבוד:

 this.howMuchDiv.addEventListener('click',(ev) => this.show(ev));

דבר שני התנאי כנראה לא נכון. התנאי הזה:

if (event.target.id === 'howMuchDiv' ||'howMuchP') {

תמיד נכון כי הוא בודק ״אם״ של שני דברים - אם ה event.target.id שווה למחרוזת מסוימת, או אם המחרוזת השניה היא בעלת ערך אמת. מאחר שמחרוזת לא ריקה ב JavaScript היא תמיד בעלת ערך אמת זה תמיד יצליח. אפשר להשתכנע עם דוגמת קוד קטנה:

if (2 > 5 || 7) { console.log('wow') }

ולראות שהוא תמיד מדפיס.

כשרוצים לבדוק שמשהו הוא אחד משני דברים אפשר להשתמש בכתיב הבא:

if (event.target.id === 'howMuchDiv' || event.target.id === 'howMuchP') { ... }

או אם יש יותר משני דברים ורוצים להיות יותר יעילים בכתיבה אפשר לכתוב:

if (new Set(['howMuchDiv', 'howMuchP']).has(event.target.id)) {...}

דבר אחרון צריך לבדוק האם this.howMuchDiv באמת מכיל את האלמנט הנכון מה DOM. הייתי מנסה לשים נקודת עצירה לידו או פקודת הדפסה כדי לראות אם מגיעים לדבר הנכון.

מה קורה ינון , תודה על התשובה רק כמה דברים קטנים שאני מנסה להבין למה הם ככה

בדקתי את שלושת האופציות האלו שאמרת לא פתרו אצלי את הבעיה

דבר ראשון זה הנושא של bind. כתבת:

 this.howMuchDiv.addEventListener('click',this.show);
 this.howMuchDiv.addEventListener('click',this.show.bind(this));
 this.howMuchDiv.addEventListener('click',(ev) => this.show(ev));

[/quote]

בנוגע ל this.howMuchDiv

עשיתי בדיקה והוא מכיל את div הנכון מה DOM וזה הזוי
כשאני eventListener בצורות האלה זה לא קולט אפילו את הלחיצה :

this.howMuchDiv.addEventListener('click',this.show.bind(this));

או

this.root.querySelector('#howMuchDiv').addEventListener('click',this.show.bind(this));  

אני לא מבין למה באופציה השניה ^ אני גם משתמש ב root ומושך את הdiv הנכון זה לא מזהה את הלחיצה ובדרך הזאתי זה כן מזהה את הלחיצה ועובד חלק :

    this.root.addEventListener('click', (event) => { 
        if (event.target.id === 'howMuchDiv' || event.target.id === 'howMuchP') {
            this.show();
        }
    }); 

תודה .

הי

אחרי קריאה חוזרת של הקוד-

  1. ה bind באמת לא משמעותי כאן כי אתה משתמש בפונקציות חץ בתוך הקלאס:
updateUI=()=>{

מה שיוצר bind אוטומטי.

  1. השגיאה שלך קורית בגלל עדכון ה DOM עם += ל innerHTML. בעצם הדבר הזה דורס את האלמנטים שהיו שם ומחליף אותם באלמנטים חדשים ועל החדשים אין event handlers.

אני ממליץ לחלק את העבודה כך שכל class יקבל DOM Element ויעבוד עליו, במקום כמו שעשית שכל הקלאסים משנים את אותו אלמנט עם +=. תנסה ב Father ליצור רק את האלמנטים עבור הילדים (בלי התוכן שלהם), ושכל ילד יקבל אלמנט בתוך האבא ויוכל להפעיל עליו innerHTML = במקום +=.

תודה רבה על כל העזרה !!