Auto-Save Rich Text Editor in Application Studio

Before I try to recreate the wheel. Has anyone figured out a way to force the Save automatically in a Rich Text Editor? I’m finding that to be very non-intuitive and I don’t believe I can deploy this to production “as-is”. I’ll fuss with it if no one has done this or attempted it. But any pointers are appreciated.

1 Like

If you really need this functionality, you can use JavaScript to click the Save button for you. Create an event that is triggered sometime as the form is first loading. In it, put either a condition or row-update Action. In that Action’s Properties, fill the Expression field with this code:

(window.WaitForIFrame = () => {
  window.AutoSaveRichText = () => {
    /* 
      Utility function for debouncing
    */
    function debounce(func, wait) {
      let timeout;
      return function(...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
      };
    }

    /* 
      Function to trigger Save with enhanced error handling and logging,
      using a small timeout to avoid undefined nativeElement.
    */
    function triggerSave() {
      setTimeout(() => {
        try {
          /* 
            Click Save buttons represented by <span> elements
          */
          const spanSaves = document.querySelectorAll('span.k-menu-link-text.ng-star-inserted');
          spanSaves.forEach(span => {
            if (span.textContent.trim() === "Save") {
              const button = span.closest('button');
              if (button) {
                console.log('Clicking Save button (span):', button);
                button.click();
              }
            }
          });

          /* 
            Click Save buttons represented by <kendo-icon> elements
          */
          const iconSaves = document.querySelectorAll('kendo-icon.k-i-save.k-button-icon.k-icon.ng-star-inserted[aria-hidden="true"]');
          iconSaves.forEach(icon => {
            const button = icon.closest('button');
            if (button) {
              console.log('Clicking Save button (kendo-icon):', button);
              button.click();
            }
          });
        } catch (error) {
          console.error('Error during triggerSave:', error);
        }
      }, 25); /* Micro-defer Save */
    }

    /* 
      Create a debounced version of triggerSave to prevent spam
    */
    const debouncedTriggerSave = debounce(triggerSave, 25);

    /* 
      Select the iframe element
    */
    const iframe = document.querySelector('div.k-editor-content.ng-star-inserted iframe.k-iframe');
    if (!iframe) {
      console.error('Iframe not found.');
      return;
    }

    let iframeDoc;
    try {
      iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
    } catch (error) {
      console.error('Cannot access iframe content:', error);
      return;
    }

    if (!iframeDoc) {
      console.error('Iframe document not found.');
      return;
    }

    const editable = iframeDoc.querySelector('[contenteditable="true"]');
    if (!editable) {
      console.error('Editable element not found inside iframe.');
      return;
    }

    /* 
      Create a MutationObserver to watch for changes, including attributes
    */
    const observer = new MutationObserver(mutationsList => {
      for (const mutation of mutationsList) {
        if (
          mutation.type === 'characterData' ||
          mutation.type === 'attributes'
        ) {
          console.log('Mutation detected:', mutation);
          debouncedTriggerSave();
          break; /* Exit after the first relevant mutation */
        }
      }
    });

    const config = {
      attributes: true,    /* Observe attribute changes */
      characterData: true, /* Observe changes to text content */
    };

    observer.observe(editable, config);
    console.log('MutationObserver set up to watch for changes in the editable element, including attributes.');
  };

  setTimeout(() => {
    window.AutoSaveRichText();
  }, 5000);})()
2 Likes