Home > 

JavaScript API > Concepts

Concepts

This topic is designed for people familiar with JavaScript and object-oriented programming concepts. You should also be familiar with Tableau visualizations from a user's point of view. If you are just getting started, a good place to begin is the Tutorial.

Programming Approach

The Tableau JavaScript API uses an object model. The entry point into the object model is to instantiate a new Viz object as follows:

var viz = new tableauSoftware.Viz(/* params omitted */);

Nearly every Tableau object allows you to navigate back to its parent Viz object by way of "parent" properties on the object.

Accessing the API

The API is about programmatically controlling embedded views published to Tableau Server on premise or a Tableau hosted server. To use it, you need access to a server running version 8.0 or later, and a published workbook on that server.

The API is provided through the file tableau_v8.js (minified) or tableau_v8.debug.js. In web pages that contain your JavaScript code for rendering Tableau views, you can reference the API files using the following address:

http://<your_server_name>/javascripts/api/

For example, using the server name localhost, you would add the following code in the page header:

<script type="text/javascript" src="http://localhost/javascripts/api/tableau_v8.js"></script>

The file system location of these files is Program Files\Tableau\Tableau Server\8.2\wgserver\public\javascripts\api.

Working With Tableau Hosted Servers

If you publish workbooks to Tableau Online or Tableau Public, you can access the API that matches the server product you use.

To the URL, add the file name for the API you want to use (tableau_v8.js or tableau_v8.debug.js), as shown in the example code earlier in the Accessing the API section.

Important: For best code stability, use the API location for the server product you work with. For example, if you publish workbooks to an on-premise Tableau Server, use the JavaScript API local to your server.

Bootstrapping

There is only one entry point into the API: instantiating a new Viz object, which creates the HTML necessary to embed a Tableau visualization. To instantiate a new Viz object, simply call the Viz constructor via new, passing the required parentElement and URL parameters and an optional set of options. The URL parameter is where you specify the name of the Tableau server:

var placeholderDiv = document.getElementById("tableauViz");
var url = "http://tabserver/views/workbookname/viewname"; var options = { hideTabs: true, width: "800px",
height: "700px"
};
var viz = new tableauSoftware.Viz(placeholderDiv, url, options);

Trusted Authentication

If Tableau Server is using trusted authentication, specify the ticket in the URL by first adding trusted after the server name, followed by the ticket. For example:

var placeholderDiv = document.getElementById("tableauViz");
var url = "http://tabserver/trusted/Etdpsm_Ew6rJY-9kRrALjauU/views/workbookname/viewname";
var options = { hideTabs: true,
width: "800px",
height: "700px" };
var viz = new tableauSoftware.Viz(placeholderDiv, url, options);

Property Getters and Setters

Getters and setters are always functions that start with get or set. They can be called multiple times with little performance impact (in other words, they should simply return cached fields or do very simple calculations). Properties are always synchronous and return immediately with the value, rather than having a callback function.

Call Behavior - Asynchronous

By default, calls are asynchronous since many of them may require a roundtrip to the server. Methods use the following naming convention:

The Tableau JavaScript API uses the CommonJS Promises/A standard. The premise behind Tableau's implementation is that asynchronous methods return an object that has a then method in the following form:

then(fulfilledHandler, errorHandler)

The fulfilledHandler is called when the promise is fulfilled (on success). The errorHandler is called when a promise fails. All arguments are optional and non-function values are ignored.

Chaining Promises

The promised result of an asynchronous method is passed as the parameter to the next then() method. For example:

var activeSheet;
viz.getWorkbook().activateSheetAsync("Sheet 1") 
  .then(selectLemon).then(filterToLemonAndMint);


function selectLemon(sheet) {
  activeSheet = sheet;
  return sheet.selectMarksAsync("Product", "Lemon", "replace");
}

function filterToLemonAndMint() {
  return activeSheet.applyFilterAsync("Product", ["Lemon", "Mint"], "replace");
}

The result of activateSheetAsync() is a promise to eventually return the Sheet object that was activated, which is passed as the first parameter to the selectLemon() method. Notice that the selectLemon() method returns a Promise object (the return value of the selectMarksAsync() method), not the result after the marks have been selected. However, since it’s a Promise object, the next then() method is not called until that promise is fulfilled.

