Friday, August 23, 2013

SAS Server Pages in Batch - Adding a BY variable to the tagCloud macro

In my last post, SAS Server Pages in Batch? Absolutely!, I talked about how SAS Server Pages could be used in batch (e.g., SAS Display Manager, SAS Enterpreise Guide, scheduled SAS processes, etc.). I presented a macro, tagCloud, that could produce a graphical display of BI data.

This post extends the macro to include a single BY variable so a separate tag cloud is produced for each value of the BY variable. This example macro produces a single HTML page with multiple tag clouds. The macro could easily be modified to produce a separate HTML for each value of the BY variable. I will leave that as an exercise for the reader ;-).

A sample macro call, along with the complete source for the macro is included below, with the changes highlighted in red.

First, here is the macro call. Assuming you save the tagCloud macro in a SAS autocall library, you can produce tag clouds with these two lines of code. In my last example I produced a tag cloud where the variable Subsidiary was the CLASS variable. Here I am producing a separate tag cloud for each Region, with Product used as the CLASS variable.

filename _webout "&root\Output\TagCloud-By-Region.html";
%tagCloud(word=Product,weight=Sales,by=Region)

First, three simple additions to the macro:
  1. A macro parameter for the BY variable is added. It defaults to null. If not provided, the macro produces the same output shown in SAS Server Pages in Batch? Absolutely!.
  2. Added a few macro variables to our local list - a Best Practice to make such utility macro variables local.
  3. Add the BY variable to the summary step so we have the statistic calculated for each combination of the BY and CLASS (the word macro parameter) variables.
%macro tagCloud(data = sashelp.shoes  /* input data */
               ,by =                  /* by variable to use */
               ,where = 1             /* subset where clause */
               ,word = Subsidiary     /* class variable */
               ,weight = Sales        /* analysis variable */
               ,statistic = Sum       /* statistics to calculate */
               ,outfile = _webout     /* output fileref */
               ,width = 600px         /* width of the tag cloud */
               ,height = 450px        /* height of the tag cloud */
               );

 %local byTitle numByGroups pageTitle pageHeader nl;

 proc summary data = &data nway;
  where &where;
  class &by &word;
  var &weight;
  output out=cloud(drop=_type_ _freq_) &statistic=;
 run;

If a BY variable is specified, get the list of distinct values and populate values for:
  1. numByGroups - note that a value of 1 is assigned if there is no BY variable specified. We need to generate just one tag cloud.
  2. pageTitle - the text for the HMTL TITLE tag. A macro variable is used since the values depends on whether or not a BY variable is specified and what the value of the BY variable is.
  3. pageHeader - Some text to use for a title/banner at the top of the page. Note that a value is only provided if there is a BY variable.
 %if %length(&by) gt 0 %then
 %do;  /* produce a separate tag cloud for each value of the by variable */
    proc sql noprint;
     select distinct &by into :by1-:by999 from cloud;
    quit;
    %let numByGroups = &sqlobs;
    %let pageTitle = Tag Clouds for each &by for the &statistic of &weight by &word;
    %let pageHeader = <h3>&pageTitle</h3>; 
 %end;
 %else
 %do;
    %let numByGroups = 1;
    %let pageTitle = Tag Cloud for the &statistic of &weight by &word;
 %end;

Call PROC STREAM to create the  HEAD section of the HTML (top_matter in SAS ODS terminology) along with the beginnings of the BODY section. The macro variable pageTitle is used to provide the value for the value for the HTML title attribute

 proc stream outfile = &outfile quoting = both;
 BEGIN
%let nl = &streamDelim newline;
&nl;<html>
&nl; <head>
&nl;  <title>&pageTitle</title>
&nl;  <script src="&GoogleApiLib/jquery/1.10.1/jquery.min.js"> </script>
&nl;  <script src="&GoogleApiLib/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
&nl;  <style>&streamDelim readfile srvrpgs(jqcloud.css);</style>
&nl;  <script>&streamDelim readfile srvrpgs(jqcloud-1.0.3.js);</script>
&nl; </head>
&nl; <body>

Add the pageHeader text (note that the value is null if there is no BY variable). Then end the current PROC STREAM invocation as we need to use a macro loop to use PROC STREAM generate the HTML and JavaScript for each BY group.

&nl;&pageHeader
;;;;

