You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lo...@apache.org on 2021/06/03 13:54:21 UTC

[myfaces-tobago] branch master updated: fix: opening popup via AJAX

This is an automated email from the ASF dual-hosted git repository.

lofwyr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git


The following commit(s) were added to refs/heads/master by this push:
     new 0b2bd5e  fix: opening popup via AJAX
0b2bd5e is described below

commit 0b2bd5eff1aa456b29266cadb3f9318807f97236
Author: Udo Schnurpfeil <ud...@irian.eu>
AuthorDate: Thu Jun 3 15:33:02 2021 +0200

    fix: opening popup via AJAX
    
    issues: TOBAGO-2083, TOBAGO-1633
---
 .../tobago/internal/renderkit/Collapse.java        |  16 +--
 .../renderer/TobagoClientBehaviorRenderer.java     |   2 +-
 .../myfaces/tobago/internal/util/JsonUtils.java    |   2 +-
 .../myfaces/tobago/renderkit/RendererBase.java     |   2 +-
 .../tobago/renderkit/html/CustomAttributes.java    |   2 +-
 .../tobago/internal/renderkit/CommandUnitTest.java |   4 +-
 .../tobago/internal/util/JsonUtilsUnitTest.java    |   4 +-
 .../content/20-component/060-popup/Popup.xhtml     |  37 ++++-
 .../tobago-theme-standard/src/main/js/tobago.js    | 153 +++++++++++++++------
 .../src/main/js/tobago.js.map                      |   2 +-
 .../src/main/js/tobago.min.js                      |   4 +-
 .../src/main/js/tobago.min.js.map                  |   2 +-
 .../src/main/ts/tobago-all.ts                      |   2 +
 .../src/main/ts/tobago-behavior-mode.test.ts       |  30 ++++
 .../src/main/ts/tobago-behavior-mode.ts            |  23 ++++
 .../main/ts/tobago-collapsible-operation.test.ts   |  30 ++++
 .../src/main/ts/tobago-collapsible-operation.ts    |  23 ++++
 .../src/main/ts/tobago-command.ts                  |  72 ++++++----
 .../src/main/ts/tobago-popup.ts                    |  49 +++++--
 19 files changed, 354 insertions(+), 105 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/Collapse.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/Collapse.java
