Monday, January 29, 2024

Tabbed Sub-Reports with SRS and SharePoint

Okay...  So the title isn't quite as colorful as the first version, but we'll make up for that with the content.  Consider this a more better, revised, updated, newly interpreted, adjusted, adapted, renovated for your future enjoyment, revisit of the previous post.  This time, I've got lots of pictures.

So here's the skinny:  Previously I posted on using SRS and SharePoint (that's the database dependent duo, just in case nobody got that) to create dynamic sub-reports.  Here, I've got a solution that was inspired by my first go-round with this technique.  Except this time I'm using a tabbed interface to present several related sub-reports.

If that was the skinny, I suppose this is the fat:  Quick and clean - here's how to do it...

Assumptions:

  • You are somewhat familiar with SRS and SharePoint - this isn't a tutorial on the basics, there's plenty of that out there.
  • You have a system with access to SRS and SharePoint.
  • SRS is installed in SharePoint integration mode.
  • You will be using Visual Studio.  (Not really required, but that's what my screen shots are of...)
  • You are somewhat familiar with HTML and Cascading Style Sheets (CSS).
  • You know what JavaScript is and, at the very least, seeing it doesn't make you curl up into a fetal position in the corner of the room.

The example depends on a silly little database I created just to have a simple data schema for my purpose.  It's a Family Details database - four tables of completely useless information:

We're working with a collection of 5 very simple reports.  One to act as a search utility and one each to report on the data in each table.  The first screen shot shows you the full layout.  The search report accepts a single parameter that's turned into a search term in the data source query:

The basic layout for the report is as simple as could be, but there are two things worthy of note:  First, we have to format the field display to look like a hyperlink, since SRS doesn't do that for us:

Second, we need to configure the field to call our JavaScript function.  We'll see the script in a bit, but here's where we configure the call:

In this implementation, that's the only place we need to configure anything out of the ordinary in the report.  The rest of the reports are all very similar.  Each accepts a single parameter indicating the ID of the family to report on.  They are not connected in any way within the RDL file or SRS.  All of the connecting magic takes place in our script.  Here's a look at the FamilyDetails report:

   

Next we move on to SharePoint (WSS required - MOSS optional).  All of the reports are deployed to the WSS document library configured in the SRS SharePoint integration setup.  For organizational purposes, I created another document library, called Dashboards, to hold the web part page that will become the Family Information dashboard.  I chose the basic Full Page, Vertical web part page template.  I only need two web parts for this implementation.  The first is a SQL Server Reporting Services Report Viewer web part.

It's configured to display the search report:

A user enters some value in the parameter box and clicks Apply.  This is where we get the report configured with the navigation links (the ones that call our JavaScript - yes...  It's coming...)

The other web part is a Content Editor Web Part.  This is where the JavaScript lives.  But before we go there, there's one more bit we need to make this complete.  The tabs themselves use images as a background.

   

One image for the selected (or hot) tab and another image for the unselected tabs.  To make the images easily accessible to the web part, I dropped them here:

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\IMAGES\Ensynch

In WSS, that TEMPLATES folder is accessible through the _layouts virtual directory in any site.

Okay, so this other web part...  Place a Content Editor Web Part under the Report Viewer web part:

For clarity, I've broken out the code into sections, here, but it's all in the one web part.  Make sure that when you go to add the code that you click the Source Editor button, not the Rich Text Editor .

The code consists of a bit of style sheet information:

<style> Element:
<style>
  .ensTab100 {    height:25px;
                  width:100px;
                  vertical-align: middle;
                  text-align: center;
                  background-image : ur(/_layouts/images/ensynch/tab100.jpg);
                  color: #ffffff;
                  font-family: Arial;
                  font-size: 12px;
                  font-weight: bold;
                  cursor: pointer; }

  .ensTabHot100 { height:25px;
                  width: 100px;
                  vertical-align: middle;
                  text-align: center;
                  background-image : url(/_layouts/images/ensynch/tabhot100.jpg);
                  color: #000000;
                  font-family: Arial;
                  font-size: 12px;
                  font-weight: bold;
                  cursor: pointer; }
</style>

A wrapper <div> that allows us to hide the stuff that has no content until we're ready for it:  (This is strictly for aesthetics.)

Wrapper <div>:
<div id="ReportWrapper" style="visibility: hidden;">

An HTML iframe for the main family report: (Note that the src attribute starts out empty and the id attribute is required.)

Main Report <iframe>:
<div style="height: 100px; width: 100%">
  <iframe id="FamilyMain" style="border-style: none; height: 100%; width: 100%" src=""></iframe>
</div>

A table structure to hold the tabs: (Note the id, class and onclick event settings - all required.)

Tab <table>:
<table cellpadding="0" cellspacing="1">
  <tr>
    <td id="tab1" class="ensTabHot100" onclick="SetCurrentTab(this.id);">Friends</td>
    <td id="tab2" class="ensTab100" onclick="SetCurrentTab(this.id);">Food</td>
    <td id="tab3" class="ensTab100" onclick="SetCurrentTab(this.id);">Colors</td>
  </tr>
</table>

 An HTML iframe for the sub-report of the active tab: (Note that the src attribute starts out empty and the id attribute is required.)

Main Report <iframe>:
<div style="height: 100px; width: 100%">
  <iframe id="FamilySub" style="border-style: none; height: 100%; width: 100%" src=""></iframe>
</div>

 Close the wrapper <div>:

Wrapper </div>:
</div>

 And, of course, the script that connects it all:

<script> Element:
<script type="text/javascript">
  var currentTab = "tab1";
  var currentID = "";

  function SetFamilyID(famID)
  {
    if (currentID != famID)
    {
      currentID = famID;
      UpdateMain();
    }
  }

  function SetCurrentTab(tabName)
  {
    if (currentTab != tabName)
    {
      document.getElementById(currentTab).className = "ensTab100";
      document.getElementById(tabName).className = "ensTabHot100";
      currentTab = tabName;
      UpdateTabs();
    }
  }

  function UpdateMain()
  {
    if (currentID != "")
    {
      document.getElementById('ReportWrapper').style.visibility = "visible";
      document.getElementById('FamilyMain').src=
        'http://win2k3/reportserver?' +
        'http://win2k3/ilm/reports/FamilyDetails.rdl&' +
        'rs:Command=Render&rc:Toolbar=false&rc:Parameters=false&' +
        'FamilyID=' + currentID;     
      UpdateTabs();
    }
  }

  function UpdateTabs()
  {
    var report = "";
    if (currentID != "")
    {
      switch(currentTab)
      {
        case "tab1":
          report = "FamilyFriends.rdl"
          break;
         case "tab2":
          report = "FamilyFood.rdl"
          break;
         case "tab3":
          report = "FamilyColors.rdl"
          break;
      }

      if (report != "")
      {
        document.getElementById('FamilySub').src=
          'http://win2k3/reportserver?' +
          'http://win2k3/ilm/reports/' + report + '&' +
          'rs:Command=Render&rc:Toolbar=false&rc:Parameters=false&' +
          'FamilyID=' + currentID;
      }
    }
  }
</script>

 Here's a basic rundown of how the plumbing works:

  • The script sets up a couple of global variables:
    • currentTab - hold the id of the current tab.  Defaults to "tab1".
    • currentID - holds the ID of the family that was selected in the search report.
  • A user clicks on a link presented by the search report, calling SetFamilyID() and passing in the ID.
  • SetFamilyID stores that value in a global variable (currentID) and then calls UpdateMain().
  • UpdateMain, assuming that there is a value in currentID:
    • Makes sure that the wrapper <div> is visible
    • Sets the proper src value for the FamilyMain iframe (including the ID parameter).
    • Calls UpdateTabs();
  • UpdateTabs() then proceeds to:
    • Determine which report to run based on the value in currentTab.
    • Set the proper src value for the FamilySub iframe (including the ID parameter).
  • When a user click on one of the tabs, a call is made to SetCurrentTab, passing in the id of the tab that was clicked on.
  • SetCurrentTab(), assuming that the clicked tab is not already the current tab:
    • Sets the current tab class to ensTab100 so it's no longer "hot".
    • Sets the new tab class to ensTabHot100 so it has the selected appearance.
    • Saves the new tab value into currentTab.
    • Calls UpdateTabs() to get the new report displayed.
  • When a user clicks a new value in the search report (yes, it's still there) - it all starts over again.
       

Use it in health!  (Well, use it in SharePoint, but you know what I mean...)

Friday, August 20, 2010

Interacting with PowerShell from C#

Recently I had a client that wanted to be able to call PowerShell scripts from their Linux based Java application.  To facilitate, I created a web service that accepts a script name and a parameters collection and returns a customized response object.  There’s a helpful post on CodeProject with some sample code for calling PowerShell from C#.  It demonstrates how to instantiate a PowerShell pipeline and call cmdlets.  It even shows how to drop your application’s variables in the Pipeline, but it isn’t quite complete.  I also needed to setup command line arguments and see what changes the script made to my variable.

Basing my code on the CodeProject sample (thanks, jpmik) referenced above, I needed to add in some code to insert command line arguments.  That’s pretty easy as the PowerShell Command object has a Parameters collection.  You just have to instantiate the command object, configure it and drop it into the Pipeline.

Making an object variable available to the script was pretty easy, too.  The CodeProject example shows the use of the SetVariable method.  But if the PowerShell script changes the object, you have to follow up with a GetVariable call to pull the changed object out of the Pipeline.  Your object comes back wrapped up in a PSObject object, so you’ll have to dig it out.

Here’s what it looks like (this is a code fragment, some supporting elements are not included such as the definition of the MyResponse object):

Interacting with PowerShell from C#:
runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
runspace.SessionStateProxy.SetVariable("MyResponse", response);
Pipeline pipeline = runspace.CreatePipeline();

Command cmd = new Command(scriptPath);
cmd.Parameters.Add(args);
pipeline.Commands.Add(cmd);

Collection<PSObject> results = pipeline.Invoke();

PSObject newResponse = (PSObject)runspace.SessionStateProxy.GetVariable("MyResponse");
response = (MyResponse)newResponse.BaseObject;

runspace.Close();

Thanks for reading.  Good luck.

Thursday, December 31, 2009

Foundation Beyond Belief

  • Foundation Beyond Belief is a non-profit charitable and educational foundation created (1) to focus, encourage and demonstrate the generosity and compassion of atheists and humanists, and (2) to provide a comprehensive education and support program for nontheistic parents.

  • The Foundation will feature ten charitable organizations per quarter.

  • Members join by signing up for a monthly automatic donation in the amount of their choice, and distribute it however they wish among the categories. Contributions are fully tax-deductible.

  • Members can join a social network and forums centered on the ten categories of giving, advocate for causes, and help us choose new beneficiaries each quarter.

  • Featured beneficiaries may be founded on any worldview so long as they do not proselytize. At the end of each quarter, 100 percent of the donations are forwarded and a new slate of beneficiaries selected.

  • On the educational side, the Foundation will help create and fund local groups for the education and social support of humanist/atheist parents.

  • Our Mission: To demonstrate humanism at its best by supporting efforts to improve this world and this life; to challenge humanists to embody the highest principles of humanism, including mutual care and responsibility; and to help and encourage humanist parents to raise confident children with open minds and compassionate hearts.

Visit the Foundation Beyond Belief Website

Tuesday, July 15, 2008

Single Page Server Side Cookie Check (With Almonds)

Here's a bit of .Net code that allows you to check the state of a browser's cookie settings... Enabled or Disabled. It does use a redirect, but it still only requires a single page:

Sample Default.aspx.cs
// default namespace directives here...

public partial class _Default : System.Web.UI.Page
{
   bool cookies = false;

   protected void Page_Load(object sender, EventArgs e)
   {
      cookies = cookiecheck();
      Response.Write("cookies = " + cookies.ToString());
   }

   private bool cookiecheck()
   {
      if (Session["CookiesEnabled"] != null)
         return true;

      if (!IsPostBack)
      {
         if (Page.Request.QueryString["ce"] == null)
         {
            Session["CookiesEnabled"] = true;
            Response.Redirect(Request.Url.ToString() + "?ce=1");
         }
      }

      return false;
   }
}

Now go eat some almonds.

Tuesday, July 8, 2008

enSynch Slated to Take Over the World

enSynch, Inc.

Based in Tempe, AZ, enSynch, Inc. is one of the best IT service organizations I've ever had the privilege to work with.  And now they're getting even better!  With their agreement to acquire EBI Solutions, enSynch is further securing its position as the leading IT service organization in the Southwest.

You can read all about enSynch here and if you slide your mouse over here you can find all the insight you need on EBI.

The full press release is here.  Truly there is no reason to look anywhere else for your Microsoft infrastructure needs.  There is a heap of talent in that organization covering Messaging (Unified or otherwise), SharePoint, Identity Management, Business intelligence, Process Automation and pretty much any other buzz-word you can think of.

Engage enSynch and success is inevitable.

Thursday, May 29, 2008

Hitler's Toothbrush

You know how anyone caught wearing a toothbrush style mustache these days would be looked at with a bit of disdain?  (Well, from most people...)  This latest article tells me that we're taking this stuff a bit too far.  I mean, really?  The iced coffee pitch is a subliminal message purporting the virtues of terrorism?  Yasser Arafat is now defining fashion in the U.S.?  Michelle Malkin, the author of the terrorism comments, ought to be ashamed of herself.  How hard do you have to be looking to see symbolism in Rachael Ray's neck scarf?  People are gullible - You say something like that and they believe you.  (See...  You believed that, didn't you?)  I mean...  To the point that they actually pulled the ads.

What if Hitler had never trimmed his mustache?  The entire mustache wearing world have been deeply affected.  Fortunately, for the world, he was caught wearing a decidedly unattractive caterpillar looking patch of pubic hair that won't soon be missed.  Let's be clear...  I am not saying that caterpillars were in any way responsible for The Holocaust!  If a guy dresses like Michael Jackson, it doesn't mean he can dance.

We need to grow up.  Outside of the fashion industry, people are not defined by their facial hair or their scarves.  In the real world (shoot... even in most virtual worlds) people are defined by their actions.  What's the real problem, Malkin?  Didn't get your caffeine that morning?