Skip to Content
Technical Articles

Preventing Custom Widget Flicker on App Startup

This post will explain how to make sure that THIS does not happen in your widget!

 

Mantra: You should render from onCustomWidgetAfterUpdate() but only after connectedCallback() has run the first time. You should also re-render whenever connectedCallback() or onCustomWidgetResize() are run.

 

If you followed along during my tutorial series on building your first widget, you might have noticed a bit of mysterious code that shows up in the github projects, that I either don’t mention in the blog posts, or brush past.

We were redrawing our widget in a separate redraw() function. This makes sense, because there are multiple things that could cause a widget to need re-rendering. E.g.

  • We could change a property.
  • We could resize the widget in the canvas.
  • We could be adding the widget to the canvas.

But….

From onCustomWidgetAfterUpdate()

onCustomWidgetAfterUpdate(oChangedProperties) {
    if (this._firstConnection){
    	this.redraw();
    }
}

 

And from connectedCallback()

connectedCallback () {
    this._firstConnection = true;
    redraw();
}

 

What is this this._firstConnection business?

 

Most of the time, callbacks execute on their own

onCustomWidgetAfterUpdate() – This is called every time that the property is updated. When the default value is applied, this method is also called. If your variable changes affect what is shown on screen, you’ll want to render here.

connectedCallback() – This is called every time tour widget is brought into life in the DOM. The app starts? This method is called. Your widget was hidden and brought back to life? This method is called. Obviously, you’ll need to render here.

onCustomWidgetResize() – This method is called every time that your widget is resized on screen. You’ll want to render here too.

During most of your widget’s life cycle, only one callback is being called at a time. A script changes a property, or a user changes its value in the design panel? onCustomWidgetAfterUpdate () is called by itself. A panel with a widget is opened? connectedCallback() is called. The screen is resized? onCustomWidgetResize() is called.

 

Except on startup

On startup, both onCustomWidgetAfterUpdate () AND connectedCallback() are called and onCustomWidgetAfterUpdate() is called before connectedCallback()! The problem that we have is that we want to redraw the widget whenever a property is changed and we also want to redraw whenever we reconnect to the widget, but at the time of initial widget bootstrapping, both are called. This can cause your widget to flicker, as it redraws itself multiple times. That is what happened in the video, above.

  • Add a variable to the constructor to track whether or not connectedCallback has been called before. We’ll call it this._firstConnection. Initialize it to false.
  • In the onCustomWidgetAfterUpdate() check to ensure that this._firstConnection is true, before rendering.
  • On startup, when this callback is called the first time, this check will return false and render won’t happen.
  • Whenever connectedCallback() is called, set this._firstConnection to true and then render. On startup, this is where render will happen.
  • From now on, whenever either of these callbacks is called, the widget will re-render.

In the below snippet, the render code would go in redraw()

(function()  {
    let tmpl = document.createElement('template');
    tmpl.innerHTML = `
        <h1>Hello World</h1>
    `;

    customElements.define('com-sap-sample-helloworld1', class HelloWorld1 extends HTMLElement {


		constructor() {
			super(); 
			this._shadowRoot = this.attachShadow({mode: "open"});
            this._shadowRoot.appendChild(tmpl.content.cloneNode(true));
            this._firstConnection = false;
		}

        //Fired when the widget is added to the html DOM of the page
        connectedCallback(){
            this._firstConnection = true;
            this.redraw();
        }

         //Fired when the widget is removed from the html DOM of the page (e.g. by hide)
        disconnectedCallback(){
        
        }

         //When the custom widget is updated, the Custom Widget SDK framework executes this function first
		onCustomWidgetBeforeUpdate(oChangedProperties) {

		}

        //When the custom widget is updated, the Custom Widget SDK framework executes this function after the update
		onCustomWidgetAfterUpdate(oChangedProperties) {
            if (this._firstConnection){
                this.redraw();
            }
        }
        
        //When the custom widget is removed from the canvas or the analytic application is closed
        onCustomWidgetDestroy(){
        }

        
        //When the custom widget is resized on the canvas, the Custom Widget SDK framework executes the following JavaScript function call on the custom widget
        // Commented out by default.  If it is enabled, SAP Analytics Cloud will track DOM size changes and call this callback as needed
        //  If you don't need to react to resizes, you can save CPU by leaving it uncommented.
        /*
        onCustomWidgetResize(width, height){
            redraw()
        }
        */

        redraw(){
        }
    });
})();

 

Hopefully, this will help you avoid double redraws on startup.  Happy Widget Building!

Be the first to leave a comment
You must be Logged on to comment or reply to a post.