Общее свойство для всех экземпляоов

Я делаю свой первый проект с ООП делаю небольшую библиотеку, с помощью которой можно будет делать всплывающие окна, и в нужный момент их вызывать.

Есть класс WindowAction от него наследуется PopupWindow, а от него в свою очередь наследуются ErrorPopup, InfoPopup и тд.

В WindowAction есть метод alert() в нем я добавляю в html окно. И в this.currentElement заношу этот элемент Потом когда я вызываю его второй раз из this.currentElement берется предыдущий элемент и удаляется из html

popup.alert() // new popup
popup.alert() // remove last element, insert new element

но я столкнулся с проблемой

если сделать два разных экземпляра (те получается два разных окна) и вызвать у них у обоих метод alert(), то ни один из них не закроется, и на странице получится два окна

popup.alert() // new popup
popup2.alert() // new popup

Как можно решить эту проблему?

Я придумал только сделать для всех экземпляров currentElement общим. Те если у popup1 currentElement = '12312', то и у popup2 currentElement будет равен '12312'. Но я не знаю как сделать его общим

Как сделать одно свойство общим для всех наследников (у всех будет одинаковое значение)??

Вот ссылка на jsFiddle. кому как удобно https://jsfiddle.net/9Lrf6oab/

код целиком (typeScript):

// проблема:
// на 25 строке объявление currentElement
// на 245 строке удаления элемента по currentElement
// на 262 277 строоках задаю значение currentElement
// на 299 строке класс ErrorPopup
// на 318 строке класс InfoPopup
// на 336 строке класс Popuper? который создает экземпляры классов (фабрика)



type AnimationAction = 'fade' | 'swipeRight' | 'swipeTop';
type Func = ()=>void;

/** Base class implementing window effects. */
class WindowAction
{
    /** Stores opening action */
    protected openAction: any = 'open_fade';

    /** Stores closing  action */
    protected closeAction: any = 'close_fade';

    /** Stores the action of clicking a button */
    protected clickAction: any = 'open_fade';
    
		// THERE MY PROBLEM!!!!!
		// THERE MY PROBLEM!!!!!
		// THERE MY PROBLEM!!!!!
		// THERE MY PROBLEM!!!!!
    /** Stores the current open window */
    public currentElement: Element | null = null;
    // THERE MY PROBLEM!!!!!
    // THERE MY PROBLEM!!!!!
    // THERE MY PROBLEM!!!!!
    // THERE MY PROBLEM!!!!!

    /** Automatic error output to the console */
    public errorReporting: boolean = true;

    /** Stores the id of the current window */
    private popupID: number = 0; 

    /** Stores the last error */
    private __lastError: string = '';

    get lastError(): string {
        if (this._lastError != '') {
            return this.__lastError;
        }

        return 'no errors';
    }

    /** if error reporting is enabled, then displays errors to the console */
    set _lastError(error: string) {
        if (this.errorReporting) {
            console.trace(error);
        }
        this.__lastError = error;
        return;
    }

    /** fade animation on open */
    protected open_fade(): void {
        // console.log('стандартная функция открытия -- fade effect');
    }
    /** swipe right animation on open */
    protected open_swipeRight(): void {
        // console.log('стандартная функция открытия -- swipe right effect');
    }
    /** swipe top animation on open */
    protected open_swipeTop(): void {
        // console.log('стандартная функция открытия -- swipe top effect');
    }

    /** fade animation on close */
    protected close_fade(): void {
        // console.log('стандартная функция закрытия -- fade effect');
    }
    /** swipe right animation on close */
    protected close_swipeRight(): void {
        // console.log('стандартная функция закрытия -- swipe right effect');
    }
    /** swipe top animation on close */
    protected close_swipeTop(): void {
        // console.log('стандартная функция закрытия -- swipe top effect');
    }
    

    /**
     * sets the effect or function when opened
     * @param action function that will work when opened or the name of the built-in effect
     */

    public onOpen(action: Func| AnimationAction): void {
        // string passed
        if (typeof action === 'string') {
            // on open performs animation
            this.openAction = 'open_' + action;
        }
        //function passed
        else if (typeof action === 'function') {
            // on open performs the user function
            this.openAction = action;
        }
    }

    /**
     * runs when open (alert ()), performs the specified action
     */

    protected open() {
        // perform custom function
        if (typeof this.closeAction === 'function') {
            this.closeAction();
        }
        // perform standart animation
        if (typeof this.closeAction === 'string' ) {
            (this as any)[this.closeAction]();
        }
    }