First reassign the output fileref so additional content is appended to it. Without the mod option, the file would be over-written.Then use a macro %DO loop to execute PROC STREAM once for each value of the BY variable. If there is no BY variable we still execute the code in the loop once.
  • The macro parameter, where, is re-used to specify the where clause for the current by group.
  • The byTitle macro variable contains the text for the current by group (it's use is described below).
  • Note that a where clause of 1 (always true) is used if there is no BY variable
  • Likewise, the byTitle macro variable contains the same text (&pageTitile) as is used for the HTML TITLE tag when there is no BY variable.

    filename &outfile "%sysfunc(pathname(&outfile))" mod;
    %do i = 1 %to &numByGroups;
       %if %length(&by) gt 0 %then
       %do;
          %let where = &by="&&by&i";
          %let byTitle = &&by&i;
       %end;
       %else
       %do; 
          %let where = 1;
          let byTitle = &pageHeader;
       %end;

Invoke PROC STREAM.

       proc stream outfile = &outfile quoting = both;
       BEGIN
%let nl = &streamDelim newline;

There are only a few changes in the HTML and JavaScript code:
  1. A margin attribute is used to provide some space so the regions for the tag clouds have some white space between them. Note that the float:left allows the output to wrap to a new row only when needed.
  2. The byTitle macro variable (defined above) is used to provide the title text displayed in the border of each tag cloud.
  3. In order for the id value for the region used for the tag cloud, as well as for the name of the JSON variable containing the tag cloud data, to be unique, we suffix the name with &i.
&nl;<div style="float:left; width:&width; height:&height; margin:5px;">
&nl;<fieldset><legend style="font-size:70%;">&byTitle</legend>
&nl;<div id="cloudCanvas&i" style="width:&width; height:&height;"></div>
&nl;</fieldset></div>
&nl;<script>
&nl; var tagCloud&i
&nl; %tagCloudJSON(data=cloud
                  ,word=&word
                  ,weight=&weight
                  ,where=&where
                  )
&nl; $(function() {
&nl;   $("#cloudCanvas&i").jQCloud(tagCloud&i);
&nl; });
&nl;</script>
;;;;
    %end;

After exiting the macro %DO loop we close use PROC STREAM to write the footer text and HTML (e.g., bottom_matter in SAS ODS terminology).

 proc stream outfile = &outfile quoting = both;
 BEGIN
%let nl = &streamDelim newline;
&nl;<div style="clear:both; font-size:70%;">%generatedAt()</div>
&nl; </body>
&nl;</html>
 ;;;;
%mend tagCloud;

And for those of you who want to download the code, I have a few more examples to blog about. Once I do that, I will package up all the pieces and parts (e.g., the AJAX container, the tag clouds SAS Server Pages, the macros, etc.) as a zip file.

Wednesday, August 21, 2013

SAS Server Pages in Batch? Absolutely!

The origins of SAS Server Pages, and thus the name, as discussed in this blog posting, was to simplify the generation of HTML in Stored Processes and SAS/IntrNet Application Dispatcher programs.

When I started work on my my eBook, SAS Server Pages: Generating Dynamic Content, I was presented with a conflict. SAS Server Pages could also be used in batch processing that did not involve any of the SAS web applications or tools. So, do I try come up with a new/better name???? After consulting colleagues who were using SAS Server Pages, the consensus was no. Just make sure to provide examples that show their use in batch processing. What I decided to do in the book and now in this blog entry (and more blog entries to follow), was to take their advice and provide batch examples.

But first let me be clear about what I mean by in batch:
  • jobs run on a scheduled or ad-hoc basis in the background (e.g., overnight job to produce reports).
  • code submitted interactively by a SAS user/programmer in SAS Display Manager.
  • code submitted interactively by a SAS user/programmer in SAS Enterprise Guide.

The example here is a Tag Cloud example and uses basically the same SAS Server Page components as discussed in Tag Cloud SAS Server Page Components. The SAS Server Page shown here has some changes/improvements over what was shown there for a number of reasons:
  • to leverage the environment in which the code is being run.
  • to highlight some features of PROC STREAM.
  • and, of course, whenever you revisit some code, it is quite common to come up with ideas to improve it.

The code has been packaged as a macro. There isn't any conditional or iterative processing (though in my post I will add conditional and iterative capabilities to the macro in order to support a BY variable). Plus, as a Best Practice I like to use macros to simply package and parameter code.

So producing this tag cloud output:


was accomplished by calling the following macro (stored in a macro autocall library).

As a convention I typically use _webout for the fileref even in batch programs. By doing that, any of my programs can easily be used in a Stored Process or SAS/IntrNet Application Dispatcher program.

filename _webout "&root\Output\TagCloud.html";
%tagCloud(data = sashelp.shoes
         ,word = Subsidiary
         ,weight = Sales
         )

So once the macro is made available via an autocall library, anyone can use it to create graphical displays of BI data as a tag cloud.

Lets now look at the macro. As stated above most of the SAS code and HTML was discussed in Tag Cloud SAS Server Page Components. This blog post will focus on the changes.
  • I provide default values for all the parameters. This is done for demo purposes only.
  • Note that anyone who wants to use this in their environment may want to add some validation of the parameters (e.g., the data set and variables exist).
  • Two macro parameters have been added to control the region size for the tag cloud.

%macro tagCloud(data = sashelp.shoes /* input data */
               ,where = 1            /* subset where clause */
               ,word = Subsidiary    /* class variable */
               ,weight = Sales       /* analysis variable */
               ,statistic = Sum      /* statistics to calculate */
               ,outfile = _webout    /* output fileref */
               ,width = 600px        /* width of the tag cloud */
               ,height = 450px       /* height of the tag cloud */
               );

As seen below, a macro variable is created to make the input SAS Server Page less cluttered. In this case the nl macro variable will contain the value of the streamDelim macro variable with the newline argument.

 %local nl;

Next we summarize the data. In the Stored Process example. this was embedded in the SAS Server Page using the dosubl function. While we could do that here, there is no need to. You could also add an option to the macro to specify that the data set has already been summarized.

 proc summary data = &data nway;
  class &word;
  var &weight;
  output out = cloud(drop=_type_ _freq_) &statistic = ;
 run;

Next call PROC STREAM with the desired options. Note that the BEGIN keyword is required as the first token after the PROC STREAM statement. Also note that instead of using %INCLUDE to include the SAS Server Page text, it is included inline. While I typically use %INCLUDE in order to maximize re-use, I wanted to illustrate this technique.

 proc stream outfile = &outfile quoting = both; 
 BEGIN

As mentioned above, assign the value for the nl macro variable so it can be used on each line of the input SAS Server Page text in order to have the raw HTML more nicely formatted. Note however that this is only really needed here to make the source more readable for debugging purposes.

%let nl = &streamDelim newline;

Just some standard HTML header.

&nl;<html>
&nl; <head>
&nl;  <title>Tag Cloud for the &statistic of &weight by &word</title>

Just as for the Stored Process example, we point to a Google api library for the JQuery pieces and we use the readfile facility to just copy the JQCloud JavaScript and Cascading Style Sheet into the generated output.

Note the use of the GoogleApiLib macro variable. That is set in my environment so that I can easily update the value and have the new value propagated without having to update all of my programs and SAS Server Pages.

&nl;  <script src="&GoogleApiLib/jquery/1.10.1/jquery.min.js"></script>
&nl;  <script src="&GoogleApiLib/jqueryui/1.10.3/jquery-ui.min.js"></script>
&nl; <style>
&nl; &streamDelim readfile srvrpgs(jqcloud.css);
&nl; </style>
&nl; <script>
&nl; &streamDelim readfile srvrpgs(jqcloud-1.0.3.js);
&nl; </script>
&nl; </head>
&nl; <body>

The fieldset tag is used the to provide the nice border with embedded text (on the border). So I need a div tag to specify the size of the region.

&nl;<div style = "float:left; width:&width; height:&height;">
&nl;<fieldset>
&nl;<legend style="font-size:70%;">
Tag Cloud for the &statistic of &weight by &word
</legend>

Now just as in the Stored Process example, define the region to contain the generated tag cloud.

&nl;<div id="cloudCanvas" style="width:&width; height:&height;"></div>

End the fieldset.

&nl;</fieldset></div>

And  since this is a batch process, lets use the generatedAt macro to provide a footnote showing when the output was created.

&nl;<div style="clear:both; font-size:70%;">%generatedAt()</div>

And finally, create the JSON text that populates the tag cloud - again, just as in the Stored Process example.

&nl;<script>
&nl; var tagCloud =
&nl; %tagCloudJSON(data=cloud
                  ,word=&word
                  ,weight=&weight
                  ,where=&where
                  )
&nl; $(function() {
&nl;   $("#cloudCanvas").jQCloud(tagCloud);
&nl; });
&nl;</script>
&nl; </body>
&nl;</html>
;;;;
%mend tagCloud;

That's really all there is to it.

And for those of you who want to download the code, I have a few more examples to blog about. Once I do that, I will package up all the pieces and parts (e.g., the AJAX container, the tag clouds SAS Server Pages, the macros, etc.) as a zip file.

And in case you had not noticed, I updated by blog template to make the text area wider so that the code is easier to read/follow. I hope you like the new format.

Wednesday, August 7, 2013

Tag Clouds vs. Pie vs. Bar Charts

When I was freshman in college I learned a lesson about making sure to emphasize important points. I got a C- on the very first paper I wrote as a college student. When I looked at the comments made by the Teaching Assistant (TA) who graded my paper, they were all very positive. In fact there were no comments at all to explain why I got the grade I got. When I asked the TA to tell me why he gave me a C-, he said that I made two mistakes. I did not meet the minimum pages requirement (my paper was 18 pages instead of 20); and I did not provide a summary. He gave me some advice about how to handle both issues in the future:
  • First, make sure to introduce the topic (i.e., talk about what you are going to talk about)
  • Next, discuss the topic (i.e., talk about it).
  • Last, summarize it (i.e., talk about what you just talked about).
So in that spirit let me summarize now my recent posts on Tag Clouds. One of my points was that Tag Clouds were a good way to display BI data in a meaningful way. Consider for example a Tag Cloud as originally described in this post:
vs. a Pie Chart
vs. a Bar Chart

on the same data (sashelp.shoes).

Don't know about you, but seems to me that the Tag Cloud does a much better job of displaying the data in a meaningful way.

In my next three posts I described:
And to re-emphasize the re-usability point, consider this example where I just specify a different form and get a different set of data presented in the Tag Cloud.

http://demos.hcsbi.com:8080/SASStoredProcess/guest?_program=/sspebook/sasServerPage&page=ajaxContainer&formUI=classform.html

In the not to distant future I will be blogging on another use of this AJAX template: a expanding/collapsing tree view of a summary data set:
Before that however I will digress a bit to talk about using PROC STREAM and SAS Server Pages in Base SAS to produce reports.

Monday, August 5, 2013

Tag Cloud SAS Server Page Components - Part 2 (of 2)

I recently posted about an AJAX and JQuery based Tag Cloud demo. I next described the re-usable AJAX template that is the foundation for the demo. In my last post I described two of the four component SAS Server Pages. This post completes the series with a description of:
  • tagCloud.html: the SAS Server Page that generates and displays the tag cloud (seen in left hand side of figure 1).
  • tagCloudJSON.sas: the SAS Macro the creates the JSON (JavaScript Object Notation) used to populate the tag cloud.
  • cloudDrill.sas: the SAS Macro that displays the drill-down results (seen in the right hand side of figure 1) when the user clicks on a word in the tag cloud.
Figure 1. Tag Cloud, including drill-down results.

The tagCloud SAS Server Page

The JQCloud JQuery widget is used to generate the tag cloud so we first have to define the style sheet and the JavaScript code to the page. Note the PROC STREAM readfile directive is used to do that. It simply includes the contents of the two files as is. Note that we could have also downloaded and put these files in our web server directory tree.

<html>
 <head>
  <style>&streamDelim readfile srvrpgs(jqcloud.css);</style>
  <script>&streamDelim readfile srvrpgs(jqcloud-1.0.3.js);</script>
 </head>


Next, we make sure that the parameters that define the tag cloud exist as macro variables and have default values.

 <body>
  %global data statistic word weight;
  %let data = %sysfunc(coalescec(&data,sashelp.shoes));
  %let statistic = %sysfunc(coalescec(&statistic,sum));
  %let word = %sysfunc(coalescec(&word,Subsidiary));
  %let weight = %sysfunc(coalescec(&weight,Sales));


In order to make the page completely stand-alone, the code to summarize the data and calculate the weight to apply to each word is executed using the dosubl function, invoked via the %sysfunc macro.

  %let rc = %sysfunc(dosubl(
               proc summary data = &data nway;
                class &word;
                var &weight;
                output out=cloud &statistic=;
               run;
            ));

Next a div tag is used to define the display area for the the tag cloud. And id value of cloudCanvas is used so the tagCloud plug-in can reference this area of the display. Note that the width, height, and border parameters could be parameterized using macro variables.

The newline directive is used (here and in the JavaScript below) to ensure that PROC STREAM does not insert a line break that could potentially cause problems. The use of the newline directive in this way should be considered a required Best Practice.

  &streamDelim newline;<div id="cloudCanvas" style="float:left;
                            width: 600px; height: 400px;
                            border: 1px solid #ccc; margin-bottom:5px">
                       </div>

The data for the tag cloud is specified in a JavaScript variable (called tagCloud) whose value is a JSON string (the macro that generates the JSON string is discussed below).

  &streamDelim newline;<script>
  &streamDelim newline; var tagCloud =

                             %tagCloudJSON
                                 (data=cloud
                                 ,word=&word
                                 ,weight=&weight
                                 )

Next is the JQuery syntax that says the the jQCloud JavaScript code should use the tagCloud variable as the source of the JSON used generate the display for the cloudCanvas div.

  &streamDelim newline; $(function() {
  &streamDelim newline;   $("#cloudCanvas").jQCloud(tagCloud);
  &streamDelim newline; });


Next is the JQuery syntax that detects when a word has been clicked on. Here, we call the cloudDrill macro and display the results using AJAX in the drillResults region of the screen.

  &streamDelim newline; $("#cloudCanvas").click(function(e) {
  &streamDelim newline;   var selected = $(e.target).text();
  &streamDelim newline;   $("#drillResults").load("&_url?_debug=&_debug%nrstr(&)_program=&_metafolder.runMacro%nrstr(&)macroToRun=cloudDrill%nrstr(&)data=&data%nrstr(&)class=&word%nrstr(&)word="+encodeURIComponent(selected));
  &streamDelim newline; });
  &streamDelim newline;</script>


And finally, is the definition of the area, with some default initial text, where the resulting drill down table is displayed.

  <div style="float:left;  margin-left:10px;" id="drillResults">
   <br>Click on a word/phase in the tag cloud to see the detail.
   The drill-down report will be displayed to the right of the

   Tag Cloud if there is enough horizontal space. Otherwise it
   will be displayed below the Tag Cloud.
  </div>
 </body>
</html>


That's all there is to it. While the JQuery syntax takes some getting used to; it really is fairly straightforward.

The tagCloudJSON Macro

A macro is used to read the data set and generate the needed JSON.

%macro tagCloudJSON
      (data = _last_
      ,where = 1
      ,word = word
      ,weight = weight
      );


The SCL data access functions are used via the %sysfunc macro function.

 %local dsid wordNum weightNum weightFmt comma;
 %let dsid = %sysfunc(open(&data(where = (&where))));
 %let wordNum = %sysfunc(varnum(&dsid,&word));
 %let weightNum = %sysfunc(varnum(&dsid,&weight));
 %let weightFmt = %sysfunc(varfmt(&dsid,&weightnum));


We loop through the data set reading each observation generating the needed JSON.
  • the text attribute is the value of the word (the class variable in SAS terminology).
  • the weight attribute is the unformatted value of the analysis variable.
  • And the html attribute is a nested JSON string the specifies that the hover text should be the formatted value of the analysis variable. And since the word is clickable, we make sure that the pointer attribute is used when the user hovers over a word.
The comma macro variable is set to blank initially and then reset so that each observations JSON is comma separated.

 %let comma=;
 &streamDelim newline;[

 %do %while(%sysfunc(fetch(&dsid))=0);
    &comma &streamDelim newline;
    {
      text:   "%qtrim(%qsysfunc(getvarc(&dsid,&wordnum)))"
    , weight: %qsysfunc(getvarn(&dsid,&weightNum))
    , html:   { title: "%qsysfunc(getvarn(&dsid,&weightNum),&weightFmt)"

              , style: "cursor:pointer"
              }
    }
    %let comma = ,;
 %end;


And finally, we close the data set and close the JSON string.

 %let dsid = %sysfunc(close(&dsid));
 &streamDelim newline; ];

%mend tagCloudJSON;

The cloudDrill Macro 

This macro is executed by the runMacro stored process whenever the user clicks on a word. Three parameters are passed to it:
  • The name of the data set (referenced here as &data) that provided the source data for the tag cloud.
  • The name of the class variable that is the value for the words (referenced here as &class).
  • The actual value the user clicked on (referenced here as &word).
Note also that the class variable is displayed in the PROC REPORT spanned header and so it is defined using the noprint define statement attribute so screen real estate is not used/wasted to display it on each row.

 %macro cloudDrill;
 options nocenter;
 proc report data = &data nowd split='_' missing;
  columns ("&Word" _all_);
  where &class = "&word";
  define &class / noprint;
  rbreak after / summarize;
 run;

%mend cloudDrill;

Summary

I hope that this all makes sense. If not, feel free to post a question.

I plan to use the AJAX template discussed here and in my last three posts in future blog posts that illustrate additional examples of what can be done. So please make sure to check them out.

Wednesday, July 31, 2013

Tag Cloud SAS Server Page Components - Part 1

I recently posted about an AJAX and JQuery based Tag Cloud demo. And in my last post I described the re-usable AJAX template that is the foundation for the demo. In this post I am I'll describe two of the component SAS Server Pages:
  • shoesUI.html: the form that is used to capture the users choices. The form to use is passed as a parameter to the AJAX template.
  • hcsFooter.html: the text to include at the bottom of the page.
These two SAS Server Pages generate the parts of the display seen in regions 3, 4 and 5 of Figure 1.

Figure 1
The other components for this demo:
  • the Tag Cloud SAS Server Page that leverages the JQuery Tag Cloud plugin.
  • the macro that creates the JSON used by the JQuery Tag Cloud plugin.
  • the macro that produces the drill down report
will be discussed in my next post.

So lets get started with the details for these two (simple) components.

The shoesUI.html SAS Server Page

It is a best practice to always propagate the value of _debug. The following two lines make sure the macro variable always exists and assigns a default value of no debugging (0).

%global _debug;
%let _debug = %sysfunc(coalesceC(&_debug,0));


This JavaScript statement customizes the title seen in the browser for the page so it is specific to this demo. Note that a macro variable can't be used here (see the discussion of messageText below) since the document title is defined in the head section of the page.

<script>document.title="Tag Clouds as a Graphical Display";</script>

Define the hidden name/value pairs used by the demo. Note that we can propagate the value of _program since we are using the same stored process.
  • As mentioned above, the tagCloud SAS Server Page will be discussed in my next blog post.
  • For purposes of this demo, the data set to use and the statistic to calculate are not parameters. But they could easily be made into parameters that the user can specify.
<input type="hidden" name="_program" value="&_program">
<input type="hidden" name="_debug" value="&_debug">
<input type="hidden" name="page" value="tagCloud">
<input type="hidden" name="data" value="sashelp.shoes">
<input type="hidden" name="statistic" value="sum">

Next is the select tag that allows the user to select which class variable is used by the Tag Cloud to define the tags/words that are displayed. See region 3 above.

NOTE: if the data set were passed in as a parameter, we could make this list dynamic using dictionary.columns (or sashelp.vcolumn), restricted to character columns. Or perhaps a custom parameter file. The key is that this could be easily customized and made data-driven.  Also seen in region 3 above.

<p>
<b>Select&nbsp;Class&nbsp;Variable</b>
<br>
<select name="word">
<option value="Product">Product</option>
<option value="Region">Region</option>
<option value="Subsidiary" selected>Subsidiary</option>
</select>


Just as for the name of the class variable, we include a select tag to allow the user to select the analysis variable used to specify the weight to assign to each tag/word. This could also be easily customized and made data-driven.

<p>
<b>Select&nbsp;Analysis&nbsp;Variable</b>
<br>
<select name="weight">
<option value="Inventory">Inventory</option>
<option value="Returns">Returns</option>
<option value="Sales" selected>Sales</option>
</select>


And now the submit button. The AJAX template includes the form and /form tags and also includes the JQuery AJAX code that handles turning this simple form into an AJAX call. Again, the button can be seen in region 3 above.

<input type="submit" value="Generate Cloud">

And the final piece is this %let statement that defines the text to be displayed in the region of the page where the output will be displayed (see region 4 above). This allows for the custom instructions for any given demo - all defined using the SAS Server Page that contains the form elements.

%let messageText=
    <b>The tag cloud will be displayed once the <i>Generate Cloud</i>

       button is clicked.</b>
    <p>The form is also hidden when you click the <i>Generate Cloud</i>

       button.
    <p>To re-display the form in order to make another selection, just

       click the arrow in the upper left corner.
;

The hcsFooter SAS Server Page

The footer is pretty simple HTML. The most complicated part is the use of div tags to line up and format the text.

First an outer div tag to contain the footer.

 <div style="clear:both; height:15;
             background-color:#E4E4E4; color:#999999;
             font-family:Geneva, Arial, Helvetica, sans-serif;
             font-size:10px; font-style:italic;">

An inner div tag to that left justifies part of the footer.

 <div style="float:left;">
   <a href="http://www.sascommunity.org/wiki/SAS_Server_Pages_and_More:_A_Framework_for_Generating_Dynamic_Content"
      style="text-decoration:none" target="_blank">
   SAS Server Pages and More: A Framework for Generating Dynamic Content
   </a>
 </div>


Another div tag to right justify part of the footer.

 <div style="float:right;">
   <a href="http://www.hcsbi.com"
      style="text-decoration:none" target="_blank">
   Henderson Consulting Services
   </a>
 </div>


And finally, close the outer div tag for the footer.

</div>

Summary

As I hope this post makes clear, parameterizing the AJAX template so it can be used for any number of custom Stored Process (or SAS/IntrNet Application Dispatcher) reports, is reasonably straightforward. And, yes, there will be more examples of using this AJAX template for different reports in future blog posts.

Monday, July 29, 2013

A SAS Server Page AJAX Template

In my last post I described a a SAS Server Page demo that used JQuery to provide both AJAX tools and a widget to produce tag clouds.

This is the first of several posts that will drill into the details of how it works. The topic for this post is a re-usable template that can be used for any number of reports: the ajaxContainer SAS Server Page (see figure 1).

Annotated ajaxContainer SAS Server Page
The contents of the SAS Server Page are described below with references to the numbers for each part of the output.

The first thing we do is ensure that the macro variables referenced/used in the template exist:
  • the formUI macro variable will resolve to the name of the SAS Server Page that contains the form fields so the user can make some selections (see 3 in figure 1). Note the use of %sysfunc with the coalesceC function to assign a default.
  • the footer macro variable will resolve to the name of the SAS Server Page that contains the HMTL to display at the bottom of the page (see 5 in figure 1). 
  • the messageText macro variable is assigned a value in the SAS Server Page referenced by formUI. This allows the text to be easily customized.
%global formUI footer messageText;
%let formUI = %sysfunc(coalesceC(&formUI,shoesForm.html));
%let footer = %sysfunc(coalesceC(&footer,hcsFooter.html));


Generate the HTML and in the head section, provide links to the hosted (by Google) JQuery tools. Using such links means that you need not download/install the basic JQuery environment script files to your server. But you do need to check for updates (e.g., the 1.10.1 and 1.10.3 in the URLs). Also note that by starting the URL with // instead of http or https allows will allow http vs. https to be used based on the base url.

<html>
<head>
<title>Sample AJAX Container</title>
<script

   src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js">
</script>
<script

   src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js">
</script>

Next is the JavaScript that controls the toggling of the UI/form. The ids showUI and hideUI reference a right arrow (show) and the left arrow (hide), discussed below that toggle the display of the form on/off. This technique allows the output to be seen on the same page as the form, while allowing to form to be hidden so as to maximize the screen real estate available to the output. 

Note that JavaScript could be included from another SAS Server Page using a macro variable similar to formUI and footer.

<script>
function toggleUI() {
 if (document.getElementById('showUI').style.display == 'none')
 { document.getElementById('showUI').style.display = 'block';
   document.getElementById('hideUI').style.display = 'none';
   document.getElementById('UI').style.display = 'none';
 }
 else
 { document.getElementById('UI').style.display = 'block';
   document.getElementById('hideUI').style.display = 'block';
   document.getElementById('showUI').style.display = 'none';
 }
}
</script>

</head>

Just some HTML to define the text that appears at the top of the page. See 1 in figure 1. This could also have been parameterized like the footer, but I choose to show both techniques in this sample.

<body>
<div style="margin-bottom:5px; clear:both; height:15;

            background-color:#E4E4E4; color:#999999;
            font-family:Geneva, Arial, Helvetica, sans-serif;
            font-size:10px; font-style:italic;">
 <div style="float:left;">

  Learn more about these techniques in the SAS Press Book
   <a href="http://support.sas.com/publishing/authors/extras/64993b.html"
      style="text-decoration:none" target="_blank">
    SAS Server Pages and More: A Framework for Generating Dynamic Content
   </a>
 </div>
 <div style="float:right;">

  More
   <a href="http://hcsbi.blogspot.com/search/label/SAS%20Server%20Pages"
      style="text-decoration:none" target="_blank">
    SAS Server Page
   </a>
    examples can be found on my
    <a href="http://hcsbi.blogspot.com/"
       style="text-decoration:none" target="_blank">
     blog
    </a>
 </div>
</div>


The arrows that can be used to toggle the display of the form are defined next. See 2 in figure 1. The images are encoded as mentioned in my blog posting on base 64 encoded images.  This allows for the page to not have to worry about a path to the images directory and makes it more easily re-used and moved.

Note also the use of &streamDelim newline; to force PROC STREAM to issue a line feed. This helps ensure that linefeeds are not issued in the middle of some HTML text that perhaps should not be broken across lines.

&streamDelim newline;
<a id="showUI" href="JavaScript:toggleUI();"
   title="Show Selections"
   style="text-decoration:none; display:none;">
&streamDelim newline;

<img src="data:image/png;base64,R0lGODlhDAALAJEAACpVgbHB0Iuz2v///yH5BAEAAAMALAAAAAAMAAsAAAIhnB+nGrks3Gg0iOvs3TtpzjUgB0zAiV7lMwDCyp7sARsFADs="
     border="0">
</a>
&streamDelim newline;

<a id="hideUI" href="JavaScript:toggleUI();"
   title="Hide Selections"
   style="text-decoration:none; display:block;">
&streamDelim newline;

<img src="data:image/png;base64,R0lGODlhDAALAJEAALHB0CpVgYuz2v///yH5BAEAAAMALAAAAAAMAAsAAAIgnA2nB7ks3AOitpto3TtozgVDAIYGKQTqKp7po6hw/BQAOw=="
     border="0">
</a>


We next begin the div tag for the form/UI (note the id value is referenced above in the JavaScript that toggles the display on/off).

And we use the %include statement which causes PROC STREAM to do a server-side include of the form tag. By making the form a parameter to this template, we can use it to produce any number of reports. The included form generates the output seen in region 3 of figure 1.

<div id="UI" style="float:left;">
<form url="&_url" id="myForm">
&streamDelim;%include srvrpgs(&formUI);


Next is the JavaScript that leverages the JQuery AJAX framework to serialize and submit the form so the output can be displayed in the same page without having to refresh the entire page.

This only uses a few of the options available to the ajax function:
  • url: the url to display
  • success: the code to run if the URL is successfully run
  • note that there is an error/failure option as well. But being an optimist, and since this is a demo, I decided to not worry about that :-).
