Динамическое создание компонентов модуля
Условно назовем размещение компонентов при визуальном редактировании и сохранение их в SFM статическим созданием компонентов. Здесь всё понятно - sFF забирает SFM-файл при запуске, читает его и создаёт в автоматическом режиме, скрытом от программиста, компоненты модуля.
Теперь предположим, что мы пишем не калькулятор, а систему посложнее: web канбан-доску (например, как в Trello). Совершенно понятно, что статическим созданием компонентов тут не обойтись. По крайней мере, добавляя карточку, мы её должны немедленно увидеть на экране (на то и web).
Это уже динамическое создание компонентов.
Чтобы создать компонент, мы должны инициировать данное действие в какой-либо функции. Например, в функции, привязанному к событию нажатия кнопки. С этой целью положим на страницу модуля Button. В чистом проекте он получит имя Button_0. На кнопке напишем (в поле свойства Buton.text Редактора свойств) "Card++". Вслед за этим нажимаем поле Signal.click, подтверждаем создание функции, после чего нас автоматически перебрасывает в Редактор Front-функций раздела Code, где в теле созданной функции on_Button_0_pane_click пишем следующее:
let pan = new Panel();
pan.Panel.parentPane=$_GC("root").pane;
pan.Panel.x=100;
pan.Panel.y=150;
pan.Panel.width=300;
pan.Panel.height=150;
pan.BgColor.default="lemonchiffon";
pan.ShadowColor.default="slategray";
pan.Panel.draggable=true;
let head = new Label();
head.Panel.parentPane=pan.pane;
head.Anch.top=pan.Panel.name+".top";
head.Anch.topMargin=5;
head.Anch.left=pan.Panel.name+".left";
head.Anch.leftMargin=5;
head.Anch.right=pan.Panel.name+".right";
head.Anch.rightMargin=5;
head.Panel.height=35;
head.BgColor.default="gold";
head.Label.text="Новая карточка";
head.Label.horzAlign="center";
head.LabelFont.size=20;
head.LabelFont.underline=true;
let but=new Button();
but.Panel.parentPane=pan.pane;
but.Anch.centerIn=pan.Panel.name;
but.Panel.width=100;
but.Button.fontSize=20;
but.Button.text="Ok";
but.Button.fontColor="green";
but.Button.position="vertCenter";
but.Signal.click=`alert("Ok from ${pan.Panel.name}");`;Обязательно сохраните изменения в редакторе функций, нажав на над списком функций, иначе изменения в проект не попадут!
Возвращаемся в раздел Front. Переоткрываем проект из Менеджера проектов, соглашаемся на предложение сохранить проект. Отключаем клавишей Shift режим редактирования в Области редактирования. Нажимаем кнопку Button_0 и видим следующее:

