Есть ли изъяны в данном коде?

143
21 февраля 2021, 13:30

Занимаюсь углубленным изучением JS. Написал простенький класс для автоматического позиционирования absolute элементов относительно любого блока при клике.
Вопросов в принципе 3:

  1. Код плох или нет?
  2. Если ответ на первый вопрос true, то чем именно и как бы вы реализовали данный участок кода?
  3. Если ответ на второй вопрос true, что мне подучить, чтоб не было такого кода в будущем. Тыкните пальцем в каких местах я слаб и какой "раздел" js подучить.

Использование

let posConfig = {
    admin: {
        btn: ".adminBtn",
        btnPos: "bottom-right",
        dropdown: ".adminDrop",
        dropdownPos: "top-right",
        animate: "fadeInDrop"
    },
    notification: {
        btn: ".notificationBtn",
        btnPos: "bottom-right",
        dropdown: ".notificationWrapper",
        dropdownPos: "top-right",
        animate: "fadeInDrop"
    },
    message: {
        btn: ".messageBtn",
        btnPos: "bottom-right",
        dropdown: ".messageWrapper",
        dropdownPos: "top-right",
        animate: "fadeInDrop"
    }
};
new Positioner(posConfig);

Сам класс

export default class Positioner {
    constructor(config){
        this.elements = {};
        Object.keys(config).map((k) => {
            let obj = config[k];
            let btn = document.querySelector(obj.btn);
            let dropdown = document.querySelector(obj.dropdown);
            this.elements[k] = {
                btn: {
                    get:  btn,
                    coordinates: obj.btnPos
                },
                dropdown: {
                    get:  dropdown,
                    coordinates: obj.dropdownPos
                },
                animate: obj.animate,
                show: false
            };
            this.elements[k].setPosition = () => this.setPosition(this.elements[k]);
            this.addEvents(this.elements[k]);
        });
        return this.elements;
    }
    addEvents(obj){
        window.addEventListener("click", () => this.setDefault(obj));
        obj.btn.get.addEventListener("click", e => {
            let currentObjectShow = obj.show;
            Object.values(this.elements).filter(v => v.show).map(v => this.setDefault(v));
            if(!currentObjectShow){
                obj.show = true;
                obj.setPosition();
                e.stopPropagation();
            }
        }, false);
        obj.dropdown.get.addEventListener("click", e =>  e.stopPropagation(), false);
    }
    setDefault(object){
        Object.assign(object.dropdown.get.style,{top:"-100%",left:"-100%"});
        object.dropdown.get.classList.remove(object.animate);
        object.show = false;
    }
    getRect(element){
        let rect = element.getBoundingClientRect();
        return {
            toTop: rect.top,
            toBottom: rect.bottom,
            toLeft: rect.left,
            toRight: rect.right,
            width: rect.width,
            height: rect.height,
            centerY: rect.top + rect.height / 2,
            centerX: rect.left + rect.width / 2
        }
    }
    setPosition(object){
        let btn = object.btn;
        let dropdown = object.dropdown;
        let btnRect = this.getRect(btn.get);
        let dropdownRect = this.getRect(dropdown.get);
        let coordinateChild = this.calcPosition(0, 0, dropdownRect, dropdown.coordinates);
        let coordinateParent = this.calcPosition(btnRect.toTop, btnRect.toLeft, btnRect, btn.coordinates);
        let left = coordinateParent.x - coordinateChild.x;
        let top  = coordinateParent.y - coordinateChild.y;
        this.fixPosition(object, top, left);
        this.addAnimateClass(object);
    }
    calcPosition(modifyTop, modifyLeft, element, position){
        let coordinate = {};
        switch(position){
            case 'top-left':
                coordinate.x = modifyLeft;
                coordinate.y = modifyTop;
                break;
            case 'top-center':
                coordinate.x = modifyLeft + element.width / 2;
                coordinate.y = modifyTop;
                break;
            case 'top-right':
                coordinate.x = modifyLeft + element.width;
                coordinate.y = modifyTop;
                break;
            case 'right-center':
                coordinate.x = modifyLeft + element.width;
                coordinate.y = modifyTop + element.height / 2;
                break;
            case 'bottom-right':
                coordinate.x = modifyLeft + element.width;
                coordinate.y = modifyTop + element.height;
                break;
            case 'bottom-center':
                coordinate.x = modifyLeft + element.width / 2;
                coordinate.y = modifyTop + element.height;
                break;
            case 'bottom-left':
                coordinate.x = modifyLeft;
                coordinate.y = modifyTop + element.height;
                break;
            case 'left-center':
                coordinate.x = modifyLeft;
                coordinate.y = modifyTop + element.height / 2;
                break;
            default:
                coordinate.x = modifyLeft + element.width / 2;
                coordinate.y = modifyTop + element.height / 2;
                break;
        }
        return coordinate;
    }
    fixPosition(object, top, left){
        let dropdownRect = this.getRect(object.dropdown.get);
        let windowHeight = document.documentElement.clientHeight;
        let windowWidth = document.documentElement.clientWidth;
        let toBottom = top + dropdownRect.height;
        let toRight = left + dropdownRect.width;
        let newChildTop = windowHeight - dropdownRect.height;
        let newChildLeft = windowWidth - dropdownRect.width;
        object.dropdown.get.style.top  = toBottom > windowHeight ? newChildTop+'px' : top;
        object.dropdown.get.style.left = toRight > windowWidth ? newChildLeft+'px' : left;
    }
    addAnimateClass(object){
        let dropdown = object.dropdown.get;
        if(dropdown.classList.contains(object.animate)){
            this.setDefault(object);
        }
        else{
            dropdown.classList.add(object.animate);
        }
    }
}
Answer 1