If a link in the chain is added after the promise has been fulfilled, the callback will be immediately called with the value that was previously returned. As the programmer, this means you don't need to determine if the response has already been received from the server. The asynchronous methods will always be called, whether it's now or later.

var promise = viz.getWorkbook().activateSheetAsync("Sheet 1");

// Pretend that activatSheeteAsync() has already returned from the server.
promise.then(callback);


// callback will be called immediately using the Sheet object
// returned from activateSheetAsync()

Return Values of Then() Methods

Whatever is returned in a then() method will get passed as the first parameter to the next then() method. It can be a scalar value (Number, Boolean, String, etc.), an object, or another Promise. The infrastructure will automatically wrap non-Promise values into a Promise value so that they can be chained.

viz.getWorkbook().activateSheetAsync("Sheet 1")

.then(function (sheet) {
     return "First link";
})

.then(function (message) {
     if (message === "First link") { alert("Expected"); }
     // no return value here means nothing is passed to the next link
})

.then(function () {
});

Breaking Out of a Chain

Technically, there’s no way to break out of a chain since that would invalidate the guarantee that subsequent links in the chain will be called. If there is an exception thrown in part of the chain, the rest of the chain is run but the errorHandler is called instead of the fulfilledHandler.

If a link in the chain depends on the results of earlier links, then you should write an if statement to check your condition. Here's an example:

viz.getWorkbook().activateSheetAsync("Sheet 1")

.then(function (sheet) {
  // I’m returning a Promise
  return sheet.selectMarksAsync("Product", "NoProduct", "replace");
})

.then(function () {
  return viz.getWorkbook().getActiveSheet().getSelectedMarksAsync();
})

.then(function (marks) {
  // The getSelectedMarksAsync call succeeded, but no marks were selected
  // because there are not any marks corresponding to "NoProduct".
  if (marks.length === 0) { 
    throw new Error("No marks selected");
  }

  var firstMarkValue = marks[0].getPairs().get("Product").value;
  return sheet.applyFilterAsync("Product", firstMarkValue, "replace");
})

.then(function (filterName) {
  // applyFilterAsync succeeded

}, function(err) {
  if (err.message === "No marks selected") {
    alert("This was caused by the first link above");
  }
})

.otherwise(function (err) {
  alert("We handled the error above, so it’s not propagated to this handler.");
});

If a callback is not provided (or is null or undefined), then the results are passed to the next link in the chain:

viz.getWorkbook().activateSheetAsync("Sheet 1")
.then()
.then(function (sheet) {
  // this is called
});

In this way, you can specify a single otherwise function to handle all errors in the chain. The always function works the same way, but it is called regardless of success or failure. The then/otherwise/always functions work similarly to a try/catch/finally block.