Note also that:
  • a please wait message is generated along with an image. I decided to not base 64 encode this as the encoded version is pretty long.
  • upon updating the output, the toggleUI JavaScript function is called to hide the form.
For the truly geeky and curious, there are lots of resources that you can find via a simple search that will how/why this works. For the rest of you, all I can say is trust me, it will work for you.

<script type="text/javascript">
    var frm = $('#myForm');
    frm.submit(function () {
    $("#region1").html('<div style="margin-left:50px">'

                     + '<h1>Processing . . . Please Wait . . . </h1>'
                     + '<img src="images/progress.gif""></div>');
    var theURL = "&_url?"+frm.serialize()+'&nocache='+Math.random();
        $.ajax({
            url: theURL,
            success: function (data) {
               $("#region1").html(data);
               toggleUI();
            }
        });
        return false;
    });
</script>
</form>
</div> 


Finally we define the region to contain the output. Note the id of region1 which is used in the AJAX code above. Note also that upon load the initial contents will be the text resulting from resolving the messageText macro variable (see 4 in figure 1). This variable is set in the code that is included when the formUI macro variable is resolved. As mentioned above, this allows for custom text for any given form.

<div id="region1" style="float:left; margin-left:10px;">
  &messageText
</div>

And last, a %include is used to include the footer text (see 5 in figure 1), followed by the /body and /html tags.
&streamDelim;%include srvrpgs(&footer);
</body>
</html>


