<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css" />
<style>
button {
margin-right : 10px;
width : 100px;
}
input {
width : 430px;
}
</style>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="//public.tableau.com/javascripts/api/tableauwdc-1.1.0.js"></script>
</head>
<body ng-app="app" ng-controller="Main as ctrl" ng-cloak="">
<div class="container">
<h1>Tableau 9.1 WDC Sample</h1>
<section class="container top-buffer">
<p>Copy the following URL, please paste to the Tableau WDC.</p>
<p><input type="text" ng-model="ctrl.siteUrl" disabled="disabled" /></p>
</section>
<section class="container top-buffer">
<button
class="btn btn-primary"
ng-repeat="company in ctrl.companies"
ng-click="ctrl.companyButtonClickHandler(company)">{{company.name}}</button>
</section>
</div>
<script>
(function() {
'use strict';
angular.module('app', [], function(){
})
.config(function($httpProvider) {
if (!$httpProvider.defaults.headers.get) {
$httpProvider.defaults.headers.get = {};
}
$httpProvider.defaults.headers.get['If-Modified-Since'] = (new Date(0)).toUTCString();
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
})
.controller('Main', function($scope, $log/*, $http*/) {
var buildUri = function(tickerSymbol, startDate, endDate) {
var startDateStr = getFormattedDate(startDate);
var endDateStr = getFormattedDate(endDate);
var queryStatement = 'select * from yahoo.finance.historicaldata where symbol = "' +
tickerSymbol +
'" and startDate = "' + startDateStr +
'" and endDate = "' + endDateStr + '"';
return '//query.yahooapis.com/v1/public/yql?q=' +
encodeURIComponent(queryStatement) +
'&diagnostics=true&env=store://datatables.org/alltableswithkeys&format=json';
};
var makeTwoDigits = function(num) {
return num < 9 ? '0' + num.toString() : num.toString();
};
var getFormattedDate = function getFormattedDate(date) {
return date.getUTCFullYear() +
'-' +
makeTwoDigits(date.getUTCMonth() + 1) +
'-' +
makeTwoDigits(date.getUTCDate());
};
var myWDC = tableau.makeConnector();
myWDC.getColumnHeaders = function() {
var fieldNames = ['Ticker', 'Day', 'Open', 'Close', 'High', 'Low'];
var fieldTypes = ['string', 'date', 'float', 'float', 'float', 'float'];
tableau.headersCallback(fieldNames, fieldTypes);
};
myWDC.getTableData = function(lastRecordToken) {
$log.debug('myWDC.getTableData : ', lastRecordToken);
var dataToReturn = [];
var hasMoreData = false;
var ticker = tableau.connectionData;
var endDate = new Date();
var startDate = new Date();
startDate.setYear(endDate.getFullYear() - 1);
var connectionUri = buildUri(ticker, startDate, endDate);
var xhr = $.ajax({
'url' : connectionUri,
'dataType' : 'json'
})
.done(function (data) {
if (data.query.results) {
var quotes = data.query.results.quote;
angular.forEach(quotes, function(quote, key) {
dataToReturn.push({
'Ticker': quote.Symbol,
'Day' : quote.Date,
'Open' : quote.Open,
'Close' : quote.Close,
'High' : quote.High,
'Low' : quote.Low
});
});
tableau.dataCallback(dataToReturn, lastRecordToken, false);
} else {
tableau.abortWithError('No results found for ticker symbol: ' + ticker);
}
})
.fail(function (data, textStatus, jqXHR) {
tableau.log('Connection error: ' + data.responseText + '\n' + jqXHR);
tableau.abortWithError('Error while trying to connect to the Yahoo stock data source.');
}
);
};
tableau.registerConnector(myWDC);
var scope = $scope;
var ctrl = this;
ctrl.companies = [
{'name' : 'AMZN' },
{'name' : 'TWTR' },
{'name' : 'GOOGL'},
{'name' : 'AAPL' }
];
ctrl.siteUrl = window.location.href;
ctrl.companyButtonClickHandler = function(company) {
$log.debug('companyButtonClickHandler : ', company.name);
tableau.connectionName = 'Stock Data for ' + company.name;
tableau.connectionData = company.name;
tableau.submit();
};
});
}());
</script>
</body>
</html>
(function() {
'use strict';
angular.module('app', [], function(){
})
.config(function() {
})
.controller('Main', function($scope) {
var buildUri = function(tickerSymbol, startDate, endDate) {
var startDateStr = getFormattedDate(startDate);
var endDateStr = getFormattedDate(endDate);
var queryStatement = 'select * from yahoo.finance.historicaldata where symbol = "' +
tickerSymbol +
'" and startDate = "' + startDateStr +
'" and endDate = "' + endDateStr + '"';
return 'http://query.yahooapis.com/v1/public/yql?q=' +
encodeURIComponent(queryStatement) +
'&diagnostics=true&env=store://datatables.org/alltableswithkeys&format=json';
};
var makeTwoDigits = function(num) {
return num < 9 ? "0" + num.toString() : num.toString();
};
var getFormattedDate = function getFormattedDate(date) {
return date.getUTCFullYear() +
'-' +
makeTwoDigits(date.getUTCMonth() + 1) +
'-' +
makeTwoDigits(date.getUTCDate());
};
var myConnector = tableau.makeConnector();
myConnector.getColumnHeaders = function() {
var fieldNames = ['Ticker', 'Day', 'Close', 'Open', 'Close', 'High', 'Low'];
var fieldTypes = ['string', 'date', 'float', 'float', 'float', 'float', 'float'];
tableau.headersCallback(fieldNames, fieldTypes);
};
myConnector.getTableData = function(lastRecordToken) {
var dataToReturn = [];
var hasMoreData = false;
var ticker = tableau.connectionData;
var endDate = new Date();
var startDate = new Date();
startDate.setYear(endDate.getFullYear() - 1);
var connectionUri = buildUri(ticker, startDate, endDate);
var xhr = $.ajax({
'url' : connectionUri,
'dataType' : 'json',
'success' : function (data) {
if (data.query.results) {
console.log('data.query.results : ', data.query.results);
var quotes = data.query.results.quote;
var ii;
for (ii = 0; ii < quotes.length; ++ii) {
var quote = quotes[ii];
var entry = {
'Ticker': quote.Symbol,
'Day' : quote.Date,
'Open' : quote.Open,
'Close' : quote.Close,
'High' : quote.High,
'Low:' : quote.Low
};
}
tableau.dataCallback(dataToReturn, lastRecordToken, false);
}
else {
tableau.abortWithError("No results found for ticker symbol: " + ticker);
}
},
'error' : function (xhr, ajaxOptions, thrownError) {
tableau.log("Connection error: " + xhr.responseText + "\n" + thrownError);
tableau.abortWithError("Error while trying to connect to the Yahoo stock data source.");
}
});
};
tableau.registerConnector(myConnector);
var scope = $scope;
var ctrl = this;
ctrl.companies = [
{'name' : 'AMZN' },
{'name' : 'TWTR' },
{'name' : 'GOOGL'},
{'name' : 'AAPL' }
];
ctrl.siteUrl = window.location.href;
ctrl.companyButtonClickHandler = function(company) {
$log.debug('ctrl.companyButtonClickHandler : ', company);
tableau.connectionName = 'Stock Data for ' + company.name;
tableau.connectionData = companyName;
tableau.submit();
};
});
}());
/* Styles go here */
(function() {
var versionNumber = "1.1.0";
var _sourceWindow;
if (typeof tableauVersionBootstrap === 'undefined') {
// tableau version bootstrap isn't defined. We are likely running in the simulator so init up our tableau object
tableau = {
connectionName: "",
connectionData: "",
password: "",
username: "",
incrementalExtractColumn: "",
initCallback: function () {
_sendMessage("initCallback");
},
shutdownCallback: function () {
_sendMessage("shutdownCallback");
},
submit: function () {
_sendMessage("submit");
},
log: function (msg) {
_sendMessage("log", {"logMsg": msg});
},
headersCallback: function (fieldNames, types) {
_sendMessage("headersCallback", {"fieldNames": fieldNames, "types":types});
},
dataCallback: function (data, lastRecordToken, moreData) {
_sendMessage("dataCallback", {"data": data, "lastRecordToken": lastRecordToken, "moreData": moreData});
},
abortWithError: function (errorMsg) {
_sendMessage("abortWithError", {"errorMsg": errorMsg});
}
};
} else { // Tableau version bootstrap is defined. Let's use it
tableauVersionBootstrap.ReportVersionNumber(versionNumber);
}
// Check if something weird happened during bootstraping. If so, just define a tableau object to we don't
// throw errors all over the place because tableau isn't defined
if (typeof tableau === "undefined") {
tableau = {}
}
tableau.versionNumber = versionNumber;
tableau.phaseEnum = {
interactivePhase: "interactive",
authPhase: "auth",
gatherDataPhase: "gatherData"
};
if (!tableau.phase) {
tableau.phase = tableau.phaseEnum.interactivePhase;
}
// Assign the functions we always want to have available on the tableau object
tableau.makeConnector = function() {
var defaultImpls = {
init: function() { tableau.initCallback(); },
shutdown: function() { tableau.shutdownCallback(); }
};
return defaultImpls;
};
tableau.registerConnector = function (wdc) {
// do some error checking on the wdc
var functionNames = ["init", "shutdown", "getColumnHeaders", "getTableData"]
for (var ii = functionNames.length - 1; ii >= 0; ii--) {
if (typeof(wdc[functionNames[ii]]) !== "function") {
throw "The connector did not define the required function: " + functionNames[ii];
}
};
window._wdc = wdc;
};
function _sendMessage(msgName, msgData) {
var messagePayload = _buildMessagePayload(msgName, msgData);
// Check first to see if we have a messageHandler defined to post the message to
if (typeof window.webkit != 'undefined' &&
typeof window.webkit.messageHandlers != 'undefined' &&
typeof window.webkit.messageHandlers.wdcHandler != 'undefined') {
window.webkit.messageHandlers.wdcHandler.postMessage(messagePayload);
} else if (!_sourceWindow) {
throw "Looks like the WDC is calling a tableau function before tableau.init() has been called."
} else {
_sourceWindow.postMessage(messagePayload, "*");
}
}
function _buildMessagePayload(msgName, msgData) {
var msgObj = {"msgName": msgName,
"props": _packagePropertyValues(),
"msgData": msgData};
return JSON.stringify(msgObj);
}
function _packagePropertyValues() {
var propValues = {"connectionName": tableau.connectionName,
"connectionData": tableau.connectionData,
"password": tableau.password,
"username": tableau.username,
"incrementalExtractColumn": tableau.incrementalExtractColumn,
"versionNumber": tableau.versionNumber};
return propValues;
}
function _applyPropertyValues(props) {
if (props) {
tableau.connectionName = props.connectionName;
tableau.connectionData = props.connectionData;
tableau.password = props.password;
tableau.username = props.username;
tableau.incrementalExtractColumn = props.incrementalExtractColumn;
}
}
function _receiveMessage(evnt) {
var wdc = window._wdc;
if (!wdc) {
throw "No WDC registered. Did you forget to call tableau.registerConnector?";
}
if (!_sourceWindow) {
_sourceWindow = evnt.source
}
var payloadObj = JSON.parse(evnt.data);
var msgData = payloadObj.msgData;
_applyPropertyValues(payloadObj.props);
switch(payloadObj.msgName) {
case "init":
tableau.phase = msgData.phase;
wdc.init();
break;
case "shutdown":
wdc.shutdown();
break;
case "getColumnHeaders":
wdc.getColumnHeaders();
break;
case "getTableData":
wdc.getTableData(msgData.lastRecordToken);
break;
}
};
// Add global error handler. If there is a javascript error, this will report it to Tableau
// so that it can be reported to the user.
window.onerror = function (message, file, line, column, errorObj) {
if (tableau._hasAlreadyThrownErrorSoDontThrowAgain) {
return true;
}
var msg = message;
if(errorObj) {
msg += " stack:" + errorObj.stack;
} else {
msg += " file: " + file;
msg += " line: " + line;
}
if (tableau && tableau.abortWithError) {
tableau.abortWithError(msg);
} else {
throw msg;
}
tableau._hasAlreadyThrownErrorSoDontThrowAgain = true;
return true;
}
window.addEventListener('message', _receiveMessage, false);
})();