# Loader gif in top of table?

**URL:** <https://forum.inductiveautomation.com/t/loader-gif-in-top-of-table/96477>\
**Category:** Ignition\
**Tags:** perspective\
**Created:** [September 26, 2024, 8:35am UTC](https://forum.inductiveautomation.com/t/loader-gif-in-top-of-table/96477 "2024-09-26T08:35:49Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Alejandro\_Alaco](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/alejandro_alaco/32/59849_2.png) [@Alejandro\_Alaco](https://forum.inductiveautomation.com/u/Alejandro_Alaco)\
**Post date:** [September 26, 2024, 8:35am UTC](https://forum.inductiveautomation.com/t/loader-gif-in-top-of-table/96477/1 "2024-09-26T08:35:49Z")

</div>

Hello!

Can I put an image in top of a table?

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/c/7/c7ee120b3ae86027d6e3d877746b4bca0c091720.png)

I just figured out how to add a loader to my table while loading data. On the button that triggers the load I added:

```python
# Set the loading state to True (show loader)
	
	self.getSibling("TabContainer").custom.isLoading = True  

and at the end of the code when table is loaded:

finally:
	    # Set the loading state to False (hide loader) after the query is completed
	    self.getSibling("TabContainer").custom.isLoading = False

```

then my custom.isLoading property is binded to the meta.visible of the container in wich I added the loader.

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/f/b/fb4005d36211bfc6e84c4401fa29624389900605.png)  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/1/8/18889a382278ef150c9c62bdb9de9bb7aadd4d60.png)

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [September 26, 2024, 1:16pm UTC](https://forum.inductiveautomation.com/t/loader-gif-in-top-of-table/96477/2 "2024-09-26T13:16:17Z")

</div>

Just use a popup.

Here's a simplified version of what I use:

> **decorator**
>
> ```python
> from functools import wraps
> 
> def loading(message="MESSAGE"):
> def _loading(func):
> @wraps(func)
> def wrapper(*args, **kwargs):
> system.perspective.openPopup(
> id = 'loader',
> view = "components/loader/popup",
> position = {'width': "300px", 'height': "300px"},
> showCloseIcon = False,
> draggable = False,
> modal = True,
> resizable = False,
> overlayDismiss = False,
> params = {
> 'message': message
> }
> )
> popup_open = True
> 			
> try:
> ret = func(*args, **kwargs)
> except:
> raise
> finally:
> system.perspective.closePopup('loader')
> return ret
> return wrapper
> return _loading
> 
> ```

> **popup view json**
>
> ```json
> {
> "custom": {},
> "params": {
> "message": "loading..."
> },
> "propConfig": {
> "params.message": {
> "paramDirection": "input",
> "persistent": true
> }
> },
> "props": {
> "defaultSize": {
> "height": 300,
> "width": 300
> }
> },
> "root": {
> "children": [
> {
> "meta": {
> "name": "spinning-circles"
> },
> "position": {
> "basis": "80px"
> },
> "props": {
> "elements": [
> {
> "elements": [
> {
> "elements": [
> {
> "cx": "42.601",
> "cy": "11.462",
> "elements": [
> {
> "attributeName": "fill-opacity",
> "calcMode": "linear",
> "dur": "1.3s",
> "name": "animate",
> "repeatCount": "indefinite",
> "type": "animate",
> "values": "1;0;0;0;0;0;0;0"
> }
> ],
> "fill": {
> "opacity": "1",
> "paint": "#fff"
> },
> "name": "circle",
> "r": "5",
> "type": "circle"
> },
> {
> "cx": "49.063",
> "cy": "27.063",
> "elements": [
> {
> "attributeName": "fill-opacity",
> "calcMode": "linear",
> "dur": "1.3s",
> "name": "animate",
> "repeatCount": "indefinite",
> "type": "animate",
> "values": "0;1;0;0;0;0;0;0"
> }
> ],
> "fill": {
> "opacity": "0",
> "paint": "#fff"
> },
> "name": "circle",
> "r": "5",
> "type": "circle"
> },
> {
> "cx": "42.601",
> "cy": "42.663",
> "elements": [
> {
> "attributeName": "fill-opacity",
> "calcMode": "linear",
> "dur": "1.3s",
> "name": "animate",
> "repeatCount": "indefinite",
> "type": "animate",
> "values": "0;0;1;0;0;0;0;0"
> }
> ],
> "fill": {
> "opacity": "0",
> "paint": "#fff"
> },
> "name": "circle",
> "r": "5",
> "type": "circle"
> },
> {
> "cx": "27",
> "cy": "49.125",
> "elements": [
> {
> "attributeName": "fill-opacity",
> "calcMode": "linear",
> "dur": "1.3s",
> "name": "animate",
> "repeatCount": "indefinite",
> "type": "animate",
> "values": "0;0;0;1;0;0;0;0"
> }
> ],
> "fill": {
> "opacity": "0",
> "paint": "#fff"
> },
> "name": "circle",
> "r": "5",
> "type": "circle"
> },
> {
> "cx": "11.399",
> "cy": "42.663",
> "elements": [
> {
> "attributeName": "fill-opacity",
> "calcMode": "linear",
> "dur": "1.3s",
> "name": "animate",
> "repeatCount": "indefinite",
> "type": "animate",
> "values": "0;0;0;0;1;0;0;0"
> }
> ],
> "fill": {
> "opacity": "0",
> "paint": "#fff"
> },
> "name": "circle",
> "r": "5",
> "type": "circle"
> },
> {
> "cx": "4.938",
> "cy": "27.063",
> "elements": [
> {
> "attributeName": "fill-opacity",
> "calcMode": "linear",
> "dur": "1.3s",
> "name": "animate",
> "repeatCount": "indefinite",
> "type": "animate",
> "values": "0;0;0;0;0;1;0;0"
> }
> ],
> "fill": {
> "opacity": "0",
> "paint": "#fff"
> },
> "name": "circle",
> "r": "5",
> "type": "circle"
> },
> {
> "cx": "11.399",
> "cy": "11.462",
> "elements": [
> {
> "attributeName": "fill-opacity",
> "calcMode": "linear",
> "dur": "1.3s",
> "name": "animate",
> "repeatCount": "indefinite",
> "type": "animate",
> "values": "0;0;0;0;0;0;1;0"
> }
> ],
> "fill": {
> "opacity": "0",
> "paint": "#fff"
> },
> "name": "circle",
> "r": "5",
> "type": "circle"
> },
> {
> "cx": "27",
> "cy": "5",
> "elements": [
> {
> "attributeName": "fill-opacity",
> "calcMode": "linear",
> "dur": "1.3s",
> "name": "animate",
> "repeatCount": "indefinite",
> "type": "animate",
> "values": "0;0;0;0;0;0;0;1"
> }
> ],
> "fill": {
> "opacity": "0",
> "paint": "#fff"
> },
> "name": "circle",
> "r": "5",
> "type": "circle"
> }
> ],
> "name": "group",
> "stroke": {
> "paint": "#FFF",
> "width": "1.5"
> },
> "transform": "translate(2 1)",
> "type": "group"
> }
> ],
> "fill": {
> "paint": "transparent",
> "rule": "evenodd"
> },
> "name": "group",
> "type": "group"
> }
> ],
> "viewBox": "0 0 58 58"
> },
> "type": "ia.shapes.svg"
> },
> {
> "meta": {
> "name": "Label"
> },
> "propConfig": {
> "props.text": {
> "binding": {
> "config": {
> "path": "view.params.message"
> },
> "type": "property"
> }
> }
> },
> "props": {
> "textStyle": {
> "color": "white"
> }
> },
> "type": "ia.display.label"
> }
> ],
> "meta": {
> "name": "root"
> },
> "props": {
> "alignItems": "center",
> "direction": "column",
> "justify": "center",
> "style": {
> "gap": "20px"
> }
> },
> "type": "ia.container.flex"
> }
> }
> 
> ```

> **css**
>
> ```css
> #popup-loader {
> border: none;
> outline: none;
> background: transparent;
> box-shadow: none;
> text-shadow: 2px 2px 3px black;
> font-size: 1.3em;
> }
> 
> ```

Here's how to use it:

When you have a function that may take time to run, put it in your library, and decorate it with the `loading` decorator.  
It should look like this:

```python
@some_module.loading("message")
def slow_function():
    sleep(20)

```

Now call that function in your transforms or whatever. That's it.  
You can replace the loading animation by just replacing the svg in the popup. You can find some here: [GitHub - SamHerbert/SVG-Loaders: Loading icons and small animations built with pure SVG.](https://github.com/SamHerbert/SVG-Loaders)  
I also have a version of that popup that uses css for the animation if you prefer.

---

<div class="post-metadata">

**Author:** ![Alejandro\_Alaco](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/alejandro_alaco/32/59849_2.png) [@Alejandro\_Alaco](https://forum.inductiveautomation.com/u/Alejandro_Alaco)\
**Post date:** [September 26, 2024, 3:00pm UTC](https://forum.inductiveautomation.com/t/loader-gif-in-top-of-table/96477/3 "2024-09-26T15:00:56Z")

</div>

Thanks Pascal for your answer!, I will try this method.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [November 25, 2025, 11:50pm UTC](https://forum.inductiveautomation.com/t/loader-gif-in-top-of-table/96477/4 "2025-11-25T23:50:16Z")

</div>

I only just found this, or maybe I saw it back when you posted when I didn’t understand decorators and didn’t appreciate its genius, but this is _really_ cool! Extremely easy to use.

One nice alternative would be the ability to tie it to a single component only so that the whole screen isn’t affected. But both have their use cases

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [November 26, 2025, 10:34am UTC](https://forum.inductiveautomation.com/t/loader-gif-in-top-of-table/96477/5 "2025-11-26T10:34:08Z")

</div>

Note that because it calls a perspective function, decorated functions will raise an error when called from the script console.  
The "real" version I use handles this by checking for specific errors:

```python
from functools import wraps
from java.lang import IllegalArgumentException, IllegalStateException

def loading(message="MESSAGE"):
	def _loading(func):
		@wraps(func)
		def wrapper(*args, **kwargs):
			try:
				system.perspective.openPopup(
					id = 'loader',
					view = "components/loader/popup",
					position = {'width': "300px", 'height': "300px"},
					showCloseIcon = False,
					draggable = False,
					modal = True,
					resizable = False,
					overlayDismiss = False,
					params = {
						'message': message
					}
				)
				popup_open = True
			except AttributeError:
				# vision/console scope error
				popup_open = False
			except IllegalArgumentException:
				# gateway scope error - Perspective session doesn't exist
				popup_open = False
			except IllegalStateException:
				# gateway scope error - Perspective session was disconnected
				popup_open = False
			
			try:
				return func(*args, **kwargs)
			except:
				raise
			finally:
				if popup_open:
					try:
						system.perspective.closePopup('loader')
					except:
						pass
		return wrapper
	return _loading

```

This may not be complete, or even entirely correct, as it was written through trial an error...

---

<div class="post-metadata">

**Author:** ![dkhayes117](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/dkhayes117/32/68344_2.png) [@dkhayes117](https://forum.inductiveautomation.com/u/dkhayes117)\
**Post date:** [November 26, 2025, 4:04pm UTC](https://forum.inductiveautomation.com/t/loader-gif-in-top-of-table/96477/6 "2025-11-26T16:04:46Z")

</div>

I handle this by adding a [scope check](https://forum.inductiveautomation.com/t/get-current-scope-in-scripting/55686/17).

```python
if utils.env.isPerspective(): # custom script
    system.perspective.openPopup(...)
```

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [November 26, 2025, 5:03pm UTC](https://forum.inductiveautomation.com/t/loader-gif-in-top-of-table/96477/7 "2025-11-26T17:03:03Z")

</div>

You know what they say: Ask for forgiveness, not for permission.  
I don't always agree, but, hey, at least there's some literature to make me feel better about myself when I just try to throw stuff at the interpreter and see what sticks.
