Eric Cressey

Tech writer - Content strategist - Developer

Displaying multi-language code samples using jQuery EasyTabs

| Comments

If your API documentation includes code samples in a few languages, put the samples in tabs and let users select the language they want. Users see samples in their preferred language and you end up with a shorter topic that’s much easier to navigate because it only includes samples your user wants to see.

It’s pretty easy to organize your code samples by tabs. In this example, I’m going to show you how to do it with an open source jQuery plugin called EasyTabs.

HTML

Here’s the basic HTML structure for your tabs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="tab-container">
    <ul>
        <!--The div for each code sample needs a unique ID, so we append a number to each -->
        <li class="tab Java"><a href="#Java1">Java</a></li>
        <li class="tab csharp"><a href="#csharp2">C#</a></li>
        <li class="tab PHP"><a href="#PHP3">PHP</a></li>
    </ul>

    <div class="codeSample Java" id="Java1">
        <pre class="programlisting"><code class="language-java">Java code here</code></pre>
    </div>

    <div class="codeSample csharp" id="csharp2">
        <pre class="programlisting"><code class="language-csharp">C# code here</code></pre>
    </div>
    <div class="codeSample PHP" id="PHP3">
        <pre class="programlisting"><code class="language-php">PHP code here</code></pre>
    </div>
</div>

All your tabs go into a tab-container div. The div contains an unordered list with a list item for each tab that links to the div where the code sample is.

In addition to the HTML markup, there’s some JavaScript to add to the page. JavaScript changes the tab on user click and makes sure that each set of code samples on the page is showing the same language tab.

If you are adding this feature to a single-page help output, you may need to add additional JavaScript to make sure the scroll position stay in the right place as the user changes languages.

JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$(document).ready(function() {
    'use strict';

    //only add tabs if there are multiple languages
    if ($('div.tab-container').length > 0) {
        $('div.tab-container').easytabs({
            animate: false,
            updateHash: false
        });
    }
    //switch all code samples to selected language on click
    $('div.tab-container ul li.tab').click(function() {

        //get the language from the active link href
        var codeLang = $(this).find('a.active').attr('href');
        //remove number and hash to get lang value without unique identifier.
        codeLang = codeLang.replace("#", "").replace(/\d/g, "");

        //find and update each set of code samples on the page
        $('div.tab-container ul li.' + codeLang).each(function() {

            // make the tab active and hide the other tabs
            $(this).addClass('active').siblings().removeClass('active');
            $(this).siblings().find('a').removeClass('active');
            $(this).find('a').addClass('active');

            //show the appropriate code sample, hide the others
            $(this).parent().siblings('div.' + codeLang).addClass('active').css('display', 'block');
            $(this).parent().siblings('div.codeSample').not('.' + codeLang).removeClass('active').css('display', 'none');
        });
    });
});

Results

Here’s what it looks like with prism.js syntax highlighting. Try it in JSFiddle

Back from STC 2017

| Comments

STC 2017 was a lot of fun. I gave a presentation about using UX strategies to produce better API documentation. You can find the presentation slides here.

The talk wasn’t all about writing; a lot of it was about working with engineering teams and positioning yourself to be successful and have enough time to write.

I always enjoy presenting at the STC and I’ll definitely submit a proposal to speak at STC 2018.

Programmatically applying regular expressions

| Comments

As content owners, we’re sometimes asked to take on big projects to maintain that content. For example:

  • Last year at Symantec, we stopped using the verisign.com domain. We needed to update all VeriSign URL references to Symantec URLs in our products and emails. The project scope was all the URLs in more than 20,000 files.

  • At a previous company, we used several old, large Flare projects. Some of the files had weird HTML that caused issues with our CSS files. We needed to remove this legacy content from the 10,000 page project.

Big projects like these go beyond what you can do with a few regular expressions. The expanded scope requires more planning and more regular expressions applied to more locations.

To tackle these projects, I wrote a program to apply regular expressions to the files in a directory. This program let me to focus on writing regular expressions and made it easy to test when I wanted to measure my progress. You can get the C# program here.

Getting started with the programmatic approach

Let’s take a look at how to customize the program for your own purposes.

The program has a main function and two named functions:

  • Main function
  • ProcessDirectory
  • ProcessFile

Updating the directory location in the Main function

The main function contains a variable for the directory you want to update.

