Note: An updated version of this blog for StoreFront 2.5 is available here.
StoreFront 2.0 has been released with XenDesktop 7.0. It is time to revisit the topic of customizing Receiver for Web and provide a comprehensive guide on how to do it for StoreFront 2.0.
As you may have already known from my previous blogs (Customizing Receiver for Web and Customizing Receiver for Web in StoreFront 1.2), Receiver for Web provides a built-in support for customization through the contrib folder. This folder is located under the Receiver for Web site in the file system (default location is C:\inetpub\wwwroot\sites\Citrix\StoreWeb\contrib) and contains the built-in customization hooks. It is recommended that all customization code and media are stored under this folder because the content of this folder will be preserved upon upgrade to the subsequent releases.
Please note that there is no guarantee that customization will always work out of the box across release upgrade, especially when the UI changes significantly. The good news is that if you have followed my previous blogs to customize earlier versions of Receiver for Web, they should work with StoreFront 2.0 with only some minor tweaking.
CSS Customization
CSS customization can be used to easily change many aspects of the site appearance, including fonts, colors and images. A CSS file named custom.style.css is included in the contrib folder. This CSS file is referenced after the site’s main CSS files and hence the CSS style definitions specified in this file override similar definitions specified elsewhere. You can make use of Web development tools such as Firebug (for Firefox) or the developer tools provided by Internet Explorer and Google Chrome to identify the CSS selectors (element types, IDs or classes) you want to customize. Here are a couple of examples.
Example 1: Rebranding the Logon Screen
First let’s try to change the background image. As illustrated in the screen shot below, using Firebug, we identify that the CSS selector for the background image is body.
We copy the new background image file moonlightsea.jpg to the contrib folder. Add the following CSS definition to custom.style.css:
body { background-image: url("moonlightsea.jpg"); }
Reload the page and the new background image has taken effect as follows:
Next, let’s change the logo. We identify that the CSS selectors for the logo are #credentialupdate-logonimage and #logonbox-logoimage.
Copy the new logo image file worldcomplogo.png to the contrib folder. Add the following CSS definition to custom.style.css:
#credentialupdate-logonimage, #logonbox-logoimage { background-image: url("worldcomplogo.png"); }
Reload the page and the new logo is displayed:
Finally, let’s change the font color for the logon form labels. Using Firebug, we identify that the CSS selector for the logon form labels is .credentialform label and the font color is determined by CSS selectors .credentialform .error, .credentialform .warning, .credentialform .information, .credentialform .inprogress, .credentialform .confirmation, .credentialform .plain.
Add the following CSS definitions to custom.style.css:
.credentialform .error, .credentialform .warning, .credentialform .information, .credentialform .inprogress, .credentialform .confirmation, .credentialform .plain { color: yellow; }
.credentialform label { font-weight: bold; }
Reload the page and you can see that the labels for the logon form become yellow and bold.
Example 2: Customizing the Home Screen
First, let’s replace the logo in the header area. We identify the CSS selector for the logo is #header-logo.
Copy the new header logo image worldcomplogoheader.png to the contrib folder. Add the following CSS definition to custom.style.css
#header-logo { background-image: url("worldcomplogoheader.png"); height: 28px; margin: 8px 0 0 22px; }
Reload the page and the logo in the header area is replaced:
Next, let’s change the font color for application names to make it more readable. We identify that the CSS selector for the fort for application names is .myapps-name.
Simply add the following CSS definition to custom.style.css:
.myapps-name { color: yellow; }
Similarly, adding the following CSS definitions to custom.style.css changes the font color for the user name in the header area and switcher buttons:
#resources-header #header-username { color: lightgreen; }
.switcher-button a { color: lightgreen; }
Here is the resultant Home Screen:
String Customization
Customized string bundle files for all supported languages are included in the contrib folder, one for each language. For example, the customized string bundle file for English is custom.wrstrings.en.js. These are simple JavaScript files that define a set of name-value pairs. Strings defined in these files override the built-in strings for the given language.
Example 3: Customizing Texts for Logoff
By default, as shown in the screen shots below, the label for the link to log off is “Log Off”. The message displayed after a successful logoff is “You have logged off successfully.” and the label for the button to get to the logon screen is “Log On”.
If we want to customize these labels and text, we need to find out the string IDs by searching the relevant strings in the built-in English string bundle file ctxs.wrstrings.js, located in scripts\en. We find that the string IDs are LogOff, YouAreLoggedOff and LogOn. We then insert the following name-value pairs to custom.wrstrings.en.js:
LogOff: 'Sign Out', YouAreLoggedOff: 'You have signed out successfully. Click the button below to sign in again', LogOn: 'Sign in',
Refresh the page and you can see the labels and text have been changed.
A similar process can be followed to change any of the existing strings found in the UI. Strings can be altered for other languages by providing the customization in the appropriate custom string file, e.g., custom.wrstrings.fr.js for French.
JavaScript Customization
JavaScript customization can be used to inject additional content into the UI and load other scripts, CSS style sheets and HTML fragments via Ajax. A JavaScript file named custom.script.js is included in the contrib folder and referenced by the HTML file for the Receiver for Web UI. This custom JavaScript file is referenced after the site’s main JavaScript files so that any functions defined in the custom file override functions with the same names defined elsewhere.
Example 4: Adding a Hyperlink to the Footer Area
Using Firebug, we identify that the ID for the footer area is #resources-footer.
We add the following JavaScript code to custom.script.js:
$(document).ready(function() { var $markup = $('<div id="help-link"><a class="class _ctxstxt_ContactHelpDesk"' + ' href="mailto:helpdesk@world.com"> </a></div>');
$('#resources-footer').append($markup); });
The CSS class _ctxstxt_ContactHelpDesk tells the localization mechanism to look for a string with ID ContactHelpDesk from the string bundle files and use it for this HTML element.
So we need to add the following name-value pair to custom.wrstrings.en.js:
ContactHelpDesk: 'Contact the Help Desk',
This string needs also to be translated into all supported languages and added to the corresponding custom language string bundles.
In addition, we need to add CSS rules in custom.style.css to place the link in the location we like and display it in a font that is visible:
#resources-footer { height: 84px; }
#help-link { padding-right: 30px; padding-bottom: 20px; float: right; }
#resources-footer a { color: lightgreen; }
#resources-footer a:hover { color: silver; text-decoration: underline; }
The resultant screen looks like with the hyperlink in the bottom-right corner:
Example 5: Loading Extra UI Files
In this example, we load extra HTML and CSS files to achieve the same effect as Example 4. If you have applied the customization in Example 4 in your site, you have to undo the changes before following the instruction below.
First, create a new file named helpdesk.htm in the contrib folder with the following HTML fragment:
<div id="help-link"> <a href="mailto:helpdesk@worldco.com"></a> </div>
Then create another file named footer.css in the contrib folder with the following CSS definitions:
#resources-footer { height: 84px; }
#help-link { padding-right: 30px; padding-bottom: 20px; float: right; }
#resources-footer a { color: lightgreen; }
#resources-footer a:hover { color: silver; text-decoration: underline; }
Finally add the following JavaScript code to custom.script.js:
$(document).ready(function() { $.ajax({ url: 'contrib/footer.css', success: function(data) { $('<style></style>').appendTo('head').html(data); } });
$.ajax({ url: 'contrib/helpdesk.htm', success: function(data) { $('#resources-footer').append(data); } }); });
The first $.ajax call loads the CSS style sheet footer.css. The second $.ajax call loads the HTML fragment defined in helpdesk.htm to the footer div. The resultant screen is exactly the same as that of Example 4.
Adding Pre-Login and Post-Login Messages
Receiver for Web provides hooks for you to add custom pre-login and/or post-login messages if you wish. You can add these messages in the built-in message box style or in your own style.
Example 6: Pre-Login and Post-Login Messages in Message Box style
To add a pre-login message, insert the following JavaScript code to custom.script.js:
$(document).ready(function() { CTXS.Application.preLoginHook = function () { var _dialogTitle = '<h1 class="messagebox-title _ctxstxt_Disclaimer"></h1>'; var _dialogBody = '<div class="messagebox-body">' + '<p class="_ctxstxt_DisclaimerStatement"></p></div>'; var _dialogButton = '<div class="messagebox-buttons">' + '<a href="#" class="custombutton _ctxstxt_Continue"></a></div>'; var dialog = _dialogTitle + _dialogBody + _dialogButton;
var $messagePane = CTXS.displayMessagePane(dialog).ctxsLocalize();
var $button = CTXS.button($messagePane.find('.custombutton')); $button.click(function () { CTXS.Events.publish(CTXS.Events.preLogin.done); return false; }).ctxsHandleEscapeKeyInDialog().ctxsPlaceFocusOnFirstElement().ctxsBindFocusWithin(); }; });
Then add the following strings to custom.wrstrings.en.js and their translated versions to string bundle files for supported languages:
Disclaimer: 'Disclaimer', DisclaimerStatement: 'This site is for the employees of World Comp only!', Continue: 'Continue',
The function CTXS.Application.preLoginHook is invoked by the Receiver for Web at run time before the user logs in. It displays the message box with the localized content. A click handler is registered on the button, which signals that Receiver for Web should switch to the next screen when it is clicked. This pre-login message looks like:
To add a post-login message, insert the following JavaScript code to custom.script.js:
$(document).ready(function() { CTXS.Application.postLoginHook = function () { var _dialogTitle = '<h1 class="messagebox-title _ctxstxt_Announcement"></h1>'; var _dialogBody = '<div class="messagebox-body"><p class="_ctxstxt_AnnouncementContent"></p></div>'; var _dialogButton = '<div class="messagebox-buttons">' + '<a href="#" class="custombutton _ctxstxt_Continue"></a></div>'; var dialog = _dialogTitle + _dialogBody + _dialogButton;
var $messagePane = CTXS.displayMessagePane(dialog).ctxsLocalize();
var $button = CTXS.button($messagePane.find('.custombutton')); $button.click(function () { CTXS.Events.publish(CTXS.Events.postLogin.done); return false; }).ctxsHandleEscapeKeyInDialog().ctxsPlaceFocusOnFirstElement().ctxsBindFocusWithin(); }; });
Then add the following strings to custom.wrstrings.en.js and their translated versions for the supported languages:
Announcement: 'Announcement', AnnouncementContent: 'This site is scheduled for maintenance between 6:00pm and 10:00pm next Saturday.',
The post-login message looks like:
Example 7: Pre-Login and Post-Login Messages Your Own Style
If you prefer more freedom of having your own fancy style for pre-login and post-login message, you can construct your own markups and attach them to #prelogin-pane and #postlogin-pane DIVs.
Here is how you add pre-login message:
Add the following JavaScript code to custom.script.js:
$(document).ready(function() { var markup = '<div id="prelogin-message">' + '<h3 class="_ctxstxt_Disclaimer"></h3>' + '<p class="_ctxstxt_DisclaimerStatement"></p>' + '<a href="#"></a>' + '</div>';
CTXS.Application.preLoginHook = function () { $('#prelogin-pane').append($(markup)).ctxsDisplayPane().find('a').click(function () { CTXS.Events.publish(CTXS.Events.preLogin.done); return false; }); }; });
Then add the CSS rules to custom.style.css:
.custom-pane { text-align: center; color: white; }
.custom-pane h3 { margin-top: 80px; font-size: 30px; }
.custom-pane p { margin: 40px 0; font-size: 18px; }
.custom-pane a, .custom-pane a:active, .custom-pane a:visited { font-size: 24px; color: tan; }
.custom-pane a:hover { text-decoration: underline; }
We re-use the strings used in the previous example and the resultant pre-login message looks like:
Add the following JavaScript code to custom.script.js would give you a post-login message with the same style of the pre-login message:
$(document).ready(function() { var postLoginMarkup = '<div id="postlogin-message">' + '<h3 class="_ctxstxt_Announcement"></h3>' + '<p class="_ctxstxt_AnnouncementContent"></p>' + '<a href="#"></a>' + '</div>'; CTXS.Application.postLoginHook = function () { $('#postlogin-pane').append($(postLoginMarkup)).ctxsDisplayPane().find('a').click(function () { CTXS.Events.publish(CTXS.Events.postLogin.done); return false; }); }; });
The post-login message looks like:
Adding Server-Side Code
Sometimes, you may want to add extra functionality to Receiver for Web in a way that requires server-side code execution. You can write ASP.NET code to be run on the server and load the result to the UI using Ajax.
Example 8: Display Client IP Address
For example, if you want to display the user’s client IP address after the user logs on, you cannot get the information from JavaScript code running in the browser. Instead you have to write some ASP.NET code to run on the server to find out.
First you have to create an ASPX file GetClientIP.aspx with the following code to obtain the client IP address:
<%@ Page Language="C#" %> <script runat="server" language="C#"> private string GetClientIP() { string ips = Request.ServerVariables["HTTP_X_FORWARDED_FOR"]; if (!string.IsNullOrEmpty(ips)) { return ips.Split(',')[0]; } return Request.ServerVariables["REMOTE_ADDR"]; } </script> <%=GetClientIP()%>
Then you need to add some ajax code to custom.script.js to load the data and display in the UI:
$(document).ready(function() { $.ajax({ url: 'contrib/GetClientIP.aspx', success: function(data) { var $markup = $('<div id="clientip"><span class="_ctxstxt_ConnectedFrom"/>' + data + '</div>'); $markup.insertAfter('#header-userinfo'); } }); });
You also need CSS rules in custom.style.css to display the Client IP address nicely on the screen:
#clientip { color: lightgreen; float: left; margin-right: 25px; margin-top: 12px; position: relative; vertical-align: middle; }
Here is the resultant screen shot:
Supporting a New Language
You can add support for a new language to Receiver for Web. This involves adding a language pack to Receiver for Web and adding relevant language resources to the Authentication Service.
Adding a language pack to Receiver for Web
New language packs are comprised of a culture definition script file and a string bundle script file for each language.
Culture definition
The format of the culture definition script file is:
(function ($) { $.globalization.availableCulture("<lang-code>", { name: "<lang-code>", englishName: "<language name in English>", nativeName: "<language name in native language>", stringBundle: "<path to the string bundle>", customStringBundle: "<path to the custom string bundle>" }); })(jQuery);
For example, a culture file for Polish looks like:
(function ($) { $.globalization.availableCulture("pl", { name: "pl", englishName: "Polish", nativeName: "polski", stringBundle: "contrib/wrstrings.pl.js", customStringBundle: "contrib/custom.wrstrings.pl.js" }); })(jQuery);
This should be saved as culture.pl.js.
String bundle
The string bundle script file defines a set of name-value pairs. You can copy an existing string bundle file from scripts/<lang-code>/ctxs.wrstrings.js, e.g. the English one from scripts/en/ctxs.wrstrings.js to wrstrings.pl.js. Then replace the language code ‘en’ with ‘pl’ and replace all string values with the translated ones.
You also need to create a custom string file for the new language, which custom.wrstrings.pl.js in this case. Put the translation of your override or new strings in this file.
Loading the language pack
After you create the language pack, you can add the following code to custom.script.js:
$(document).ready(function () { CTXS.Localization.getScript("contrib/culture.pl.js"); });
This code loads the culture definition and string bundle script files.
Adding language resources to Authentication Service
The user interface for the logon form is provided by the StoreFront Authentication Service. Localizing these strings requires creating 3 additional resource files. Continuing with using Polish (language code ‘pl’) as an example:
In Windows Explorer, open the folder C:\inetpub\wwwroot\Citrix\Authentication\App_Data\resources.
- Copy ExplicitAuth.resx to ExplicitAuth.pl.resx
- Copy ExplicitCore.resx to ExplicitCore.pl.resx
- Copy ExplicitFormsCommon.resx to ExplicitFormsCommon.pl.resx
In all the newly copied files, locate each <data> element and translate the string in the corresponding <value> element. Save each file using UTF-8 encoding. Run the command iisreset on the Storefront to restart IIS.
Adjusting UI elements for the new language
On a Polish system, loading the Receiver for Web site in a browser should now display the UI with all strings appearing in Polish. If any translated strings are longer in Polish than the built-in languages and are not correctly positioned, it is worth noting that the language code (‘pl’ in this case) appears as a class name on the <body> tag, and this can be used to create CSS definitions to adjust elements as necessary. For example, the following CSS definition would make the logon box slightly larger when using Polish, to accommodate longer strings:
.pl #logonbox-logonform { width: 420px; }