Synchronizace UI se stavem na serveru - refreshEvent
Synchronizace UI se stavem na serveru - refreshEvent
Refesh eventy představují mechanizmus, který udržuje stav zobrazený v adminstraci konzistetní se stavem na serveru. Např. pokud uživatel vytvoří novinku v seznamu, pomocí událostí dojde k zobrazení přidané novinky i všem právě přihlášeným uživatelům, kteří pracují se stejným seznamem.
Ještě častěji je využito k řešení situace, kde z data gridu otevře uživatel editaci položky (nebo její vytvoření), která se otevírá do nového panelu Edee. Po uložení a uzavření editačního panelu jsou pak provedené změnu viditelné i zde.
Implementace probíhá pomocí polling requestů - periodicky je tedy prováděn dotaz na server, zda neexistují nové události pro zpracování. Jedná se o požadavky na adresu /adm/*/edeeui/userState/get, která vrací případné události jako JSON objekty.
Příklad události
1 {2"event":{3 "2":{4 "id":2,5 "event":"change",6 "type":"tree",7 "attr":{8 "pageLang":"cs",9 "pageId":1001,10 "parentId":111 },12 "username":null13 }14},15"process":{},16"message":{}17}
Událost má následující atributy:
- id - identifikátor události - integer sekvence nulovaná při restartu serveru
- event - identifikátor akce, případně operace (např. create, update, move, remove)
- type - typ entity (např. tag, tree, moduleTree)
- attr - dodatečné atributy - typicky id entity pro možnost cílení události
- username - případné jméno, pokud je událost cílena na vybraného uživatele
V Javě je událost reprezentovaná objektem com.fg.edee.integration.cps.user.EdeeUiEvent.
Zkopírovat odkaz na sekcikonfigurace refresh eventy
Naslouchání na refresh event se specifikuje v metadatech komponenty, konkrétně v datasetu - metadata.dataset.refreshEvent.
Zkopírovat odkaz na sekcievent
Primárně se specifikuje název události - tag refreshEvent/event.
Hodnota se skládá z dvou klíčů události event + type (podle JSONU např. changetree) a nebo lze použít pouze type (podle JSONU např. tree). Na velikosti písmen nezáleží (POZOR, evidujeme problém kdy někdy se to bere v potaz), typicky se dodržuje camel case konvence.
Je možné uvést i více událostí oddělených čárkou, případně definovat více refreshEvent bloků
1 <metadata>2 <dataset mode="combine">3 <refreshEvent>4 <event>updateContentBlock, createContentBlock</event>5 <validAttributes>6 <linkedType>#@inheritedMetadataObjectEval[blockPlacementObjectType]#</linkedType>7 <linkedId>#@inheritedMetadataObjectEval[blockPlacementObjectId]#</linkedId>8 </validAttributes>9 </refreshEvent>10 <refreshEvent>11 <event>updateContentBlockRelation</event>12 <validAttributes>13 <sourceId>#@inheritedMetadataObjectEval[blockPlacementObjectId]#</sourceId>14 </validAttributes>15 </refreshEvent>16 </dataset>17</metadata>
Zkopírovat odkaz na sekcivalidAttributes
Událost lze omezit pomocí refreshEvent/validAttributes. Aktualizace komponenty pak proběhne pouze pokud atributy přijaté události a atributy vnořené v konfiguraci jsou totožné.
Využitelné např. v editoru entity, kde se mohou projevit změny jiného uživatele, ale žádoucí jsou pouze změny konrétního záznamu.
Zkopírovat odkaz na sekcipuSettings
Aktualizace komponenty je provedena pomocí partial update, tento požadavek lze pomocí refreshEvent/puSettings dále parametrizovat.
1 <metadata>2 <dataset>3 <refreshEvent>4 <event>createLocalization</event>5 <validAttributes>6 <sourceId>#widget[record].record.primaryKey!0#</sourceId>7 </validAttributes>8 <puSettings>9 <removeFormId>true</removeFormId>10 <removeFormWidgetData>true</removeFormWidgetData>11 <type>GET</type>12 </puSettings>13 </refreshEvent>14 </dataset>15</metadata>
Zkopírovat odkaz na sekcizkrácený zápis
Lze použít i zkrácený zápis pouze se jménem události
Je nutné si uvědomit, že bez specifikace atributů bude událost provádět aktualizaci všem aktuálně přihlášeným uživatelům ve všech místech s danou událostí.
1 <metadata>2 <dataset mode="combine">3 <refreshEvent>createItem</refreshEvent>4 <refreshEvent>removeItem</refreshEvent>5 <refreshEvent>updateItem</refreshEvent>6 </dataset>7</metadata>
Zkopírovat odkaz na sekcipodpora
Na přijaté události, reagují widgety ve stránce. Refresh eventy nepodporují automaticky všechny komponenty. Základem je přiřazený dekorátor, který dodává komponentám dynamické funkce.
Komponenty bez decorátoru lze pro refresh využít definicí základního dekorátoru:
1 <metadata>2 <dataset>3 <decorator>fg.ui.Component</decorator>4 </dataset>5</metadata>
Zkopírovat odkaz na sekcigenerování události na straně serveru
Pozor - ui refresh událost je na serveru něco jiného než událost modulu ze Spring framework i když jsou někdy spojené, tj. aplikační Spring událost vyvolá i UI událost.
Zkopírovat odkaz na sekcicommand
Pro generování ui události z commandu lze použít com.fg.edee.integration.web.ui.UiEventFiringCommand. Připravený command lze použít jako předka na úrovni java kódu, případně lze spojit pomocí agregačního commandu.
Příklad xml specifikace
1 <command type="aggregation">2 <command class="com.fg...MyCommand"/>3 <command class="com.fg.edee.integration.web.ui.UiEventFiringCommand" 4 uiEventName="update" uiEventType="myItem"5 />6</command>
Do verze 10.13.0 lze specifikovat i atributy události
Příklad s asynchronním zpracováním
1 <command eventName="execute" type="asyncExecutorCommand">2 <command class="com.fg...MyCommand"/>3 <command type="uiEventFiringCommand"4 uiEventName="update" uiEventType="test"5 attributeName="testName" attributeValue="1"6 />7</command>
Příklad java extenze
1 public class MyCommand extends UiEventFiringCommand {2 3 public MyCommand() {4 super("update", "myItem");5 }6 7 @Override8 public Resolution performCommand(RequestContext context, Map<String, Object> parameters) throws CommandExecutionException {9 // command logic10 11 // publish ui event12 processUiEvent(context);13 14 // or publish ui event with custom attrs15 //publishUiEvent(createEdeeUiEvent(context,ImmutableMap.<String, Serializable>of("id", itemId)));16 17 // or since 9.0 with single method call18 //publishUiEvent(context,ImmutableMap.of("sourceId", sourceIdValue,"blockId",blockIdValue));19 20 return resolution;21 }22 23}
Zkopírovat odkaz na sekciuserService
Událost lze publikovat i přímo z vlastní servisní vrstvy pomocí com.fg.edee.integration.service.UserService.
1 public class MyService {2 3 @Autowired4 UserService userService;5 6 protected void publishUiEvent(Tag tag, CrudOperation operation) {7 Map<String,Serializable> attrs = new HashMap<>();8 attrs.put("id",tag.getId());9 attrs.put("systemId", tag.getSystemId());10 userService.publishUiEvent(new EdeeUiEvent(operation.toString(), Tag.E_NAME, attrs));11 }12}
Případně lze definovat factory pro automatickou konverzi Spring událostí na UI události. Pak je nutné provést implementaci třídy com.fg.edee.integration.cps.user.EdeeUiEventFactory a zajistit registraci do UserService - v rámci edeeui modulu probíhá automaticky.
Ukázka registrace event factory v modulu
1 import com.fg.webapp.cps.v1.modules.spring.CpsSpringModuleAbstract;2 3public class MyModule extends CpsSpringModuleAbstract {4 5 @Override6 public void prepareStartModule() {7 super.prepareStartModule();8 UserService userService = getModuleSupport().getModulePublicBean(UserService.class);9 getModuleContext().getBeansOfType(EdeeUiEventFactory.class)10 .values()11 .forEach(userService::registerUiEventFactory);12 }13 14}
Ukázka reálné implementace factory.
1 public class ContentBlockActiveVersionChangedUiEventFactory implements EdeeUiEventFactory<ContentBlockActiveVersionChangedEvent> {2 3 @Override4 public EdeeUiEvent create(ContentBlockActiveVersionChangedEvent source) {5 // create required attrs for possible `validAttributes` filtration6 final Map<String, Serializable> attr = ImmutableMap.of("blockId", source.getBlockId());7 // instantiate event - operation name, entity type, attributes map 8 return new EdeeUiEvent("activeVersionChange", "block", attr);9 }10 11 @Override12 public Class[] getSourceClass() {13 // specify listening events including possible extended implementations14 return new Class[]{15 ContentBlockActiveVersionChangedEvent.class16 };17 }18}