Я бы переписал это как сервис.

Конструктор класса инициализирует свои переменные и навешивает глобальный эвент(чтобы не плодить их для каждого элемента).

this.elements = {};
document.addEventListener('click', () => {
  Object.keys(this.elements).forEach(key => this.reset(key));
});

Метод добавления элемента

add(key, props) {}

Все методы, работающие с конкретными элементами на данный момент статические, не используют состояние класса(смысл всего класса при этом становится непонятным).

Это можно исправить, передавая в эти методы не сами элементы, а ключи.
Это избавит клиентский код от необходимости хранить у себя все элементы, достаточно будет хранить ключи.

fixPosition(key, top, left) {
  const obj = this.elements[key]; 
  //...
}

В итоге, как это будет выглядеть:

const positioner = new Positioner();
// Object.keys(posConfig).forEach(key => positioner.add(key, posConfig[key]));
positioner.add('admin', {
  btn: {selector: '.btn', pos: {x: 'center', y: 'top'}}, ...
});
positioner.moveX('admin', 'btn', 'center');

Обратите внимание, что я разделил позицию элементов на вертикальную и горизонтальную. В текущей реализации у вас в switch 3*3=9 case, но в разных кейсах по частям происходит одно и то же. Это лучше в раздельных конструкциях по 3 варианта.

READ ALSO
Динамическая высота у textarea

Динамическая высота у textarea

Всю голову сломал, делаю следующее: копирую текстовый контент в модальное окно-форму в textareaНеобходимо авто-изменение высоты textarea без события...

102
Почему POST запрос не хочет передаваться?

Почему POST запрос не хочет передаваться?

Я попробовал несколько библиотек, но ни одна не дала результатов, пробую вот это:

91
JS не находит картинку для backgroundImage

JS не находит картинку для backgroundImage

Проблема заключается в том, что как только я вкладываю картинку в папку JS ее не видитПроще говоря так работает:

125
res.send возвращает [object Promise]

res.send возвращает [object Promise]

хочу отренедрить разметку, после получения данных из fetch, при попытке отправить разметку, отправляется [object Promise]

134