W3tTr3y's blog

A personal technology focused blog

Learn More Button

While Splunk is certainly a very valuable tool, Mark typically compares it to a kitchen; there isn’t a menu that you can just order something off of and expect to get food. Our environemnt is quite challenging in that we don’t know what technologies depeartments have deployed, what changes they are planning, etc. There are some things we can assume (there will be some active directory, there will be firewalls, etc) so we’d like to provide some basic dashboards, but its really important that we describe how we’re getting the data and any expected limitations.

In S.o.S. – Splunk on Splunk, there are very helpful learn more buttons; they default to a collapsed state, but when clicked a panel expands providing some very helpful background information

Learn More button in default collapsed state

Leanr More Buttin in spaceded state

While we’ve commented that they would be nice previously, the topic came up again on Friday so this weekend I spent some time to work out some of the details.

Tools Available

So the first step was to see what tools I had available; while its possible to roll raw javascript to accomplish this task, I’m a big fan of worker smarter not harder. It looks like jQuery is included with Splunk by default so what will certainly make life easier.

Goal

So the next step is to work out a plan of action.

Splunk 6 supports butting ran html into the simpleXML dashbaord; since the descriptive text is almost certinaly best done in HTML to suppor some formatting this seems like a really good direction.

I’d like to keep this simple, so my first thought is to leverage a class on a div tag similar to:

Planned HTML to embed in a SimpleXML Dashboard
1
2
3
<div class="learnmore">
    This is the explanatory text.  There probably should be a lot more text here, but I'll keep it simple for the moment.
</div>

First Steps

As a first step, I’m going to define a bit more html and see if we can get the desired effect with Javascript.

More Complex Html
1
2
3
4
<div class="learnmore">
    <p class="label">Learn More</p>
    <div class="inner">This is the explanatory text. There probably should be a lot more text here, but I'll keep it simple for the moment.</div>
</div>

In this HTML example I’ve added a Learn More label and I’ve but the text inside an inner div.

Now comes the fun part, making a first pass at the Javascript (luckily we have jQuery to work with)

Hide the inner div when the Learn More is clicked
1
2
3
$("div.learnmore > p.label").click(function () {
    $(this).siblings(".inner").toggle();
});
Quick Explanation
  • $("div.learnmore > p.label") Selects paragraph (p) tags with a class of label that are children of a div with the class learnmore
  • $("").click( function(){...} ) .click is a jQuery function that add a click event handler to the element. In this case, it’s being passed a function to invoke when the click occurs
  • $(this).siblings(".inner") takes the element that was clicked and searchs its siblings in the tree (only the div in the example above) and looks for one with an class attribute of inner
  • toggle() toggle will take the visibility of the element its called upon and invest it (e.g. hide it if it is vurrently visisble, show it if it is currently hidden)
Try it Out:

Trying switching over to the result tab and clicking on Learn More; you should see the explanatory text appearing/disappearing.

That’s a wonderful first step; its a tad more complex than required becuase it uses $(this).siblings(".inner") if we’re only going to have one Learn More button, then you could simplify it to just $(".inner"). Example with multiple Learn More’s and one Learn More controlling multiple divs

Simplifing Required HTML

I stated that I wanted to keep the required html to make this work fairly simple; something along the lines of:

Planned HTML to embed in a SimpleXML Dashboard
1
2
3
<div class="learnmore">
    This is the explanatory text.  There probably should be a lot more text here, but I'll keep it simple for the moment.
</div>

So now we need to write Javascript to transform that into the HTML used in the previous example.

Transform the HTML
1
2
3
4
5
6
7
8
$("div.learnmore").each(function (i, l) {
    pre = '<p class="label">Learn More</p><div class="learnmore inner">';
    post = '</div></p>';
    l = $(l);
    if (l.children("p.label").length === 0) {
        l.html(pre.concat(l.html(), post));
    }
});

The javascript basically finds div’s with a learnmore class and if they do not have an inner paragraph with a label class, it changes their content to be:

1
2
3
4
5
<p class="label">Learn More</p>
  <div class="learnmore inner">
    --- Insert Original content here
  </div>
</p>

You could combine the two javascript functions, but by keeping them seperate you allow the more verbose version to continue working which allows users to customize the Learn More text.

Adding the Arrow

In order to add the arrow, we’ll use a Unicode symbol (▶).

First, we’ll modify the javascript that transoforms the html to include the arrow.

1
2
3
4
5
6
7
8
$("div.learnmore").each(function(i, l){
    pre = '<p class="label"><span class="arrow" style="display: inline-block">&#9654;</span> Learn More</p><div class="learnmore inner">'
    post = '</div></p>'
    l = $(l);
    if(l.children("p.label").length == 0) {
        l.html( pre.concat(l.html(), post))
    }
})

I cheated a bit; our next step is to rotate the arrow when the label is clicked and in order for this to work the arrow must be a block element so I added the display: inline-block styling.

Next, we’re going to write a function to rotate the arrow.

1
2
3
var rotate = function(target, degree) {
    $(target).css({'transform': 'rotate(' + degree + 'deg)' });
}

