Change Custom Tiles from Using Iframes

Custom tiles are great but quite slow to load. I believe this is due to them being an iframe and the whole internal page needing to load each time the dashboard loads. If you create a custom tile that is used multiple times on the same dashboard e.g. a vertical slider, each tile is loading the same scripts and images resources multiple times.

Not sure of the best solution but thinking that dynamically adding the custom tile to the DOM would be faster and maybe allowing for script loading once per dashboard load would be good too.

Hi Ben - thanks for posting the feature request.

Would you mind sharing a video of the performance issues you are seeing? Custom Tiles are rendered in a separate iframe in a separate domain so they remain properly sandboxed (eg. secure).

I did some quick and dirty tests and the overhead for getting the tile loaded, including basic HTML rendered to the screen is about ~250ms. I would note that a key part of the performance of a custom tile is what features that tile developer includes and how it’s implemented.

For example, many custom tiles will have a series of things they wait on before rendering the content. They might wait for the stio library to be ready, wait for a third-party javascript library to be ready, make some network calls to retrieve data, parse that data, then display it in the tile. This can further exacerbate the feeling of things being slow to render. There’s lots of different approaches that can be taken to make this feel faster but it really depends on the particular custom tile as to what would make the most sense.

Appreciate what your saying but at the end of the day, the custom tile being an iframe is essentially a separate web page so if your having 3 of the same custom tile on the same dashboard and those custom tiles load some scripts for example, they will all have to load the same script.
Ok so this a bit rough atm but the current implementation is a vertical slider for controlling my blinds. Here is the code:

<!-- Do not edit below -->
<script src="https://cdn.sharptools.io/js/custom-tiles.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>

<script type="application/json" id="tile-settings">
{
  "schema": "0.1.0",
  "settings": [
    {"name": "title", "type": "STRING", "label": "Title"},
    {"name": "makerApiUrl", "label": "Maker API URL", "type": "STRING"}
  ],
  "name": "Vertical Slider",
  "dimensions": {"height": 3, "width": 1}
}
</script>
<!-- Do not edit above -->
<style>
input[name = range] {
	position: absolute;
    top: 45px;
    width: 231px;
    height: 307px;
    border: 0 -webkit-transform: rotate(270deg);
    -moz-transform: rotate(270deg);
    transform: rotate(270deg);
    left: -68px;
}

  .title {
    color: gray;
    left: 21px;
    position: relative;
  }

  .svg-img {
    position: absolute;
    top: 38px;
    color: gray;
    width: 35px;
    left: 31px;
  }
</style>

<span id="title" class="title"></span>

<svg id="closed" data-v-106e6f16="" aria-hidden="true" 
  focusable="false" data-prefix="fa" data-icon="blinds" 
  role="img" xmlns="http://www.w3.org/2000/svg" 
  viewBox="0 0 512 512" class="svg-img fa-4x fa-fw svg-inline--fa fa-blinds fa-w-16"><path data-v-106e6f16="" fill="currentColor" d="M96,226.94V160H16L0,256H66.94A47.82,47.82,0,0,1,96,226.94ZM157.06,288a47.73,47.73,0,0,1-90.12,0H16L0,384H512l-16-96ZM16,416,0,512H512l-16-96ZM512,48V16A16,16,0,0,0,496,0H16A16,16,0,0,0,0,16V48A15.85,15.85,0,0,0,10.84,63L0,128H96V64h32v64H512L501.16,63A15.85,15.85,0,0,0,512,48ZM128,160v66.94A47.82,47.82,0,0,1,157.06,256H512l-16-96Z"></path></svg>

<svg id="opened" data-v-106e6f16="" aria-hidden="true" 
  focusable="false" data-prefix="fa" data-icon="blinds-open" 
  role="img" xmlns="http://www.w3.org/2000/svg" 
  viewBox="0 0 512 512" class="svg-img fa-4x fa-fw svg-inline--fa fa-blinds-open fa-w-16"><path data-v-106e6f16="" fill="currentColor" d="M16,464,0,512H512l-16-48ZM64,208.21V192H16L0,240H39.16A80.21,80.21,0,0,1,64,208.21ZM175.59,320a79.18,79.18,0,0,1-127.18,0H16L0,368H512l-16-48ZM160,192v16.21A80.12,80.12,0,0,1,184.84,240H512l-16-48ZM512,48V16A16,16,0,0,0,496,0H16A16,16,0,0,0,0,16V48A16,16,0,0,0,16,64L0,112H64V64H96V226.94a48,48,0,1,0,32,0V64h32v48H512L496,64A16,16,0,0,0,512,48Z" class=""></path></svg>

<input id="rangeCtrl" type="range" name="range" min="0" max="" step="1" value="2">

<script>
  var title = document.getElementById("title");
  var markerApiUrl = "";
  var setPositionUrl = "";
  var rangeCtrl = document.getElementById("rangeCtrl");
  
stio.ready().then(function(data) {
  userSettings = data.settings; 
  makerApiUrl = userSettings.makerApiUrl;
  title.innerHTML = userSettings.title;
  setPositionUrl = makerApiUrl.replace("?", "/setPosition/[level]?")
  
  rangeCtrl.addEventListener('change', function(){
      var debouncedRange = _.debounce(handleRangeChange, 300, {
        'leading': false,
        'trailing': true
      });
      debouncedRange();
    }, true);

  getCurrentRange();
});

  function handleRangeChange(ev) {
    var level = rangeCtrl.value;
    setRange(level);
  }
  
  function setRange(level){    
    var url = setPositionUrl.replace("[level]", level);
      	return axios.get(url)
          .then(function(res) { 
          setImage(level);
      	}).catch(function(err) {
      		console.log(err);      		
      	});
  }

  function setImage(currentPos){
    if(currentPos < 100) {
      $('#closed').hide();
      $('#opened').show();
    }else
    {
      $('#closed').show();
      $('#opened').hide();
    }
  }
  
  function getCurrentRange(){  
	return axios.get(makerApiUrl)
      .then(function(data) {
        var currentPos = data.data.attributes[5].currentValue;
        rangeCtrl.value = currentPos; 
        setImage(currentPos);        
    }).catch(function(err) {
      		console.log(err);      		
      	});
  }
</script>

Thanks for sharing the additional details.

The browser will make the request for the resource once and will then cache that resource for future requests (this happens regardless of if it’s in an iframe or rendered directly within the DOM). That’s one of the benefits of loading resources from a CDN with reasonable caching settings.

Here’s a screenshot from the Network tab of the developer tools where you can see that all of the JavaScript files are being loaded from cache:

image

It would still be helpful to see a video of the performance issue so I can better understand what it looks like for you. :slight_smile:

Hi. Thanks that’s good to know.
Here is a video of the dashboard opening.Dash

It doesn’t look terribly slow on that video but compared to when the main dashboard that has no custom tiles its quite slow. Especially as there is only 2 tiles on this one.