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> ' + @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;
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">
@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.
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"
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 = '×'; 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> ' + @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;
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