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 2024/10/28 14:14:34 UTC

(myfaces-tobago) branch main updated: fix: Better validation for upload (#5634)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new 57e09ed8f5 fix: Better validation for upload (#5634)
57e09ed8f5 is described below

commit 57e09ed8f54f0d6117f81a8b791733df6f4a85ed
Author: Udo Schnurpfeil <lo...@apache.org>
AuthorDate: Mon Oct 28 15:14:29 2024 +0100

    fix: Better validation for upload (#5634)
    
    Better client validation before uploading
    large files. Uploading too large files
    should fail eary.
    
    issue: TOBAGO-2361
    issue: TOBAGO-2353
---
 .../webapp/content/100-upload/File_Upload.xhtml    |  1 +
 .../tobago-theme-standard/src/main/js/tobago.js    |  8 +--
 .../src/main/js/tobago.js.map                      |  2 +-
 .../src/main/ts/tobago-behavior.ts                 | 82 +++++++++++++++++++++-
 .../src/main/ts/tobago-file.ts                     |  9 ++-
 .../src/main/ts/tobago-init.ts                     |  8 ++-
 6 files changed, 101 insertions(+), 9 deletions(-)

diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/100-upload/File_Upload.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/100-upload/File_Upload.xhtml
index 785c02a304..4dc9cf49f7 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/100-upload/File_Upload.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/100-upload/File_Upload.xhtml
@@ -82,6 +82,7 @@
     <p>Ajax can be activated by adding <code class="language-markup">&lt;f:ajax/></code>
       to the <code class="language-markup">&lt;tc:file/></code> tag.</p>
     <tc:file label="Ajax" value="#{uploadController.fileAjax}" id="fileAjax">
+      <tc:validateFileItem maxSize="1048576"/>
       <f:ajax listener="#{uploadController.uploadAjax}" render="s3 list :page:messages"/>
     </tc:file>
   </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 7a6e2ee8cf..4d1f05a991 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
@@ -1,19 +1,19 @@
-!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";var e,t;!function(e){e.ACTIVE="active",e.AUTOCOMPLETE="autocomplete",e.AUTOCOMPLETE_INPUT="autocomplete-input",e.BOTTOM_0="bottom-0",e.COLLAPSE="collapse",e.COLLAPSING="collapsing",e.D_NONE="d-none",e.DISABLED="disabled",e.DROPDOWN="dropdown",e.DROPDOWN_ITEM="dropdown-item",e.DROPDOWN_MENU="dropdown-menu",e.DROPDOWN_MENU_END="dropdown-menu-end",e.END_0="end-0",e.FADE="fade",e.FIXED_BOTTOM="fixed-bo [...]
+!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";var e,t;!function(e){e.ACTIVE="active",e.AUTOCOMPLETE="autocomplete",e.AUTOCOMPLETE_INPUT="autocomplete-input",e.BOTTOM_0="bottom-0",e.COLLAPSE="collapse",e.COLLAPSING="collapsing",e.D_NONE="d-none",e.DISABLED="disabled",e.DROPDOWN="dropdown",e.DROPDOWN_ITEM="dropdown-item",e.DROPDOWN_MENU="dropdown-menu",e.DROPDOWN_MENU_END="dropdown-menu-end",e.END_0="end-0",e.FADE="fade",e.FIXED_BOTTOM="fixed-bo [...]
 /*!
       * Bootstrap v5.3.3 (https://getbootstrap.com/)
       * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
       * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
       */
-const $e=new Map,Pe={set(e,t,s){$e.has(e)||$e.set(e,new Map);const i=$e.get(e);i.has(t)||0===i.size?i.set(t,s):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(i.keys())[0]}.`)},get:(e,t)=>$e.has(e)&&$e.get(e).get(t)||null,remove(e,t){if(!$e.has(e))return;const s=$e.get(e);s.delete(t),0===s.size&&$e.delete(e)}},Be="transitionend",He=e=>(e&&window.CSS&&window.CSS.escape&&(e=e.replace(/#([^\s"#']+)/g,((e,t)=>`#${CSS.escape(t)}`))),e),q [...]
+const pt=new Map,gt={set(e,t,s){pt.has(e)||pt.set(e,new Map);const i=pt.get(e);i.has(t)||0===i.size?i.set(t,s):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(i.keys())[0]}.`)},get:(e,t)=>pt.has(e)&&pt.get(e).get(t)||null,remove(e,t){if(!pt.has(e))return;const s=pt.get(e);s.delete(t),0===s.size&&pt.delete(e)}},ft="transitionend",mt=e=>(e&&window.CSS&&window.CSS.escape&&(e=e.replace(/#([^\s"#']+)/g,((e,t)=>`#${CSS.escape(t)}`))),e),b [...]
 /**
      * @license
      * Copyright 2017 Google LLC
      * SPDX-License-Identifier: BSD-3-Clause
-     */const lr=globalThis,cr=lr.trustedTypes,dr=cr?cr.createPolicy("lit-html",{createHTML:e=>e}):void 0,ur="$lit$",hr=`lit$${Math.random().toFixed(9).slice(2)}$`,pr="?"+hr,gr=`<${pr}>`,fr=document,mr=()=>fr.createComment(""),br=e=>null===e||"object"!=typeof e&&"function"!=typeof e,vr=Array.isArray,_r="[ \t\n\f\r]",yr=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,wr=/-->/g,Er=/>/g,Ar=RegExp(`>|${_r}(?:([^\\s"'>=/]+)(${_r}*=${_r}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),Sr=/'/g,Lr [...]
+     */const ar=globalThis,lr=ar.trustedTypes,cr=lr?lr.createPolicy("lit-html",{createHTML:e=>e}):void 0,dr="$lit$",ur=`lit$${Math.random().toFixed(9).slice(2)}$`,hr="?"+ur,pr=`<${hr}>`,gr=document,fr=()=>gr.createComment(""),mr=e=>null===e||"object"!=typeof e&&"function"!=typeof e,br=Array.isArray,vr="[ \t\n\f\r]",_r=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,yr=/-->/g,wr=/>/g,Er=RegExp(`>|${vr}(?:([^\\s"'>=/]+)(${vr}*=${vr}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),Ar=/'/g,Sr [...]
   <button type='button'
       class='tobago-button btn btn-secondary badge' aria-label='deselect ${e}'
       ${s>0?" tabindex='"+String(s)+"'":""}
       @click="${this.removeBadge.bind(this)}"
       @focus="${this.focusEvent.bind(this)}"
-      @blur="${this.blurEvent.bind(this)}"><i class='bi-x-lg'></i></button>`}removeBadge(e){var t;const s=e.target.closest(".btn-group").dataset.tobagoValue,i=this.hiddenSelect.querySelector(`[value="${s}"]`);i.selected=!1;const n=this.badges.querySelector(`[data-tobago-value="${s}"]`),o=n.previousElementSibling,r="SPAN"===(null===(t=n.nextElementSibling)||void 0===t?void 0:t.tagName)?n.nextElementSibling:null;o?o.querySelector("button.btn.badge").focus():r?r.querySelector("button.btn.ba [...]
+      @blur="${this.blurEvent.bind(this)}"><i class='bi-x-lg'></i></button>`}removeBadge(e){var t;const s=e.target.closest(".btn-group").dataset.tobagoValue,i=this.hiddenSelect.querySelector(`[value="${s}"]`);i.selected=!1;const n=this.badges.querySelector(`[data-tobago-value="${s}"]`),o=n.previousElementSibling,r="SPAN"===(null===(t=n.nextElementSibling)||void 0===t?void 0:t.tagName)?n.nextElementSibling:null;o?o.querySelector("button.btn.badge").focus():r?r.querySelector("button.btn.ba [...]
 //# sourceMappingURL=tobago.js.map
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 5947bad160..3c83d89642 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-css.ts","../ts/tobago-collapse-operation.ts","../ts/tobago-bar.ts","../../../../node_modules/@popperjs/core/lib/enums.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../../../node_modules/@popperjs/core/lib/modifiers/applyStyles.js","../../../../node_modules/@poppe [...]
\ No newline at end of file
+{"version":3,"file":"tobago.js","sources":["../ts/tobago-css.ts","../ts/tobago-key.ts","../ts/tobago-bar.ts","../ts/tobago-date.ts","../ts/tobago-menu-store.ts","../../../../node_modules/@popperjs/core/lib/enums.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","../../../../node_modules/@popperjs/core/lib/dom-utils/getWindow.js","../../../../node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","../../../../node_modules/@popperjs/core/lib/modifiers/applyStyles [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior.ts
index 6c23fcf4e2..ad8d310a09 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior.ts
@@ -74,6 +74,24 @@ class Behavior extends HTMLElement {
       }
     }
 
+    switch (this.mode) {
+      case BehaviorMode.ajax:
+        if (!this.validate(this.execute)) {
+          console.warn("Validation failed! No request will be sent.");
+          return;
+        }
+        break;
+      case BehaviorMode.full:
+        // todo: not implemented/activated yet, because this implementation doesn't support sub-forms
+        // if (!this.validate(Page.page(this).id)) {
+        //   console.warn("Validation failed! No request will be sent.");
+        //   return;
+        // }
+        break;
+      default:
+        // nothing to do
+    }
+
     if (this.collapseOperation && this.collapseTarget) {
       const rootNode = this.getRootNode() as ShadowRoot | Document;
       Collapse.execute(this.collapseOperation, rootNode.getElementById(this.collapseTarget), this.mode);
@@ -89,7 +107,7 @@ class Behavior extends HTMLElement {
             if (partialElement) {
               let id: string;
               if (partialElement.tagName === "TOBAGO-POPUP") {
-                // popup needs no overlay, is has no area to show
+                // popup needs no overlay, it has no area to show
                 id = partialElement.querySelector(".modal-dialog").id;
               } else {
                 id = partialElement.id;
@@ -101,6 +119,43 @@ class Behavior extends HTMLElement {
             }
           }
         }
+        /*
+        // TODO: Sketch for Progressbar
+        // register callback for the AJAX-event
+        faces.ajax.addOnEvent(function (data) {
+          if (data.status === "success") {
+            // Access the XmlHttpRequest-Object after successful AJAX-request
+            const xhr: XMLHttpRequest = data.source;
+            // Event listener to monitor the upload progress
+            console.log("xhr", xhr);
+            if (xhr) {
+              console.log("xhr.upload", xhr.upload);
+              if (xhr.upload) {
+                xhr.upload.onprogress = function (event) {
+                  if (event.lengthComputable) {
+                    // Calculate the percentage of upload completed
+                    const percentComplete = (event.loaded / event.total) * 100;
+                    console.info(`Progress: ${Math.round(percentComplete)}%`);
+                  }
+                };
+              } else {
+                console.error("xhr.upload null");
+              }
+            } else {
+              console.error("xhr null");
+            }
+            console.log("AJAX-request success.");
+          } else if (data.status === "begin") {
+            console.log("AJAX-request begin.");
+          } else if (data.status === "complete") {
+            console.log("AJAX-request complete.");
+          }
+        });
+        // register error handling
+        faces.ajax.addOnError(function (data) {
+          console.error("Error AJAX-request:", data);
+        });
+        */
         faces.ajax.request(
             this.actionElement,
             event,
@@ -168,6 +223,31 @@ class Behavior extends HTMLElement {
     }
   }
 
+  validate(ids: string): boolean {
+    let allValid = true;
+    if (ids) {
+      console.debug("Is all valid? ids=", ids);
+      for (const id of ids.split(/\s+/)) {
+        const element = document.getElementById(id);
+        if (element) {
+          const formElements = element.querySelectorAll<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(
+              "input, textarea, select"
+          );
+          for (const formElement of formElements) {
+            if (!formElement.checkValidity()) {
+              console.debug("invalid", formElement.id);
+              allValid = false;
+            } else {
+              console.debug("valid", formElement.id);
+            }
+          }
+        }
+      }
+      console.debug("Is all valid?", allValid);
+      return allValid;
+    }
+  }
+
   get mode(): BehaviorMode {
     if (this.render || this.execute) {
       return BehaviorMode.ajax;
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-file.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-file.ts
index 11f6cbd618..484f723992 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-file.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-file.ts
@@ -121,16 +121,21 @@ export class File extends HTMLElement {
     const input = event.currentTarget as HTMLInputElement;
     const files = input.files;
     if (files) {
+      input.setCustomValidity("");
       let error = false;
       for (const file of files) {
         if (file.size > this.maxSize) {
-          window.alert(this.maxSizeMessage);
           input.value = "";
+          input.setCustomValidity(this.maxSizeMessage);
+          input.reportValidity();
           error = true;
+          break;
         }
       }
       if (error) {
-        event.preventDefault;
+        // only when error to prevent green checks, because we can't be sure, this is really valid (TBD)
+        this.classList.add("was-validated");
+        event.preventDefault();
         event.stopPropagation();
       }
     }
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-init.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-init.ts
index 201265ce41..baa58f2673 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-init.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-init.ts
@@ -15,9 +15,13 @@
  * limitations under the License.
  */
 
+/*
+formal ordering rules:
+- "tobago-file" must be before "tobago-behavior"
+*/
+
 // custom elements
 import "./tobago-bar";
-import "./tobago-behavior";
 import "./tobago-date";
 import "./tobago-dropdown";
 import "./tobago-file";
@@ -53,6 +57,8 @@ import "./tobago-tree";
 import "./tobago-tree-listbox";
 import "./tobago-tree-node";
 import "./tobago-tree-select";
+// the behavior must be registered after the custom elements, because of validation
+import "./tobago-behavior";
 
 //polyfill
 import "./tobago-polyfill";