Reusable UI Components in Splunk: A Search Help Menu [Part 2]

This tutorial goes over the build-out process of creating a custom, reusable search help menu in Splunk. This helps the user as it provides additional context.



Part 2: Setting Up Our Help Menu Template

One of the goals of this project is to make what we're creating as reusable as possible. If we have multiple panels on a dashboard we want to make it so that all a user has to do is add some HTML into their dashboard’s SimpleXML and it will work.

Again, you are welcome to follow along with the screencast and/or read through the blog post. The second part of the screencast for Part 2 is shown below.

Reminder, you can also download the completed version on the HurricaneLabs GitHub.

To make this work, the first item we will set up is the HTML template:

      <html>
      	<div class="help-button" data-link-text="?" data-text=
      	"This is the text that will describe our search."/>
      </html>

SimpleXML allows us to easily insert HTML by using the <html> tag wrappers. Inside of this we define a div that will render out the button and a couple of data attributes that we can define.

First, we have the class called ‘help-button’. This is important and we will be referencing this class directly in our JavaScript. Next is the data attribute called link-text, this is what will render inside the button. I chose a '?', but it could be ‘Help’ or anything that would make sense to your uses -- something simple is preferable.

The next data attribute is text, which is the text that appears in the popdown once you click on the button. Setting it up this way allows us to easily create multiple instances of this help menu.

Open up your dashboard’s source by going to ‘Edit’ < ‘Edit Source’ and inside of the first panel add:

      <html>
      	<div class="help-button" data-link-text="?" 
data-text="This is a list of all the Saved Searches on our local Search Head."/>
      </html>

Of course, you can add whatever text makes the most sense for your search. In the end you should have something like this: 

Click Save. When the dashboard refreshes you will notice nothing is showing up. We will fix this issue next.

Building the Basics

Open up index.js. The first thing we're going to do is reference the Popdown component that ships with Splunk. You may have noticed it being used periodically throughout Splunk’s UI.

First load in 'views/shared/delegates/Popdown' and then pass it as a parameter in the function, like so:

require([
    'underscore',
    'backbone',
    'jquery',
    'splunkjs/mvc',
    'views/shared/delegates/Popdown',
    'splunkjs/mvc/simplexml/ready!'
], function(_, Backbone, $, mvc, Popdown) {

    console.log('I need help!');

});

Next, remove the console.log and add the following:

$('.help-button').each(function() {

       var link_text=$(this).data('link-text');
       var text=$(this).data('text');

       console.log('LINK TEXT: ' + link_text + ' TEXT: ' + text);

});

This will loop over every element with a class of help-button. It pulls the data from our data attributes in our help-button div using jQuery’s data() method. Once you save this and refresh your dashboard you should see the text we’ve logged out.

Go back to the dashboard and add in another help-menu HTML element to the second panel on the dashboard. Fill it in with whatever makes sense – mine looks like this:

<html>
      <div class="help-button" data-link-text="?" 
     	data-text="This is a list of all the apps on our local Search Head."/>
</html>

Save your changes. In the console window in your browser you should now see two log outputs:

LINK TEXT: ? TEXT: This is a list of all the Saved Searches on our local Search Head.

index.js:19 LINK TEXT: ? TEXT: This is a list of all the apps on our local Search Head.

This gives us a good idea of how this will work. At this point we need to build out the view. We will do that next.

Building Out the View

Open up index.js. Inside of the .each() method the first thing we will add is a reference to the help menu container that will hold the popdown help menu view:

var helpButtonContainer = $(this);

Build the Template

Below our helpButtonContainer variable we will add the template string for our help menu:

var linkPopDownTpl = '<a href="#" class="popdown-toggle viewMore"><%-linkText %></a>' +
'<div class="dropdown-menu pivot-dropdown">' +
'<div class="arrow" style="margin-left: -8px;"></div>' + 
'<div class="slideNavPlaceHolder help-text">' + 
'<div class="auto">' +
'<p class="popdown-body"><%-text %></p>'
'</div></div></div>';

The first part of the code is the link the user will click to view the help menu. In our example it will be the ‘link-text’ data attribute we defined in our HTML with a value of ‘?’. The value of ‘?’ will be passed into the variable linkText, which is defined using using the Underscore templating: <%- %>

'<a href="#" class="popdown-toggle viewMore"><%-linkText %></a>'

Next is the container for the popdown menu. The <%- text %> variable will be replaced with our data attribute called ‘text’, which describes the purpose of a specific search.

'<div class="dropdown-menu pivot-dropdown">' +
'<div class="arrow" style="margin-left: -8px;"></div>' + 
'<div class="slideNavPlaceHolder help-text">' + 
'<div class="auto">' +
'<p class="popdown-body"><%-text %></p>'
'</div></div></div>';

