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/17 06:49:20 UTC

[myfaces-tobago] branch master updated: refactor: Typescript submit impl

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 9c951fd  refactor: Typescript submit impl
9c951fd is described below

commit 9c951fd4de4667b69ca1e33c4ceb27c184843671
Author: Udo Schnurpfeil <ud...@irian.eu>
AuthorDate: Thu Jun 17 08:41:33 2021 +0200

    refactor: Typescript submit impl
    
    * removing unused code
    * refactor static calls to class methods
    
    issue: TOBAGO-1633
---
 .../30-concept/23-transition/Transition.xhtml      |    2 +-
 .../tobago-theme-standard/src/main/js/tobago.js    | 1710 ++++++++++----------
 .../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-command.ts                  |  185 +--
 .../src/main/ts/tobago-page.ts                     |   17 +-
 7 files changed, 875 insertions(+), 1047 deletions(-)

diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/30-concept/23-transition/Transition.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/30-concept/23-transition/Transition.xhtml
index 3c9e068..a0f8a9c 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/30-concept/23-transition/Transition.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/30-concept/23-transition/Transition.xhtml
@@ -38,7 +38,7 @@ action="\#{transitionController.sleep5sAndRedirect}"/>
 &lt;tc:link label="link with transition OFF" transition="false"
 action="\#{transitionController.sleep5sAndRedirect}"/></demo-highlight>
 
-    <tc:link label="link with transition ON" action="#{transitionController.sleep5sAndRedirect}"/>
+    <tc:link label="link with transition ON" action="#{transitionController.sleep5sAndRedirect}"/> (default)
     <br/>
     <tc:link label="link with transition OFF" transition="false" action="#{transitionController.sleep5sAndRedirect}"/>
   </tc:section>
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 4f22a9e..001683d 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
@@ -2913,6 +2913,339 @@
   Config.set("Tobago.waitOverlayDelay", 1000);
   Config.set("Ajax.waitOverlayDelay", 1000);
 
+  /*
+   * 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.
+   */
+  class Page extends HTMLElement {
+      constructor() {
+          super();
+          this.submitActive = false;
+      }
+      /**
+       * The Tobago root element
+       */
+      static page(element) {
+          const rootNode = element.getRootNode();
+          const pages = rootNode.querySelectorAll("tobago-page");
+          if (pages.length > 0) {
+              if (pages.length >= 2) {
+                  console.warn("Found more than one tobago-page element!");
+              }
+              return pages.item(0);
+          }
+          console.warn("Found no tobago page!");
+          return null;
+      }
+      /**
+       * "a:b" -> "a"
+       * "a:b:c" -> "a:b"
+       * "a" -> null
+       * null -> null
+       * "a:b::sub-component" -> "a"
+       * "a::sub-component:b" -> "a::sub-component" // should currently not happen in Tobago
+       *
+       * @param clientId The clientId of a component.
+       * @return The clientId of the naming container.
+       */
+      static getNamingContainerId(clientId) {
+          if (clientId == null || clientId.lastIndexOf(":") === -1) {
+              return null;
+          }
+          let id = clientId;
+          while (true) {
+              const sub = id.lastIndexOf("::");
+              if (sub == -1) {
+                  break;
+              }
+              if (sub + 1 == id.lastIndexOf(":")) {
+                  id = id.substring(0, sub);
+              }
+              else {
+                  break;
+              }
+          }
+          return id.substring(0, id.lastIndexOf(":"));
+      }
+      connectedCallback() {
+          this.registerAjaxListener();
+          this.form.addEventListener("submit", this.beforeSubmit.bind(this));
+          window.addEventListener("unload", this.onUnload.bind(this));
+          this.addEventListener("keypress", (event) => {
+              let code = event.which; // XXX deprecated
+              if (code === 0) {
+                  code = event.keyCode;
+              }
+              if (code === 13) {
+                  const target = event.target;
+                  if (target.tagName === "A" || target.tagName === "BUTTON") {
+                      return;
+                  }
+                  if (target.tagName === "TEXTAREA") {
+                      if (!event.metaKey && !event.ctrlKey) {
+                          return;
+                      }
+                  }
+                  const name = target.getAttribute("name");
+                  let id = name ? name : target.id;
+                  while (id != null) {
+                      const command = document.querySelector(`[data-tobago-default='${id}']`);
+                      if (command) {
+                          command.dispatchEvent(new MouseEvent("click"));
+                          break;
+                      }
+                      id = Page.getNamingContainerId(id);
+                  }
+                  return false;
+              }
+          });
+      }
+      beforeSubmit() {
+          this.submitActive = true;
+          if (this.transition) {
+              new Overlay(this);
+          }
+          this.transition = this.oldTransition;
+      }
+      /**
+       * Wrapper function to call application generated onunload function
+       */
+      onUnload() {
+          console.info("on unload");
+          if (Page.page(this).submitActive) {
+              if (this.transition) {
+                  new Overlay(this);
+              }
+              this.transition = this.oldTransition;
+          }
+      }
+      registerAjaxListener() {
+          jsf.ajax.addOnEvent(this.jsfResponse.bind(this));
+      }
+      jsfResponse(event) {
+          console.timeEnd("[tobago-jsf] jsf-ajax");
+          console.time("[tobago-jsf] jsf-ajax");
+          console.debug("[tobago-jsf] JSF event status: '%s'", event.status);
+          if (event.status === "success") {
+              event.responseXML.querySelectorAll("update").forEach(this.jsfResponseSuccess.bind(this));
+          }
+          else if (event.status === "complete") {
+              event.responseXML.querySelectorAll("update").forEach(this.jsfResponseComplete.bind(this));
+          }
+      }
+      jsfResponseSuccess(update) {
+          const id = update.id;
+          let rootNode = this.getRootNode();
+          // XXX in case of "this" is tobago-page (e.g. ajax exception handling) rootNode is not set correctly???
+          if (!rootNode.getElementById) {
+              rootNode = document;
+          }
+          console.debug("[tobago-jsf] Update after jsf.ajax success: %s", id);
+      }
+      jsfResponseComplete(update) {
+          const id = update.id;
+          if (JsfParameter.isJsfId(id)) {
+              console.debug("[tobago-jsf] Update after jsf.ajax complete: #", id);
+              Overlay.destroy(id);
+          }
+      }
+      get form() {
+          return this.querySelector("form");
+      }
+      get locale() {
+          let locale = this.getAttribute("locale");
+          if (!locale) {
+              locale = document.documentElement.lang;
+          }
+          return locale;
+      }
+  }
+  document.addEventListener("tobago.init", (event) => {
+      if (window.customElements.get("tobago-page") == null) {
+          window.customElements.define("tobago-page", Page);
+      }
+  });
+  class JsfParameter {
+      static isJsfId(id) {
+          switch (id) {
+              case JsfParameter.VIEW_STATE:
+              case JsfParameter.CLIENT_WINDOW:
+              case JsfParameter.VIEW_ROOT:
+              case JsfParameter.VIEW_HEAD:
+              case JsfParameter.VIEW_BODY:
+              case JsfParameter.RESOURCE:
+                  return false;
+              default:
+                  return true;
+          }
+      }
+      static isJsfBody(id) {
+          switch (id) {
+              case JsfParameter.VIEW_ROOT:
+              case JsfParameter.VIEW_BODY:
+                  return true;
+              default:
+                  return false;
+          }
+      }
+  }
+  JsfParameter.VIEW_STATE = "javax.faces.ViewState";
+  JsfParameter.CLIENT_WINDOW = "javax.faces.ClientWindow";
+  JsfParameter.VIEW_ROOT = "javax.faces.ViewRoot";
+  JsfParameter.VIEW_HEAD = "javax.faces.ViewHead";
+  JsfParameter.VIEW_BODY = "javax.faces.ViewBody";
+  JsfParameter.RESOURCE = "javax.faces.Resource";
+
+  /*
+   * 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.
+   */
+  class DatePicker extends HTMLElement {
+      constructor() {
+          super();
+      }
+      connectedCallback() {
+          if (this.type == "date") {
+              console.debug("check input type=date support", DatePicker.SUPPORTS_INPUT_TYPE_DATE);
+              if (!DatePicker.SUPPORTS_INPUT_TYPE_DATE) {
+                  this.setAttribute("type", "text");
+                  this.initVanillaDatePicker();
+              }
+          }
+      }
+      initVanillaDatePicker() {
+          var _a;
+          const field = this.field;
+          const locale = Page.page(this).locale;
+          const i18n = this.i18n;
+          i18n.titleFormat = "MM y"; // todo i18n
+          i18n.format = this.pattern;
+          Datepicker.locales[locale] = i18n;
+          const options = {
+              buttonClass: "btn",
+              orientation: "auto",
+              autohide: true,
+              language: locale,
+              todayBtn: this.todayButton,
+              todayBtnMode: 1,
+              minDate: this.min,
+              maxDate: this.max,
+              // todo readonly
+              // todo show week numbers
+          };
+          const datepicker = new Datepicker(field, options);
+          // XXX these listeners are needed as long as we have a solution for:
+          // XXX https://github.com/mymth/vanillajs-datepicker/issues/13
+          // XXX the 2nd point is missing the "normal" change event on the input element
+          field.addEventListener("keyup", (event) => {
+              // console.info("event -----> ", event.type);
+              if (event.metaKey || event.key.length > 1 && event.key !== "Backspace" && event.key !== "Delete") {
+                  return;
+              }
+              // back up user's input when user types printable character or backspace/delete
+              const target = event.target;
+              target._oldValue = target.value;
+          });
+          field.addEventListener("focus", (event) => {
+              // console.info("event -----> ", event.type);
+              this.lastValue = field.value;
+          });
+          field.addEventListener("blur", (event) => {
+              // console.info("event -----> ", event.type);
+              const target = event.target;
+              // no-op when user goes to another window or the input field has no backed-up value
+              if (document.hasFocus() && target._oldValue !== undefined) {
+                  if (target._oldValue !== target.value) {
+                      target.datepicker.setDate(target._oldValue || { clear: true });
+                  }
+                  delete target._oldValue;
+              }
+              if (this.lastValue !== field.value) {
+                  field.dispatchEvent(new Event("change"));
+              }
+          });
+          datepicker.element.addEventListener("changeDate", (event) => {
+              // console.info("event -----> ", event.type);
+              field.dispatchEvent(new Event("change"));
+          });
+          // simple solution for the picker: currently only open, not close is implemented
+          (_a = this.querySelector(".tobago-date-picker")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", (event) => {
+              this.field.focus();
+          });
+      }
+      get todayButton() {
+          return this.hasAttribute("today-button");
+      }
+      set todayButton(todayButton) {
+          if (todayButton) {
+              this.setAttribute("today-button", "");
+          }
+          else {
+              this.removeAttribute("today-button");
+          }
+      }
+      get type() {
+          var _a;
+          return (_a = this.field) === null || _a === void 0 ? void 0 : _a.getAttribute("type");
+      }
+      get min() {
+          var _a;
+          return (_a = this.field) === null || _a === void 0 ? void 0 : _a.getAttribute("min");
+      }
+      get max() {
+          var _a;
+          return (_a = this.field) === null || _a === void 0 ? void 0 : _a.getAttribute("max");
+      }
+      get pattern() {
+          let pattern = this.getAttribute("pattern");
+          return pattern ? pattern : "yyyy-mm-dd";
+      }
+      get i18n() {
+          const i18n = this.getAttribute("i18n");
+          return i18n ? JSON.parse(i18n) : undefined;
+      }
+      get field() {
+          const rootNode = this.getRootNode();
+          return rootNode.getElementById(this.id + "::field");
+      }
+  }
+  DatePicker.SUPPORTS_INPUT_TYPE_DATE = (() => {
+      const input = document.createElement("input");
+      input.setAttribute("type", "date");
+      const thisIsNoDate = "this is not a date";
+      input.setAttribute("value", thisIsNoDate);
+      return input.value !== thisIsNoDate;
+  })();
+  document.addEventListener("tobago.init", function (event) {
+      if (window.customElements.get("tobago-date") == null) {
+          window.customElements.define("tobago-date", DatePicker);
+      }
+  });
+
   var top = 'top';
   var bottom = 'bottom';
   var right = 'right';