    /**
     * runs when close (close ()), performs the specified action
     */

    public onClose(action: Func | AnimationAction): void {
        // perform custom function
        if (typeof action === 'function') {
            this.closeAction = action;
        }
        // perform standart animation
        if (typeof action === 'string') {
            this.closeAction = 'close_' + action;
        }
    }

    /**
     * closes an open window
     */

    close(): boolean{
        // if there is an open window 
        if (this.currentElement !== null) {
            // perform custom function
            if (typeof this.closeAction === 'function') {
                this.closeAction();
            }
            // perform standart animation
            if (typeof this.closeAction === 'string' ) {
                (this as any)[this.closeAction]();
            }
        
            // close current window
            this.currentElement.remove();
            this.currentElement = null;
            
            return true;
        }

        // if there is not an open window 
        this._lastError = 'no popups open';
        return false
    }
    
}


type insertMethod = 'add' | 'replace' | 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend';

class PopupWindow extends WindowAction{
    /** type of popup */
    public type: string = '';

    /** are there any changes in layout */
    private changes: boolean = true;

    /** layout container */
    private _layout: string = '';

    /** layout template 
     * {key} : value
     * @example popup.layout = '<div>{myValue}</div>'
    */
    protected layoutPattern: string = '';

    /** stores variables to be inserted into the popup
     * @tutorial 
     * markup marked {key}
     * the value with the key key is inserted
     * @example popup.variables = {"myValue" : 'my message!!'}
     */
    private _variables: {[key: string]: any} = {};

    /** is the window open */
    private openning: boolean = false;

    set message(message: string) {
        this._variables.message = message;
        this.changes = true;
    }
    set button(button: string) {
        this._variables.button = button;
        this.changes = true;
    }
    set layout(layout: string) {
        this.layoutPattern = layout;
        this.changes = true;
    }
    set variables(vars: {[key: string]: any}) {
        this._variables = vars;
        this.changes = true;
    }

    /** inserts a window in html in the selected method
     * @param out element to insert
     * @param insertMethod insertion method
     */

    insert (out: Element, insertMethod: insertMethod): void{
        switch (insertMethod) {
            case 'add':
                out.innerHTML += this._layout;
                break;
            case 'replace':
                out.innerHTML = this._layout;
                break;
            case 'beforebegin':
            case 'afterbegin':
            case 'beforeend':
            case 'afterend':
                out.insertAdjacentHTML(insertMethod, this._layout);
                break;
        }
        // execute function when open
        this.open();
    }

    /**
     * displays a popup on a page
     * @param outElement element to insert
     * @param insertMethod insertion method
     */

    alert (outElement: string | Element | null, insertMethod: insertMethod = 'add'): Boolean{
        // close window if open
        // НУЖНО ЧТОБЫ У POPUP1 и POPUP2 были одинаковые currentElement
        // ЧТОЬЫ МОЖНО БЫЛО ЗАКРЫВАТЬ ИЗ POPUP1 POPUP2
        // пример использования ниже
        if (this.openning === true && this.currentElement !== null) {
            this.currentElement.remove();
        }

        // if there is a change in layout, update the layout template
        if (this.changes) {
            this.buildLayout();
            this.changes = false;
        }
        
        if (typeof outElement === 'string') {
            // get element by string
            const out: Element | null = document.querySelector(outElement);
            if (out) {
                // insert popup in html
                this.insert(out, insertMethod);

								// ТУТ ЗАПИСЫВАЮ В currentElement html ЭЛЕМЕНТ, КОТОРЫЙ ТОЛЬКО ЧТО ВСТАВИЛ
                this.currentElement = document.querySelector(outElement +' .popup');
            }
            
            else {
                this._lastError = `such an element does not exist (${outElement})`;
                return false;
            }
        }
        
        else if (outElement instanceof Element) {
            // insert popup in html
            this.insert(outElement, insertMethod);

						// ТУТ ЗАПИСЫВАЮ В currentElement html ЭЛЕМЕНТ, КОТОРЫЙ ТОЛЬКО ЧТО ВСТАВИЛ
            this.currentElement = outElement.querySelector('.popup');
        } 

        // no element
        else {
            this._lastError = `such an element does not exist`;
            return false;
        }

        this.openning = true;
        return true;
    }

