Edee.one · Dokumentace

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
{"event":{  "2":{    "id":2,    "event":"change",    "type":"tree",    "attr":{      "pageLang":"cs",      "pageId":1001,      "parentId":1    },    "username":null  }},"process":{},"message":{}}

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.

konfigurace refresh eventy

Naslouchání na refresh event se specifikuje v metadatech komponenty, konkrétně v datasetu - metadata.dataset.refreshEvent.

event

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
<metadata>    <dataset mode="combine">        <refreshEvent>            <event>updateContentBlock, createContentBlock</event>            <validAttributes>                <linkedType>#@inheritedMetadataObjectEval[blockPlacementObjectType]#</linkedType>                <linkedId>#@inheritedMetadataObjectEval[blockPlacementObjectId]#</linkedId>            </validAttributes>        </refreshEvent>        <refreshEvent>            <event>updateContentBlockRelation</event>            <validAttributes>                <sourceId>#@inheritedMetadataObjectEval[blockPlacementObjectId]#</sourceId>            </validAttributes>        </refreshEvent>    </dataset></metadata>

validAttributes

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.

puSettings

Aktualizace komponenty je provedena pomocí partial update, tento požadavek lze pomocí refreshEvent/puSettings dále parametrizovat.

xml
<metadata>    <dataset>        <refreshEvent>            <event>createLocalization</event>            <validAttributes>                <sourceId>#widget[record].record.primaryKey!0#</sourceId>            </validAttributes>            <puSettings>                <removeFormId>true</removeFormId>                <removeFormWidgetData>true</removeFormWidgetData>                <type>GET</type>            </puSettings>        </refreshEvent>    </dataset></metadata>

zkrá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
<metadata>    <dataset mode="combine">        <refreshEvent>createItem</refreshEvent>        <refreshEvent>removeItem</refreshEvent>        <refreshEvent>updateItem</refreshEvent>    </dataset></metadata>

podpora

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
<metadata>    <dataset>        <decorator>fg.ui.Component</decorator>    </dataset></metadata>

generová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.

command

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
<command type="aggregation">    <command class="com.fg...MyCommand"/>    <command class="com.fg.edee.integration.web.ui.UiEventFiringCommand"              uiEventName="update" uiEventType="myItem"    /></command>

Do verze 10.13.0 lze specifikovat i atributy události

Příklad s asynchronním zpracováním

xml
<command eventName="execute" type="asyncExecutorCommand">    <command class="com.fg...MyCommand"/>    <command type="uiEventFiringCommand"             uiEventName="update" uiEventType="test"             attributeName="testName" attributeValue="1"    /></command>

Příklad java extenze

java
public class MyCommand extends UiEventFiringCommand {
	public MyCommand() {		super("update", "myItem");	}
    @Override    public Resolution performCommand(RequestContext context, Map<String, Object> parameters) throws CommandExecutionException {        // command logic                // publish ui event        processUiEvent(context);            // or publish ui event with custom attrs        //publishUiEvent(createEdeeUiEvent(context,ImmutableMap.<String, Serializable>of("id", itemId)));                // or since 9.0 with single method call        //publishUiEvent(context,ImmutableMap.of("sourceId", sourceIdValue,"blockId",blockIdValue));
        return resolution;    }
}

userService

Událost lze publikovat i přímo z vlastní servisní vrstvy pomocí com.fg.edee.integration.service.UserService.

java
public class MyService {     @Autowired    UserService userService;
    protected void publishUiEvent(Tag tag, CrudOperation operation) {        Map<String,Serializable> attrs = new HashMap<>();        attrs.put("id",tag.getId());        attrs.put("systemId", tag.getSystemId());        userService.publishUiEvent(new EdeeUiEvent(operation.toString(), Tag.E_NAME, attrs));    }}

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
import com.fg.webapp.cps.v1.modules.spring.CpsSpringModuleAbstract;
public class MyModule extends CpsSpringModuleAbstract {        @Override    public void prepareStartModule() {        super.prepareStartModule();        UserService userService = getModuleSupport().getModulePublicBean(UserService.class);        getModuleContext().getBeansOfType(EdeeUiEventFactory.class)                .values()                .forEach(userService::registerUiEventFactory);    }    }

Ukázka reálné implementace factory.

java
public class ContentBlockActiveVersionChangedUiEventFactory implements EdeeUiEventFactory<ContentBlockActiveVersionChangedEvent> {
    @Override    public EdeeUiEvent create(ContentBlockActiveVersionChangedEvent source) {        // create required attrs for possible `validAttributes` filtration        final Map<String, Serializable> attr = ImmutableMap.of("blockId", source.getBlockId());        // instantiate event - operation name, entity type, attributes map         return new EdeeUiEvent("activeVersionChange", "block", attr);    }
    @Override    public Class[] getSourceClass() {        // specify listening events including possible extended implementations        return new Class[]{            ContentBlockActiveVersionChangedEvent.class        };    }}