@@ -9331,679 +9664,284 @@
       }
 
       if (callback) {
-        callback();
-      }
-    } // Static
-
-
-    static jQueryInterface(config) {
-      return this.each(function () {
-        const data = Data.get(this, DATA_KEY$1) || new Tab$1(this);
-
-        if (typeof config === 'string') {
-          if (typeof data[config] === 'undefined') {
-            throw new TypeError(`No method named "${config}"`);
-          }
-
-          data[config]();
-        }
-      });
-    }
-
-  }
-  /**
-   * ------------------------------------------------------------------------
-   * Data Api implementation
-   * ------------------------------------------------------------------------
-   */
-
-
-  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
-    if (['A', 'AREA'].includes(this.tagName)) {
-      event.preventDefault();
-    }
-
-    if (isDisabled(this)) {
-      return;
-    }
-
-    const data = Data.get(this, DATA_KEY$1) || new Tab$1(this);
-    data.show();
-  });
-  /**
-   * ------------------------------------------------------------------------
-   * jQuery
-   * ------------------------------------------------------------------------
-   * add .Tab to jQuery only if jQuery is present
-   */
-
-  defineJQueryPlugin(Tab$1);
-
-  /**
-   * --------------------------------------------------------------------------
-   * Bootstrap (v5.0.1): toast.js
-   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
-   * --------------------------------------------------------------------------
-   */
-  /**
-   * ------------------------------------------------------------------------
-   * Constants
-   * ------------------------------------------------------------------------
-   */
-
-  const NAME = 'toast';
-  const DATA_KEY = 'bs.toast';
-  const EVENT_KEY = `.${DATA_KEY}`;
-  const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`;
-  const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`;
-  const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`;
-  const EVENT_FOCUSIN = `focusin${EVENT_KEY}`;
-  const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`;
-  const EVENT_HIDE = `hide${EVENT_KEY}`;
-  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
-  const EVENT_SHOW = `show${EVENT_KEY}`;
-  const EVENT_SHOWN = `shown${EVENT_KEY}`;
-  const CLASS_NAME_FADE = 'fade';
-  const CLASS_NAME_HIDE = 'hide';
-  const CLASS_NAME_SHOW = 'show';
-  const CLASS_NAME_SHOWING = 'showing';
-  const DefaultType = {
-    animation: 'boolean',
-    autohide: 'boolean',
-    delay: 'number'
-  };
-  const Default = {
-    animation: true,
-    autohide: true,
-    delay: 5000
-  };
-  const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="toast"]';
-  /**
-   * ------------------------------------------------------------------------
-   * Class Definition
-   * ------------------------------------------------------------------------
-   */
-
-  class Toast extends BaseComponent {
-    constructor(element, config) {
-      super(element);
-      this._config = this._getConfig(config);
-      this._timeout = null;
-      this._hasMouseInteraction = false;
-      this._hasKeyboardInteraction = false;
-
-      this._setListeners();
-    } // Getters
-
-
-    static get DefaultType() {
-      return DefaultType;
-    }
-
-    static get Default() {
-      return Default;
-    }
-
-    static get NAME() {
-      return NAME;
-    } // Public
-
-
-    show() {
-      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);
-
-      if (showEvent.defaultPrevented) {
-        return;
-      }
-
-      this._clearTimeout();
-
-      if (this._config.animation) {
-        this._element.classList.add(CLASS_NAME_FADE);
-      }
-
-      const complete = () => {
-        this._element.classList.remove(CLASS_NAME_SHOWING);
-
-        this._element.classList.add(CLASS_NAME_SHOW);
-
-        EventHandler.trigger(this._element, EVENT_SHOWN);
-
-        this._maybeScheduleHide();
-      };
-
-      this._element.classList.remove(CLASS_NAME_HIDE);
-
-      reflow(this._element);
-
-      this._element.classList.add(CLASS_NAME_SHOWING);
-
-      this._queueCallback(complete, this._element, this._config.animation);
-    }
-
-    hide() {
-      if (!this._element.classList.contains(CLASS_NAME_SHOW)) {
-        return;
-      }
-
-      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
-
-      if (hideEvent.defaultPrevented) {
-        return;
-      }
-
-      const complete = () => {
-        this._element.classList.add(CLASS_NAME_HIDE);
-
-        EventHandler.trigger(this._element, EVENT_HIDDEN);
-      };
-
-      this._element.classList.remove(CLASS_NAME_SHOW);
-
-      this._queueCallback(complete, this._element, this._config.animation);
-    }
-
-    dispose() {
-      this._clearTimeout();
-
-      if (this._element.classList.contains(CLASS_NAME_SHOW)) {
-        this._element.classList.remove(CLASS_NAME_SHOW);
-      }
-
-      super.dispose();
-    } // Private
-
-
-    _getConfig(config) {
-      config = { ...Default,
-        ...Manipulator.getDataAttributes(this._element),
-        ...(typeof config === 'object' && config ? config : {})
-      };
-      typeCheckConfig(NAME, config, this.constructor.DefaultType);
-      return config;
-    }
-
-    _maybeScheduleHide() {
-      if (!this._config.autohide) {
-        return;
-      }
-
-      if (this._hasMouseInteraction || this._hasKeyboardInteraction) {
-        return;
-      }
-
-      this._timeout = setTimeout(() => {
-        this.hide();
-      }, this._config.delay);
-    }
-
-    _onInteraction(event, isInteracting) {
-      switch (event.type) {
-        case 'mouseover':
-        case 'mouseout':
-          this._hasMouseInteraction = isInteracting;
-          break;
-
-        case 'focusin':
-        case 'focusout':
-          this._hasKeyboardInteraction = isInteracting;
-          break;
-      }
-
-      if (isInteracting) {
-        this._clearTimeout();
-
-        return;
-      }
-
-      const nextElement = event.relatedTarget;
-
-      if (this._element === nextElement || this._element.contains(nextElement)) {
-        return;
-      }
-
-      this._maybeScheduleHide();
-    }
-
-    _setListeners() {
-      EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide());
-      EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true));
-      EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false));
-      EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true));
-      EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false));
-    }
-
-    _clearTimeout() {
-      clearTimeout(this._timeout);
-      this._timeout = null;
+        callback();
+      }
     } // Static
 
 
     static jQueryInterface(config) {
       return this.each(function () {
-        let data = Data.get(this, DATA_KEY);
-
-        const _config = typeof config === 'object' && config;
-
-        if (!data) {
-          data = new Toast(this, _config);
-        }
+        const data = Data.get(this, DATA_KEY$1) || new Tab$1(this);
 
         if (typeof config === 'string') {
           if (typeof data[config] === 'undefined') {
             throw new TypeError(`No method named "${config}"`);
           }
 
-          data[config](this);
+          data[config]();
         }
       });
     }
 
   }