Finally, we need to modify the click handler so that it adjusts the arrow to indicate if the panel is open/shut.

1
2
3
4
5
6
$("div.learnmore > p.label").click( function() {
    degree = $(this).siblings(".inner").is(':visible') ? 0 : 90;
    target = $(this).children("span.arrow")
    rotate(target, degree)
    $(this).siblings(".inner").toggle()
})

There is only one problem; the initial state for the learn more text is to be displayed, but initially the arrow points to the right indicating the content is hidden. While there are a couple of ways to handle this, I’m going to be lazy and just use javascript to fix it.

1
2
3
4
$("div.learnmore > p.label").each(function () {
    rotate($(this).children("span.arrow"),
           $(this).siblings(".inner").is(':visible') ? 90 : 0);
});

Instead of hardcoding the arrow’s state, I’ve left the check in to see if the explanatory text is visible so you can have the content default to a hidden state if desired.

Finally, one last little tweak: instead of utilizing toggle, I’ve switch it to slideToggle. This way the text doesn’t just appear, it slides in giving the user some clue as to what has happened. Even if they aren’t sure why, this should give them a clue that if the slide down they can still see the dashboard content.

Integrating With Splunk

The first step is to create your dashboard. For this tutorial, an empty dashboard will be utilized.

The first step towards integrating this into Splunk is createa javascript file in the proper directory; it must be $SPLUNKHOME/etc/apps/app_name/appserver/static/learnmore.js

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
require(["splunkjs/ready!", 'jquery'], function(r, $) {

    $("div.learnmore").each(function (i, l) {
        pre = '<p class="label"><span class="arrow" style="display: inline-block">▶</span> Learn More</p><div class="learnmore inner">';
        post = '</div></p>';
        l = $(l);
        if (l.children("p.label").length === 0) {
            l.html(pre.concat(l.html(), post));
        }
        l.children("span.arrow").each(function (i, l) {
            degree = $(this).siblings(".inner").is(':visible') ? 0 : 90;
            target = $(this).children("span.arrow");
            rotate(target, degree);
        });
    });

    var rotate = function (target, degree) {
        target.css({
            'transform': 'rotate(' + degree + 'deg)'
        });
    };

    $("div.learnmore > p.label").click(function () {
        degree = $(this).siblings(".inner").is(':visible') ? 0 : 90;
        target = $(this).children("span.arrow");
        rotate(target, degree);
        $(this).siblings(".inner").slideToggle();
    });

    $("div.learnmore > p.label").each(function () {
        rotate($(this).children("span.arrow"),  
               $(this).siblings(".inner").is(':visible') ? 90 : 0);
    });
}

You’ll notice that this file is slightly different in that the previous code is surrounded by: require(["splunkjs/ready!", 'jquery'], function(r, $) { [...] }); This code utilizes the require library, waits for some libraries to load, and gets a reference to jQuery as $.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dashboard script="learnmore.js">
  <label>Learn_More</label>
  <description/>
  <row>
  <html>
    <div class="learnmore">
      This is just a test of the more learning system.  This is only a test.  Had this been an actual emergency, confusing words would have been put here to prevent you from learning.  This is only a test.
    </div>
    <div>
      This is just some more text; please ignore me.  There is no man behind the curtain.
    </div>
  </html>
  </row>
</dashboard>

The dashboard is pretty simple. In the dashboard tag, the javascript file is loaded. The learn more text is inside of a div with a learnmore class just like our test html. I’ve also added some extra text just to show you can have some that doesn’t get hidden.

Styling

While the previous example works, the button is kinda ugly. You can easily leverage the styles that ship with Splunk to stylize the text as a button. Unfortunately, this means changing the html a bit and since we’re using jQuery that means changing the 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
33
require(["splunkjs/ready!", 'jquery'], function(r, $) {               
    $("div.learnmore").each(function (i, l) {
        pre = '<fieldset><button class="btn btn-primary"><span class="arrow" style="display: inline-block">▶</span> Learn More</button></fieldset><div class="learnmore inner">';
        post = '</div></p>';
    
        l = $(l);
        if (l.children("button.btn").length === 0) {
            l.html(pre.concat(l.html(), post));
            l.addClass("hidden-print");
        }
        l.children("span.arrow").each(function (i, l) {
            degree = $(this).siblings(".inner").is(':visible') ? 0 : 90;
            target = $(this).children("span.arrow");
            rotate(target, degree);
        });
    });

    var rotate = function (target, degree) {
        target.css({transform: 'rotate(' + degree + 'deg)'});
    };

    $("div.learnmore  button").click(function () {
        inner = $(this).parent('fieldset').siblings(".inner");
        degree = inner.is(':visible') ? 0 : 90;
        target = $(this).parent('fieldset').children('button').children("span.arrow");
        rotate(target, degree);
        inner.slideToggle();
    });

    $("div.learnmore  button").each(function () {
        $(this).parent("fieldset").siblings(".inner").hide();
    });
});

Accessing from Other Applications

You can make an application just to hold the javascript and then reference it from other apps by modifying the script attribute on the dashboard tag; just prefix it with app_name: