Building Web Apps with SAS
Surprisingly little attention is given to the (huge) potential of the SAS platform for building web apps. On the assumption this is due to sheer ignorance, the aim of this post is to share some of my experience, and give budding SAS web-developers a kick start in the right direction.
But first, what kind of things can you build? Well, anything really, by combining HTML / CSS / Javascript and using SAS as a server side language (via the Stored Process Server) you have an immensely powerful / scalable application development capability.
For instance, to reference some examples of web apps I’ve built over the years:
- Drilldown Reports - Click a number, see the numbers & formula used to create that number, all the way back to source system.
- Build & Test harness - Allowed selection of a development branch from remote GIT repo, checked out all macros / metadata, imported / deployed the jobs, ran entire solution plus tests, scanned logs for warnings / errors, worked for multiple users.
- Release Management System - To manage the entire workflow from creating a DEV package through peer review, checking in / out of SVN, interfacing via APIs to internal and external systems, moving promotion artefacts between SAS environments, reporting etc.
- Data Editor - A generic browser based tool for making auditable changes to data, moving the data through an approvals process (showing exact changes to base) with signoff resulting in immediate loading to the target table.
Plus a number of smaller, client specific tools. Am sure you can think of lots of use cases where a small web app would be helpful to your customers.
The following is a quick start guide to get you talking to SAS from your favourite browser (which for me is Firefox, it has the best debugging capabilities).
Step 1 - Create a folder structure
This should go in the root of the web server, eg ROOT.war in JBOSS ( eg Jbossjboss-asserverSASServer1deployjboss-web.deployerROOT.war) on the mid-tier machine, or equivalent for a different web server.
You may choose to organise .html, .js, .css files and images etc into separate folders or distinguish by project.. But either way the number of these files will grow so be sure to have a plan to keep things organised. At this point you may wish to ask your admin to create a network share on this directory, so you can push files without having to log into the server.
Step 2 - Create your HTML file (example.html)
Example contents below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge;" charset="utf-8"/>
<title>SAS Web App#1</title>
<script src=../js/jquery.js></script>
<script src=../js/h54s.js></script >
<script src=../js/example.js></script>
</head>
<body>
<h1> Click submit to see a listing of SAS groups </h1>
<ul id=SASgroups></ul>
<button id=sasSubmit>Submit</ button>
</body>
</html>
Some things to notice regarding this markup:
- <!DOCTYPE html> means this is a HTML5 document
- We are referencing a jQuery library (highly recommended). The source code is here.
- We have a dedicated .js file to manage our new app (example.js)
- The document is fairly loosely typed, for brevity (unquoted ids for instance). This is all legal in HTML5.
- Syntax highlighting from http://qbnz.com/highlighter/demo.php
Step 3 - Create your Javascript file (example.js)
Example contents below:
$(document).ready(function(){
$("#sasSubmit").on('click',function (){ youClicked() });
});
function youClicked(){ $('#SASgroups').empty(); /* empty list before repopulating */ var adapter = new h54s(); /* only need one instance */ var myParams = {}; /* create empty object */ myParams.VARNAME='Variable Value'; /* create a variable & value */ var jsTablesObject = newh54s. Tables([myParams],'SASControlTable' ); /* make a H54s dataset */
adapter.call('/Webapp/example', jsTablesObject,function(err,res ) {
/* we just submitted an STP request, now deal with response */
$.each(res.fromSAS, function(i, item) {
/* cycle through each attribute and add to html list */
$('#SASgroups').append('<li>' + item.GROUPNAME + '</li>');
});
});
};
function youClicked(){ $('#SASgroups').empty(); /* empty list before repopulating */ var adapter = new h54s(); /* only need one instance */ var myParams = {}; /* create empty object */ myParams.VARNAME='Variable Value'; /* create a variable & value */ var jsTablesObject = new
Step 4 - Create your SAS Stored Process (example.sas)
Two parts - first create the .sas file (example contents below, save in the usual STP location), then register as an STP in Management Console (be sure it has STREAMING output).
/* get Boemska data connector macros */
%inc "SASEnvironmentSASCodeProgramsh54short.sas" ;
/* load parameters (not actually used, just for demo) */ %hfsGetDataset(SASControlTable, work.controlparameters );
data groups; /* grab all metadata groups */ attrib uriGrp uriMem GroupId GroupName Group_or_Role length=$64; attrib GroupDesc length=$256; attrib rcGrp rcMem rc i j length=3; call missing (of _all_); drop uriGrp uriMem rcGrp rcMem rc i j Group_or_Role; i=1; rcGrp=metadata_getnobj("omsobj:IdentityGroup?@id contains '.'" ,i,uriGrp);
do while (rcGrp>0);
call missing (rcMem,uriMem,GroupId,GroupName,Group_or_Role );
rc = metadata_getattr(uriGrp,"Id",GroupId );
rc = metadata_getattr(uriGrp,"Name",GroupName );
rc = metadata_getattr(uriGrp,"PublicType" ,Group_or_Role);
rc = metadata_getattr(uriGrp,"Desc",GroupDesc );
if Group_or_Role = 'UserGroup' then output;
i+1;
rcGrp=metadata_getnobj("omsobj:IdentityGroup?@id contains '.'" ,i,uriGrp);
end;
run;
/* send data back */ %hfsHeader; /* sets up the json */ %hfsOutDataset(fromSAS,WORK, groups); /* contains our desired data */ %hfsOutDataset(justDemo,WORK, controlparameters); /* demo sending more data */ %hfsFooter;
/* load parameters (not actually used, just for demo) */ %hfsGetDataset(SASControlTable
data groups; /* grab all metadata groups */ attrib uriGrp uriMem GroupId GroupName Group_or_Role length=$64; attrib GroupDesc length=$256; attrib rcGrp rcMem rc i j length=3; call missing (of _all_); drop uriGrp uriMem rcGrp rcMem rc i j Group_or_Role; i=1; rcGrp=metadata_getnobj("
/* send data back */ %hfsHeader; /* sets up the json */ %hfsOutDataset(fromSAS,WORK, groups); /* contains our desired data */ %hfsOutDataset(justDemo,WORK, controlparameters); /* demo sending more data */ %hfsFooter;
The main thing to note about the above is that we are using the (free!) Boemska data connector (source code). This makes it sooo easy to send / receive datasets from the browser, as well as being incredibly efficient (will send as many records as will fit in a 32k macro variable, then further macro variables as necessary). I do rate this piece of kit and will be doing a separate blog on it at some point.
Step 5 - upload all your files
Suggested locations:
- example.html -> ROOT.war/web (midtier)
- example.js -> ROOT.war/js (midtier)
- jquery.js -> ROOT.war/js (midtier)
- h54s.js -> ROOT.war/js (midtier)
- example.sas -> SASEnvironmentSASCodeStored_Processes (sasapp)
- h54short.sas -> SASEnvironmentSASCodePrograms (sasapp)
For the STP, I'd recommend creating a new sas metadata folder (eg /Webapp/) and putting all your web apps in that (for fine grained permissions control).
Step 6 - Open your latest SAS Web App!
Example URL; https://dev-sasmidtier.yourCompany.int:8080/web/example.html
And that's it! If you've built web apps with SAS before, it's likely this approach will be unfamiliar. I mean, where are the put statements?! And why so many files?
Believe me, it may seem like a lot of configuration initially (although it isn't), but the benefits of this approach are HUGE.
- Having separate CSS files (not in the demo) mean that styles can be quickly tweaked
- Having dedicated HTML files means that layouts can be easily be reconfigured
- Using the Boemska connector ensures that the application logic can be built with jquery / javascript, whilst the actual data management part can be cleanly separated and managed with SAS. Apart from tidy code, this also means that web developers and SAS developers can work alongside each other - without having to know each other's craft.
- Serving html files direct from the web server means that functionality can be provided even when the SAS server is down, or slow
Hopefully the above makes sense and is enough to get you started. A far more detailed guide is available in my SGF paper Build Lightning Fast Web Apps with SAS. Also, check out the SPWA tag on sasensei!