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

json
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ů

xml
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.

xml
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í.

xml
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:

xml
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

xml
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

xml
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

java
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.

java
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

java
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.

java
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}