Pull to refresh

Концепция «Родные Элементы»(Native Elements)

Reading time5 min
Views8K
image

Всем доброго здравия.
Хотел предложить концепцию JavaScript(JS) библиотеки для удобного использования Custom Elements(по-русски назову «собственные элементы»). Перед тем как предлагать Polymer, X-Tag, Bosonic и другие, прошу прочитать текст до конца.

Введение


Главная идея состоит в том, чтобы сделать использование собственных элементов более похожим на нативный подход стилизации встроенных HTML-элементов. Например, я определил элемент вертикальной гистограммы(ui-vboxhistogramm). Добавил несколько гистограмм на страницу. В случае использования Polymer мне необходимо описывать визуальный стиль для каждой гистограммы по отдельности. И в случае повторения визуальных свойств(не CSS-свойств, а определённых вручную для собственного элемента) для каждой гистограммы описание становится избыточным. Поясню на примере:
<ui-vboxhistogramm id="histo1" column-count="5" column-width="10px"></ui-vboxhistogramm>
<ui-vboxhistogramm id="histo2" column-count="5" column-width="10px"></ui-vboxhistogramm>
<ui-vboxhistogramm id="histo3" column-count="5" column-width="10px"></ui-vboxhistogramm>

Тут сразу напрашивается система классов из HTML. Но классы работают только для нативных CSS-свойств. В итоге возникла задача сделать похожую систему классов на JS.

Концепция «Родные Элементы»(Native Elements)


В первую очередь хотелось бы сделать доступ к свойствам визуализации по аналогии с нативным стилем изменения этих свойств:
divElement.style.background = "color";

Собственные элементы позволяют воплотить в жизнь эту дерзкую идею:
//Это у нас базовый класс, который отвечает за создание ссылки на корневой элемент и метод для обработки декларационных объектов. Этот метод обходит свойства рекурсивно и вызывает соответствующие сеттеры для элемента.
class ExtendedStyleDeclaration
{
  constructor(parentNode)
  {
    this._root = parentNode;
  }
  declare(declarationObject)
  {
    //тут мы получаем свойства для описания собственного элемента и применяем их в соответствии с названием сеттера
  }
}

//Это класс стилизатора(element.xstyle.column), вложенного в корневой
class VBoxHistogrammCulumnStyle extends ExtendedStyleDeclaration
{
  constructor(parentNode)
  {
    super(parentNode);
  }
  set count(value)
  {
    //тут по аналогии с сеттером первого уровня, объекта xstyle
    saveProperty(this, "count", value);
    //далее работаем со встроенными в собственный элемент компонентами(canvas или svg, в случае с гистограммами)
  }
  get count()
  {
    return getLastProperty(this, "count");
  }
  set width(value){}
  get width()     {}
}

//Это класс корневого стилизатора(element.xstyle) для гистограммы
class VBoxHistogrammStyle extends ExtendedStyleDeclaration
{
  constructor(parentNode)
  {
    super(parentNode);
    this.column = new VBoxHistogrammCulumnStyle(this);
  }

  set width(value)
  {
    //Перед тем как изменить внешний вид элемента сначала сохраняем значение в специальный контейнер,
    //так как значение может задаваться из разных мест(напрямую или через группу(класс)), поэтому надо иметь возможность получать оба значения, а точнее последнее.
    saveProperty(this, "width", value);

    //Не совсем удачный пример, так как я просто продублировал CSS-свойство.
    //Но предположим, что здесь более сложная логика, чем просто установка width напрямую, например ширина высчитывается на основе каких-то условий
    if(condition)
    {
      this._root.style.width = this.width;
    }
    else
    {
      this._root.style.width = "расчёт по сложному алгоритму";
    }
  }
  get width()
  {
    return getLastProperty(this, "width");
  }
}

//Это наш будущий виджет
class VBoxHistogrammProto extends HTMLElement
{
  createdCallback()
  {
    setGroupsToQueue(this); //этой функцией мы проверяем наличие групп в атрибуте group, чтобы добавить их в очередь на установку в функции declareGroup
    this.xstyle = new VBoxHistogrammStyle(this); //передаём указатель на узел нашего элемента для дальнейших манипуляций с ним
  }
}

