<!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);
})();