I hope this all makes sense to you. If not, feel free to post questions about it.

In my next post I will discuss the two included files: shoesUI.HTML and hcsFooter.html.

Wednesday, July 17, 2013

Win a Free Copy of SAS Server Pages: A Framework for Generating Dynamic Content

Lots going on right now surrounding my eBook, SAS Server Pages:  A Framework for Generating Dynamic Content.
  1. Created a great cool demo using tag clouds that I plan to discuss in several follow-on blog entries.
  2. PROC STREAM, a key part of server pages, is now in production with SAS 9.4.
  3. Check the Apple iBookstore soon for an updated version of my eBook. 

The Demo – Tag Clouds as a Graphical Display of Data

You’ve all probably seen Tag Clouds. In fact, my blog uses tag clouds to display the labels (or keywords) that I have tagged individual blog entries with. The more blog entries a label is used in, the bigger it is. The tag cloud for this blog clearly has SAS Server Pages as the most frequent and thus is larger than the other tags. So that got me thinking that maybe Tag Clouds could be used for BI data – they are much more interesting (at least IMO) than bar charts or pie charts. So here is a link to a SAS Server Page that generates Tag Clouds using the SASHELP.SHOES data set that you can explore:

http://demos.hcsbi.com:8080/SASStoredProcess/guest?_program=/sspebook/sasServerPage&page=ajaxContainer