1
2
3
4
    string directory = @"C:\Users\username\Desktop\SampleDocs";
    //For option 2, the program will use the directory where the .exe file is.
    //Use the following line instead of the previous one (line 25). 
    //string directory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

By default, the directory variable points to a folder. If you want to point to a specific folder, there’s no need to build the application to run it. Instead, just hit the Start button on the Visual Studio toolbar.

If you want to run the program as an executable in the directory of your choice, you can do that by making a few changes:

1
2
3
4
    //string directory = @"C:\Users\username\Desktop\SampleDocs";
    //For option 2, the program will use the directory where the .exe file is.
    //Use the following line instead of the previous one (line 25). 
    string directory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

Now when you’re ready to test, build the application (CTRL + SHIFT + B) and then grab the RegexForWriters.exe file from the visual studio project’s RegexForWriters\RegexForWriters\bin\Debug folder.

Run the .exe file in the directory you want to update.

Setting allowed file extensions with ProcessDirectory()

If you only want to process specific file types, you can make a few changes to the ProcessDirectory method. Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    //get all the files in the folder and process them
    string[] fileEntries = Directory.GetFiles(directory);

    List<string> file_list = new List<string>(fileEntries);
    try
    {
        Parallel.ForEach(file_list, file =>
        {
            //you can apply edits to specific file types. 
            //If you don't want to specify file types, delete lines 59 and 61.
            if (Path.GetExtension(file) == ".txt" || (Path.GetExtension(file) == ".html")) {
                ProcessFile(file);
            }
        });
    }

