Forum

Notifications
Clear all

Toast Message

6 Posts
3 Users
5 Reactions
662 Views
(@hackbart)
Posts: 24
Trusted Member
Topic starter
 

Hi,

 

not sure if this fits under HTML5 in general, but if you need a Toast Message in your project, you might find the following snippet useful.

 

type TToastMessage = (tmConfirm, tmError, tmWarning, tmInfo, tmMessage);

const
 C_ToastColors: Array[TToastMessage] of string =
  ('#77D491','#E15531','#F3BE4A','#4384CF','#353A3F');
 C_ToastIcons: Array[TToastMessage] of string =
  ('fa-circle-check', 'fa-circle-xmark', 'fa-circle-exclamation','fa-circle-info','fa-circle-info');

procedure ShowToast(const AMessage: String;const ADuration: Integer;const AType: TToastMessage);
begin
 asm
  const toast = document.createElement('div');
  toast.classList.add('toast');
  toast.style.backgroundColor = @C_ToastColors[@AType];
  toast.style.color = '#fff';
  toast.style.fontSize = '1.2rem';
  toast.style.padding = '10px';
  toast.style.position = 'fixed';
  toast.style.top = '50%';
  toast.style.left = '50%';
  toast.style.transform = 'translate(-50%, -50%)';
  toast.style.opacity = '0';
  toast.style.boxShadow = '2px 2px 6px rgba(0, 0, 0, 0.5)';
  toast.style.borderRadius = '5px';

  toast.innerHTML = '<i class="fa-solid '+@C_ToastIcons[@AType]+'"></i>&nbsp;' + @AMessage;

    document.body.appendChild(toast);

    toast.animate([{ opacity: '0' }, { opacity: '1' }], {
      duration: 200,
      fill: 'forwards',
    });

    setTimeout(function() {
      toast.animate([{ opacity: '1' }, { opacity: '0' }], {
        duration: 200,
        fill: 'forwards',
      }).onfinish = function() {
        document.body.removeChild(toast);
      };
    }, @ADuration);
  end;
end;
 
Posted : 05/05/2023 4:00 pm
Jon Lennart Aasenden
(@tao4all)
Posts: 44
Member Admin
 

Lovely! Works like a charm!
NOTE: This depends on font-awesome! Add the following line to the HTML->Head section in your index.html file:

    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" rel="stylesheet" defer="true">
 
Posted : 17/08/2023 2:21 pm
RTOlivier reacted
(@rtolivier)
Posts: 6
Active Member
 

@tao4all Would you remind me please how to update automatically header section of the html file from the pas file  using directives ? Something like {$R "Fontawesome.css"} would be nice.  We need it for packages so that is it is added automatically to the html file.  

In the html file, we can have something like

<!-- CSS SECTION STARTS HERE -->

<!-- CSS SECTION ENDS HERE -->

And the compiler inserts there assets from {$R} directives.  The same for JS files.  

 
Posted : 07/09/2023 10:44 am
(@rtolivier)
Posts: 6
Active Member
 

For packages,  a configuration file can be used too to add dependencies and assets for the package

[general]

package-name=QTXToast

version=1.0

[assets]

1="Fontawesome.css"

2="CDN link"

[dependencies]

1="RTL Package"

 
Posted : 07/09/2023 10:51 am
(@hackbart)
Posts: 24
Trusted Member
Topic starter
 

AI updated this function a bit:

type
  TToastMessage = (tmConfirm, tmError, tmWarning, tmInfo, tmMessage);

const
 C_ToastColors: Array[TToastMessage] of string =
  ('#77D491','#E15531','#F3BE4A','#4384CF','#353A3F');
 C_ToastIcons: Array[TToastMessage] of string =
  ('fa-circle-check', 'fa-circle-xmark', 'fa-circle-exclamation','fa-circle-info','fa-circle-info');

