Community Spring Cleaning week is here! Join your fellow Maveryx in digging through your old posts and marking comments on them as solved. Learn more here!

Engine Works

Under the hood of Alteryx: tips, tricks and how-tos.
ned_blog
8 - Asteroid

Alteryx Web Services and the applications they support have been in development for some time. Very shortly, we will release the beginning of a public Web Services API that will allow customers and partners alike to begin consuming Alteryx Web Services to support internal business intelligence needs.

 

The basic configuration

<!DOCTYPE html PUBLIC "-//WEC//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="https://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
	<title>Extend 2010 - Developer Summit</title>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
	
	<link href="./css/src.ac.Loader.css" type="text/css" rel="stylesheet" />
	
	<script type="text/javascript" src="./source/lib/prototype-1.6.0.2.js"></script>
	<script type="text/javascript" src="./source/src.ac.js"></script>
	<script type="text/javascript" src="./source/clientConfiguration.js"></script>
	
	<script type="text/javascript">
	//<![CDATA[
	//]]>
</script>
</head>
<body>
</body>
</html>

 

I'll spend a little time discussing how users might begin consuming Alteryx Web Services from the context of a simple web page. So let's start with a simple page that does nothing but load the Alteryx Web Services client UI library...and nothing more.

 

This document doesn't render anything, and is certainly lacking in the utility category. There are however, some lines of code that provide a hint of what is to come. Specifically the <script/> tags that load the prototype-1.6.0.2, src.ac.js and clientConfiguration script libraries. Nestled in the src.ac.js library is the Alteryx src.ac.Loader class. The Loader class is responsible for loading any dependent JavaScript libraries to the page, while at the same time providing a status dialog that lets the user know what is happening.

 

...

var loader = null;

function loadSource() {
  loader = new src.ac.Loader();
  
  //Add the script.aculo.us libraries first...
  loader.addScript( './source/lib/builder.js', statusCallback );
  loader.addScript( './source/lib/effects.js', statusCallback );
  loader.addScript( './source/lib/dragdrop.js', statusCallback );
  loader.addScript( './source/lib/dragdropextra.js', statusCallback );
  
  //Add the SRC Map API...
  loader.addScript( './source/SRCOpenLayers-2.7.js', statusCallback );
  
  //Add the compiled Alteryx Connect library
  loader.addScript ( './source/AlteryxConnect-1.4.18356.js', completeCallback );
  
  loader.load();
}

function statusCallback( source, loaded ) {
  if (loaded == false);
    loader.setStatusMessage( '' + source.substring(source.lastIndexOf( '/' ) +1) + '' );
}

function completeCallback( source, loaded ) {
  if (loaded==false) {
    loader.setStatusMessaeg( '' + source.substring(source.lastIndexOf( '/' ) +1) + '' )
  } else {
    //Hide the loader splash screen...
    loader.hideStatusWindow();
    
    //Now that everything's loaded...run the init() method.
    //init();
  }
  
};
  
...

 

Now that I've made mention of it let's add the Loader class to the page and load the Alteryx Web Services library. To do this, we're actually going to create three new functions on the page: loadSource, statusCallback, and completeCallback.

 

...

//The document intialization code - when the DOM is loaded, run loadSource().
document.observe('dom:loaded', function() {
  var bc = new src.ac.BrowserCheck();
  if (bc.isBrowserAcceptable() == false)
    alert('Unsupported browser...sorry.');
  else
    loadSource();
});

...

 

If we do nothing, the page should load, but we need to signal to the page to execute the loadSource method at the page load.

 

...

var appKey = 'D4C869F6C407479EBA86B4A92D03B232'; //FreeD Beta
var appName = "Developer Example";
var componentBus = null;
var _errorDialog = null;

//This is a simple onerror handler function that will alert us to any problems that have occured.
function showError( message ) {
  //alert(message);
  this._errorDialog = new src.ac.ui.ErrorDialog( message );
  this._errorDialog.error();
};

//The main initialization routine
function init() {
  //Initialize the ComponentBus class - the main communication and management class.
  componentBus = new src.ac.ComponentBus();
  
  //Register the 'onerror' event in case anything goes wrong, we'll get notified.
  componentBus.registerEvent( 'onerror', showError );
  
  //Load the user information...this of course would be a dialog used to load information as opposed to hard-coding the application.
  componentBus.setApplicationKey( appKey );
};

...

 

Now, when we load the page, the Alteryx Web client UI will be loaded and we can begin to do some interesting things with the UI library. But before we actually load any libraries, we need to create an init() method that will initialize a class called the ComponentBus. The ComponentBus is responsible for managing all communication with the Alteryx Web Services server and returning the result to classes that register themselves with the ComponentBus.

 

That's it. That's the basis of an Alteryx Web client application. We can add new and interesting functionality to our site now that includes searching for geographies and rendering reports.

 