    // replace {key} in the layout with the values ​​from variables
    buildLayout(): void {
        this._layout = this.layoutPattern;
        for (let variable in this._variables){
            this._layout = this._layout.replace(`{${variable}}`, this._variables[variable]);
        }
    }
}

class ErrorPopup extends PopupWindow
{
    type = 'error';
    constructor(standartMessage: string, standertButton: string | undefined){
        super();
        this.variables = {'message': standartMessage, 'button': standertButton};

        this.layoutPattern = `<div class="popup popupError">
                                <div class="popupMessage">{message}</div>
                                <div class="popupFooter">
                                <button class="popupButton">{button}</button>
                                </div>
                              </div>`;
        return this;
    }
}
class InfoPopup extends PopupWindow
{
    type = 'info';
    constructor(standartMessage: string, standertButton: string | undefined){
        super();
        this.variables = {'message': standartMessage, 'button': standertButton};

        this.layoutPattern = `<div class="popup popupInfo">
                                <div class="popupMessage">{message}</div>
                                <div class="popupFooter">
                                  <button class="popupButton">{button}</button>
                                </div>
                              </div>`;
        return this;
    }
}

class Popuper
{
    /**
     * creates a popup object
     * 
     * @param standartMessage standart message
     * @param standartButton standart button text
     */

    constructor(public standartMessage: string, public standartButton? : string){
        this.standartMessage = standartMessage;
        this.standartButton = standartButton;
    }

    /**
     * creates a popup object
     * 
     * @param type type of popup
     * @returns popup window object 
     */

    create(type: 'error' | 'info' | 'succsses' | 'sure'): PopupWindow{
        let typeWindow: any = null;
        let popup: {[key: string]: any} = {};

        switch (type) {
            case 'error':
                // СОЗДАЮ ЭКЗЕМПЛЯР ErrorPopup
                return new ErrorPopup(this.standartMessage, this.standartButton);

            case 'info':
            		// СОЗДАЮ ЭКЗЕМПЛЯР InfoPopup
                return new InfoPopup(this.standartMessage, this.standartButton);

            case 'succsses':
                return new SuccssesPopup(this.standartMessage, this.standartButton);

            case 'sure':
                return new SurePopup();
        }
    }
}

/* ПРИМЕР */

let popuperError = popuper.create('error');
let popuperInfo = popuper.create('info');

console.log(popuperError.currentElement); // null
console.log(popuperInfo.currentElement); // null

popuperError.alert(document.body);

console.log(popuperError.currentElement); // error element
console.log(popuperInfo.currentElement); // null

popuperInfo.alert('body');

// НУЖНО ЧТОБЫ У НИХ БЫЛО ОБЩЕЕ СВОЙСТВО currentELement
// ТОГДА МОЖНО БУДЕТ В ЧЕРЕЗ ОДИН ЭКЗЕМПЛЯР КЛАССА, УДАЛЯТЬВСПЛЫВАЮЩЕЕ ОКНО ДРУГОГО ЭКЗЕМПЛЯРА
body{
    position: absolute;
    width: 100%;
    height: 100%;
    background: blue;
    display: flex;
    align-items: center;
    justify-content: center;
}
.popup{
    position: relative;
    background: #fff;
    width: 400px;
    height: 200px;
    overflow: hidden;
    border-radius: 35px;
    display: flex;
    align-self: center;
}
.popup .popupFooter{
    position: absolute;
    width: 100%;
    bottom: 0;
    height: 30%;
    background: linear-gradient(160deg, #39B9FF 10%, #003BE4);
    padding: 5px;
}
.popup .popupFooter button{
    position: absolute;
    right: 50px;
    top: 50%;
    transform: translateY(-50%);
}
.popup .popupMessage{
    width: 100%;
    height: 100%;
    padding: 15px;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PopuperJS</title>
</head>
<body>
   
</body>
</html>


Ответы (1 шт):

Автор решения: qwabra

Общее свойство для всех экземпляоов ©

песочница - там

"use strict";
let Parent = /** @class */ (() => {
    class Parent {
        get counter() { return Parent.counter; }
        set counter(v) { Parent.counter = v; }
    }
    Parent.counter = 0;
    return Parent;
})();

class A extends Parent {
    increment() { this.counter = this.counter + 2; }
}

class B extends Parent {
    increment() { this.counter = this.counter + 10; }
}

const _A = new A;
const _B = new B;

console.clear();

_A.increment();
console.log(_A.counter);

_B.increment();
console.log(_B.counter);

→ Ссылка