Теперь мы можем задавать стиль для наших элементов по аналогии с нативным подходом и/или даже более продвинутым способом:
let histo1 = document.getElementById("histo1");

histo1.xstyle.width = "100px";
//or
histo1.xstyle.declare(
{
  width : "100px",
  column:
  {
    count: 5,
    width: "10px"
  }
});

histo1.xstyle.column.count = 15; //тут произойдёт обновление гистограммы и на ней появится 15 столбцов. Свойство из группы теряет свою силу, так как свойство было изменено напрямую. Теперь свойство count не может быть изменено через группу(аналогия с CSS-классами).
//or
histo1.xstyle.column.declare(
{
  count: 5,
  width: "10px"
});


Группы — они же классы


Возвращаемся к вопросу стилизации через классы. Так как ключевое слово class уже зарезервировано для этого атрибута, то наиболее близким по значению названием для него по моему мнению является ключевое слово group. Группировка элементов происходит по аналогии с заданием класса для встроенных элементов:
<ui-vboxhistogramm id="histo1" group="histogroup1"></ui-vboxhistogramm>
<ui-vboxhistogramm id="histo2" group="histogroup1"></ui-vboxhistogramm>
<ui-vboxhistogramm id="histo3" group="histogroup1 histogroup2"></ui-vboxhistogramm>

Теперь наши элементы сгруппированы по общим сходным свойствам, определенным вручную в собственном элементе. Декларация групп происходит в JS-коде:
//функция declareGroup возвращает ссылку на массив groups, содержащий описание групп
let groups = declareGroup(
{
  histogroup1:
  {
    width: "100px",
    column:
    {
      count: 5,
      width: "10px"
    }
  },
  histogroup2:{}
});

//теперь можно добавить объявленные группы к элементам разными способами
//добавим группу histogroup1 к элементу histo5
let histo5 = document.getElementById("histo5");
histo5.groupList.add(groups.histogroup1);

//добавим все группы из массива groups к элементу histo6
let histo6 = document.getElementById("histo6");
histo6.xstyle.declare(
{
  group: groups
});

Алгоритм декларации группы реализуется следующим образом. В момент создания собственного элемента(функция обратного вызова createdCallback) необходимо организовать список групп(например в глобальном объекте window) с очередью из группируемых элементов на применение с помощью специального объекта groupList.

groupList — это аналог нативного classList только для собственного элемента. Он также имеет методы add и remove. Метод add(по аналогии с методом declare для отельного элемента) принимает декларационный объект, содержащий в себе описание визуальных свойств элемента. Таким образом функция declareGroup обходит все группы из своего декларационного объекта и добавляет их для всех элементов из очереди.

Приоритет задаваемых свойств можно реализовать по аналогии с классами. Если свойство задаётся напрямую через сеттер или метод declare, то свойства из групп теряют свою силу, то есть перекрываются.

Заключение


Подытожим. Суть концепции заключается в том, чтобы предоставить набор классов(JS) и функций для реализации поведения, показанного в примерах выше:
  • динамические сеттеры для задания стиля собственного элемента, близкие по механике к нативной стилизации через element.style.property
  • группировка элементов(аналог классов для CSS-свойств) схожих по стилю, для исключения избыточности при описании одних и тех же пользовательских свойств(например число и ширина столбцов гистограммы) для нескольких элементов

В итоге мы получаем близкий к нативному метод описания визуальных свойств для собственных элементов, содержащий в себе ряд преимуществ и недостатков. Хотелось бы послушать мнение знающих front end разработчиков, чтобы определиться с целесообразностью реализации этого подхода.
Only registered users can participate in poll. Log in, please.
Применяете ли вы Веб Компоненты в ваших рабочих проектах?
9.21% Нет, не слышал14
44.74% Нет, жду стандартизации68
13.16% Нет, не вижу смысла в этом, так как использую свой проверенный набор технологий20
21.71% React33
11.18% Да(в каком виде используете, с библиотекой(Polymer, etc.) или без)17
152 users voted. 40 users abstained.
Tags:
Hubs:
Total votes 8: ↑8 and ↓0+8
Comments25

Articles