index bbf8119..02bcd0f 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/Collapse.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/Collapse.java
@@ -21,23 +21,23 @@ package org.apache.myfaces.tobago.internal.renderkit;
 
 public class Collapse {
 
-  private Action action;
-  private String forId;
+  private final Operation operation;
+  private final String forId;
 
-  public Collapse(final Action action, final String forId) {
-    this.action = action;
+  public Collapse(final Operation operation, final String forId) {
+    this.operation = operation;
     this.forId = forId;
   }
 
-  public Action getAction() {
-    return action;
+  public Operation getOperation() {
+    return operation;
   }
 
   public String getFor() {
     return forId;
   }
 
-  public enum Action {
-    show, hide
+  public enum Operation {
+    none, show, hide, toggle
   }
 }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java
index e97818c..f6bb460 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java
@@ -226,7 +226,7 @@ public class TobagoClientBehaviorRenderer extends javax.faces.render.ClientBehav
         value = operation.getFor();
       }
       final String forId = ComponentUtils.evaluateClientId(facesContext, component, value);
-      collapse = new Collapse(Collapse.Action.valueOf(operation.getName()), forId);
+      collapse = new Collapse(Collapse.Operation.valueOf(operation.getName()), forId);
     }
 
     //// TBD: is this nice?
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java
index 17bf813..7d1bebb 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java
@@ -224,7 +224,7 @@ public class JsonUtils {
     builder.append("\":{");
     final int initialLength = builder.length();
 
-    final Collapse.Action action = collapse.getAction();
+    final Collapse.Operation action = collapse.getOperation();
     if (action != null) {
       encode(builder, "transition", action.name());
     }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/RendererBase.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/RendererBase.java
index 929812e..ec69960 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/RendererBase.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/RendererBase.java
@@ -268,7 +268,7 @@ public abstract class RendererBase<T extends UIComponent> extends Renderer {
         command.getTransition() != null && !command.getTransition());
     final Collapse collapse = command.getCollapse();
     if (collapse != null) {
-      writer.writeAttribute(CustomAttributes.COLLAPSE_ACTION, collapse.getAction().name(), false);
+      writer.writeAttribute(CustomAttributes.COLLAPSE_OPERATION, collapse.getOperation().name(), false);
       writer.writeAttribute(CustomAttributes.COLLAPSE_TARGET, collapse.getFor(), false);
     }
     writer.writeAttribute(CustomAttributes.DELAY, command.getDelay());
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
index 8111546..25aac26 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
@@ -21,7 +21,7 @@ package org.apache.myfaces.tobago.renderkit.html;
 
 public enum CustomAttributes implements MarkupLanguageAttributes {
 
-  COLLAPSE_ACTION("collapse-action"),
+  COLLAPSE_OPERATION("collapse-operation"),
   COLLAPSE_TARGET("collapse-target"),
   CONFIRMATION("confirmation"),
   CLIENT_ID("client-id"),
diff --git a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/CommandUnitTest.java b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/CommandUnitTest.java
index 3a4b06d..f97ce90 100644
--- a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/CommandUnitTest.java
+++ b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/CommandUnitTest.java
@@ -30,11 +30,11 @@ public class CommandUnitTest {
 
     final Command a = new Command(
         "a client id", "a field id", null, null, "a execute", null, "a conf", null,
-        new Collapse(Collapse.Action.show, "a collapse"), false);
+        new Collapse(Collapse.Operation.show, "a collapse"), false);
 
     final Command b = new Command(
     "b client id", "b field id", null, null, "b execute", null, "b conf", null,
-        new Collapse(Collapse.Action.show, "b collapse"), false);
+        new Collapse(Collapse.Operation.show, "b collapse"), false);
 
     a.merge(b);
 
diff --git a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java
index 0591696..abf3e0e 100644
--- a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java
+++ b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java
@@ -101,7 +101,7 @@ public class JsonUtilsUnitTest extends AbstractTobagoTestBase {
         "_blank",
         StringUtils.join(Arrays.asList("id1", "id2"), ' '),
         StringUtils.join(Arrays.asList("id1", "id2"), ' '),
-        "Really?", 1000, new Collapse(Collapse.Action.show, "myId"), true));
+        "Really?", 1000, new Collapse(Collapse.Operation.show, "myId"), true));
     final String expected = (
         "{"
             + "'click':{"
@@ -195,7 +195,7 @@ public class JsonUtilsUnitTest extends AbstractTobagoTestBase {
         ClientBehaviors.blur,
         new Command(
             "doit", null, false, "field", "execute", "render", "Do \"you\" want?", 100,
-            new Collapse(Collapse.Action.hide, "box"), false));
+            new Collapse(Collapse.Operation.hide, "box"), false));
 
     final String expected
         = ("{'blur':"
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/060-popup/Popup.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/060-popup/Popup.xhtml
index 98a9dcf..ac6a37e 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/060-popup/Popup.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/060-popup/Popup.xhtml
@@ -110,7 +110,7 @@
           </tc:button>
           <tc:button id="submitClose2" label="Submit &amp; Close">
             <tc:operation name="hide" for="clientPopup"/>
-            <f:ajax execute="in2" render="in2 :::out clientPopupMessages"/>
+            <f:ajax execute="in2" render="in2 :::out clientPopupMessages clientPopup :page:messages"/>
           </tc:button>
           <tc:button id="cancel2" label="Cancel" omit="true">
             <tc:operation name="hide" for="clientPopup"/>
@@ -151,18 +151,49 @@
   </tc:section>
 
   <tc:section label="Plain Popup">
+<!--    todo-->
+    <tc:badge markup="warning">!</tc:badge> missing content
+  </tc:section>
+
+  <tc:section label="Popup opend after AJAX call">
+    <tc:button label="Open Popup" immediate="true" id="ajaxButton">
+      <tc:operation name="show" for="ajaxPopup"/>
+      <f:ajax render="@this ajaxPopup" execute="@this ajaxPopup"/>
+    </tc:button>
 
+    <tc:popup id="ajaxPopup" collapsedMode="absent" markup="small">
+      <tc:box label="Popup">
+        Simple Popup loaded by AJAX.
+        <tc:button label="Close" omit="true">
+          <tc:operation name="hide" for="ajaxPopup"/>
+        </tc:button>
+      </tc:box>
+    </tc:popup>
   </tc:section>
 
   <tc:section label="Refresh Content">
     <p>The content of the popup will be refreshed with AJAX after opening the popup. The refresh method waits two
       seconds before execution.</p>
-    <tc:button label="Open Popup" immediate="true">
+
+    <!--    todo-->
+    <p>
+    <tc:badge markup="warning">tbd (to be defined)!</tc:badge> in this example the "show" should be
+    called directly, and not after the ajax call is complete. On the other and, to avoid flickering,
+    the popup should be shown after the ajax call.
+    (<tc:link link="https://issues.apache.org/jira/browse/TOBAGO-2083" label="Jira TOBAGO-2083" image="bi-box-arrow-up-right"/>)
+    </p>
+
+    <tc:button label="Open Popup (to dicuss)" immediate="true">
       <tc:operation name="show" for="refreshPopup"/>
       <f:ajax render="refreshPopup:refreshPopupContent" listener="#{popupController.refreshContent}"/>
     </tc:button>
 
-    <tc:popup id="refreshPopup" collapsedMode="hidden" markup="small">
+    <tc:button label="Open Popup (to alternative)" immediate="true">
+      <tc:operation name="show" for="refreshPopup"/>
+      <f:ajax render="@this refreshPopup" execute="@this refreshPopup" listener="#{popupController.refreshContent}"/>
+    </tc:button>
+
+    <tc:popup id="refreshPopup" collapsedMode="absent" markup="small">
       <tc:box id="refreshPopupContent" label="Current Time">
         <tc:out label="Current Time" value="#{popupController.time}"/>
         <tc:button label="Close" omit="true">
diff --git a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
index 545ff04..04896f3 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
@@ -273,6 +273,54 @@
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
+  var BehaviorMode;
+  (function (BehaviorMode) {
+      BehaviorMode[BehaviorMode["none"] = 0] = "none";
+      BehaviorMode[BehaviorMode["client"] = 1] = "client";
+      BehaviorMode[BehaviorMode["ajax"] = 2] = "ajax";
+      BehaviorMode[BehaviorMode["full"] = 3] = "full";
+  })(BehaviorMode || (BehaviorMode = {}));
+
+  /*
+   * Licensed to the Apache Software Foundation (ASF) under one or more
+   * contributor license agreements.  See the NOTICE file distributed with
+   * this work for additional information regarding copyright ownership.
+   * The ASF licenses this file to You under the Apache License, Version 2.0
+   * (the "License"); you may not use this file except in compliance with
+   * the License.  You may obtain a copy of the License at
+   *
+   *      http://www.apache.org/licenses/LICENSE-2.0
+   *
+   * Unless required by applicable law or agreed to in writing, software
+   * distributed under the License is distributed on an "AS IS" BASIS,
+   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   * See the License for the specific language governing permissions and
+   * limitations under the License.
+   */
+  var CollapseOperation;
+  (function (CollapseOperation) {
+      CollapseOperation[CollapseOperation["none"] = 0] = "none";
+      CollapseOperation[CollapseOperation["show"] = 1] = "show";
+      CollapseOperation[CollapseOperation["hide"] = 2] = "hide";
+      CollapseOperation[CollapseOperation["toggle"] = 3] = "toggle";
+  })(CollapseOperation || (CollapseOperation = {}));
+
+  /*
+   * Licensed to the Apache Software Foundation (ASF) under one or more
+   * contributor license agreements.  See the NOTICE file distributed with
+   * this work for additional information regarding copyright ownership.
+   * The ASF licenses this file to You under the Apache License, Version 2.0
+   * (the "License"); you may not use this file except in compliance with
+   * the License.  You may obtain a copy of the License at
+   *
+   *      http://www.apache.org/licenses/LICENSE-2.0
+   *
+   * Unless required by applicable law or agreed to in writing, software
+   * distributed under the License is distributed on an "AS IS" BASIS,
+   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   * See the License for the specific language governing permissions and
+   * limitations under the License.
+   */
   const TobagoDropdownEvent = {
       HIDE: "tobago.dropdown.hide",
       HIDDEN: "tobago.dropdown.hidden",
@@ -9712,7 +9760,6 @@
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
-  // import {Modal} from "bootstrap/dist/js/bootstrap.bundle";
   class Popup extends HTMLElement {
       constructor() {
           super();
@@ -9720,14 +9767,28 @@
       connectedCallback() {
           const options = {};
           this.modal = new Modal(this, options);
+          if (!this.collapsed) {
+              this.show();
+          }
+      }
+      disconnectedCallback() {
+          this.hide();
+          this.modal.dispose();
       }
-      show() {
+      show(behaviorMode) {
           console.log("show");
-          this.modal.show();
+          if (behaviorMode == null || behaviorMode == BehaviorMode.client) {
+              this.modal.show();
+          }
       }
-      hide() {
+      hide(behaviorMode) {
           console.log("hide");
-          this.modal.hide();
+          if (behaviorMode == null || behaviorMode == BehaviorMode.client) {
+              this.modal.hide();
+          }
+      }
+      get collapsed() {
+          return JSON.parse(Collapse.findHidden(this).value);
       }
   }
   document.addEventListener("tobago.init", function (event) {
@@ -9741,22 +9802,22 @@
           return rootNode.getElementById(element.id + "::collapse");
       }
   }
-  Collapse.execute = function (action, target) {
+  Collapse.execute = function (operation, target, behaviorMode) {
       const hidden = Collapse.findHidden(target);
       let newCollapsed;
-      switch (action) {
-          case "hide":
+      switch (operation) {
+          case CollapseOperation.hide:
               newCollapsed = true;
               break;
-          case "show":
+          case CollapseOperation.show:
               newCollapsed = false;
               break;
           default:
-              console.error("unknown action: '" + action + "'");
+              console.error("unknown operation: '" + operation + "'");
       }
       if (newCollapsed) {
           if (target instanceof Popup) {
-              target.hide();
+              target.hide(behaviorMode);
           }
           else {
               target.classList.add("tobago-collapsed");
@@ -9764,7 +9825,7 @@
       }
       else {
           if (target instanceof Popup) {
-              target.show();
+              target.show(behaviorMode);
           }
           else {
               target.classList.remove("tobago-collapsed");
@@ -9815,40 +9876,52 @@
           }
       }
       callback(event) {
-          if (this.collapseAction && this.collapseTarget) {
+          if (this.collapseOperation && this.collapseTarget) {
               const rootNode = this.getRootNode();
-              Collapse.execute(this.collapseAction, rootNode.getElementById(this.collapseTarget));
-          }
-          if (this.execute || this.render) { // this means: AJAX case?
-              if (this.render) {
-                  // prepare overlay for all by AJAX reloaded elements
-                  const partialIds = this.render.split(" ");
-                  for (let i = 0; i < partialIds.length; i++) {
-                      const partialElement = document.getElementById(partialIds[i]);
-                      if (partialElement) {
-                          new Overlay(partialElement, true);
-                      }
-                      else {
-                          console.warn("No element found by id='%s' for overlay!", partialIds[i]);
+              Collapse.execute(this.collapseOperation, rootNode.getElementById(this.collapseTarget), this.mode);
+          }
+          switch (this.mode) {
+              case BehaviorMode.ajax:
+                  if (this.render) {
+                      // prepare overlay for all by AJAX reloaded elements
+                      const partialIds = this.render.split(" ");
+                      for (let i = 0; i < partialIds.length; i++) {
+                          const partialElement = document.getElementById(partialIds[i]);
+                          if (partialElement) {
+                              new Overlay(partialElement, true);
+                          }
+                          else {
+                              console.warn("No element found by id='%s' for overlay!", partialIds[i]);
+                          }
                       }
                   }
-              }
-              jsf.ajax.request(this.actionElement, event, {
-                  "javax.faces.behavior.event": this.event,
-                  execute: this.execute,
-                  render: this.render
-              });
-          }
-          else {
-              if (!this.omit) {
+                  jsf.ajax.request(this.actionElement, event, {
+                      "javax.faces.behavior.event": this.event,
+                      execute: this.execute,
+                      render: this.render
+                  });
+                  break;
+              case BehaviorMode.full:
                   setTimeout(this.submit.bind(this), this.delay);
-              }
+                  break;
+              // nothing to do
           }
       }
       submit() {
           const id = this.fieldId != null ? this.fieldId : this.clientId;
           CommandHelper.submitAction(this, id, this.decoupled, this.target);
       }
+      get mode() {
+          if (this.render || this.execute) {
+              return BehaviorMode.ajax;
+          }
+          else if (!this.omit) {
+              return BehaviorMode.full;
+          }
+          else {
+              return BehaviorMode.client;
+          }
+      }
       get event() {
           return this.getAttribute("event");
       }
@@ -9908,11 +9981,11 @@
       set confirmation(confirmation) {
           this.setAttribute("confirmation", confirmation);
       }
-      get collapseAction() {
-          return this.getAttribute("collapse-action");
+      get collapseOperation() {
+          return CollapseOperation[this.getAttribute("collapse-operation")];
       }
-      set collapseAction(collapseAction) {
-          this.setAttribute("collapse-action", collapseAction);
+      set collapseOperation(collapseOperation) {
+          this.setAttribute("collapse-operation", CollapseOperation[collapseOperation]);
       }
       get collapseTarget() {
           return this.getAttribute("collapse-target");
diff --git a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
index 775c7c2..c8081fa 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
@@ -1 +1 @@
-{"version":3,"file":"tobago.js","sources":["../ts/tobago-listener.ts","../ts/tobago-utils.ts","../ts/tobago-bar.ts","../ts/tobago-dropdown.ts","../../../../node_modules/vanillajs-datepicker/js/lib/utils.js","../../../../node_modules/vanillajs-datepicker/js/lib/date.js","../../../../node_modules/vanillajs-datepicker/js/lib/date-format.js","../../../../node_modules/vanillajs-datepicker/js/lib/event.js","../../../../node_modules/vanillajs-datepicker/js/i18n/base-locales.js","../../../../nod [...]
\ No newline at end of file
+{"version":3,"file":"tobago.js","sources":["../ts/tobago-listener.ts","../ts/tobago-utils.ts","../ts/tobago-bar.ts","../ts/tobago-behavior-mode.ts","../ts/tobago-collapsible-operation.ts","../ts/tobago-dropdown.ts","../../../../node_modules/vanillajs-datepicker/js/lib/utils.js","../../../../node_modules/vanillajs-datepicker/js/lib/date.js","../../../../node_modules/vanillajs-datepicker/js/lib/date-format.js","../../../../node_modules/vanillajs-datepicker/js/lib/event.js","../../../../nod [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-standard/src/main/js/tobago.min.js b/tobago-theme/tobago-theme-standard/src/main/js/tobago.min.js
index 93bea92..95d8d5d 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.min.js
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.min.js
@@ -1,8 +1,8 @@
-!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";var e,t;!function(e){e[e.DOCUMENT_READY=0]="DOCUMENT_READY",e[e.WINDOW_LOAD=1]="WINDOW_LOAD",e[e.BEFORE_SUBMIT=2]="BEFORE_SUBMIT",e[e.AFTER_UPDATE=3]="AFTER_UPDATE",e[e.BEFORE_UNLOAD=4]="BEFORE_UNLOAD",e[e.BEFORE_EXIT=5]="BEFORE_EXIT"}(e||(e={})),function(e){e[e.EARLIER=0]="EARLIER",e[e.EARLY=1]="EARLY",e[e.NORMAL=2]="NORMAL",e[e.LATE=3]="LATE",e[e.LATER=4]="LATER"}(t||(t={}));class s{constructor() [...]
+!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";var e,t,s,i;!function(e){e[e.DOCUMENT_READY=0]="DOCUMENT_READY",e[e.WINDOW_LOAD=1]="WINDOW_LOAD",e[e.BEFORE_SUBMIT=2]="BEFORE_SUBMIT",e[e.AFTER_UPDATE=3]="AFTER_UPDATE",e[e.BEFORE_UNLOAD=4]="BEFORE_UNLOAD",e[e.BEFORE_EXIT=5]="BEFORE_EXIT"}(e||(e={})),function(e){e[e.EARLIER=0]="EARLIER",e[e.EARLY=1]="EARLY",e[e.NORMAL=2]="NORMAL",e[e.LATE=3]="LATE",e[e.LATER=4]="LATER"}(t||(t={}));class n{construct [...]
 /*!
     * Bootstrap v5.0.1 (https://getbootstrap.com/)
     * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
     * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
     */
-const Es={find:(e,t=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(t,e)),findOne:(e,t=document.documentElement)=>Element.prototype.querySelector.call(t,e),children:(e,t)=>[].concat(...e.children).filter((e=>e.matches(t))),parents(e,t){const s=[];let i=e.parentNode;for(;i&&i.nodeType===Node.ELEMENT_NODE&&3!==i.nodeType;)i.matches(t)&&s.push(i),i=i.parentNode;return s},prev(e,t){let s=e.previousElementSibling;for(;s;){if(s.matches(t))return[s];s=s.previousE [...]
+const Ls={find:(e,t=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(t,e)),findOne:(e,t=document.documentElement)=>Element.prototype.querySelector.call(t,e),children:(e,t)=>[].concat(...e.children).filter((e=>e.matches(t))),parents(e,t){const s=[];let i=e.parentNode;for(;i&&i.nodeType===Node.ELEMENT_NODE&&3!==i.nodeType;)i.matches(t)&&s.push(i),i=i.parentNode;return s},prev(e,t){let s=e.previousElementSibling;for(;s;){if(s.matches(t))return[s];s=s.previousE [...]
 //# sourceMappingURL=tobago.min.js.map
diff --git a/tobago-theme/tobago-theme-standard/src/main/js/tobago.min.js.map b/tobago-theme/tobago-theme-standard/src/main/js/tobago.min.js.map
index 5db1dfd..6a82f6f 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.min.js.map
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"tobago.min.js","sources":["../ts/tobago-listener.ts","../ts/tobago-utils.ts","../ts/tobago-bar.ts","../ts/tobago-dropdown.ts","../../../../node_modules/vanillajs-datepicker/js/lib/utils.js","../../../../node_modules/vanillajs-datepicker/js/lib/date.js","../../../../node_modules/vanillajs-datepicker/js/lib/date-format.js","../../../../node_modules/vanillajs-datepicker/js/lib/event.js","../../../../node_modules/vanillajs-datepicker/js/i18n/base-locales.js","../../../.. [...]
\ No newline at end of file
+{"version":3,"file":"tobago.min.js","sources":["../ts/tobago-listener.ts","../ts/tobago-behavior-mode.ts","../ts/tobago-collapsible-operation.ts","../ts/tobago-utils.ts","../ts/tobago-bar.ts","../ts/tobago-dropdown.ts","../../../../node_modules/vanillajs-datepicker/js/lib/utils.js","../../../../node_modules/vanillajs-datepicker/js/lib/date.js","../../../../node_modules/vanillajs-datepicker/js/lib/date-format.js","../../../../node_modules/vanillajs-datepicker/js/lib/event.js","../../../.. [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-all.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-all.ts
index 77241a8..af29364 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-all.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-all.ts
@@ -17,6 +17,8 @@
 
 import "./tobago-listener";
 import "./tobago-bar";
+import "./tobago-behavior-mode";
+import "./tobago-collapsible-operation";
 import "./tobago-dropdown";
 import "./tobago-date";
 import "./tobago-command";
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior-mode.test.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior-mode.test.ts
new file mode 100644
index 0000000..06fa0ad
--- /dev/null
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior-mode.test.ts
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {BehaviorMode} from "./tobago-behavior-mode";
+
+test("enum values to strings", () => {
+  expect(BehaviorMode[BehaviorMode.client]).toBe("client");
+  expect(BehaviorMode[BehaviorMode.ajax]).toBe("ajax");
+  expect(BehaviorMode[BehaviorMode.full]).toBe("full");
+});
+
+test("strings to enum values", () => {
+  expect(BehaviorMode["client"]).toBe(BehaviorMode.client);
+  expect(BehaviorMode["ajax"]).toBe(BehaviorMode.ajax);
+  expect(BehaviorMode["full"]).toBe(BehaviorMode.full);
+});
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior-mode.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior-mode.ts
new file mode 100644
index 0000000..18358d4
--- /dev/null
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior-mode.ts
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export enum BehaviorMode {
+  "none",
+  "client",
+  "ajax",
+  "full"
+}
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-collapsible-operation.test.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-collapsible-operation.test.ts
new file mode 100644
index 0000000..b1c6e11
--- /dev/null
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-collapsible-operation.test.ts
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {CollapseOperation} from "./tobago-collapsible-operation";
+
+test("enum values to strings", () => {
+  expect(CollapseOperation[CollapseOperation.show]).toBe("show");
+  expect(CollapseOperation[CollapseOperation.hide]).toBe("hide");
+  expect(CollapseOperation[CollapseOperation.toggle]).toBe("toggle");
+});
+
+test("strings to enum values", () => {
+  expect(CollapseOperation["show"]).toBe(CollapseOperation.show);
+  expect(CollapseOperation["hide"]).toBe(CollapseOperation.hide);
+  expect(CollapseOperation["toggle"]).toBe(CollapseOperation.toggle);
+});
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-collapsible-operation.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-collapsible-operation.ts
new file mode 100644
index 0000000..a5aaa04
--- /dev/null
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-collapsible-operation.ts
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export enum CollapseOperation {
+  none,
+  show,
+  hide,
+  toggle
+}
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-command.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-command.ts
index 02044b1..3a9a334 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-command.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-command.ts
@@ -19,6 +19,8 @@ import {Listener} from "./tobago-listener";
 import {Overlay} from "./tobago-overlay";
 import {Collapse} from "./tobago-popup";
 import {Page} from "./tobago-page";
+import {CollapseOperation} from "./tobago-collapsible-operation";
+import {BehaviorMode} from "./tobago-behavior-mode";
 
 class Behavior extends HTMLElement {
 
@@ -49,36 +51,40 @@ class Behavior extends HTMLElement {
 
   callback(event?: Event): void {
 
-    if (this.collapseAction && this.collapseTarget) {
+    if (this.collapseOperation && this.collapseTarget) {
       const rootNode = this.getRootNode() as ShadowRoot | Document;
-      Collapse.execute(this.collapseAction, rootNode.getElementById(this.collapseTarget));
+      Collapse.execute(this.collapseOperation, rootNode.getElementById(this.collapseTarget), this.mode);
     }
 
-    if (this.execute || this.render) { // this means: AJAX case?
-      if (this.render) {
-        // prepare overlay for all by AJAX reloaded elements
-        const partialIds = this.render.split(" ");
-        for (let i = 0; i < partialIds.length; i++) {
-          const partialElement = document.getElementById(partialIds[i]);
-          if (partialElement) {
-            new Overlay(partialElement, true);
-          } else {
-            console.warn("No element found by id='%s' for overlay!", partialIds[i]);
+    switch (this.mode) {
+      case BehaviorMode.ajax:
+        if (this.render) {
+          // prepare overlay for all by AJAX reloaded elements
+          const partialIds = this.render.split(" ");
+          for (let i = 0; i < partialIds.length; i++) {
+            const partialElement = document.getElementById(partialIds[i]);
+            if (partialElement) {
+              new Overlay(partialElement, true);
+            } else {
+              console.warn("No element found by id='%s' for overlay!", partialIds[i]);
+            }
           }
         }
-      }
-      jsf.ajax.request(
-          this.actionElement,
-          event,
-          {
-            "javax.faces.behavior.event": this.event,
-            execute: this.execute,
-            render: this.render
-          });
-    } else {
-      if (!this.omit) {
+        jsf.ajax.request(
+            this.actionElement,
+            event,
+            {
+              "javax.faces.behavior.event": this.event,
+              execute: this.execute,
+              render: this.render
+            });
+        break;
+      case BehaviorMode.full:
         setTimeout(this.submit.bind(this), this.delay);
-      }
+        break;
+      default:
+        // nothing to do
+
     }
   }
 
@@ -87,6 +93,16 @@ class Behavior extends HTMLElement {
     CommandHelper.submitAction(this, id, this.decoupled, this.target);
   }
 
+  get mode(): BehaviorMode {
+    if (this.render || this.execute) {
+      return BehaviorMode.ajax;
+    } else if (!this.omit) {
+      return BehaviorMode.full;
+    } else {
+      return BehaviorMode.client;
+    }
+  }
+
   get event(): string {
     return this.getAttribute("event");
   }
@@ -163,12 +179,12 @@ class Behavior extends HTMLElement {
     this.setAttribute("confirmation", confirmation);
   }
 
-  get collapseAction(): string {
-    return this.getAttribute("collapse-action");
+  get collapseOperation(): CollapseOperation {
+    return CollapseOperation[this.getAttribute("collapse-operation")];
   }
 
-  set collapseAction(collapseAction: string) {
-    this.setAttribute("collapse-action", collapseAction);
+  set collapseOperation(collapseOperation: CollapseOperation) {
+    this.setAttribute("collapse-operation", CollapseOperation[collapseOperation]);
   }
 
   get collapseTarget(): string {
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-popup.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-popup.ts
index bd3047d..f5aca6f 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-popup.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-popup.ts
@@ -18,12 +18,14 @@
 // import * as bootstrap from "bootstrap/dist/js/bootstrap.esm";
 // import "bootstrap/dist/js/bootstrap.esm";
 // import "bootstrap/dist/js/bootstrap";
-import {Modal} from "bootstrap";
 // import {Modal} from "bootstrap/dist/js/bootstrap.bundle";
+import {Modal} from "bootstrap";
+import {BehaviorMode} from "./tobago-behavior-mode";
+import {CollapseOperation} from "./tobago-collapsible-operation";
 
 export class Popup extends HTMLElement {
 
-  modal:any;
+  modal: Modal;
 
   constructor() {
     super();
@@ -31,18 +33,37 @@ export class Popup extends HTMLElement {
 
   connectedCallback(): void {
     const options = {};
-
     this.modal = new Modal(this, options);
+    if (!this.collapsed) {
+      this.show();
+    }
+  }
+
+  disconnectedCallback(): void {
+    this.hide();
+    this.modal.dispose();
   }
 
-  show():void {
+  show(behaviorMode?: BehaviorMode): void {
     console.log("show");
-    this.modal.show();
+    if (behaviorMode == null || behaviorMode == BehaviorMode.client) {
+      this.modal.show();
+    } else {
+      // otherwise the update from server will show the popup
+    }
   }
 
-  hide():void {
+  hide(behaviorMode?: BehaviorMode): void {
     console.log("hide");
-    this.modal.hide();
+    if (behaviorMode == null || behaviorMode == BehaviorMode.client) {
+      this.modal.hide();
+    } else {
+      // otherwise the update from server will hide the popup
+    }
+  }
+
+  get collapsed(): boolean {
+    return JSON.parse(Collapse.findHidden(this).value);
   }
 }
 
@@ -59,28 +80,28 @@ export class Collapse {
     return rootNode.getElementById(element.id + "::collapse") as HTMLInputElement;
   }
 
-  static execute = function (action: string, target: HTMLElement): void {
+  static execute = function (operation: CollapseOperation, target: HTMLElement, behaviorMode: BehaviorMode): void {
     const hidden = Collapse.findHidden(target);
     let newCollapsed;
-    switch (action) {
-      case "hide":
+    switch (operation) {
+      case CollapseOperation.hide:
         newCollapsed = true;
         break;
-      case "show":
+      case CollapseOperation.show:
         newCollapsed = false;
         break;
       default:
-        console.error("unknown action: '" + action + "'");
+        console.error("unknown operation: '" + operation + "'");
     }
     if (newCollapsed) {
       if (target instanceof Popup) {
-        target.hide();
+        target.hide(behaviorMode);
       } else {
         target.classList.add("tobago-collapsed");
       }
     } else {
       if (target instanceof Popup) {
-        target.show();
+        target.show(behaviorMode);
       } else {
         target.classList.remove("tobago-collapsed");
       }