viz.getWorkbook().activateSheetAsync("Sheet 1")
.then(function () {
     return sheet.selectMarksAsync(...);
})
.then(function (marks) { // Do something with the marks. }) .otherwise(function (err) { // I’m handling all errors in one place. console.log(err.message); }) .always(function () { // Do some cleanup or logging });

Collections

Many classes have collections of items, where each item has a key (typically an ID or a name). Examples include a collection of sheets keyed by name or the list of parameters on a sheet keyed by name. Collections are publicly immutable, exposing read-only functionality. Each Collection array is keyed with its elements’ identifiers. For example, the result of Workbook.getPublishedSheetsInfo() is an array with the index corresponding to the position of the sheet in the workbook. It is also keyed by the sheet name so that you can access it like this:

var sheet = workbook.getPublishedSheetsInfo()[0];
var sameSheet = workbook.getPublishedSheetsInfo().get("Sheet 1");

Collection Interface

Name

Return Type

Description

get(key : string)

Collection item type

Gets the element in the collection associated with the key, or undefined if there is nothing associated with it.

has(key : string)

bool

Returns true if there is an element in the collection associated with the key; otherwise, false.

Events

The Viz class acts as the central event hub. This way you only have to go to one place for all events. It also means that events can be raised on an object that may not have been created yet. For example, the marksselection event can be raised for a particular sheet even though the Sheet object hasn't been created yet. Each event contains an anonymous object with information pertaining to that event, such as the sheet the event occurred on.

Listening to an event is done by calling Viz.addEventListener(type, callback) and passing in a function callback. Here's an example of listening to an event:

viz.addEventListener("marksSelection", function (marks) {
changeMySelectionUI(marks);
});

Removing a listener is done by calling Viz.removeEventListener(type, listener) and passing in the same callback function that was passed into Viz.addEventListener(). For example:

function changeMySelectionUI(marks) {
viz.removeEventListener("marksSelection", changeMySelectionUI);
}
viz.addEventListener("marksSelection", changeMySelectionUI);

Events are multicast delegates, meaning that multiple listeners are supported. The order in which notifications are called is not specified. Every event callback takes a single object containing a pointer to the Viz that raised the event. Each event also adds additional fields to the event, as specified in the API Reference.

Filtering

When you program filtering you are mimicking the user behavior of clicking a filter in a view to narrow down the data that is displayed. Here's an example of filtering on a single value:

worksheet.applyFilterAsync("Container", "Jumbo Box",
   tableauSoftware.FilterUpdateType.REPLACE);

There is a difference between querying existing filter state and setting new or existing filters. Querying filters is done via Worksheet.getFiltersAsync() which returns a collection of Filter classes. Setting filters is done via Worksheet.applyFilterAsync (and its variants) and is a function call that doesn't require you to instantiate a Filter class.

When you specify fields in a filter, you should use the caption as shown in the user interface, not the database field name. For example, you should use Container (the caption) instead of Product Container (the actual field name). In some cases, Tableau Desktop renames fields after they've been dragged to a shelf. For example the Date field might be renamed to YEAR(Date) after being dragged to the rows shelf. In this case, you should use YEAR(Date) as the parameter. The exception is hierarchical filters, which use the full hierarchical name (for example, [Product].[All Product].[Espresso]). Captions can use the optional [] delimiters around names.

Here are samples for many types of filtering:

var worksheet;
viz.getWorkbook().activateSheetAsync("Sheet 4").then(function (sheet) {
  worksheet = sheet;
})

// Single value
.then(function () {
  return worksheet.applyFilterAsync("Product Type", "Coffee",
    tableauSoftware.FilterUpdateType.REPLACE);
})

// Multiple values
.then(function () {
  return worksheet.applyFilterAsync(
    "Product Type", ["Coffee", "Tea"],
    tableauSoftware.FilterUpdateType.REPLACE);
})

// Multiple Values - adding and removing
.then(function () {
  return worksheet.applyFilterAsync("Product", ["Lemon", "Mint"],
    tableauSoftware.FilterUpdateType.ADD);
})

.then(function () {
  return worksheet.applyFilterAsync("Product", ["Caffe Latte", "Green Tea"],
    tableauSoftware.FilterUpdateType.REMOVE);
})

// All values
.then(function () {
  return worksheet.applyFilterAsync("Product Type", "",
    tableauSoftware.FilterUpdateType.ALL);
})

// Date Range
.then(function () {
  return; worksheet.applyRangeFilterAsync("Date", {
    min: new Date(Date.UTC(2010, 3, 1)),
    max: new Date(Date.UTC(2010, 12, 31))
  });
})

// Clearing a Filter
.then(function () {
  return worksheet.clearFilterAsync("Date");
})


// Relative Date
.then(function () {
  return worksheet.applyRelativeDateFilterAsync("Date", {
    anchorDate: new Date(Date.UTC(2011, 5, 1)),
    periodType: tableauSoftware.PeriodType.YEAR,
    rangeType: tableauSoftware.DateRangeType.LASTN,
    rangeN: 1
  });
})

// Quantitative Filters
// SUM(Sales) > 2000 and SUM(Sales) < 4000
.then(function () {
  return worksheet.applyRangeFilterAsync("SUM(Sales)", {
    min: 2000,
    max: 4000
  });
})

// SUM(Sales) > 1000
.then(function () {
  return worksheet.applyRangeFilterAsync("SUM(Sales)", {
    min: 1000
  });
})

// Hierarchical Filters - selecting all on a level
.then(function () {
  return worksheet.applyHierarchicalFilterAsync("[Product].[Product Categories]", {
    levels: [0, 1]
  }, tableauSoftware.FilterUpdateType.ADD);
}, function (err) { /* ignore errors */ })

// Hierarchical Filters - adding one item
.then(function () {
  return worksheet.applyHierarchicalFilterAsync(
    "[Product].[Product Categories].[Product Name]",
    "Accessories.Bike Racks.Hitch Rack - 4-Bike",
    tableauSoftware.FilterUpdateType.REPLACE);
}, function (err) { /* ignore errors */ })

// Hierarchical Filters - adding multiple items
.then(function () {
  return worksheet.applyHierarchicalFilterAsync(
  "[Product].[Product Categories].[Product Name]",
  [
    "Accessories.Bike Racks.Hitch Rack - 4-Bike",
    "Accessories.Bike Stands.All-Purpose Bike Stand"
  ],
  tableauSoftware.FilterUpdateType.REPLACE);
}, function (err) { /* ignore errors */ })

.otherwise(function (err) {
  console.log(err);
});

Selecting Marks

Selecting marks is almost identical to filtering. For filtering,you use one of the Worksheet.applyFilterAsync() methods. For selecting marks, you use Worksheet.selectMarksAsync(). The parameters for mark selection are almost identical to those used for filtering.

worksheet.selectMarksAsync("Product", "Caffe Latte",
tableauSoftware.SelectionUpdateType.REPLACE);

Here are samples of other types of selecting you can use:

var worksheet;
viz.getWorkbook().activateSheetAsync("Sheet 4").then(function (sheet) {
  worksheet = sheet;
})

// Single dimensions work just like filtering

// Single dimension - single value
.then(function () {
  return worksheet.selectMarksAsync("Product", "Mint",
    tableauSoftware.SelectionUpdateType.REPLACE);
})

// Single dimension - Multiple values
.then(function () {
  return worksheet.selectMarksAsync(
    "Product", ["Chamomile", "Mint"],
    tableauSoftware.SelectionUpdateType.REPLACE);
})

// Single dimension - Multiple values (delta)
.then(function () {
  return worksheet.selectMarksAsync("Product", ["Lemon", "Earl Grey"],
    tableauSoftware.SelectionUpdateType.ADD);
})
.then(function () {
  return worksheet.selectMarksAsync(
    "Product", ["Chamomile", "Earl Grey"],
    tableauSoftware.SelectionUpdateType.REMOVE);
})

// Quantitative selection
.then(function () {
  return worksheet.selectMarksAsync({
    "State": ["Florida", "Missouri"],
    "SUM(Sales)": { min: 3000, max: 4000 }
}, tableauSoftware.SelectionUpdateType.REPLACE);
})

// Hierarchical dimensions
.then(function () {
  return worksheet.selectMarksAsync(
    "[Product].[Product Categories].[Category]",
    "Bikes",
    tableauSoftware.SelectionUpdateType.REPLACE);
}, function (err) { /* ignore errors */ })

// Multiple dimensions - multiple values
// ((State = Washington OR Oregon) AND Product = Lemon)
// OR
// (State = Oregon AND Product = Chamomile)
.then(function () {
  return worksheet.selectMarksAsync({
    "State": ["Washington", "Oregon"],
    "Product": "Lemon"
}, tableauSoftware.SelectionUpdateType.REPLACE);
})
.then(function () {
  return worksheet.selectMarksAsync({
    "State": "Oregon",
    "Product": "Chamomile"
}, tableauSoftware.SelectionUpdateType.ADD);
})

// Round-tripping selection
.then(function () {
  return worksheet.selectMarksAsync(
    "Product",
    "Lemon",
tableauSoftware.SelectionUpdateType.REPLACE);
})
.then(function () {
  return worksheet.getSelectedMarksAsync();
}).then(function (marks) {
  // filter out only the Washington and Oregon marks
  var onlyWashingtonAndOregon = [];
  for (var i = 0, len = marks.length; i < len; i++) {
    var m = marks[i];
    var pair = m.getPairs().get("State");
    if (pair &&
      (pair.value === "Washington" || pair.value === "Oregon")) {
      onlyWashingtonAndOregon.push(m);
    }
  }
  return worksheet.selectMarksAsync(
    onlyWashingtonAndOregon,
    tableauSoftware.SelectionUpdateType.REPLACE);
})

.otherwise(function (err) {
  console.log(err);
});