Reporting Services - Add a logo to the Report Manager
The SQL Server Reporting Services Report Manager is functional, but it's not very customizable. The ASPX pages just reference compiled assemblies, so the only real way to modify them is via CSS.
What makes that more difficult is that the SSRS HTML is poorly constructed so that the tags you'd most want to customize don't have Id's or Classes assigned. For instance, the most obvious customization anyone would want to make is to add a corporate logo, and there's no hook for that at all. The folder image is a simple <img> tag, nestled between two other spacer <img> tags, packed inside an unidentified <td>, and all those elements are written out in code1. The problem there is that there's no way, even with some descendant selector trickery, to target that folder image.
That's not entirely true, part 1:
IE7 adds support for some combinators (e.g. child, adjacent, first:sibling) which allow you to directly target elements without classes or id's. Why I'm not using that approach here:
- IE6 doesn't support it.
- IE7's support is kind of quirky - it sees HTML comments as page elements, for instance. That means it's difficult to write cross browser combinator based CSS rules.
- Targeting deeply nested elements with combinators is pretty difficult.
- Combinator based rules are inherently unstable, since any changes to the page's structure (including HTML comments, as mentioned above) can really mess up your page. Structure changes can cause your rule to be ignored or - much worse - point it at another page element.
That's not totally true, part 2:
You could replace the image files (C:\Program Files\Microsoft SQL Server\MSSQL.X\Reporting Services\ReportManager\images\48folderopen.jpg
as well as all the other files in that directory named 48*.jpg)2 with your corporate logo. Problems with that approach:
- You're constrained to a 48x48 px jpg
- Subsequent upgrades could overwrite it
- Those images do serve some use - they change depending on the current function you're performing in the site.
Who needs an Upper Title, anyway?
I went for the next best target - the rather useless "upper title" - since it's a div which actually has a class (div.msrs-uppertitle)assigned. First, let's look at the style changes, then we'll talk about the different ways to add them.
.msrs-uppertitle { BACKGROUND: url(http://www.sitename.com/images/logo.gif) no-repeat; HEIGHT: 35px; WIDTH: 120px; TEXT-INDENT: -5000px; }
Some things to notice:
- We set a background image by URL. That will keep the reports up to date with our company logo as it may change in the future. Make sure to pick an image that's an appropriate size for that space.
- We set the background to no-repeat so it just displays once rather than tiling. That's handy even if the logo image has a border, since we don't have to be so precise on your height and width settings.
- We set a height and width which are large enough to display the logo. These should be just larger than your image size; you can just see via trial and error what looks best. This is one of those times where on the fly CSS editing tools like the IE DevToolbar or the Firefox Web Developer extension come in really handy.
- Now we need to get rid of the site title. I didn't want to mess with trying to blank it out in the SSRS Site Settings since it might be useful elsewhere, or someone might fill it in without knowing that it would conflict with the logo. The easiest solution is to use the Phark Image Replacement technique and hide the text by setting the indent to -5000px.
Done and done.
Oh, wait. I said I'd be talking about where to add this style, didn't I? A few options:
- Edit ReportingServices.css (in the C:\Program Files\Microsoft SQL Server\MSSQL.3\Reporting Services\ReportManager\Styles folder).
- Use another stylesheet - method 1: change the HTMLViewerStyleSheet parameter in RSReportServer.config
- Use another stylesheet - method 2: specify a stylesheet in rc:StyleSheet parameter of the report url:
http://localhost/reportserver?/AdventureWorksSampleReports/Product+Line+Sales&rs:Command=Render&rc:Stylesheet=MyStyleSheet
I went with a variation of option 1 - I edited ReportingServices.css, but just to add an import statement that points to another CSS file where I'll put all my Reporting Services CSS customizations:
- Add the following to the top of ReportingServices.css:
@import url(customizations.css); - Save the block of CSS (the bit that starts with ".msrs-uppertitle") to a file called customizations.css to the same folder as ReportingServices.css. You might want to add other customizations to it while you're at it, like this CSS fix for Firefox.
That's it! We're done!
Great, but the logo should link to the home page of our intranet!
What!? Feature creep! Version 2!
Oh, okay. It's not easy, though...
Changing style isn't a piece of cake, but at least there's a supported hook for it. You can't add a link via CSS, though. CSS2 allows for some limited content generation, but it doesn't support adding links; even if it did we'd be out of luck because IE7 doesn't support it.
Bill Vaughn and Peter Blackburn proposed a solution using DHTML Behaviors. That's a pretty slick workaround - behaviors allow you to tie Javascript functionality to DOM elements in CSS. Unfortunately, only IE supports behaviors. Plus, if you're going to hack Javascript, why not edit the ReportingServices.js file...3
C:\Program Files\Microsoft SQL Server\MSSQL.n\Reporting Services\ReportManager\js\ReportingServices.js
There it is... the fabled treasure... a way to modify the content of the Reporting Services Manager...
I added the following code to the top of the JS file (after backing it up, of course):
addLoadEvent(SetLogoUrl); function addLoadEvent(fn) { if (window.addEventListener) window.addEventListener('load', fn, false) else if (window.attachEvent) window.attachEvent('onload', fn); } function SetLogoUrl() { var header = document.getElementById('ui_sharedArea'); if (!header) return; var headerDivs = header.getElementsByTagName('div'); for (var i=0;i<headerDivs.length;i++) { if(headerDivs[i].className == 'msrs-uppertitle') { headerDivs[i].onclick = new Function('top.location="/"'); headerDivs[i].style.cursor = 'pointer'; //headerDivs[i].style.backgroundImage = 'url(http://images.slashdot.org/topics/topiccommunications.gif)'; //headerDivs[i].style.backgroundRepeat = 'no-repeat'; //headerDivs[i].style.width = '96px'; //headerDivs[i].style.height = '62px'; } } }
Let's talk about it:
- That addLoadEvent business is there because we can't modify the page until it has finished loading, but this script is included at the top of the page. A less sophisticated solution is to set a timeout to call SetLogoUrl, but addLoadEvent leverages the built in event model to call the function after the page has finished loading. IE uses attachEvent, while everyone else uses addEventListener, so we'll include some logic to make sure the right one gets called.
- SetLogoUrl's job is to modify the Upper Title DIV. This would be easier if that DIV had an ID assigned, since Javascript doesn't have built in support for retrieving an element by class name. Most sample code which returns an element by class just iterates all elements in the DOM looking for a class name match, but since we know a bit about the page structure we can be a little more efficient. We narrow the search by grabbing the nearest parent element with an ID assigned and only searching through its children.
- Once we've got our DIV, we can just set the onclick behavior to navigate to the correct URL. We'll need to change the cursor to indicate that it's a link, too.
Hey, since we're messing with that DIV, why not just change the image in Javascript and simplify things? Well, I've left some commented code there to get you started if that's what you want to do, but I think it's better to keep the image modification in CSS.
Why? Well, there are a few good reasons. The biggest reason is that the Javascript doesn't execute until the page has finished loading, so the original DIV text is displayed, then changes to the logo. Ugh. Plus, from a maintenance and architectural perspective, it's a much better practice to keep your presentation information in CSS and your behaviors in Javascript.
And that's it.
But, can we change the navigation to use WPF/E with Ajax and pull in some Google Analytics?
No.
1 reflector://Microsoft.ReportingServices.UI.SharedArea.InitTitleArea()
2 Reporting Service installs into different directories depending on the order you install SQL Server services. A default installation will put Reporting Services in \Program Files\Microsoft SQL Server\MSSQL.3\Reporting Services, but that MSSQL.3 folder may be different on your machine.
3 I actually stumbled across the HTC technique after I'd finished making this work as a Javascript include.