Build the View

Now that we have our template in place we need to build out our reusable view component. For this we will be using a basic Backbone View.

Below where we added our template we will begin to create our new view. We will call this view ‘MenuLinkView’:

var MenuLinkView = Backbone.View.extend({ });

Next we will build out the basic structure of our view:

var MenuLinkView = Backbone.View.extend({
    initialize: function(attrs) {

    },
    tagName: 'span',
    render: function() {

    }
});

Above, the first thing we are defining is our initialize function. We will use the initialize function to pull in the data attributes from our HTML. The tagName defines that wrapper of this view as a span and finally the render function will handle the rendering of this view.

Next, let’s pull in the data attributes we’ve defined in our HTML:

    initialize: function(attrs) {
          this.text = attrs['text'];
          this.linkText = attrs['link-text']
    },

The attrs object that is passed in will make more sense once we create a new instance of our MenuLinkView, but they pull in the data attribute that we defined in our HTML:

<html>
      <div class="help-button" data-link-text="?" 
     	data-text="This is a list of all the apps on our local Search Head."/>
</html>

So, data-link-text will pass its values to attrs[‘link-text’] and data-text will pass its value to attrs[‘text’]. The ‘this’ part allows us to access the variable through the entire view. This will be helpful next when we reference the variables this.text and this.linkText in our render function.

Next, in the render function, we will first add the template we defined above:

this.$el.html(_.template(linkPopDownTpl, {
   linkText: this.linkText,
   text: this.text
}));

Above we reference this.$el, a reference to the view’s element, which is the container we end up rendering this view into. The html part is saying to populate this element with the template linkPopDownTpl, which we are able to render out and pass variables to using Underscore’s _.template function.

Finally, we populate the two variables we defined in our template <%- linkText %> and <%- text %>. We are passing those template variables the values of the this.linkText and this.text variables we defined above in our initialize function.

Then, below, we create a new instance of the popdown menu:

         this.popdown = new Popdown({
         	 el:this.$el, 
             dialog: '.pivot-dropdown', 
             attachDialogTo: 'body', 
             mode: 'dialog'
   });

This takes a couple of parameters. The first ‘el’ is the element in which the popdown should render. In this case it is our view’s element or this.$el as is described above. The dialog parameter takes a class to pass to the popdown element. The attachDialogTo needs us to define what part of the DOM we want to attach this popdown to. Here we are attaching it to the body. Finally, the mode is a dialog.

The final structure of our MenuLinkView view is below:

var MenuLinkView = Backbone.View.extend({
    initialize: function(attrs) {
          this.text = attrs['text'];
          this.linkText = attrs['link-text']
    },
    tagName: 'span',
    render: function() {
    
	   this.$el.html(_.template(linkPopDownTpl, {
             linkText: this.linkText,
             text: this.text
    	   }));

         this.popdown = new Popdown({
         	 el:this.$el, 
             dialog: '.pivot-dropdown', 
             attachDialogTo: 'body', 
             mode: 'dialog'
   });

     }
});

The final part of setting up our view is that we need to create a new instance of the view so we can pass our variables to it, which are this.text and this.linkText.

var MenuLinkView = new MenuLinkView({ 
    "link-text": helpButtonContainer.data('link-text'), 
    "text": helpButtonContainer.data('text') 
});

In our newly created instance of the MenuLinkView view we pass in the values for our link-text and text variables, which if you remember are defined as this.linkText and this.text inside the view’s initialize function. To do this we are using jQuery’s $.data() method to pull our HTML’s data attributes.

Note: Using the $.data() method we don’t need to define the data- part found in our HTML - it is simply implied when we reference it. So, instead of writing helpButtonContainer.data('data-link-text'), we can simply insert .data('link-text') without the preceding data- part.

Next, we append our MenuLinkView element to our current helpButtonContainer (whichever one we are currently clicking on):

helpButtonContainer.append(MenuLinkView.$el);

Finally we render our MenuLinkView out:

MenuLinkView.render();

Save index.js and go back to your dashboard and refresh. You should now see the ?’s showing up at the bottom of your panels. When you click on one the popdown panel should appear like so:

This works great, but because we want this to be as presentable as possible, the last thing we will need to do is add some style.

That is it for part 2. We have successfully built out our reusable view. In part 3 we will wrap everything up by defining a JavaScript module for our MenuLinkView. This will clean up our code significantly and make it more reusable. We will also learn about using define() in require.js to set up our view in a separate file.

In case you missed it, here's the link for Part 1 of the tutorial. Part 3 is now also available!




Close off Canvas Menu