If you want to change the file types to edit, change this line: if (Path.GetExtension(file) == ".txt" || (Path.GetExtension(file) == ".html")) { As written, only .txt and .html files are processed. To process .properties, .xml, and .htm files instead:

if (Path.GetExtension(file) == ".properties" || (Path.GetExtension(file) == ".xml") || (Path.GetExtension(file) == ".htm")) {

Adding regular expressions to ProcessFile()

The ProcessFile method is the place you’ll add regular expressions and tell the program how to update the text. Here’s the entire method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    public static void ProcessFile(string file)
    {
        /** This method:
         * 1. Gets all the text in a file
         * 2. Performs a series of regular expressions
         * 3. Saves the file with the updated text
         * */

        //get all text
        string text = File.ReadAllText(file);

        //use regex.replace function and pass in the text to search, regex, and replacement string
        text = Regex.Replace(text, @"(?<=Greeting=).*", "Hello, world!");

        //you can remove text by passing in a blank replacement string
        //use regex options at the end if you want to ignore case in your match
        //if the regex has quotes in it, escape them as shown here
        text = Regex.Replace(text, @"<p.*?class="".*?unnecessary.*?"".*?>.*?<\/p>", "", RegexOptions.IgnoreCase);

        //if you want to do a simple text replacement without regex, use the string.replace method
        text = text.Replace(@"Old text", "New text");

        /**
         *  Add more regular expressions here, as many as you like. 
         * */

        //Finally, save the file with the updated text
        File.WriteAllText(file, text);
    }

Use the Regex.Replace() method to apply regular expressions to the text. The method takes three or four arguments: Regex.Replace(text to update, regular expression, replacement text, regex options)

The text to update is the file text, stored in the text variable. After that, specify your regular expression. If your regular expression has quotes in it, you may want to escape it with an @ at the beginning and then an additional quote before each quote, as shown here: @"<p.*?class="".*?unnecessary.*?"".*?>.*?<\/p>". Always put the regex and replacement text in quotes because they’re strings.

Using group text in replacements

One of the cool things about regex groups is that you can reference group values in your replacement text. This can save a lot of time. The syntax is the same as usual. Read this post to learn more about regex groups.

1
2
    //you can reference capturing groups in your replacement string by number, $1, $2, etc.
    text = Regex.Replace(text, @"<span.*?class=""bold"".*?>(.*?)<\/span>", "<strong>$1</strong>");

More uses for this program

Because this program quickly updates files in a directory, it is broadly applicable to file-related grunt work. You might use it to: * rename files in bulk * customize a web help output by inserting HTML and CSS and JavaScript references

Recently, we migrated some emails from XML to properties and I used this program to fetch content from XML and create the appropriate properties files. If you’re interested in using the programmatic approach to text editing tasks, this program is a great place to start.

Regex for writers - groups

| Comments

Here’s a sneak peak of some of the content I’m covering at the STC Summit this year, plus a few things I had to cut. If you’re interested in attending my presentation, visit the STC Summit home page or the page for my class.

Groups are a big part of regular expression syntax. Groups:

  • Support OR logic
  • Account for optional or repeated content by letting you specify how many times to match a series of characters
  • Let you qualify a match based on nearby content
  • Let you reference matched content in replacement strings

Feel free to try the example regular expressions at regex101.com.

OR logic and variance

If we want to match variations of URLs, for example, we might use a regular expression like this:

(https?:\/\/)?(www.)?domain.(com|org|net)

Which matches all these URLs:

Using the ? token after a group, (www.)?, matches the group zero or one time. That’s useful if there are optional parts in the text we’re trying to match.

Including values on either side of a pipe character in a group, (com|org|net), specifies OR logic. With an OR group, the regex can match any of the values in the group.

Qualifying matches based on nearby content

Lookbehinds and lookaheads are special groups. They do not contribute to a match, but help qualify the match based on nearby content. Positive lookbehinds use this pattern:

(?<=previous content)content to match

Positive lookbehinds return matched content only if it comes after some other content. Lookbehinds have a fixed length, so you need to know the content inside the lookbehind. There are negative lookbehinds, too. They work the way you think they do. The negative lookbehind pattern is: (?<!not this content)content to match

Positive lookaheads follow this pattern: content to match(?=following content)

Positive lookaheads return matched content only when the content after it matches something. Unlike lookbehinds, lookaheads are variable-length and support greedy matches. Negative lookbehinds do the opposite.

Negative lookbehinds follow this pattern: content to match(?!not this following content)

In both cases, the content inside the lookahead or lookbehind group is not part of the match. Lookahead and lookbehind content only serve to qualify matches based on nearby content.

Here’s an example using both patterns to get content from a paragraph tag. (?<=p class="test">).*?(?=<\/p>)

Given <p class="test">Hello this is a test</p>, the regex matches Hello this is a test.

That’s good enough for many tasks, but what if you want to use that content as part of a replacement string? You can do that, too.

Referencing matched content in a replacement string

Groups allow you to mark sub-patterns in a regular expression. Sub-patterns are useful because you can reference them in replacement strings. To make a sub-pattern, just put that part of the regular expression in parenthesis. Consider the previous example: (?<=p class="test">).*?(?=<\/p>)

This matches “Hello this is a test.” But, because the lazy match isn’t in a group, you can’t use it in your replacement string. Putting it in a group does the trick.

(?<=p class="test">)(.*?)(?=<\/p>)

Groups are also called “capturing groups.” In this example, there’s one capture group. (Remember, lookbehinds and lookaheads don’t contribute to matches.) To reference a capture group, use this pattern in your replacement string: $n, where n is the capturing group’s number.

Capture groups are numbered in the order they occur in the expression. In our example, we’d use $1. Let’s put it all together:

Original text: <p class="test">Hello this is a test</p>

Regex: (?<=p class="test">)(.*?)(?=<\/p>)

Replacement string: $1 of the emergency broadcast system.

Updated text: <p class="test">Hello this is a test of the emergency broadcast system.</p>

That’s pretty cool. Not only can we match unknown content, we can use that content in our replacement strings. There are a lot of ways this is useful, but here’s an example:

You’re working with some legacy HTML pages produced by an older version of your CMS. This legacy content has content you want to remove, like span tags with “bold” as the class.

Capturing groups allow us to move the content in one tag to another.

Original text: <p class="test">Hello this is a <span class="bold">test</span></p>

Regex: <span.*?class="bold".*?>(.*?)<\/span>

Replacement string: <strong>$1</strong>

Updated text: <p class="test">Hello this is a <strong>test</strong></p>

Conclusion

I’ve covered a few of the most common ways that technical writers can use groups to make their tasks easier. There are other kinds of groups and ways to use them. More regex posts to come!

If there are topics you’d like me to write about, leave them in the comments!

I’m presenting at this year’s STC Summit

| Comments

Something’s missing from the technical writer’s toolkit: regular expressions. It makes sense why tech writers haven’t embraced regular expressions. No one has really made a case for using regular expressions as a technical writer. And, if a writer has encountered regular expressions before, it’s likely they’re a little scarred from the experience.

Regular expressions are a great tool to have in your toolbelt as a technical writer. They’re not glamorous, but neither are the tasks they’re great at performing. Often as technical writers we have to do repetitive text editing tasks, like removing legacy content from our web help or making sure all our ampersands are encoded in XML files. These aren’t tasks that anyone really enjoys doing and usually we get stuck with them because they’re somewhat related to content.

These tasks are value drainers. They’re mindless and don’t allow us to demonstrate value to our organization. As writers we want to focus our efforts on user experience and producing quality content. These tasks take us away from the valuable, satisfying work that we like to think of as “what we do.”

That’s where regular expressions come in. They’re not a silver bullet by any means, but they’re pretty useful in automating a lot of these repetitive text editing tasks. Regular expressions find patterns in text and that ends up being pretty easy to do when the text is structured. Luckily for us as technical writers, most of the content we work with is structured in some way.

Because we work with structured content, our regular expressions don’t have to do as much work as what developers might be trying to do. The problems we’re solving are typically a lot simpler than the problems developers solve with regular expressions. So, unlike developers, technical writers can get a lot of value from knowing a few common regular expression patterns.

Regular expressions can save a lot of time on daily tasks and one of the big reasons to use them is efficiency. As with learning any other skill, it’ll take time for using regular expressions to be more efficient than doing it manually. Even if the tasks take the same amount of time, only one method is actively building your skillset. The payoff for using regular expressions can be pretty huge.

If you’ve got a daunting project that sounds like it’ll take weeks or months, attending the conference might be worth it just for this presentation. In the past five years, I tackled two huge projects with regular expressions, saving an estimated 4 months of effort. If you’re wondering why regular expressions, that’s why.

To learn more about my course and the STC Summit, visit the STC Summit home page or the page for my class. See you in Anaheim!

Adding In-Page Navigation to Long Topics

| Comments

People scan help pages. If a page doesn’t look like it contains the right information, people will abandon it and look elsewhere. If you want your topics to be useful, make it immediately clear what they contain and put the most useful information at the top. If you can, keep them short. Short topics are easy to scan and usually fit on the screen without scrolling.

Sometimes you’ll write long topics. Naturally, these topics are harder to scan. They cover more material and often contain several layers of information that might not be immediately clear when the page loads. However, you can create in-page navigation links to make these pages much easier to scan and use. Here’s an example from Microsoft’s help.

Notice the box that says “In this article.” It tells the user what’s in the topic and allows him to jump to a particular section. Imagine how unwieldy this topic would be without that box. That’s what I’m going to show you how to add to your Flare output.

Rather than manually adding these navigation boxes to long topics, let’s get JavaScript and CSS to do the work for us. Now, when a topic loads, JavaScript will check to see if there is more than one heading on the page and, if there is, it’ll create a navigation box so users can quickly jump to the other headings. In addition, it’ll also create “To the top” links to allow users to jump back to the top of the page from any of those headings.

Save the following code as a JavaScript file in your web help project.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$(document).ready(function(){

    var topic = $(':header').not(':first');
    if (topic.length > 0) {
        $('<div class="sidebar"><span class="sidebarheading">In this topic</span><div class="sidebartoc"><ul class="innerstep"></ul></div></div>').insertBefore('h1');

        $(topic).each(function () {
            var topicName = $(this).text(); //get the name that will be displayed in the nav box
            var linkName = topicName.replace(/\s/g, ''); //edit the name for use as an anchor
            $('<a name="' + linkName + '"></a>').prependTo(this); //puts an anchor with a name attribute we created by the topic so we can link to it
            $('<li><a class="topiclink navlinks" href="#' + linkName + '">' + topicName + '</a></li>').appendTo('ul.innerstep'); //nests the subheading under its heading in the navigation box
        })

        for (var i = 0; i < topic.length; i++) {
            $('<div><a href="#" class="totop">To the top</a></div>').insertBefore(topic[i]); //add to the top button to links on page.
        }
    }
});

Add this JavaScript file to your master page

  1. Open your project in MadCap Flare. Open your target’s master page.
  2. Place your cursor under the topic body proxy.
  3. Click Insert on the ribbon. In the Symbols section, click Script. The Insert Script dialog box will open.
  4. Next to Language, select text/javascript.
  5. Next to Script Link, click Browse and open the script file you created earlier.
  6. Click OK to insert the script on your master page. Remember to save the file.

Add the CSS styles for this navigation box to your CSS file

  1. In the Content Explorer, browse to and select the CSS file for your target.
  2. Right-click the CSS file and open it with the text editor of your choice.
  3. Insert the following CSS rules below all of your other rules. Remember to save the file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
div.sidebar {
    background-color: #f0f0f0;
    color: #f7f7f7;
    display: block;
    float: right;
    font: normal 14px Arial;
    line-height: 19px;
    margin: 0px 0px 15px 15px;
    padding: 12px 0 0 0;
    width: 190px;
    outline: 10px solid white;
    border: 1px solid #e0e0e0;
}
.sidebarheading {
    margin-left:15px;
    color: #0C4380;
    display: inline;
    font: bold 16px Arial;
    height: auto;
    line-height: 24px;
    width: auto;
}

div.sidebartoc {
    color: #f7f7f7;
    display: block;
    font: normal 13px Arial;
    line-height: 19px;
    padding: 7px 0px 19px 0px ;
    width: 190px;
}

a.navlinks {
    cursor: pointer;
    color: #555;
    letter-spacing: normal;
    font-weight: normal;
    font-style: normal;
    font-family: Arial, sans-serif;
    text-decoration: none;
}

a.navlinks:hover {
    text-decoration: underline;
}

div.sidebartoc li, div.sidebartoc ul {
    padding-right: 0px;
    margin-top: 0px;
    padding-left: 0px;
    margin-left: 0px;
    list-style: none;
    margin-bottom: 0px;
}

.totop {
    float:right;
    font: bold 11px Arial;
    text-decoration: none;
}
.totop:hover {
    text-decoration: underline;
}

.sidebartoc ul li a {
    padding:5px 15px;
    display: block;
    line-height: 19px;
}

.topiclink {
    line-height: 12px;
    padding:0px 20px;
    display: block;
}

Build your web help and enjoy the in-page navigation! If you have questions or want to know how to customize this solution, let me know in the comments!

Ensuring Your MadCap Flare Topics Load within the Default Page

| Comments

Getting your MadCap Flare help topics to rank highly in search results is a challenge. Flare’s help structure (topic pages loading within an IFrame on a default page) makes its help difficult to index. To make things worse, the topics that show up in search results usually load without the default page. See MadCap Flare’s help for an example.

Obviously, you want your users to see the rest of your help when they open a topic. Without the default page, users won’t have access to your table of contents or search and will probably not have a very good experience browsing your help.

MadCap gets around this problem by including an Open topic with navigation link in each topic. This approach is valid, but there’s another way. You can check to see if the topic loaded in the IFrame and, if it didn’t, dynamically reload the topic within the default page. This approach is a little more work but it ensures that your topics will be seen in the appropriate context.

To get started, you’ll need the URL for your web help’s default page. Paste the following code into a text editor and replace web help URL with the appropriate URL. Be sure to keep the value in quotes.

1
2
3
4
5
6
7
8
9
10
11
12
$(document).ready(function () {

    //if the location.href contains the "content" folder, it means that the page loaded without the default page.
    if (window.top.location.href.toLowerCase().indexOf("content/") != -1) {

        //get the path to the topic
        var topicPath = "#" + window.top.location.href.toLowerCase().match(/\/content.*/gi)[0].substring(9);

        //add the appropriate base URL
        window.top.location = "web help URL" + topicPath;
        }
});

If you publish your web help to one URL, your code doesn’t need to be more complicated than that. If you’d like to use the same code for help projects that will be published separately, you’ll need to adjust the code to intelligently redirect to the correct URL. Once you’ve modified the code, follow these steps to add it to your help project.

  1. Open your project in MadCap Flare. Open your project’s default skin.
  2. In the HTML5 Skin Editor, click the Toolbar tab.
  3. Under the Custom JavaScript to include in Toolbar page textbox click Edit….
  4. Paste your modified code into the Toolbar JavaScript window and click OK.
  5. Save your changes.

Getting Useful Data with Segments in Google Analytics

| Comments

You set up your Google Analytics tracker, goals, and filters. Now you need to learn something from all that data you’re collecting. When you look at the reporting for your web property, the first thing you’ll see is an audience overview that shows a variety of seemingly useful metrics, like average session duration, number of users, and bounce rate. That’s fine, but is it useful?

Knowing that the bounce rate for all of your sessions is 35% doesn’t really tell you much. You don’t have any context for why those sessions bounced or if some areas of your help had higher bounce rates than others. If you want your data to answer questions, you need to be more specific. Stop looking at all sessions and start using segments.

Segments allow you to filter your data. You could create a segment for users that enter your help on the default page and another for users who don’t. You can apply these segments and start getting more interesting data. Now you can compare users who entered the help on your default page to those who didn’t and see what’s different. Is every page in your help actually page one? Now you have the segments and data to find out.

Here are some interesting segments:

  • Sessions that searched or used the table of contents
  • Sessions that searched but didn’t exit on a search result
  • Sessions that entered through the main landing page
  • Sessions that came from one of your other web properties
  • Sessions visiting from a server machine
  • Landing page bounces

If you’re having trouble coming up with segments to create, take a step back. Figure out what questions you want your data to answer, for example:

  • Are users able to navigate my help without using the table of contents or search?
  • Are people using my context-sensitive help?
  • Are user searches effective?

Then, create segments to help answer those questions.

Getting Started with Filters and Goals in Google Analytics

| Comments

Now that you’ve modified your Google Analytics tracker to track virtual page views, there are a few steps to take before you can sit back and let the data flow in. First, you’ll want to filter the data to ensure it is higher quality and easier to parse.

I recommend setting up two filters:

  • One to block traffic from your company’s IP address
  • One to set all page paths to lowercase

To create a filter that sets all page paths to lowercase

  1. Log in to Google Analytics. Click the Admin tab.
  2. Select the appropriate Account, Property, and View for your web help.
  3. In the View column, select Filters.
  4. Click + New Filter.
  5. Under Choose method to apply filter to view, select Create new Filter.
  6. Next to Filter Name, enter Case Sensitive.
  7. Next to Filter Type, select Custom Filter. Then, select the Lowercase radio button.
  8. In the Filter Field drop-down, select Request URI.
  9. Click Save.

To create a filter to block traffic from your company’s IP address

  1. Repeat steps 1-5 shown above.
  2. Next to Filter Name, enter Block Internal Traffic.
  3. Next to Filter Type, select Predefined filter.
  4. Set the filter to Exclude traffic from the IP addresses that are equal to.
  5. Next to IP address, enter your company’s IP address or any other IP address you want to exclude.
  6. Click Save.

Using Goals to track searches and navigation through the TOC

Tracking searches in Google Analytics is tricky; you can’t use the Site Search Settings for your view because of how Flare implements its search and constructs search URLs. In addition to searches, it’s also useful to know how effective the table of contents is in helping the user navigation successfully. You can use goals to track searches and navigation through the TOC. I’ve shared these goals in the Google Analytics Solutions Gallery, so all you need to do is import them to your own Google Analytics project.

To import the goals for tracking TOC navigation and searches

  1. Click here.
  2. Click Import.
  3. In the Select a view drop-down, select the view for your web help.
  4. Ensure both goals are selected and click Create.

Once you start receiving data, you can check to see search results and TOC navigation from the Reporting page in Google Analytics.

To see search and TOC navigation data

  1. Login to Google Analytics and click the view for your web help.
  2. In the options on the left, click Conversions, then Goals, then Overview.
  3. On the Overview page, you’ll see broad data for the goals you set up. To see the results for a particular goal, select it the drop-down under Goal Option.

Now that you’ve set up goals and filters, you’re all set to start receiving high quality, informative data that can help you identify areas for improvement in your online help. You’ve done all of the groundwork for ensuring that you’ll get great data, but there’s more you can do. By creating custom segments and dashboards and generating meaningful reports, you can make your data as informative and easy to use as possible. I’ll cover segments, dashboards, and reports in my next post.

Using Google Analytics with MadCap Flare’s HTML5 Help

| Comments

Using web analytics with your online help sites allows you to gather real insights into your users’ help experience. In addition, you can use analytics data to identify deficiencies in your help and measure how effective your improvements and updates really are. In this series of posts, I’ll cover how to add Google Analytics to MadCap Flare’s HTML 5 help and how to set up Google Analytics to get the most useful data possible.

If you’ve tried implementing Google Analytics with your Flare help before, you may have run into one of two problems:

  • You put the tracker on your master page and didn’t get any search results or table of contents navigation details.
  • You put the tracker on the default page and didn’t get any page views at all.

Flare’s help consists of a default page that contains the table of contents, header, search bar, and an IFrame where your topics load. Users stay on the default page even as they browse through and search the help. This differs from the usual websites that Google Analytics tracks because help topic page views are virtual page views, since topics load within the default page.

Of course, we want Google Analytics to treat visits to different topics as page views. To do this, we’ll need to adjust the Google Analytics tracking code to send pageview events when the page URL changes. In this example, I’m using Ben Alman’s jQuery hashchange event plugin along with my tracking code.

Get started in a few easy steps

  1. Sign up for Google Analytics and get a tracking code for your website. If you haven’t already, you’ll need to set up a property for your site. When you have it, take note of the Tracking ID for your website. You’ll use this tracking ID with a tracking code modified to send page view events when the page URL changes. Learn more about finding your site’s tracking ID.
  2. Update the following modified tracking code with your tracking ID, replacing UA-tracking-ID with the appropriate information.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    /*
     * jQuery hashchange event - v1.3 - 7/21/2010
     * http://benalman.com/projects/jquery-hashchange-plugin/
     * 
     * Copyright (c) 2010 "Cowboy" Ben Alman
     * Dual licensed under the MIT and GPL licenses.
     * http://benalman.com/about/license/
     */
    (function ($, e, b) { var c = "hashchange", h = document, f, g = $.event.special, i = h.documentMode, d = "on" + c in e && (i === b || i > 7); function a(j) { j = j || location.href; return "#" + j.replace(/^[^#]*#?(.*)$/, "$1") } $.fn[c] = function (j) { return j ? this.bind(c, j) : this.trigger(c) }; $.fn[c].delay = 50; g[c] = $.extend(g[c], { setup: function () { if (d) { return false } $(f.start) }, teardown: function () { if (d) { return false } $(f.stop) } }); f = (function () { var j = {}, p, m = a(), k = function (q) { return q }, l = k, o = k; j.start = function () { p || n() }; j.stop = function () { p && clearTimeout(p); p = b }; function n() { var r = a(), q = o(m); if (r !== m) { l(m = r, q); $(e).trigger(c) } else { if (q !== m) { location.href = location.href.replace(/#.*/, "") + q } } p = setTimeout(n, $.fn[c].delay) } $.browser.msie && !d && (function () { var q, r; j.start = function () { if (!q) { r = $.fn[c].src; r = r && r + a(); q = $('<iframe tabindex="-1" title="empty"/>').hide().one("load", function () { r || l(a()); n() }).attr("src", r || "javascript:0").insertAfter("body")[0].contentWindow; h.onpropertychange = function () { try { if (event.propertyName === "title") { q.document.title = h.title } } catch (s) { } } } }; j.stop = k; o = function () { return a(q.location.href) }; l = function (v, s) { var u = q.document, t = $.fn[c].domain; if (v !== s) { u.title = h.title; u.open(); t && u.write('<script>document.domain="' + t + '"<\/script>'); u.close(); q.location.hash = v } } })(); return j })() })(jQuery, this);
    
    if (!/bot|googlebot|crawler|spider|robot|crawling/i.test(navigator.userAgent))
    
    {
        var _gaq = _gaq || [];
        _gaq.push(['_setAccount', 'UA-tracking-ID']);
        _gaq.push(['_trackPageview']);
    
        (function () {
            var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
            ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
            var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
        })();
    
    
        $(window).hashchange(function () {
            _gaq.push(['_trackPageview', location.pathname + location.search + location.hash]);
    
        });
    }
    

  3. Open your project in MadCap Flare. Open your project’s default skin.
  4. In the HTML5 Skin Editor, click the Toolbar tab.
  5. Under the Custom JavaScript to include in Toolbar page textbox, click Edit….
  6. Paste the modified tracking code from step 2 into the Toolbar JavaScript window and click OK.
  7. Save your changes.
  8. Build your help and publish it to the URL you specified when you set up Google Analytics.
  9. Login to Google Analytics, select the correct web property and click Admin.
  10. In the Property column, click Tracking Info. Then, click Tracking Code.
  11. Take note of the status at the top of the page. It should say Receiving Data.
  12. Click Reporting at the top of the page.
  13. In the left pane, click Real-Time. Then, click Overview.
  14. Open the published web help and browse around. You should see this activity in the Real-Time Overview.

Congrats! You’ve taken the first step to getting Google Analytics to work with your MadCap Flare web help. In the next post, I’ll cover the filters, goals, and segments you’ll want to set up in Google Analytics.