Компоненты, составляющие карточку, созданы динамически, то есть в процессе работы приложения. Они позиционированы друг относительно друга привязками и относительно root координатами, имеют свою заливку, размер, цвет и начертание шрифта, у них есть тень. К нажатию кнопки присоединена функция-обработчик. Карточку можно таскать по странице (всё-таки это канбан).
Теперь опишем смысл наших действий.
В строке let pan = new Panel() происходит объявление новой локальной переменной pan, которой приравнивается новый создаваемый экземпляр класса компонента Panel. Термин локальная переменная означает, что за пределами функции on_Button_0_paneclick она видна не будет. Если мы желаем и далее каким-то образом обращаться к вновь созданной карточке, нужно позаботиться о доступе к ней (в sFF основной метод - доступ к компоненту через его имя функцией вызова $_GC(имя).comp, но есть и другие способы). Далее следуют строки прямого присвоения значений свойствам компонента.
Строка pan.Panel.parentPane=$GC("root").pane предписывает компоненту pan стать дочерним компонентом root. Если изменить root на иной компонент, тот станет родителем pan со всеми вытекающими последствиями. Поскольку свойство parentPane принадлежит встроенному классу Panel, то его адресация записывается как компонент.встроенныйКласс.свойство. Поскольку имя компонента - строковое значение свойства Panel.name, то в функции вызова $_GC() оно записано в кавычках. При этом, после вызова есть уточнение pane (HTML-элемент div входящий в Panel и предназначенный для позиционирования), указывающее, что мы обращаемся к одному из нескольких возвращаемых функцией значений (name, comp, pane, instance, id). Если бы мы обратились к comp, то получили бы доступ к компоненту.
Далее следуют назначения свойств с Panel.x по Panel.height, отвечающих за геометрические размеры и положение компонента. sFF знает что Panel.width измеряется в пикселях а не в процентах потому, что значение свойства Panel.widthMeasure в конструкторе класса Panel установлено по умолчанию в px.
Строки pan.BgColor.default="lemonchiffon" и pan.ShadowColor.default="slategray" устанавливают соответственно цвета заднего фона и тени. Геометрия тени именно такая, потому что она также по умолчанию установлена в конструкторе класса.
Строка pan.Panel.draggable=true устанавливает доступность компонента для операций drag-and-drop. То есть теперь карточку можно тащить по экрану.
Далее мы создаём компонент Label и сохраняем его в локальную переменную head. Родительским элементом для неё станет pan.pane, то есть head теперь будут тащить вместе с pan (точнее, тащить будут pan вместе со всеми его дочерними компонентами).
Далее следуют назначения свойств включенного класса Anch. Сразу оговорюсь, что Anch - это агрегат. Он создан в классе Panel, но не в виде объекта, а в виде класса, ради соблюдения трёхуровневой адресации, чтобы в будущем обращаться к его свойствам не как head.Panel.Anch.left, а head.Anch.left (в случае pan - pan.Anch.left), что, согласитесь, удобнее. Привязка (Anch.left) принимает строчное значение вида компонент.линия, что удобнее при создании экземпляров классов (Если мы хотим сбросить привязку, назначаем свойству Anch.left значение null). Строка head.Anch.leftMargin=5 задает отступ в пикселях (Anch.leftMarginMeasure по умолчанию px) между границами объектов.
Строка head.Label.horzAlign="center" задаёт горизонтальное выравнивание текста внутри head. Выпадающий список свойства Label.horzAlign - массив строк, гарантированно обрабатываемый акцессором set (сеттером). Теоретически, свойству можно назначить любое значение, но это будет бессмысленно, поскольку сеттер не сможет его корректно обработать.
Строкой let but=new Button() мы создаём новый Button и назначаем его локальной переменной but.
В строке but.Signal.click=`alert("Ok from ${pan.Panel.name}");` мы динамически создаём анонимную функцию, вызываемую по событию Signal.click. Дабы избежать развлечений с двойными и одинарными кавычками и их экранированием при написанием тела функции внутри строки, воспользуемся обратными кавычками из синтаксиса ES6, куда заодно подставим значение pan.Panel.name, пригодное для обращения вне функции создания.
Итак, карточка динамически создана, она имеет какую-то структуру, некий внешний вид, может перетаскиваться и, отслеживая события, выполнять функции. Задача выполнена?
Да.
Но...
Замечание первое.
Прямое поочерёдное присваивание свойств происходит относительно медленно. При динамическом создании одного компонента это практически незаметно. Но если компонентов требуется несколько десятков, их создание может затянуться на секунды. По нашим замерам, использование прямого присваивания компонентам их значений медленнее ориентировочно раз в 12-14 использования карт входных параметров.
Начиная с ES6 существует стандартный встроенный объект (класс) Map (карта) - коллекция ключ/значение, но имеющий методы управления ключами и значениями. В принципе, Map чем-то напоминает Array (массив), но ключами массива должны быть числа, а ключами карты - любые уникальные объекты. Более всего класс Map похож на ассоциативный массив (в JS таковых не существует). Map обслуживает приблизительно те же потребности, что и массив, но операции с ним проходят на более высокой скорости.
sFF позволяет передавать требуемые значения свойств компонента непосредственно в виде входных параметров в конструктор класса компонента. Для этого и применяется карта входных параметров. Мы просто запишем в неё, соблюдая некоторые правила, те же самые значения тех же самых свойств, которые применили в первом варианте записи команд создания компонентов. При этом, в первом варианте будет осуществлено столько вызовов метода Panel.autoPos() производящего позиционирование компонента, сколько раз назначались родители, координаты, размеры, привязки и отступы привязок (в нашем примере это произошло 16 раз), а при использовании карты входных параметров - только один большой $_GC().Anch.totalAutoPos() для всех сразу по завершению создания всех компонентов.
Чтобы передать параметры в конструктор класса, мы должны видоизменить код:
Результат тот же, скорость выше.
Единственное коренное отличие - после создания всех динамических компонентов при помощи этого способа, необходимо выполнить функцию $_GC().Anch.totalAutoPos(), запускающую механизм позиционирования всех компонентов на странице приложения.
Замечание второе.
Мы получили в странице приложения три новых динамически созданных компонента, привязанных друг к другу. Но из трёх динамически созданных объектов не получается одна динамически созданная карточка. У нас есть внешний вид, но у нас нет внутренних свойств карточки, нет её внутренних методов, отсутствует логика функционирования. У нас есть только три динамически созданных компонента. Чтобы они стали карточкой, необходимо написать класс карточки, а потом создать его экземпляр с помощью директивы new.
Last updated