Adding some content

...

var viewer = null;
var userDataManager = null;
var geographyManager = null;
var reportManager = null;

...

 

While it's fine to say I've loaded the Alteryx Web UI, it's more interesting if I begin to take advantage of the vast number of controls at my disposal; one in particular, is the Viewer control. The Viewer control is responsible for rendering report content on the page in a nice, neat, tidy container. I'll begin by adding a few new classes that will help me find available reports and manage and search for geographies.

 

I'll begin by adding a couple of global variable references:

...

//The main initialization routine...

function init() {

  ...
  
  //The very next thing we do is create a ReportManager and GeographyManager component.
  //By doing this, we can monitor (via classes) changes in the active report and geography.
  userDataManager = new src.ac.ui.UserDataManager( componentBus );
  reportManager = new src.ac.ui.ReportManager( componentBus );
  geographyManager = new src.ac.ui.GeographyManager( componentBus );
  
  geographyManager.registerEvent( 'onsearch_received', searchReceived );
  reportManager.registerEvent( 'onreports_changed', reportsChanged );

};

...

 

And once they are added, I'll make a couple of changes to the init() method to actually load the classes when the page loads.

...

var lastResults = [];
var searchResults_onclick_fx = null;

function reportsChanged( reports ) {
  for (var i = 0; i < reports.length; i++) {
    if (reports[i].name.indexOf('Overview Comparison') == 0) {
      reportManager.setActiveReport( reports[i] );
      return;
    }
  }
};

function searchReceived( results ) {
  lastResults = results;
  var sr = $('mySearchResults');
  while (sr.firstDescendant()) {
    sr.firstDescendant().remove();
  }
  
  for (var i = 0; i < results.length; i++) {
    sr.insert( '<div id="myResultIndex_' + i + '">' + results[i].Name + ' [' + results[i].MatchType + ']</div>' );
  }
};

...

 

Once the classes are created, I want to be notified when certain events occur within the GeographyManager and ReportManager classes. Specifically, I want to know when the list of available reports changes or when a search result is returned. The reasons for doing so will be clear in a few moments. But before we're done, we need to add the handler functions (searchReceived and reportsChanged) that will process the registered events.

 

...

<body>

  <div id="myReport" style="width: 600px; height: 500px; border: 1px solid #666; float: left;"></div>

  <div id="myAddress" style="border: 1px solid #006; float: left;">
  
    <table>
      <tbody>
        <tr>
          <td>Search:</td>
          <td>
            <input id="geoSearch" type="text" style="width: 200px;"/>
          </td>
          <td>
            <input type="button" value="Search" onclick="search();"/>
          </td>
        </tr>
      </tbody>
    </table>
    
    <table>
      <tbody>
        <tr>
          <td>
            <div id="mySearchResults"/>
          </td>
        </tr>
      </tbody>
    </table>
    
  </div>
         
  <div style="clear: both;" />
  
</body>

...

 

Now I have a couple of event handler functions that reference some HTML elements that don't yet exist...so I'll create them.

Part of the code we added includes a search text box. We'd like users to be able to enter any geography into the search bar and change the report content based on the search result. To meet these needs, we add two final functions that will display the search results, allow a user to select one from the list and transform the selected search result into a new src.ac.GeographyCollection.

 

...

function searchResults_onclick( e ) {
  var id = e.element().id;
  if (id.indexOf('myResultIndex_') == 0) {
    id = id.replace( /^myResultIndex\_/gi, '' );
    var index = parseInt(id);
    
    var gc = createGeography( lastResults[index] );
    if (gc)
      geographyManager.setActiveGeography( gc );
    else
      alert('Unable to create a geography definition.');
  }
};

function createGeography( result, radii ) {
  if ((!radii) || (!(radii instanceof Array)))
    radii = [1,3,5];
  var gc = null;
  var label = (result.Label ? result.Label : (result.Name ? result.Name : result.name ) );
  var x = (result.Longitude ? result.Longitude : (result:Lon ? result.Long : null));
  var y = (result.Latitude ? result.Latitude : (result:Lat ? result.Lat : null));
  
  if (result.MatchType == 'Address') {
    gc = new src.ac.GeographyCollection( null, label, 'Address', {x: x, y: y} );
    radii.each( function ( r ) {
      gc.addGeography( new src.ac.RadiusGeography( null, label, label, r, x, y, 'Miles' )
    });
  } else if (result.MatchType == 'StandardGeography') {
    gc = new src.ac.GeographyCollection( null, label, '', null );
    gc.addGeography( new src.ac.StandardGeography( null, label, label, result.ShortGeoName, result.GeoKey, geographyManager._activeDataset.key ) );
  }
  return gc;
};

...

 

That's it. A simple, AJAX-driven Alteryx Web Services client!