-  /**
-   * ------------------------------------------------------------------------
-   * jQuery
-   * ------------------------------------------------------------------------
-   * add .Toast to jQuery only if jQuery is present
-   */
-
-
-  defineJQueryPlugin(Toast);
-
-  /*
-   * 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.
-   */
-  class Popup extends HTMLElement {
-      constructor() {
-          super();
-      }
-      connectedCallback() {
-          const options = {};
-          this.modal = new Modal(this, options);
-          if (!this.collapsed) {
-              this.show();
-          }
-      }
-      disconnectedCallback() {
-          this.hide();
-          this.modal.dispose();
-      }
-      show(behaviorMode) {
-          console.log("show");
-          if (behaviorMode == null || behaviorMode == BehaviorMode.client) {
-              this.modal.show();
-          }
-      }
-      hide(behaviorMode) {
-          console.log("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) {
-      if (window.customElements.get("tobago-popup") == null) {
-          window.customElements.define("tobago-popup", Popup);
-      }
-  });
-  class Collapse {
-      static findHidden(element) {
-          const rootNode = element.getRootNode();
-          return rootNode.getElementById(element.id + "::collapse");
-      }
-  }
-  Collapse.execute = function (operation, target, behaviorMode) {
-      const hidden = Collapse.findHidden(target);
-      let newCollapsed;
-      switch (operation) {
-          case CollapseOperation.hide:
-              newCollapsed = true;
-              break;
-          case CollapseOperation.show:
-              newCollapsed = false;
-              break;
-          default:
-              console.error("unknown operation: '%s'", operation);
-      }
-      if (newCollapsed) {
-          if (target instanceof Popup) {
-              target.hide(behaviorMode);
-          }
-          else {
-              target.classList.add("tobago-collapsed");
-          }
-      }
-      else {
-          if (target instanceof Popup) {
-              target.show(behaviorMode);
-          }
-          else {
-              target.classList.remove("tobago-collapsed");
-          }
-      }
-      hidden.value = newCollapsed;
-  };
-
-  /*
-   * 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.
-   */
-  class Behavior extends HTMLElement {
-      constructor() {
-          super();
-      }
-      connectedCallback() {
-          switch (this.event) {
-              case "load": // this is a special case, because the "load" is too late now.
-                  this.callback();
-                  break;
-              case "resize":
-                  document.body.addEventListener(this.event, this.callback.bind(this));
-                  break;
-              default:
-                  const eventElement = this.eventElement;
-                  if (eventElement) {
-                      eventElement.addEventListener(this.event, this.callback.bind(this));
-                  }
-                  else {
-                      // if the clientId doesn't exists in DOM, it's probably the id of tobago-behavior custom element
-                      this.parentElement.addEventListener(this.event, this.callback.bind(this));
-                      // todo: not sure if this warning can be removed;
-                      console.warn("Can't find an element for the event. Use parentElement instead.", this);
-                  }
-          }
-      }
-      callback(event) {
-          if (this.collapseOperation && this.collapseTarget) {
-              const rootNode = this.getRootNode();
-              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
-                  });
-                  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");
-      }
-      set event(event) {
-          this.setAttribute("event", event);
-      }
-      get clientId() {
-          return this.getAttribute("client-id");
-      }
-      set clientId(clientId) {
-          this.setAttribute("client-id", clientId);
-      }
-      get fieldId() {
-          return this.getAttribute("field-id");
-      }
-      set fieldId(fieldId) {
-          this.setAttribute("field-id", fieldId);
-      }
-      get execute() {
-          return this.getAttribute("execute");
-      }
-      set execute(execute) {
-          this.setAttribute("execute", execute);
-      }
-      get render() {
-          return this.getAttribute("render");
-      }
-      set render(render) {
-          this.setAttribute("render", render);
-      }
-      get delay() {
-          return parseInt(this.getAttribute("delay")) || 0;
-      }
-      set delay(delay) {
-          this.setAttribute("delay", String(delay));
-      }
-      get omit() {
-          return this.hasAttribute("omit");
-      }
-      set omit(omit) {
-          if (omit) {
-              this.setAttribute("omit", "");
-          }
-          else {
-              this.removeAttribute("omit");
-          }
-      }
-      get target() {
-          return this.getAttribute("target");
-      }
-      set target(target) {
-          this.setAttribute("target", target);
-      }
-      get confirmation() {
-          return this.getAttribute("confirmation");
-      }
-      set confirmation(confirmation) {
-          this.setAttribute("confirmation", confirmation);
+  /**
+   * ------------------------------------------------------------------------
+   * Data Api implementation
+   * ------------------------------------------------------------------------
+   */
+
+
+  EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
+    if (['A', 'AREA'].includes(this.tagName)) {
+      event.preventDefault();
+    }
+
+    if (isDisabled(this)) {
+      return;
+    }
+
+    const data = Data.get(this, DATA_KEY$1) || new Tab$1(this);
+    data.show();
+  });
+  /**
+   * ------------------------------------------------------------------------
+   * jQuery
+   * ------------------------------------------------------------------------
+   * add .Tab to jQuery only if jQuery is present
+   */
+
+  defineJQueryPlugin(Tab$1);
+
+  /**
+   * --------------------------------------------------------------------------
+   * Bootstrap (v5.0.1): toast.js
+   * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+   * --------------------------------------------------------------------------
+   */
+  /**
+   * ------------------------------------------------------------------------
+   * Constants
+   * ------------------------------------------------------------------------
+   */
+
+  const NAME = 'toast';
+  const DATA_KEY = 'bs.toast';
+  const EVENT_KEY = `.${DATA_KEY}`;
+  const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`;
+  const EVENT_MOUSEOVER = `mouseover${EVENT_KEY}`;
+  const EVENT_MOUSEOUT = `mouseout${EVENT_KEY}`;
+  const EVENT_FOCUSIN = `focusin${EVENT_KEY}`;
+  const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`;
+  const EVENT_HIDE = `hide${EVENT_KEY}`;
+  const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
+  const EVENT_SHOW = `show${EVENT_KEY}`;
+  const EVENT_SHOWN = `shown${EVENT_KEY}`;
+  const CLASS_NAME_FADE = 'fade';
+  const CLASS_NAME_HIDE = 'hide';
+  const CLASS_NAME_SHOW = 'show';
+  const CLASS_NAME_SHOWING = 'showing';
+  const DefaultType = {
+    animation: 'boolean',
+    autohide: 'boolean',
+    delay: 'number'
+  };
+  const Default = {
+    animation: true,
+    autohide: true,
+    delay: 5000
+  };
+  const SELECTOR_DATA_DISMISS = '[data-bs-dismiss="toast"]';
+  /**
+   * ------------------------------------------------------------------------
+   * Class Definition
+   * ------------------------------------------------------------------------
+   */
+
+  class Toast extends BaseComponent {
+    constructor(element, config) {
+      super(element);
+      this._config = this._getConfig(config);
+      this._timeout = null;
+      this._hasMouseInteraction = false;
+      this._hasKeyboardInteraction = false;
+
+      this._setListeners();
+    } // Getters
+
+
+    static get DefaultType() {
+      return DefaultType;
+    }
+
+    static get Default() {
+      return Default;
+    }
+
+    static get NAME() {
+      return NAME;
+    } // Public
+
+
+    show() {
+      const showEvent = EventHandler.trigger(this._element, EVENT_SHOW);
+
+      if (showEvent.defaultPrevented) {
+        return;
       }
-      get collapseOperation() {
-          return CollapseOperation[this.getAttribute("collapse-operation")];
+
+      this._clearTimeout();
+
+      if (this._config.animation) {
+        this._element.classList.add(CLASS_NAME_FADE);
       }
-      set collapseOperation(collapseOperation) {
-          this.setAttribute("collapse-operation", CollapseOperation[collapseOperation]);
+
+      const complete = () => {
+        this._element.classList.remove(CLASS_NAME_SHOWING);
+
+        this._element.classList.add(CLASS_NAME_SHOW);
+
+        EventHandler.trigger(this._element, EVENT_SHOWN);
+
+        this._maybeScheduleHide();
+      };
+
+      this._element.classList.remove(CLASS_NAME_HIDE);
+
+      reflow(this._element);
+
+      this._element.classList.add(CLASS_NAME_SHOWING);
+
+      this._queueCallback(complete, this._element, this._config.animation);
+    }
+
+    hide() {
+      if (!this._element.classList.contains(CLASS_NAME_SHOW)) {
+        return;
       }
-      get collapseTarget() {
-          return this.getAttribute("collapse-target");
+
+      const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE);
+
+      if (hideEvent.defaultPrevented) {
+        return;
       }
-      set collapseTarget(collapseTarget) {
-          this.setAttribute("collapse-target", collapseTarget);
+
+      const complete = () => {
+        this._element.classList.add(CLASS_NAME_HIDE);
+
+        EventHandler.trigger(this._element, EVENT_HIDDEN);
+      };
+
+      this._element.classList.remove(CLASS_NAME_SHOW);
+
+      this._queueCallback(complete, this._element, this._config.animation);
+    }
+
+    dispose() {
+      this._clearTimeout();
+
+      if (this._element.classList.contains(CLASS_NAME_SHOW)) {
+        this._element.classList.remove(CLASS_NAME_SHOW);
       }
-      get decoupled() {
-          return this.hasAttribute("decoupled");
+
+      super.dispose();
+    } // Private
+
+
+    _getConfig(config) {
+      config = { ...Default,
+        ...Manipulator.getDataAttributes(this._element),
+        ...(typeof config === 'object' && config ? config : {})
+      };
+      typeCheckConfig(NAME, config, this.constructor.DefaultType);
+      return config;
+    }
+
+    _maybeScheduleHide() {
+      if (!this._config.autohide) {
+        return;
       }
-      set decoupled(decoupled) {
-          if (decoupled) {
-              this.setAttribute("decoupled", "");
-          }
-          else {
-              this.removeAttribute("decoupled");
-          }
+
+      if (this._hasMouseInteraction || this._hasKeyboardInteraction) {
+        return;
       }
-      get actionElement() {
-          const rootNode = this.getRootNode();
-          const id = this.clientId;
-          return rootNode.getElementById(id);
+
+      this._timeout = setTimeout(() => {
+        this.hide();
+      }, this._config.delay);
+    }
+
+    _onInteraction(event, isInteracting) {
+      switch (event.type) {
+        case 'mouseover':
+        case 'mouseout':
+          this._hasMouseInteraction = isInteracting;
+          break;
+
+        case 'focusin':
+        case 'focusout':
+          this._hasKeyboardInteraction = isInteracting;
+          break;
       }
-      get eventElement() {
-          const rootNode = this.getRootNode();
-          const id = this.fieldId ? this.fieldId : this.clientId;
-          return rootNode.getElementById(id);
+
+      if (isInteracting) {
+        this._clearTimeout();
+
+        return;
       }
-  }
-  document.addEventListener("tobago.init", function (event) {
-      if (window.customElements.get("tobago-behavior") == null) {
-          window.customElements.define("tobago-behavior", Behavior);
+
+      const nextElement = event.relatedTarget;
+
+      if (this._element === nextElement || this._element.contains(nextElement)) {
+        return;
       }
-  });
-  class CommandHelper {
-  }
-  CommandHelper.isSubmit = false;
-  /**
-   * Submitting the page with specified actionId.
-   * @param source
-   * @param actionId
-   * @param decoupled
-   * @param target
-   */
-  CommandHelper.submitAction = function (source, actionId, decoupled = false, target) {
-      Transport.request(function () {
-          if (!CommandHelper.isSubmit) {
-              CommandHelper.isSubmit = true;
-              const form = document.getElementsByTagName("form")[0];
-              const oldTarget = form.getAttribute("target");
-              const sourceHidden = document.getElementById("javax.faces.source");
-              sourceHidden.disabled = false;
-              sourceHidden.value = actionId;
-              if (target) {
-                  form.setAttribute("target", target);
-              }
-              const listenerOptions = {
-                  source: source,
-                  actionId: actionId /*,
-                  options: commandHelper*/
-              };
-              const onSubmitResult = CommandHelper.onSubmit(listenerOptions);
-              if (onSubmitResult) {
-                  try {
-                      form.submit();
-                      // reset the source field after submit, to be prepared for possible next AJAX with decoupled=true
-                      sourceHidden.disabled = true;
-                      sourceHidden.value = "";
-                  }
-                  catch (e) {
-                      Overlay.destroy(Page.page(form).id);
-                      CommandHelper.isSubmit = false;
-                      alert("Submit failed: " + e); // XXX localization, better error handling
-                  }
-              }
-              if (target) {
-                  if (oldTarget) {
-                      form.setAttribute("target", oldTarget);
-                  }
-                  else {
-                      form.removeAttribute("target");
-                  }
-              }
-              if (target || decoupled || !onSubmitResult) {
-                  CommandHelper.isSubmit = false;
-                  Transport.pageSubmitted = false;
-              }
-          }
-          if (!CommandHelper.isSubmit) {
-              Transport.requestComplete(); // remove this from queue
+
+      this._maybeScheduleHide();
+    }
+
+    _setListeners() {
+      EventHandler.on(this._element, EVENT_CLICK_DISMISS, SELECTOR_DATA_DISMISS, () => this.hide());
+      EventHandler.on(this._element, EVENT_MOUSEOVER, event => this._onInteraction(event, true));
+      EventHandler.on(this._element, EVENT_MOUSEOUT, event => this._onInteraction(event, false));
+      EventHandler.on(this._element, EVENT_FOCUSIN, event => this._onInteraction(event, true));
+      EventHandler.on(this._element, EVENT_FOCUSOUT, event => this._onInteraction(event, false));
+    }
+
+    _clearTimeout() {
+      clearTimeout(this._timeout);
+      this._timeout = null;
+    } // Static
+
+
+    static jQueryInterface(config) {
+      return this.each(function () {
+        let data = Data.get(this, DATA_KEY);
+
+        const _config = typeof config === 'object' && config;
+
+        if (!data) {
+          data = new Toast(this, _config);
+        }
+
+        if (typeof config === 'string') {
+          if (typeof data[config] === 'undefined') {
+            throw new TypeError(`No method named "${config}"`);
           }
-      }, true);
-  };
-  CommandHelper.onSubmit = function (listenerOptions) {
-      CommandHelper.isSubmit = true;
-      const element = document.documentElement; // XXX this might be the wrong element in case of shadow dom
-      Page.page(element).onBeforeUnload();
-      return true;
-  };
-  class Transport {
+
+          data[config](this);
+        }
+      });
+    }
+
   }
-  Transport.requests = [];
-  Transport.currentActionId = null;
-  Transport.pageSubmitted = false;
   /**
-   * @return true if the request is queued.
+   * ------------------------------------------------------------------------
+   * jQuery
+   * ------------------------------------------------------------------------
+   * add .Toast to jQuery only if jQuery is present
    */
-  Transport.request = function (req, submitPage, actionId) {
-      let index = 0;
-      if (submitPage) {
-          Transport.pageSubmitted = true;
-          index = Transport.requests.push(req);
-          //console.debug('index = ' + index)
-      }
-      else if (!Transport.pageSubmitted) { // AJAX case
-          console.debug("Current ActionId='%s' action='%s'", Transport.currentActionId, actionId);
-          if (actionId && Transport.currentActionId === actionId) {
-              console.info("Ignoring request");
-              // If actionId equals currentActionId assume double request: do nothing
-              return false;
-          }
-          index = Transport.requests.push(req);
-          //console.debug('index = ' + index)
-          Transport.currentActionId = actionId;
-      }
-      else {
-          console.debug("else case");
-          return false;
-      }
-      console.debug("index='%s'", index);
-      if (index === 1) {
-          console.info("Execute request!");
-          Transport.startTime = new Date();
-          Transport.requests[0]();
-      }
-      else {
-          console.info("Request queued!");
-      }
-      return true;
-  };
-  // TBD XXX REMOVE is this called in non AJAX case?
-  Transport.requestComplete = function () {
-      Transport.requests.shift();
-      Transport.currentActionId = null;
-      console.debug("Request complete! Duration: %s ms; "
-          + "Queue size : %s", new Date().getTime() - Transport.startTime.getTime(), Transport.requests.length);
-      if (Transport.requests.length > 0) {
-          console.debug("Execute request!");
-          Transport.startTime = new Date();
-          Transport.requests[0]();
-      }
-  };
+
+
+  defineJQueryPlugin(Toast);
 
   /*
    * Licensed to the Apache Software Foundation (ASF) under one or more
@@ -10021,179 +9959,79 @@
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
-  class Page extends HTMLElement {
+  class Popup extends HTMLElement {
       constructor() {
           super();
       }
-      /**
-       * The Tobago root element
-       */
-      static page(element) {
-          const rootNode = element.getRootNode();
-          const pages = rootNode.querySelectorAll("tobago-page");
-          if (pages.length > 0) {
-              if (pages.length >= 2) {
-                  console.warn("Found more than one tobago-page element!");
-              }
-              return pages.item(0);
-          }
-          console.warn("Found no tobago page!");
-          return null;
-      }
-      /**
-       * "a:b" -> "a"
-       * "a:b:c" -> "a:b"
-       * "a" -> null
-       * null -> null
-       * "a:b::sub-component" -> "a"
-       * "a::sub-component:b" -> "a::sub-component" // should currently not happen in Tobago
-       *
-       * @param clientId The clientId of a component.
-       * @return The clientId of the naming container.
-       */
-      static getNamingContainerId(clientId) {
-          if (clientId == null || clientId.lastIndexOf(":") === -1) {
-              return null;
-          }
-          let id = clientId;
-          while (true) {
-              const sub = id.lastIndexOf("::");
-              if (sub == -1) {
-                  break;
-              }
-              if (sub + 1 == id.lastIndexOf(":")) {
-                  id = id.substring(0, sub);
-              }
-              else {
-                  break;
-              }
-          }
-          return id.substring(0, id.lastIndexOf(":"));
-      }
       connectedCallback() {
-          this.registerAjaxListener();
-          this.querySelector("form").addEventListener("submit", CommandHelper.onSubmit);
-          window.addEventListener("unload", this.onUnload.bind(this));
-          this.addEventListener("keypress", (event) => {
-              let code = event.which; // XXX deprecated
-              if (code === 0) {
-                  code = event.keyCode;
-              }
-              if (code === 13) {
-                  const target = event.target;
-                  if (target.tagName === "A" || target.tagName === "BUTTON") {
-                      return;
-                  }
-                  if (target.tagName === "TEXTAREA") {
-                      if (!event.metaKey && !event.ctrlKey) {
-                          return;
-                      }
-                  }
-                  const name = target.getAttribute("name");
-                  let id = name ? name : target.id;
-                  while (id != null) {
-                      const command = document.querySelector(`[data-tobago-default='${id}']`);
-                      if (command) {
-                          command.dispatchEvent(new MouseEvent("click"));
-                          break;
-                      }
-                      id = Page.getNamingContainerId(id);
-                  }
-                  return false;
-              }
-          });
-      }
-      onBeforeUnload() {
-          if (this.transition) {
-              new Overlay(this);
-          }
-          this.transition = this.oldTransition;
-      }
-      /**
-       * Wrapper function to call application generated onunload function
-       */
-      onUnload() {
-          console.info("on onload");
-          if (CommandHelper.isSubmit) {
-              if (this.transition) {
-                  new Overlay(this);
-              }
-              this.transition = this.oldTransition;
+          const options = {};
+          this.modal = new Modal(this, options);
+          if (!this.collapsed) {
+              this.show();
           }
       }
-      registerAjaxListener() {
-          jsf.ajax.addOnEvent(this.jsfResponse.bind(this));
-      }
-      jsfResponse(event) {
-          console.timeEnd("[tobago-jsf] jsf-ajax");
-          console.time("[tobago-jsf] jsf-ajax");
-          console.debug("[tobago-jsf] JSF event status: '%s'", event.status);
-          if (event.status === "success") {
-              event.responseXML.querySelectorAll("update").forEach(this.jsfResponseSuccess.bind(this));
-          }
-          else if (event.status === "complete") {
-              event.responseXML.querySelectorAll("update").forEach(this.jsfResponseComplete.bind(this));
-          }
+      disconnectedCallback() {
+          this.hide();
+          this.modal.dispose();
       }
-      jsfResponseSuccess(update) {
-          const id = update.id;
-          let rootNode = this.getRootNode();
-          // XXX in case of "this" is tobago-page (e.g. ajax exception handling) rootNode is not set correctly???
-          if (!rootNode.getElementById) {
-              rootNode = document;
+      show(behaviorMode) {
+          console.log("show");
+          if (behaviorMode == null || behaviorMode == BehaviorMode.client) {
+              this.modal.show();
           }
-          console.info("[tobago-jsf] Update after jsf.ajax success: %s", id);
       }
-      jsfResponseComplete(update) {
-          const id = update.id;
-          if (JsfParameter.isJsfId(id)) {
-              console.debug("[tobago-jsf] Update after jsf.ajax complete: #", id);
-              Overlay.destroy(id);
+      hide(behaviorMode) {
+          console.log("hide");
+          if (behaviorMode == null || behaviorMode == BehaviorMode.client) {
+              this.modal.hide();
           }
       }
-      get locale() {
-          let locale = this.getAttribute("locale");
-          if (!locale) {
-              locale = document.documentElement.lang;
-          }
-          return locale;
+      get collapsed() {
+          return JSON.parse(Collapse.findHidden(this).value);
       }
   }
-  document.addEventListener("tobago.init", (event) => {
-      if (window.customElements.get("tobago-page") == null) {
-          window.customElements.define("tobago-page", Page);
+  document.addEventListener("tobago.init", function (event) {
+      if (window.customElements.get("tobago-popup") == null) {
+          window.customElements.define("tobago-popup", Popup);
       }
   });
-  class JsfParameter {
-      static isJsfId(id) {
-          switch (id) {
-              case JsfParameter.VIEW_STATE:
-              case JsfParameter.CLIENT_WINDOW:
-              case JsfParameter.VIEW_ROOT:
-              case JsfParameter.VIEW_HEAD:
-              case JsfParameter.VIEW_BODY:
-              case JsfParameter.RESOURCE:
-                  return false;
-              default:
-                  return true;
-          }
+  class Collapse {
+      static findHidden(element) {
+          const rootNode = element.getRootNode();
+          return rootNode.getElementById(element.id + "::collapse");
       }
-      static isJsfBody(id) {
-          switch (id) {
-              case JsfParameter.VIEW_ROOT:
-              case JsfParameter.VIEW_BODY:
-                  return true;
-              default:
-                  return false;
+  }
+  Collapse.execute = function (operation, target, behaviorMode) {
+      const hidden = Collapse.findHidden(target);
+      let newCollapsed;
+      switch (operation) {
+          case CollapseOperation.hide:
+              newCollapsed = true;
+              break;
+          case CollapseOperation.show:
+              newCollapsed = false;
+              break;
+          default:
+              console.error("unknown operation: '%s'", operation);
+      }
+      if (newCollapsed) {
+          if (target instanceof Popup) {
+              target.hide(behaviorMode);
+          }
+          else {
+              target.classList.add("tobago-collapsed");
           }
       }
-  }
-  JsfParameter.VIEW_STATE = "javax.faces.ViewState";
-  JsfParameter.CLIENT_WINDOW = "javax.faces.ClientWindow";
-  JsfParameter.VIEW_ROOT = "javax.faces.ViewRoot";
-  JsfParameter.VIEW_HEAD = "javax.faces.ViewHead";
-  JsfParameter.VIEW_BODY = "javax.faces.ViewBody";
-  JsfParameter.RESOURCE = "javax.faces.Resource";
+      else {
+          if (target instanceof Popup) {
+              target.show(behaviorMode);
+          }
+          else {
+              target.classList.remove("tobago-collapsed");
+          }
+      }
+      hidden.value = newCollapsed;
+  };
 
   /*
    * Licensed to the Apache Software Foundation (ASF) under one or more
@@ -10211,125 +10049,213 @@
    * See the License for the specific language governing permissions and
    * limitations under the License.
    */
-  class DatePicker extends HTMLElement {
+  class Behavior extends HTMLElement {
       constructor() {
           super();
       }
       connectedCallback() {
-          if (this.type == "date") {
-              console.debug("check input type=date support", DatePicker.SUPPORTS_INPUT_TYPE_DATE);
-              if (!DatePicker.SUPPORTS_INPUT_TYPE_DATE) {
-                  this.setAttribute("type", "text");
-                  this.initVanillaDatePicker();
-              }
+          switch (this.event) {
+              case "load": // this is a special case, because the "load" is too late now.
+                  this.callback();
+                  break;
+              case "resize":
+                  document.body.addEventListener(this.event, this.callback.bind(this));
+                  break;
+              default:
+                  const eventElement = this.eventElement;
+                  if (eventElement) {
+                      eventElement.addEventListener(this.event, this.callback.bind(this));
+                  }
+                  else {
+                      // if the clientId doesn't exists in DOM, it's probably the id of tobago-behavior custom element
+                      this.parentElement.addEventListener(this.event, this.callback.bind(this));
+                      // todo: not sure if this warning can be removed;
+                      console.warn("Can't find an element for the event. Use parentElement instead.", this);
+                  }
           }
       }
-      initVanillaDatePicker() {
-          var _a;
-          const field = this.field;
-          const locale = Page.page(this).locale;
-          const i18n = this.i18n;
-          i18n.titleFormat = "MM y"; // todo i18n
-          i18n.format = this.pattern;
-          Datepicker.locales[locale] = i18n;
-          const options = {
-              buttonClass: "btn",
-              orientation: "auto",
-              autohide: true,
-              language: locale,
-              todayBtn: this.todayButton,
-              todayBtnMode: 1,
-              minDate: this.min,
-              maxDate: this.max,
-              // todo readonly
-              // todo show week numbers
-          };
-          const datepicker = new Datepicker(field, options);
-          // XXX these listeners are needed as long as we have a solution for:
-          // XXX https://github.com/mymth/vanillajs-datepicker/issues/13
-          // XXX the 2nd point is missing the "normal" change event on the input element
-          field.addEventListener("keyup", (event) => {
-              // console.info("event -----> ", event.type);
-              if (event.metaKey || event.key.length > 1 && event.key !== "Backspace" && event.key !== "Delete") {
-                  return;
+      callback(event) {
+          if (this.collapseOperation && this.collapseTarget) {
+              const rootNode = this.getRootNode();
+              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
+                  });
+                  break;
+              case BehaviorMode.full:
+                  setTimeout(this.submit.bind(this), this.delay);
+                  break;
+              // nothing to do
+          }
+      }
+      /**
+       * Submitting the page (= the form).
+       */
+      submit() {
+          console.info("Execute submit!");
+          const page = Page.page(this);
+          if (!page.submitActive) {
+              page.submitActive = true;
+              const actionId = this.fieldId != null ? this.fieldId : this.clientId;
+              const form = page.form;
+              const oldTarget = form.getAttribute("target");
+              const sourceHidden = document.getElementById("javax.faces.source");
+              sourceHidden.disabled = false;
+              sourceHidden.value = actionId;
+              if (this.target) {
+                  form.setAttribute("target", this.target);
               }
-              // back up user's input when user types printable character or backspace/delete
-              const target = event.target;
-              target._oldValue = target.value;
-          });
-          field.addEventListener("focus", (event) => {
-              // console.info("event -----> ", event.type);
-              this.lastValue = field.value;
-          });
-          field.addEventListener("blur", (event) => {
-              // console.info("event -----> ", event.type);
-              const target = event.target;
-              // no-op when user goes to another window or the input field has no backed-up value
-              if (document.hasFocus() && target._oldValue !== undefined) {
-                  if (target._oldValue !== target.value) {
-                      target.datepicker.setDate(target._oldValue || { clear: true });
+              page.beforeSubmit();
+              try {
+                  form.submit();
+                  // reset the source field after submit, to be prepared for possible next AJAX with decoupled=true
+                  sourceHidden.disabled = true;
+                  sourceHidden.value = "";
+              }
+              catch (e) {
+                  console.error("Submit failed!", e);
+                  Overlay.destroy(page.id);
+                  page.submitActive = false;
+                  alert(`Submit failed: ${e}`); // XXX localization, better error handling
+              }
+              if (this.target) {
+                  if (oldTarget) {
+                      form.setAttribute("target", oldTarget);
+                  }
+                  else {
+                      form.removeAttribute("target");
                   }
-                  delete target._oldValue;
               }
-              if (this.lastValue !== field.value) {
-                  field.dispatchEvent(new Event("change"));
+              if (this.target || this.decoupled) {
+                  page.submitActive = false;
               }
-          });
-          datepicker.element.addEventListener("changeDate", (event) => {
-              // console.info("event -----> ", event.type);
-              field.dispatchEvent(new Event("change"));
-          });
-          // simple solution for the picker: currently only open, not close is implemented
-          (_a = this.querySelector(".tobago-date-picker")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", (event) => {
-              this.field.focus();
-          });
+          }
       }
-      get todayButton() {
-          return this.hasAttribute("today-button");
+      get mode() {
+          if (this.render || this.execute) {
+              return BehaviorMode.ajax;
+          }
+          else if (!this.omit) {
+              return BehaviorMode.full;
+          }
+          else {
+              return BehaviorMode.client;
+          }
       }
-      set todayButton(todayButton) {
-          if (todayButton) {
-              this.setAttribute("today-button", "");
+      get event() {
+          return this.getAttribute("event");
+      }
+      set event(event) {
+          this.setAttribute("event", event);
+      }
+      get clientId() {
+          return this.getAttribute("client-id");
+      }
+      set clientId(clientId) {
+          this.setAttribute("client-id", clientId);
+      }
+      get fieldId() {
+          return this.getAttribute("field-id");
+      }
+      set fieldId(fieldId) {
+          this.setAttribute("field-id", fieldId);
+      }
+      get execute() {
+          return this.getAttribute("execute");
+      }
+      set execute(execute) {
+          this.setAttribute("execute", execute);
+      }
+      get render() {
+          return this.getAttribute("render");
+      }
+      set render(render) {
+          this.setAttribute("render", render);
+      }
+      get delay() {
+          return parseInt(this.getAttribute("delay")) || 0;
+      }
+      set delay(delay) {
+          this.setAttribute("delay", String(delay));
+      }
+      get omit() {
+          return this.hasAttribute("omit");
+      }
+      set omit(omit) {
+          if (omit) {
+              this.setAttribute("omit", "");
           }
           else {
-              this.removeAttribute("today-button");
+              this.removeAttribute("omit");
           }
       }
-      get type() {
-          var _a;
-          return (_a = this.field) === null || _a === void 0 ? void 0 : _a.getAttribute("type");
+      get target() {
+          return this.getAttribute("target");
       }
-      get min() {
-          var _a;
-          return (_a = this.field) === null || _a === void 0 ? void 0 : _a.getAttribute("min");
+      set target(target) {
+          this.setAttribute("target", target);
       }
-      get max() {
-          var _a;
-          return (_a = this.field) === null || _a === void 0 ? void 0 : _a.getAttribute("max");
+      get confirmation() {
+          return this.getAttribute("confirmation");
       }
-      get pattern() {
-          let pattern = this.getAttribute("pattern");
-          return pattern ? pattern : "yyyy-mm-dd";
+      set confirmation(confirmation) {
+          this.setAttribute("confirmation", confirmation);
       }
-      get i18n() {
-          const i18n = this.getAttribute("i18n");
-          return i18n ? JSON.parse(i18n) : undefined;
+      get collapseOperation() {
+          return CollapseOperation[this.getAttribute("collapse-operation")];
       }
-      get field() {
+      set collapseOperation(collapseOperation) {
+          this.setAttribute("collapse-operation", CollapseOperation[collapseOperation]);
+      }
+      get collapseTarget() {
+          return this.getAttribute("collapse-target");
+      }
+      set collapseTarget(collapseTarget) {
+          this.setAttribute("collapse-target", collapseTarget);
+      }
+      get decoupled() {
+          return this.hasAttribute("decoupled");
+      }
+      set decoupled(decoupled) {
+          if (decoupled) {
+              this.setAttribute("decoupled", "");
+          }
+          else {
+              this.removeAttribute("decoupled");
+          }
+      }
+      get actionElement() {
           const rootNode = this.getRootNode();
-          return rootNode.getElementById(this.id + "::field");
+          const id = this.clientId;
+          return rootNode.getElementById(id);
+      }
+      get eventElement() {
+          const rootNode = this.getRootNode();
+          const id = this.fieldId ? this.fieldId : this.clientId;
+          return rootNode.getElementById(id);
       }
   }
-  DatePicker.SUPPORTS_INPUT_TYPE_DATE = (() => {
-      const input = document.createElement("input");
-      input.setAttribute("type", "date");
-      const thisIsNoDate = "this is not a date";
-      input.setAttribute("value", thisIsNoDate);
-      return input.value !== thisIsNoDate;
-  })();
   document.addEventListener("tobago.init", function (event) {
-      if (window.customElements.get("tobago-date") == null) {
-          window.customElements.define("tobago-date", DatePicker);
+      if (window.customElements.get("tobago-behavior") == null) {
+          window.customElements.define("tobago-behavior", Behavior);
       }
   });
 
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 5b59908..97a2325 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-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","../../../../node_modules/vanillajs-datepic [...]
\ No newline at end of file
+{"version":3,"file":"tobago.js","sources":["../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","../../../../node_modules/vanillajs-datepic [...]
\ 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 bba2635..80439db 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";class e{static selfOrQuerySelectorAll(e,t){const s=new Array;e||(e=document.documentElement),e.matches(t)&&s.push(e);for(const i of e.querySelectorAll(t))s.push(i);return s}static getTransitionTime(e){const t=window.getComputedStyle(e);return 1e3*(Number.parseFloat(t.transitionDelay)+Number.parseFloat(t.transitionDuration))}}class t extends HTMLElement{constructor(){super(),this.CssClass={SHOW:"sho [...]
+!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";class e{static selfOrQuerySelectorAll(e,t){const s=new Array;e||(e=document.documentElement),e.matches(t)&&s.push(e);for(const i of e.querySelectorAll(t))s.push(i);return s}static getTransitionTime(e){const t=window.getComputedStyle(e);return 1e3*(Number.parseFloat(t.transitionDelay)+Number.parseFloat(t.transitionDuration))}}class t extends HTMLElement{constructor(){super(),this.CssClass={SHOW:"sho [...]
 /*!
     * 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 ws={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 Ss={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 2d1ee05..0b48653 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-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","../../../../node_modules/vanillajs-dat [...]
\ No newline at end of file
+{"version":3,"file":"tobago.min.js","sources":["../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","../../../../node_modules/vanillajs-dat [...]
\ No newline at end of file
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 987bd1c..b51494f 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
@@ -87,9 +87,48 @@ class Behavior extends HTMLElement {
     }
   }
 
+  /**
+   * Submitting the page (= the form).
+   */
   submit(): void {
-    const id = this.fieldId != null ? this.fieldId : this.clientId;
-    CommandHelper.submitAction(this, id, this.decoupled, this.target);
+    console.info("Execute submit!");
+    const page = Page.page(this);
+    if (!page.submitActive) {
+      page.submitActive = true;
+      const actionId = this.fieldId != null ? this.fieldId : this.clientId;
+      const form = page.form;
+      const oldTarget = form.getAttribute("target");
+      const sourceHidden = document.getElementById("javax.faces.source") as HTMLInputElement;
+      sourceHidden.disabled = false;
+      sourceHidden.value = actionId;
+      if (this.target) {
+        form.setAttribute("target", this.target);
+      }
+
+      page.beforeSubmit();
+
+      try {
+        form.submit();
+        // reset the source field after submit, to be prepared for possible next AJAX with decoupled=true
+        sourceHidden.disabled = true;
+        sourceHidden.value = "";
+      } catch (e) {
+        console.error("Submit failed!", e);
+        Overlay.destroy(page.id);
+        page.submitActive = false;
+        alert(`Submit failed: ${e}`); // XXX localization, better error handling
+      }
+      if (this.target) {
+        if (oldTarget) {
+          form.setAttribute("target", oldTarget);
+        } else {
+          form.removeAttribute("target");
+        }
+      }
+      if (this.target || this.decoupled) {
+        page.submitActive = false;
+      }
+    }
   }
 
   get mode(): BehaviorMode {
@@ -217,21 +256,6 @@ class Behavior extends HTMLElement {
     const id = this.fieldId ? this.fieldId : this.clientId;
     return rootNode.getElementById(id);
   }
-
-  /* XXX todo:
-    get element(): HTMLElement {
-      let e = this.parentElement;
-      // XXX special case, using the row, but <tobago-behavior> can't be a child of <tr>
-      while (e.matches("td.tobago-sheet-cell-markup-filler")
-      // XXX fix position of <tobago-behavior> inside of input-group
-      || e.matches(".input-group")
-      || e.matches(".tobago-input-group-outer")) {
-        e = e.parentElement;
-      }
-      return e;
-    }
-  */
-
 }
 
 document.addEventListener("tobago.init", function (event: Event): void {
@@ -239,130 +263,3 @@ document.addEventListener("tobago.init", function (event: Event): void {
     window.customElements.define("tobago-behavior", Behavior);
   }
 });
-
-export class CommandHelper {
-  static isSubmit: boolean = false;
-
-  /**
-   * Submitting the page with specified actionId.
-   * @param source
-   * @param actionId
-   * @param decoupled
-   * @param target
-   */
-  public static submitAction = function (
-      source: HTMLElement, actionId: string, decoupled: boolean = false, target?: string): void {
-
-    Transport.request(function (): void {
-      if (!CommandHelper.isSubmit) {
-        CommandHelper.isSubmit = true;
-        const form = document.getElementsByTagName("form")[0] as HTMLFormElement;
-        const oldTarget = form.getAttribute("target");
-        const sourceHidden = document.getElementById("javax.faces.source") as HTMLInputElement;
-        sourceHidden.disabled = false;
-        sourceHidden.value = actionId;
-        if (target) {
-          form.setAttribute("target", target);
-        }
-        const listenerOptions = {
-          source: source,
-          actionId: actionId/*,
-          options: commandHelper*/
-        };
-        const onSubmitResult = CommandHelper.onSubmit(listenerOptions);
-        if (onSubmitResult) {
-          try {
-            form.submit();
-            // reset the source field after submit, to be prepared for possible next AJAX with decoupled=true
-            sourceHidden.disabled = true;
-            sourceHidden.value = "";
-          } catch (e) {
-            Overlay.destroy(Page.page(form).id);
-            CommandHelper.isSubmit = false;
-            alert("Submit failed: " + e); // XXX localization, better error handling
-          }
-        }
-        if (target) {
-          if (oldTarget) {
-            form.setAttribute("target", oldTarget);
-          } else {
-            form.removeAttribute("target");
-          }
-        }
-        if (target || decoupled || !onSubmitResult) {
-          CommandHelper.isSubmit = false;
-          Transport.pageSubmitted = false;
-        }
-      }
-      if (!CommandHelper.isSubmit) {
-        Transport.requestComplete(); // remove this from queue
-      }
-    }, true);
-  };
-
-  static onSubmit = function (listenerOptions: any): boolean {
-
-    CommandHelper.isSubmit = true;
-
-    const element: HTMLElement = document.documentElement; // XXX this might be the wrong element in case of shadow dom
-    Page.page(element).onBeforeUnload();
-
-    return true;
-  };
-
-}
-
-class Transport {
-  static requests = [];
-  static currentActionId = null;
-  static pageSubmitted = false;
-  static startTime: Date;
-
-  /**
-   * @return true if the request is queued.
-   */
-  static request = function (req: () => void, submitPage: boolean, actionId?: string): boolean {
-    let index = 0;
-    if (submitPage) {
-      Transport.pageSubmitted = true;
-      index = Transport.requests.push(req);
-      //console.debug('index = ' + index)
-    } else if (!Transport.pageSubmitted) { // AJAX case
-      console.debug("Current ActionId='%s' action='%s'", Transport.currentActionId, actionId);
-      if (actionId && Transport.currentActionId === actionId) {
-        console.info("Ignoring request");
-        // If actionId equals currentActionId assume double request: do nothing
-        return false;
-      }
-      index = Transport.requests.push(req);
-      //console.debug('index = ' + index)
-      Transport.currentActionId = actionId;
-    } else {
-      console.debug("else case");
-      return false;
-    }
-    console.debug("index='%s'", index);
-    if (index === 1) {
-      console.info("Execute request!");
-      Transport.startTime = new Date();
-      Transport.requests[0]();
-    } else {
-      console.info("Request queued!");
-    }
-    return true;
-  };
-
-  // TBD XXX REMOVE is this called in non AJAX case?
-
-  static requestComplete = function (): void {
-    Transport.requests.shift();
-    Transport.currentActionId = null;
-    console.debug("Request complete! Duration: %s ms; "
-        + "Queue size : %s", new Date().getTime() - Transport.startTime.getTime(),  Transport.requests.length);
-    if (Transport.requests.length > 0) {
-      console.debug("Execute request!");
-      Transport.startTime = new Date();
-      Transport.requests[0]();
-    }
-  };
-}
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-page.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-page.ts
index 7ad4611..0201ad2 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-page.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-page.ts
@@ -15,13 +15,13 @@
  * limitations under the License.
  */
 
-import {CommandHelper} from "./tobago-command";
 import {Overlay} from "./tobago-overlay";
 
 export class Page extends HTMLElement {
 
   private transition: boolean;
   private oldTransition: boolean;
+  submitActive: boolean = false;
 
   /**
    * The Tobago root element
@@ -78,7 +78,7 @@ export class Page extends HTMLElement {
 
     this.registerAjaxListener();
 
-    this.querySelector("form").addEventListener("submit", CommandHelper.onSubmit);
+    this.form.addEventListener("submit", this.beforeSubmit.bind(this));
 
     window.addEventListener("unload", this.onUnload.bind(this));
 
@@ -112,7 +112,8 @@ export class Page extends HTMLElement {
     });
   }
 
-  onBeforeUnload(): void {
+  beforeSubmit(): void {
+    this.submitActive = true;
     if (this.transition) {
       new Overlay(this);
     }
@@ -123,8 +124,8 @@ export class Page extends HTMLElement {
    * Wrapper function to call application generated onunload function
    */
   onUnload(): void {
-    console.info("on onload");
-    if (CommandHelper.isSubmit) {
+    console.info("on unload");
+    if (Page.page(this).submitActive) {
       if (this.transition) {
         new Overlay(this);
       }
@@ -154,7 +155,7 @@ export class Page extends HTMLElement {
     if (!rootNode.getElementById) {
       rootNode = document;
     }
-    console.info("[tobago-jsf] Update after jsf.ajax success: %s", id);
+    console.debug("[tobago-jsf] Update after jsf.ajax success: %s", id);
   }
 
   jsfResponseComplete(update: Element): void {
@@ -165,6 +166,10 @@ export class Page extends HTMLElement {
     }
   }
 
+  get form(): HTMLFormElement {
+    return this.querySelector("form");
+  }
+
   get locale(): string {
     let locale = this.getAttribute("locale");
     if (!locale) {