Here is a screenshot of a sample:


The demo integrates a few different technologies to produce this output:
  1. SAS Server Pages and PROC STREAM are used to generate the web/html output as well as to provide the User Interface where the user can specify what variables in the SAS data set are used to generate the Tag Cloud.
  2. The SAS BI/EBI Stored Process Server is used to deliver the results, including the drill-down functionality (e.g., clicking on the word Vancouver generated the tabular results).
  3. The JQuery framework provides several key technology pieces that both simplified the development of the demo as well as to deliver the content.
    1. The JQCloud JQuery widget produces the Tag Cloud based on data passed to it from the SAS Server Page.
    2. The JQuery AJAX tools are used to manage the layout of the display, including displaying the results in a single integrated page.
The use of the JQuery framework is particularly important as that allows any SAS developer, via SAS Server Pages, to leverage all of the user-interface, reporting, and graphical tools that developers all around the world have contributed to JQuery.

Over the next several weeks I will be describing and documenting all the pieces and parts of this demo; a demo that is built on top of a re-usable framework that uses the technology described in my eBook SAS Server Pages:  A Framework for Generating Dynamic Content.

Winning a Copy of the Book

Winning a copy of this eBook, specifically an Apple iBookstore copy, is simple. The first two readers who make a blog comment that is either:

  • An interesting set of publically available data that lends itself to a display like that shown in the demo.  And yes, this could result in my creating a demo using that data.
  • How you might use tag clouds for BI data.
  • A suggested enhancement for the demo.
For a chance to win, you must be a US resident and you must be a follower of this blog. This way, if you are a winner, the SAS Press folks (who are providing the free copies) and I can easily contact you and send you a code to download your free copy. To become a follower, just click on the Join this site button under the Followers widget.  See the Help Info on this for more information.

And, of course, if you don’t win a copy, you can always buy one!