Date slider in Tableau provides an easy way to refine date period displayed in Tableau viz. However, when speaking of large volumes of data, for the sake of performance, it might be necessary to display default date slider range for shorter time coverage. This is especially true for events analytics which require certain actions to be based inpromptu.

Update: 2019-03-19

Even if Tableau introduced .trex objects which allows you to automate big part of the Tableau desktop activities, JS insertion is still pretty common. Looking back at the original post (almost 2 years ago!) it’s funny how my JS and Tableau skills improve. I would like to share improved version of the lastperiod file which is easier to understand. Please note that part of the script (filter change) might not work in IE < Edge. This might require additional polyfill.

The problem

Problems start with viz design. There is an option to setup date filter as Relative but this option will ‘freeze’ end date for today. Therefore, same period analytics in history will not be available. How about absolute filter? If you set it to 30 days (even if you alter only one end), then .twb(x) file will have default date slider range fixed for good:

30 day slider Tableau JS API

Note the syntax Tableau uses in the file. You will find it familiar in a moment.

It looks like, that with the current setting it’s impossible to have both features in one dashboard: to have last 30 days shown when dashboard is loaded and to have fully fledged date slider you can play with.

The solution

This is where Tableau JS API comes handy. Actually it’s a pretty nice hack which was presented to me by Bryant Howell in his blog post here https://tableauandbehold.com/2017/02/02/defaulting-to-today-relative-date-on-a-date-range-filter-in-tableau/ . His solution was almost complete, however, in my case required couple more tweaks to work without issues.

Overview

General idea is to use Tableau JS API on existing dashboard in a way that it will change default filtering once and then allow user to change dashboard however needed. It’s fairly easy task when dashboard is being embedded on customer portal of any kind or filter is being provided from outside. In this case, I needed to have Javascript logic embedded in the published dashboard.

The hack is based on Tableau Server capability to import web data connectors which are nothing more than Javascript / HTML files containing instructions to fetch data from third party services. If one can use script to import data why not run Tableau JS API commands itself?

Required files

Script that we are about to use, requires couple .js files to run. They are available without any problems over the Internetz, so you shouldn’t have problems downloading/referencing them. We need:

  • jQuery.js file
  • Moment.js file

jQuery.js is a library for the jQuery (duh!) which is a fast, small, and feature-rich JavaScript library you can use to manipulate your pages in HTML. It can be downloaded from jquery.com or you can reference it directly in the script (as I did).

Moment.js is a library which makes date manipulation in Javascript easier. It can be downloaded from momentjs.com.

You can download / reference either *.min.js or regular *.js versions of the files. In most of cases they differ only in file size.

Importing Web Data connectors

For Tableau Server then the easiest way to make your script files available in the viz, is to simply import then to Tableau Server directory and then refer to then directly on the Viz. In our case, files that would need to be imported are lastperiod.html and moment.min.js file. To do so, you need to have access to machine where Tableau Server is installed, copy files to the temporary folder, run cmd.exe, navigate to tabadmin location (most likely c:\Program Files\Tableau\Tableau Server\10.x\bin\) and fire up commands:

tabadmin import_webdataconnector d:\fileLocation\lastperiod.html --overwrite
tabadmin import_webdataconnector d:\fileLocation\moment.min.js --overwrite

after each prompt you should receive confirmation:

30 day slider Tableau JS API

For Tableau Online it might be difficult to use such webconnector as most likely running it will cause cross-origin issue..

Using Tableau JS API

When we have moment.min.js file uploaded to server, we can start writing the code that will do the magic. Here’s what worked for me (click to expand):

<!DOCTYPE html>
<html>
    <head>
  <meta http-equiv="Content-Security-Policy" content="default-src *; style-src 'self' http://* 'unsafe-inline'; script-src 'self' http://* 'unsafe-inline' 'unsafe-eval'" />
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  
        <title>Set Date Filter to Last N Periods </title>
        <script src="jquery-3.2.0.min.js"></script>
        <script src='moment.min.js' async></script>
        <script type="text/javascript">

//Author: mlazecki - www.meowbi.com | please don't remove this copyright

console.log("starting script");
var viz;
var current_sheet;
var filter_found;
var parameters_object = {};
var workbook;
var sheet;
var worksheetArray;
var worksheets;

let date_part = 'days';
let periods_back = 30;

let searchedFilterName = "Data_date";
let searchedParameterName = "date_parameter";

let now_max;
let now_min;

function getTableau() {
  //console.log("getTableau");
  return parent.parent.tableau;
};

function getCurrentViz() {
  //console.log("getcurrentviz");
   return getTableau().VizManager.getVizs()[0];
};


function detectBrowser(){	
  isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
  isFirefox = typeof InstallTrigger !== 'undefined';
  isSafari = /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || safari.pushNotification);
  isIE = /*@cc_on!@*/false || !!document.documentMode;
  isEdge = !isIE && !!window.StyleMedia;
  isChrome = !!window.chrome;
  isBlink = (isChrome || isOpera) && !!window.CSS;
  
  let browser;
  
  
  if (isIE==true){
    browser=3000;
  } else {
    browser=0;
  }

  return browser;
}

