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
        

    User-added image

    Before

    User-added image

    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 execution ends. 

    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

     //script parameters 
     customLoaderId = "javascriptLoader"  
     
     //Where is that spinning icon on the bottom right corner of the screen? 
     target = document.querySelector("[class^='sf-svg-loader']")  
    
     loader = document.getElementById(customLoaderId) 
     loader.hidden=true;  
     
     //wrap contents to box for theme 
     loader.innerHTML = `<div class="box">${loader.innerHTML}</div>`  
     
     //Show or hide the loader depending on the target style display state 
     trigger = function(x){ 	
        loader.hidden = target.style.display=="none" 	
        console.log("loader is now "+ (loader.hidden?"hidden":"visible")) 
      }  
     
     //Run the trigger function if the target attribute changes  
     mutationObserver = new MutationObserver(trigger) 
     mutationObserver.observe( target , {attributes:true} );  
    
     //hide progress overlay 
     loader.onclick  = function(){
       loader.hidden = true
       console.log("loader was clicked") 
     }  
     
     //add css 
     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

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

    Circled Theme

     

    User-added imageUser-added image

    javascriptLoader_v1.0_circlered.css

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

    Insert Theme

    Insert Theme

    User-added imageUser-added image

    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 ); 
     

    Blurred Rings Theme

    User-added imageUser-added image

     

      

    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

    There are no comments to display.


×
×
  • Create New...