Jump to content
  • JavaScript Custom Progress Indicator for Spotfire


    Create a more obvious loading indicator for Spotfire when a data function runs or something else is processing in the background

    Introduction

    The loading indicator in Spotfire is not so obvious sometimes. A customer came to me with this request and I was happy to create a function to solve this use case. The progress indicator can be seen at the bottom right of Spotfire
        

    before.thumb.gif.cc9cbae1a12433cc491a8af7ac9e3832.gif

    Before

    after.thumb.gif.16ddef3b4e7fea205d5e16a8d286d3bb.gif

    After

     

    How it works

    The JavaScript code on the text area monitors the spinner loader icon from the bottom right of the screen. When the element attribute changes from hidden to visible, a JavaScript function kicks in to show another custom div that is overlaid on top the analysis. This layer can be dismissed by clicking on it. Sometimes process in the background can run while performing other tasks, but at least we have a nice big notification indicating that something is going on. We can place additional information on our div to indicate that the message can be dismissed by clicking on it or when the loading time finishes.  The loading indicator will not show again until the current process ends and another separate process runs again. 

    image.png.1e1d57da517a6544890c1f1e454060ac.png

    How to Use

    Create a text area and add the following HTML

     <div id="javascriptLoader">   
       <h1>Loading</h1> 
     </div> 
     

    Insert a new JavaScript Progress Indicator file with the following code

    JavaScript_Loader_V1.0.js

      customLoaderId = "javascriptLoader";
      target = document.querySelector("[class^='sf-svg-loader']");
      loader = document.getElementById(customLoaderId);
      loader.hidden = true;
    
      // Create a new div with class 'box'
      const boxDiv = document.createElement('div');
      boxDiv.className = 'box';
    
      // Move all child nodes of loader to the new div
      while (loader.firstChild) {
        boxDiv.appendChild(loader.firstChild);
      }
    
      // Append the new div back to the loader
      loader.appendChild(boxDiv);
    
      trigger = function(x) {
        loader.hidden = target.style.display == "none";
        console.log("loader is now " + (loader.hidden ? "hidden" : "visible"));
      };
      mutationObserver = new MutationObserver(trigger);
      mutationObserver.observe(target, { attributes: true });
    
      css = `
      <style> 
       /* Absolute Center Loader */ 
       #${customLoaderId} {
          position: fixed;
          z-index: 999;   
          height: 2em;   
          width: 2em;   
          overflow: visible;   
          margin: auto;   
          top: 0;   
          left: 0;   
          bottom: 0;   
          right: 0; 
        }  
    
       /* Transparent Overlay */ 
       #${customLoaderId}:before {
          content: '';
          display: block;  
          position: fixed;  
          top: 0;   
          left: 0;
          width: 100%;
          height: 100%;
          background-color: rgba(0,0,0,0.3); 
        }
    
       /*default theme*/ 
       #${customLoaderId} .box{
          color: gray;     
          background: whitesmoke; 
          text-align: center; 
          transform: translate(-50%,-50%);
          position: absolute;
          cursor: pointer;
          width: 333px; 
        }  
      </style>`;
      
      loader.insertAdjacentHTML('beforeend', css);

    That's it!

    Custom Themes

    The default look and feel is simple and sober, but you can get crazy by customizing the look and feel of the loader. To use a theme, just add the theme after the JavaScript_Loader_V1.0.js file 
    image.png.41436423eec9a815390d436dd206ebfa.png

    Here are some custom theme examples:

    Dark Theme

    image.gif.50e9065d7380ec395ce42bf146dceaa2.gif 

     

    javascriptLoader_v1.0_dark.css

    dark.thumb.gif.37ee1acd2808af39dcaea3da7b26a64a.gif

     javascriptLoader = "javascriptLoader" 
     css=` 
     <style> 
    #javascriptLoader .box {
        color: white;
        background: #00000090;
        text-align: center;
        border: 1px solid #fffbfb;
        transform: translate(-50%,-50%);
        position: absolute;
    }
     </style>`  
     
     customLoader = document.getElementById(javascriptLoader) 
     customLoader.insertAdjacentHTML( 'beforeend', css ); 
     

    Circled Theme

    circle.thumb.gif.ce1ae32a25ba69a0059f2bbaf7927b5a.gif

    javascriptLoader_v1.0_circlered.css

    let javascriptLoader = "javascriptLoader";
    let css = `
    <style>
      #${javascriptLoader} .box {
        color: gray;
        background: whitesmoke;
        text-align: center;
        position: absolute;
        cursor: pointer;
        border: 2px solid gray;
        border-radius: 50%;
        height: 120px;
        width: 120px;
        display: flex;
        justify-content: center;
        align-items: center;
      }
    </style>
    `;
    
    let customLoader = document.getElementById(javascriptLoader);
    customLoader.insertAdjacentHTML('beforeend', css);
     

    Insert Theme

    Insert Theme

    insert.thumb.gif.680993aba6648aa6c2f1e2a11b739675.gif

    javascriptLoader_v1.0_InsEarth.css

    javascriptLoader_v1.0_InsEarth.css

     javascriptLoader = "javascriptLoader" 
     css=` 
     <style> #javascriptLoader .box{   
         color:black;   
         background:whitesmoke;   
         text-align:center;
         border:0px solid black;   
         box-shadow: inset -1px 5px 12px 0px;   
         transform: translate(-50%,-50%);    
         position: absolute;   width:333px; 
       } 
     </style>`  
     
     customLoader = document.getElementById(javascriptLoader) 
     customLoader.insertAdjacentHTML( 'beforeend', css ); 
     

    Inserted Circle

    You can combine themes! just add them both. Note: The order of script precedence is important
    circledinsert.thumb.gif.6f1d232dd043dd4cd6fb12f7e89af689.gif
     
     
     

    Blurred Rings Theme

    rings.thumb.gif.00106000a198301fd98a5be2a797fe5b.gif

     

      

    javascriptLoader_v1.0_SabbleRings.css

    javascriptLoader = "javascriptLoader"
    
    contents = document.querySelector(`#${javascriptLoader} .box`)
    
    html=`
    <main>
    
      <div class="loading_container">
        <div class="loading_text"><div class="glow">
    	${contents.innerHTML}
    	</div></div>
        <article>
          <div class="bar_1"></div>
          <div class="bar_2"></div>
          <div class="bar_3"></div>
          <div class="bar_4"></div>
        <article>
      </div>
    
    </main>
    
    <style>
    main {
       font-family: 'Roboto', sans-serif;
    }
    
    /* === Below, the circles main color.
    if you want different colors for each circle, just replace to 'var(--main-color-circle)' in box-shadow of .bar_1, .bar_2, .bar_3 and .bar_4 to the color you want.
    === */
    :root {
        --main-color-circle: #555;
    }
    
    .loading_container {
      height:30vw;
    }
    
    /* === How much blur you want on the circles === */
    .loading_container article {
        width: 100%;
        height: 100%;
        filter: blur(1.2vw);
    }
    
    .loading_text {
        width: 100%;
        height: 10%;
        position: absolute;
        bottom: 50%;
    }
    
    .title {
      width:100%;
      position:absolute;
      top:5vw;
      text-align:center;
      color:#aff;
      font-weight:100;
      font-size:6vw;
    }
    
    @keyframes center_rotate {
      0% {
        transform:rotateY(0deg);
      }
    
      100% {
        transform:rotateY(1080deg);
      }
    }
    
    .loading_container article div {
      width:80%;
      height:80%;
      position:absolute;
      right: 10%;
      left:10%;
      bottom:10%;
      top:10%;
      border-radius:100%;
    }
    
      .bar_1 {
        box-shadow:inset 0 0 0 2vw #0ff;
        animation:bar_1_trans 6s ease-in-out infinite;
      }
    
      .bar_2 {
        box-shadow:inset 0 0 0 2vw #55f;
        animation:bar_2_trans 6s ease-in-out infinite;
      }
    
      .bar_3 {
        box-shadow:inset 0 0 0 2vw #a5f;
        animation:bar_3_trans 6s ease-in-out infinite;
      }
    
      .bar_4 {
        box-shadow:inset 0 0 0 2vw #aaf;
        animation:bar_4_trans 6s ease-in-out infinite;
      } /* === The one in front === */
    
    @keyframes bar_1_trans {
      0% {
        transform:skewY(0deg) rotateY(0deg);
      }
    
      30% {
        transform:skewY(20deg) rotateY(-180deg);
      }
    
      80% {
        transform:skewY(0deg) rotateY(360deg);
      }
    
      100% {
        transform:skewY(0deg) rotateY(360deg);
      }
    }
    
    @keyframes bar_2_trans {
      0% {
        transform:skewY(0deg) rotateY(0deg);
      }
    
      40% {
        transform:skewY(-10deg) rotateY(-180deg);
      }
    
      70% {
        transform:skewY(10deg) rotateY(400deg);
      }
    
      100% {
        transform:skewY(0deg) rotateY(360deg);
      }
    }
    
    @keyframes bar_3_trans {
      0% {
        transform:skewX(0deg) rotateY(0deg);
      }
    
      25% {
        transform:skewX(-10deg) rotateY(-90deg);
      }
    
      65% {
        transform:skewX(10deg) rotateY(360deg);
      }
    
      100% {
        transform:skewX(0deg) rotateY(360deg);
      }
    }
    
    @keyframes bar_4_trans {
      0% {
        transform:skewX(0deg) rotateY(0deg);
      }
    
      20% {
        transform:skewX(20deg) rotateY(-90deg);
      }
    
      70% {
        transform:skewX(-10deg) rotateY(330deg);
      }
    
      100% {
        transform:skewX(0deg) rotateY(360deg);
      }
    }
    
    .back_buttons {
      position:absolute;
      right:0;
      left:61.8%;
      padding-right:3%;
      padding-left:3%;
      bottom:0;
      background:#ffffff05;
    }
    
    .back_buttons h3, .back_buttons button p {
      color:#555;
      font-weight:400;
      font-family: 'Roboto', sans-serif;
      font-size:19px;
      margin: 8px 0;
      text-align:center;
    }
    
    .back_buttons button {
      width:50%;
      margin:1vw auto;
      background:#fffa;
      border:none;
      display:table;
      cursor:pointer;
    }
    
      .back_buttons button:focus {
        outline:none;
      }
    
        .black_back:hover {
          background:#111;
        }
    
            .black_back:hover p {
              font-weight:400;
            }
    
        .white_back:hover {
          background:#eee;
        }
    
            .white_back:hover p {
              color:black;
              font-weight:400;
            }
    
    .developed {
      position:absolute;
      bottom:5%;
      left:5%;
      right:50%;
      color:#ddd;
      font-weight:100;
    }
    
     .glow  {
      color: #fff;
      text-shadow: 0 0 2px #fff, 0 0 10px #fff, 0 0 20px #0ba9ca, 0 0 30px #0ba9ca, 0 0 40px #0ba9ca, 0 0 50px #0ba9ca;
      animation: blink 1.5s infinite alternate;
    }
     @keyframes blink {
      100% {
       text-shadow: 0 0 2px #fff, 0 0 10px #fff, 0 0 20px #fff, 0 0 40px #0ba9ca, 0 0 70px #0ba9ca, 0 0 80px #0ba9ca;
    	}
    }
    
    </style>
    `
    
    contents.innerHTML = html
    
    javascriptLoader = "javascriptLoader"
    css=`
    <style>
    #${javascriptLoader} .box{
        xcolor: blue;
        background: unset;
        text-align: center;
    }
    </style>`
    
    customLoader = document.getElementById(javascriptLoader)
    customLoader.insertAdjacentHTML( 'beforeend', css );
     


    Back to the JavaScript Component Hub for Spotfire

    Disclaimer

    This method makes assumptions about the inner workings of Spotfire version 12.0.0 LTS Any analyses created using this hack may cause instability and performance issues and are likely to break when Spotfire is upgraded or hotfixed in the future.  Any issues resulting from applying this hack are not covered by Spotfire maintenance agreements and Support will not assist in troubleshooting problems with analysis files where this approach is used.
     

     

     

     

     

     

     


    User Feedback

    Recommended Comments


×
×
  • Create New...