procedure ShowToast(const AMessage: String; ADuration: Integer = 4000; AType: TToastMessage = tmError);
begin
  asm
    if (!document.getElementById('toast-container')) {
      const container = document.createElement('div');
      container.id = 'toast-container';
      container.style.position = 'fixed';
      container.style.top = '20px';
      container.style.right = '20px';
      container.style.zIndex = '99999';
      document.body.appendChild(container);
      var toastContainer = container;
    } else {
      var toastContainer = document.getElementById('toast-container');
    }

    const toast = document.createElement('div');
    toast.classList.add('toast');
    toast.style.backgroundColor = @C_ToastColors[@AType];
    toast.style.color = '#fff';
    toast.style.fontSize = '1.2rem';
    toast.style.padding = '10px';
    toast.style.marginBottom = '10px';
    toast.style.opacity = '0';
    toast.style.boxShadow = '2px 2px 6px rgba(0, 0, 0, 0.5)';
    toast.style.borderRadius = '5px';
    toast.style.width = '300px';
    toast.style.position = 'relative';
    toast.style.overflow = 'hidden';

    const closeButton = document.createElement('button');
    closeButton.innerHTML = '&times;';
    closeButton.style.position = 'absolute';
    closeButton.style.top = '5px';
    closeButton.style.right = '5px';
    closeButton.style.background = 'none';
    closeButton.style.border = 'none';
    closeButton.style.color = '#fff';
    closeButton.style.fontSize = '1.5rem';
    closeButton.style.cursor = 'pointer';
    closeButton.style.padding = '0';
    closeButton.style.lineHeight = '1';
    toast.appendChild(closeButton);

    const content = document.createElement('div');
    content.innerHTML = '<i class="fa-solid '+@C_ToastIcons[@AType]+'"></i>&nbsp;' + @AMessage;
    content.style.paddingRight = '20px'; // Make room for close button
    toast.appendChild(content);

    const progressBar = document.createElement('div');
    progressBar.style.position = 'absolute';
    progressBar.style.bottom = '0';
    progressBar.style.left = '0';
    progressBar.style.height = '4px';
    progressBar.style.width = '100%';
    progressBar.style.backgroundColor = 'rgba(255, 255, 255, 0.5)';
    progressBar.style.transition = 'width linear';
    toast.appendChild(progressBar);

    toastContainer.insertBefore(toast, toastContainer.firstChild);

    toast.animate([{ opacity: '0', transform: 'translateX(100%)' }, { opacity: '1', transform: 'translateX(0)' }], {
      duration: 200,
      fill: 'forwards',
    });

    progressBar.style.transition = 'width ' + @ADuration + 'ms linear';
    setTimeout(() => progressBar.style.width = '0%', 10);

    let timeoutId = setTimeout(closeToast, @ADuration);

    function closeToast() {
      toast.animate([{ opacity: '1', transform: 'translateX(0)' }, { opacity: '0', transform: 'translateX(100%)' }], {
        duration: 200,
        fill: 'forwards',
      }).onfinish = function() {
        toastContainer.removeChild(toast);
      };
    }

    closeButton.onclick = function() {
      clearTimeout(timeoutId);
      closeToast();
    };
  end;
end;
 
Posted : 09/07/2024 9:02 am
Jon Lennart Aasenden
(@tao4all)
Posts: 44
Member Admin
 

You really dont need to do this as inline JS, most of this is perfectly doable in clean pascal. Also, the start of that code is very slow since it calls getElementById() twice if the element already exists, which traverses the entire DOM (!).

if (!document.getElementById('toast-container')) {
      const container = document.createElement('div');

This would be a much faster variation:

var container := document.getElementById('toast-container');
if (!container) {
  container = document.createElement('div');
 /* Init element here*/
}

But you can do the exact same in pascal with more or less 1:1 translation

    var container: JElement;
    container := TQTXBrowser.BrowserObjects.Document.getElementByid('toast-container');
    if container = nil then
    begin
      container := JElement( TQTXBrowser.CreateElement('div') );
      container.id := 'toast-container';
      container.style.position := 'fixed';
      container.style.top := 20;
      container.style.right := 20;
      container.style.zIndex := 99999;
      TQTXBrowser.BrowserObjects.Document.appendChild( Container );
    end;

When setting values that are by default integer (top, right etc) - by assigning values as a number type, you save JS having to parse the "20px" string, which is likewise faster. It always default to px unless you have specified a different measurement unit via meta tags.

Also, you can speed it up even further by having a style defined for it in a separate css file, which is based on the element structure. CSS allows you to create conditional css, meaning css that only kicks in if a specific parent/child construct exists. This way, you just need to quickly create the elements, assign styles to them -- and css will assign the majority of values for you (including position). So the code can be optimized even further this way.

For example, you can quickly create multiple child elements by simply setting the innerhtml value of the container:

container.innerHtml := '<div class="toast-head"><span class="toast-text"/><button class="toast-close">Ok</button>';

This is actually a lot faster than manually creating 3 elements by code. In your CSS you can now setup a structure of ".toast-container > toast-head {}" which will only kick in for that specific combo. So whenever it meets an element with "toast-container" that has "toast-head" in a child element, which further has a child element with "toast-text" -- it will style that element.

Look into the ".SomeStyle > Secondstyle" syntax of css, and you can optimize the hell out of this! And you can do it from clean pascal rather than doing it all in inline js <3

This post was modified 5 months ago 2 times by Jon Lennart Aasenden
 
Posted : 11/07/2024 11:21 am
Share: