Toan Hoang

Creating a Tableau Extension / Part Four

In Creating a Tableau Extension / Part One we introduced you to Git, GitHub, Yarn, Visual Studio Code as well as Bootstrap, jQuery and the Tableau Extension API library. In Creating a Tableau Extension / Part Two we learned how to retrieve data from a worksheet and to display this data using a third-party visualisation library, Chart.js, yep, we built an interactive doughnut chart. In Creating a Tableau Extension / Part Three we built a configuration mechanism to allow users to set values to control the extension.

In this final tutorial, we will we work putting into place listeners to enable our Extension to listen to changes, and actions, within our Tableau Dashboard.

Part 4: Listeners

The way that Listeners work is that we can register our Extension to listen to events that will be sent by Tableau. As such, we will need to code in what events we want to listening to, as well as the mechanism to stop listening.

Our starting point for this tutorial will be the output of Creating a Tableau Extension / Part Three. If you have not done so, open up your directory, or if you have skipped the first three parts, use Git to clone the repository to your local machine:

git clone https://github.com/tableaumagic/tableau-extensions-tutorial-part-three

Now, as mentioned in part three, we already added a listener to our extension to ensure that when settings change, the extension will redraw. Let us explore how we achieved this by opening up the application.js and looking at the first several lines of code:

'use strict';