async function updateFilter(){
  let filter_blob =[];
  
  let viz = getCurrentViz();
  let sheet=viz.getWorkbook().getActiveSheet();
  let worksheetArray = sheet.getWorksheets();
  let worksheets = sheet.getWorksheets();

  // ** make super tableau viz object * //
  
  let workbook_obj={
    'viz':viz,
    'sheet':sheet,
    'worksheetArray':worksheetArray,
    'worksheets': worksheets,
    'filters':[]
  };

  // ** build filters array * //
  
  for (let worksheet of workbook_obj.worksheetArray){
    let filters = await worksheet.getFiltersAsync();
    
  // ** look for the filter with specified name and add it to returned array * //
    for (let filter of filters){
      let field = await filter.getFieldName();
      if (field.search(searchedFilterName)>=0){        
         let indexu = workbook_obj.worksheetArray.indexOf(worksheet);
        workbook_obj.filters.push(indexu);
        filter_blob.push(field);
        }

    }
  }
  
  // ** iterate through found filters and change start and end value * //
  
  let filtername = filter_blob[0];		
  for (let sheet=0, sheetl=workbook_obj.filters.length;sheet<sheetl;sheet++){
    let sheet_no = workbook_obj.filters[sheet];
    let ff = await workbook_obj.worksheetArray[sheet_no].applyRangeFilterAsync(filtername,
         	{	min: now_min.toDate(),
         		max: now_max.toDate()
         	});
  }

  return true;

}


function updateParameter(){
  let viz = getCurrentViz();
  let workbook = viz.getWorkbook();
  
  let sheet=workbook.getActiveSheet();	
  workbook.changeParameterValueAsync(searchedParameterName, now_min.toDate());
  return true;
  
};


$(window).on('load', function() {

 now_max = moment();
 now_min = moment().subtract(periods_back, date_part);


  // ** start everything up ** //
  let browser = detectBrowser();
  
  // ** old IE tend to fire 'load' event before critical Tableau libraries are loaded.. therefore it's needed to add artificial delay ** //
  setTimeout(function(){
    viz = getCurrentViz();
    workbook = viz.getWorkbook();
    sheet=workbook.getActiveSheet();			
    updateParameter();	
    /** OR **/
    updateFilter();	

  },browser);					
});


   </script>
</head>
<body>
</body>
</html>

Note that in the script there is not reference to Tableau API? This is because we are not embedding dashboard on the custom .html but we are in fact all the time in Tableau environment.

This script can be copy-pasted to lastperiod.html file directly without any changes.

Once you have the file updated, you need to repeat import action for the lastperiod.html file described in the previous point or update the file directly on the server machine in the webdataconnectors folder.

How does it work?

For the main parts of the scripts I’ve added notes in the code but general workflow is as follows:

  • note that filter/parameter that we will be altering is hardcoded at the beginning of the script. Change to fit your viz – remember that this has to reflect physical filter name not the alias
  • window.on(‘load’) checks if all libraries are loaded. This can be replaced by body.on(‘load’) but in this case it won’t change anything as main viz content is loaded dynamically
  • setTimeout function reacts to browser – IE does not fire load event properly and therefore artificial break is needed
  • updateParameter() or updateFilter() function is launched (comment out non-aplicable)
    • updateParameter() – simply takes name of the parameter and makes API call. Situation here is simple as Parameters are stored at datasource level and we don’t have to look for it
    • updateFilter() – here situation is slightly different. We have to find source sheet of the filter placed on the dashboard. Script loops through all sheets and if specified filter name is found, API call is made

 

Setting Default date slider range for Viz

Knowing everything about script, it’s time to implement it onto Tableau Dashboard. All you need to do is pull Web Page object to your dashboard.

30 day slider Tableau JS API

It can be set to Floating and 0px. Nothing will be displayed there as the page itself contains only script, there’s not content. In the Web Page address enter:

http://servername/webdataconnectors/lastperiod.html

What is really important here is the servername. It must exactly (and I mean exactly) the address you see in the Web Browser upon publishing the viz. Otherwise, you will receive cross link error from API.

Publish the dashboard on the same server as you provided in the servername, open the dashboard in the browser and voila! You have your date filtered!

Filter Prerequisites

As this script does most of the work for you, it may not work exactly from the beginning. After many hours trying to debug this script, I’ve come to conclusion that there are few prerequisites that must be fulfilled for this solution to work:

  • DateField name must be consistent with the name you see in the Edit Filter Dialog not with the name on Dashboard.

30 day slider Tableau JS API

  • DateField you reference to in the URL must be placed on the filter shelf of the sheet you’ve selected it from on the dashboard. If you have only one sheet then it’s not a problem but when using multiple sheets on single dashboard you must make sure that slider field is on filter shelf. Not a calculated field based on this field – this field exactly.
  • Filtering granulation must be the same for URL parameter and Field. If you use DatePart = days then DateField on filter shelf should be in the day granulation. Meaning, year(DateField) is no good.
  • filter type must be set to Range of Dates not Special:

30 day slider Tableau JS API

Troubleshooting

I’ve come across couple of strange behaviors using original script and needed to adapt it to own needs. If script does not work for you then see the suggestions below but it might require you to rework the script itself..

  • Filter works but range is only limited to the filtered range (there’s not free slider in the past to play with) – check filters prerequisites, especially granulation point
  • Filter is not working – check filter prerequisites section – especially filter name. If doesn’t help, use browser console to see what’s going on. Before loading the page right click in the server project page and select Inspect, Console. You might find out that there’s a problem with Server.
30 day slider Tableau JS API 30 day slider Tableau JS API
  • I changed the last 30 days filter, refreshed the page and still got this new range – it’s not a bug, it’s a feature. This prevents script from running multiple times in one session. Open dashboard in new tab and it should work
  • The page you are looking for could not be found – check if the web page URL does not contain spelling mistakes and refer to webconnector importing section to make sure that .html file is present on Server30 day slider Tableau JS API