Всем Привет! Хочу поделиться своим опытом работы с огромным и непонятным legacy кодом на фронте. Я не специалист в работе с чистым JS и возможно мой подход был совсем не верным, поэтому просьба оставлять аргументированные комментарии, буду благодарен.

Наш проект написан на шаблонах PHP Yii2, на фронте повсеместно используется всеми не любимый jQuery. Перед мной стояла задача реализовать динамическую отрисовку элемента, при добавлении N - стиля на элемент родителя. Основная проблема с которой мне пришлось столкнуться, это работа с DOM-деревом. В проекте используются шаблоны и дополнительные виджеты, то классы стилей на странице могут дублироваться. И логика работы виджетов прописана на глобальном уровне, что влечет за собой сложность простого обращения к элементам с использованием jQuery. В примерах использовал функциональный подход, потому что есть вероятность использования в старых браузерах.

Пример:

<div class="clear">
    <div class="select-item">
        <?= $form->field($model, 'place_work')->textInput()->label();?>
    </div>
</div>

В ТЗ обязательным пунктом было, что добавление стиля должно было включать определенную логику при добавлении элемента. При этом сам элемент должен обладать доп. функционалом, опять же мы упираемся на глобальное DOM-дерево на странице. Поэтому добавлять уникальный ID мы не можем, тем более что данный класс может добавляться с помощью Ajax запросов, а это тянет за собой тоже определенные сложности.

Перечитав множество статей и руководств, решил попробовать атомарно работать с родительским элементом, таким образом мы изолируем от общего DOM, что позволяет управлять его детьми без нарушения целостности страницы. При обращении jQuery к элементам DOM, нам возвращается объект. Используя наследование, мы можем расширить функционал, либо изменять текущий элемент.

Пример реализации вставки input поиска в выпадающее меню:

$(function () {
    if ($('div').hasClass('test')) {
        const test = $('.my-class');

        for (let obj of test) {
            new GenerateObj($(obj));
        }
    }
});


function GenerateObj(obj){
    if((typeof obj) === "object" && obj != undefined){
        this.__proto__ = obj;
    } else {
        console.warn("Дружок, что то не так.")
    }
}

Первая функция срабатывает в тот момент когда DOM-дерево было построено и производим поиск нужного нам класса в DOM. При дублировании классов стилей на странице jQuery возвращает массив объектов с заданным стилем. При вызове ключевого слова new мы создаем объект функции, которая аргументом принимает в себя объекты от которых мы будем наследоваться и с которыми будем работать.

function GenerateObj(obj){
  // Инициализация из примера выше.
  // Какая то дополнительная логика. 
  this._createInput = function(object){
      // Функция для вставки элемента в страницу. 
      // Описываем элемент кторый нужно вставить в DOM. 
          const input = $(`<li class="input">` +
              `<input type="search" id="input" class="text-input" placeholder="Мой элемент"/>` +
              `</li>`);
          $(input).prependTo(object.find(".selectric ul"));
      }
  //Вызов метода, так как я расширяю функционал отрисовку делал в конструкторе.
  //Если есть потребность то проще вынести в отдельный метод вызов всей логики.
  this.__createInput(this);
}

А вот теперь самый интересный момент объясняющий почему пришлось использовать данный подход.

function GenerateObj(obj){
  // Инициализация из примера выше.
  // Какая то дополнительная логика. 
  this.__createOnClick = function(obj){
      //метод отслеживающий событие щелчка мыши по элементу. 
          obj.find(".input").on('click', function(event){
              event.stopPropagation();
            
              return obj.find(".selectric-wrapper").addClass(function(e){
                  if(this===e){
                      return "selectric-open";
                  } else {
                      return "selectric-below";
                  }
              });
        });
    }

  //Какой то еще код. 
  this.__createOnClick(this);
}

Данный метод отслеживает щелчок мыши по элементу, останавливает логику глобальных стилей и логики. Без данного метода при нажатии элементов ( в нашем случае <li>) внутри родителя ( <ul> ) происходило закрытие выпадающего списка. Так как мы вставили input и нам нужно на него нажать, что бы ввести текст по которому необходимо провести поиск. То при дефолтном глобальном поведении при нажатии по input происходило закрытие выпадающего списка, поэтому мы отменяем глобальную обработку данного события, и передаем в родительский элемент класс описывающий его поведение при нажатии на дочерний элемент.

Все вроде просто, но черт кроется в деталях. Если на странице повторяются стили и элементы с которыми мы работаем, то если мы не производим изоляцию объекта и не работаем с ним изолированно, то поведение распространяется на все объекты, в нашем случаем, с требуемым нам стилем. Но самое интересное, что когда мы получаем текст с input можно уловить текст вводимый с другого inputa к классу или id которого мы цепляемся.

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

Комментарии (1)


  1. Steepik
    29.06.2023 13:15
    +1

    Из одного комка грязи в другой)