(function () {
   $(document).ready(function () {
      tableau.extensions.initializeAsync({ 'configure':configure }).then(function () {
         drawChartJS();
         unregisterSettingsEventListener = tableau.extensions.settings.addEventListener(tableau.TableauEventType.SettingsChanged, (settingsEvent) => {
               drawChartJS();
            });
         }, function () { console.log('Error while Initializing: ' + 
      err.toString());
   });
});

So to break this down:

This is the basic mechanics of how we can listen to events within the Tableau Dashboard and update our Extension accordingly. Now let us explore the additional three listeners that are available to us:

We are going to add some more listeners to our Extension.

Note: You will want to save your unregister function pointers in a place where you will be able to call them in the rest of your code.

FilterChanged

Let us change the application.js to be the following:

'use strict';

(function () {
   let unregisterFilterEventListener = null;
   let worksheet = null;
   let worksheetName = null;
   let categoryColumnNumber = null;
   let valueColumnNumber = null;

   $(document).ready(function () {
      tableau.extensions.initializeAsync({ 'configure':configure }).then(function () {
         // Draw the chart when initialising the dashboard.
         getSettings();
         drawChartJS();
         // Set up the Settings Event Listener.
         unregisterSettingsEventListener = tableau.extensions.settings.addEventListener(tableau.TableauEventType.SettingsChanged, (settingsEvent) => {
            // On settings change.
            getSettings();
            drawChartJS();
         });
      }, function () { console.log('Error while Initializing: ' + err.toString()); });
   });

   function getSettings() {
      // Once the settings change populate global variables from the settings.
      worksheetName = tableau.extensions.settings.get("worksheet");
      categoryColumnNumber = tableau.extensions.settings.get("categoryColumnNumber");
      valueColumnNumber = tableau.extensions.settings.get("valueColumnNumber");

      // If settings are changed we will unregister and re register the listener.
      if (unregisterFilterEventListener != null) {
         unregisterFilterEventListener();
      }

      // Get worksheet
      worksheet = tableau.extensions.dashboardContent.dashboard.worksheets.find(function (sheet) {
         return sheet.name===worksheetName;
      });

      // Add listener
      unregisterFilterEventListener = worksheet.addEventListener(tableau.TableauEventType.FilterChanged, (filterEvent) => {
         drawChartJS();
      });
   }

   function drawChartJS() {
      worksheet.getSummaryDataAsync().then(function (sumdata) {
         var labels = [];
         var data = [];
         var worksheetData = sumdata.data;
         for (var i = 0; i<worksheetData.length; i++) {
            labels.push(worksheetData[i][categoryColumnNumber-1].formattedValue);
            data.push(worksheetData[i][valueColumnNumber-1].value);
         }

      var ctx = $("#myChart");
      myChart = new Chart(ctx, {
         type: 'doughnut',
         data: {
            labels: labels,
            datasets: [{
backgroundColor: ["#3e95cd", "#8e5ea2", "#3cba9f", "#e8c3b9", "#c45850"],
            data: data
            }]
         }
      });
     });
   }

   function configure() {
      const popupUrl=`${window.location.origin}/dialog.html`;
      let defaultPayload="";
      tableau.extensions.ui.displayDialogAsync(popupUrl, defaultPayload, { height:300, width:500 }).then((closePayload) => {
         drawChartJS();
      }).catch((error) => {
         switch (error.errorCode) {
            case tableau.ErrorCodes.DialogClosedByUser:
            console.log("Dialog was closed by user");
            break;
         default:
            console.error(error.message);
         }
      });
   }
})();

Check the comments in the code for the update, it is quite a big one.

Let us test this change by:

Note: We are rebuilding the Chart.js visualisation each time we change the quick filter. With a bit more time and logic we would be able to animate the data change, but that will be for another day.

With our Extension now listening to Filter Changes, I would like to add a listener for mark changes.

MarkSelectionChanged

Let us change the application.js to be the following:

'use strict';
 
(function () {
 
   let unregisterFilterEventListener = null;
   let unregisterMarkSelectionEventListener = null;
   let worksheet = null;
   let worksheetName = null;
   let categoryColumnNumber = null;
   let valueColumnNumber = null;
 
   $(document).ready(function () {
      tableau.extensions.initializeAsync({ 'configure':configure }).then(function () {
         // Draw the chart when initialising the dashboard.
         getSettings();
         drawChartJS();
         // Set up the Settings Event Listener.
         unregisterSettingsEventListener = tableau.extensions.settings.addEventListener(tableau.TableauEventType.SettingsChanged, (settingsEvent) => {
            // On settings change.
            getSettings();
            drawChartJS();
         });
      }, function () { console.log('Error while Initializing: ' +err.toString()); });
   });
 
   function getSettings() {
      // Once the settings change populate global variables from the settings.
      worksheetName = tableau.extensions.settings.get("worksheet");
      categoryColumnNumber = tableau.extensions.settings.get("categoryColumnNumber");
      valueColumnNumber = tableau.extensions.settings.get("valueColumnNumber");
 
      // If settings are changed we will unregister and re register the listener.
      if (unregisterFilterEventListener != null) {
         unregisterFilterEventListener();
      }

      // If settings are changed we will unregister and re register the listener.
      if (unregisterMarkSelectionEventListener != null) {
         unregisterMarkSelectionEventListener();
      }
 
      // Get worksheet
      worksheet = tableau.extensions.dashboardContent.dashboard.worksheets.find(function (sheet) {
         return sheet.name===worksheetName;
      });
 
      // Add listener
      unregisterFilterEventListener = worksheet.addEventListener(tableau.TableauEventType.FilterChanged, (filterEvent) => {
         drawChartJS();
      });

      unregisterMarkSelectionEventListener = worksheet.addEventListener(tableau.TableauEventType.MarkSelectionChanged, (filterEvent) => {
         drawChartJS();
      });
   }
 
   function drawChartJS() {
      worksheet.getSummaryDataAsync().then(function (sumdata) {
         var labels = [];
         var data = [];
         var worksheetData = sumdata.data;
   
         for (var i=0; i<worksheetData.length; i++) {
            labels.push(worksheetData[i][categoryColumnNumber-1].formattedValue);
            data.push(worksheetData[i][valueColumnNumber-1].value);
         }

         var ctx = $("#myChart");
         myChart = new Chart(ctx, { 
            type: 'doughnut', 
            data: { 
               labels: labels, 
               datasets: [{ 
                  backgroundColor: ["#3e95cd", "#8e5ea2", "#3cba9f", "#e8c3b9", "#c45850"],
                  data: data
               }]
            }
         });
      });
   }

   function configure() {
      const popupUrl=`${window.location.origin}/dialog.html`;
      let defaultPayload="";
    
      tableau.extensions.ui.displayDialogAsync(popupUrl, defaultPayload, { height:300, width:500 }).then((closePayload) => {
         drawChartJS();
      }).catch((error) => {
         switch (error.errorCode) {
            case tableau.ErrorCodes.DialogClosedByUser:
               console.log("Dialog was closed by user");
               break;
            default:
               console.error(error.message);
         }
      });
   }
})();

As you can see we have not made too many changes to the application.js, but what we did do is the following:

As with above, now test your Extension on the Tableau Workbook. Your Extension should now update based on a change in Quick Filters and also when you select data marks on the visualisation.

Note: FilterChanged and MarkSelectionChanged event listeners are applied to a worksheet. SettingsChanged is applied to an Extension. 

There is only one more task to look at, which is ParameterChanged and how to set up the listener for that. However, this is something I would like you to look into and discover for yourself, after all, it is the best way of learning and you can find the required information here: https://github.com/tableau/extensions-api/tree/master/Samples/Parameters, and also via the official API Reference here: https://tableau.github.io/extensions-api/docs/index.html

You can find my repository for this project here: https://github.com/simplifyinsights/tableau-extensions-tutorial-part-four

Summary

In this tutorial, we looked at modifying our Extension to listen to events fired by Tableau, captured these events, and then updated our Extension. The four main event types are:

All I can say now is go forth and build your extensions and do not forget to send it to me, I would love to know what you all build. and with that said, it is a wrap for this lengthy series on how to create a Tableau Extension. We also looked at:

If you like our work, do consider supporting us on Patreon, and for supporting us, we will give you early access to tutorials, exclusive videos, as well as access to current and future courses on Udemy:

Also, do be sure to check out our various courses:

Exit mobile version