Динамическое отображение контролов

Большая часть сервисов СБИС используют серверную вёрстку. То есть при открытии веб-страницы на клиент приходят готовая HTML-разметка и список зависимостей, которые необходимы для "оживления" страницы и отображения стилей. Благодаря этому вёрстка не "прыгает", потому что отображение страницы происходит после загрузки стилей, и сами процессы загрузки исходников и "оживления" страницы происходят быстрее, потому что они не прерывают друг друга: сначала загружаются все необходимые исходники, а потом начинается "оживление".

Список загружаемых исходников собирается путём обхода по дереву зависимостей, корнем которого является самый верхний контрол (тот, у которого в шаблоне описан контрол Controls/Application).

Бывают ситуации, когда в это дерево не попадают зависимости, которые необходимы для построения веб-страницы. Например, инициализируемый список модулей зависит от данных, которые придут с бизнес-логики. Если загружать такие зависимости "на лету" (при "оживлении" на клиенте), то процесс "оживления" будет идти значительно дольше из-за прерываний на загрузку. Если же грузить на клиенте все возможные зависимости, то получится лишний трафик.

Для решения задачи сбора зависимостей контролов, динамически появляющихся на странице, был разработан контрол Controls/Container/Async (далее по тексту Async). После построения на сервере, когда все запросы на бизнес-логику (далее по тексту "БЛ") отработают, и вёрстка полностью построится, становится известно какие модули будут необходимы для построения страницы на клиенте.

Компоненты, которые вставляются в шаблоне через Async, и все их зависимости (включая CSS) добавятся в список зависимостей, необходимых для "оживления" страницы на клиенте. То есть, на момент отображения вёрстки и "оживления" страницы все необходимые CSS и JS файлы уже будет загружены.

Для того чтобы Async подгрузил контролы, не указанные явно в зависимостях, необходимо передать название этого контрола в опцию templateName. Async работает как обычный компонент высшего порядка, то есть ему можно передавать контент, который вставляется в шаблоне Async'а через ws:partial), а templateOptions передаются как scope в этот ws:partial. Если content не указан, то Async в опцию template ws:partial'а (внутри своего шаблона) передает templateName.

Примеры

Простой пример использования контрола Async:

<Controls.Container.Async templateName="MyModule/Test">
   <ws:partial template="{{ content.resolvedTemplate }}" />
</Controls.Container.Async>

В опцию template тега ws:partial в контенте Async'а нужно передавать content.resolvedTemplate, а не строчку с именем модуля, иначе во время построения контента шаблонизатору необходимо будет еще раз по имени модуля находить сам модуль. В content.resolvedTemplate уже будет лежать необходимая функция.

Такое использование актуально, например, если контрол вставлен под условием:

<ws:if data="{{ someCondition }}">
   <Controls.Container.Async templateName="MyModule/Test">
      <ws:partial template="{{ content.resolvedTemplate }}" />
   </Controls.Container.Async>
</ws:if>

Таким образом, MyModule/Test попадет в зависимости в итоговой html-страничке только в том случае, если условие someCondition будет выполняться.

Также контрол Async можно использовать внутри тега <ws:for>, если, например, вы пробегаетесь по списку модулей, который приходит с БЛ:

<ws:for data="key, value in data">
   <Controls.Container.Async templateName="{{ value }}" templateOptions="{{ templateOptions }}"> 
      <div>
         <ws:partial template="{{ content.resolvedTemplate }}"></ws:partial>
      </div>
   </Controls.Container.Async>
</ws:for>

Пример использования с указанием content и без указания:

<!--Без указания контента-->
<Controls.Container.Async templateName="template1" templateOptions="{{ itemOptions }}">
</Controls.Container.Async>
 
<!--С указанием контента-->
<Controls.Container.Async templateName="template2">
   <ws:partial template="{{ content.resolvedTemplate }}" on:click="handler()"></ws:partial>
</Controls.Container.Async>

Использование контрола Controls/Container/Async с указанием контента целесообразно только для подписки на события контента Async. Все опции следует передавать в опцию templateOptions у контрола Async. При изменении опций контента необходимо отдавать в templateOptions новый объект. В случае передачи опций на ws:partial возникнет проблема при обновлении контента Async. Так как опции, переданные на partial, обновятся в первом же цикле синхронизации, а контент Async, если он еще не был загружен, обновится в одном из следующий циклов синхронизации. Таким образом возникнет ситуация, когда в старый контент будут переданы новые опции.

Внутри описания контента Async в опцию template нужно передавать content.resolvedTemplate. Эта опция передается в шаблоне Async в partial, в котором вставляется контент. Если в ws:partial в template передать константную строчку, то она попадет в дерево зависимостей, в зависимости шаблона, и всегда будет прилетать в зависимостях на клиент.

Еще пример использования:

<ws:if data="{{ item.type === 'Collage' }}">
   <Controls.Container.Async templateName="templates/Collage"/>
</ws:if>
<ws:if data="{{ item.type === 'Vote' }}">
   <Controls.Container.Async templateName="templates/Vote"/>
</ws:if>
<ws:if data="{{ item.type === 'Simple' }}">
   <Controls.Container.Async templateName="templates/Simple"/>
</ws:if>

Так может выглядеть itemTemplate для полиморфного списка новостей, написанный с использованием контрола Async.

При отключенной серверной верстке, участок интерфейса, который вставляется в шаблоне через Async подгрузится динамически. То есть исходники всех необходимых контролов, включая css, будут загружены в _beforeMount Async'а.