Advanced Concepts Using CDF and CDE - Learning Pentaho CTools (2016)

Learning Pentaho CTools (2016)

Chapter 7. Advanced Concepts Using CDF and CDE

Today, when we are requested to build a dashboard, most of the time we find ourselves faced with customers who want you to make the dashboard available in multiple languages, and to be honest, this makes sense. Why would you have a dashboard available only in your language and not in other languages? In this chapter, we will teach you how you can do this.

CDF and CDE provide you with some really cool components that are more flexibility then others, and allow you to build your own visualizations, so we will also cover these components. But for now let's suppose that you are working with a customer who has hired you to build a dashboard but also to train his team to build the remaining dashboards. If there is a visualization that you need to implement/develop, you will certainly be capable of creating a delivery that includes a custom component that they can use/reuse later. This is possible, and we are going to cover how to do it, as well as how to extend template and table components by creating new add-ins.

In one of the sections of the book, we also provided some information about reusing templates and styles, and we already showed you how to create a new template, save it, and reuse it. But we also may want to create a style, and this is the chapter where we are going to do that.

A very interesting possibility is building a dashboard and embedding it in another one. Do you realize how cool this could be? Just imagine creating a dashboard that can be used a section of another dashboard, and reusing it multiple times. You must be having some ideas already, so let's start with the chapter.

The topics covered include:

· Creating new add-ins for table and template components

· Making use of the template add-in for table and template components

· Creating and changing the style of the dashboards

· Using the dashboard component to reuse dashboards

· Creating new add-ins and new components

· Making use of bookmarkable parameters

References to components, parameters, and layout elements

First, I want to start by covering one important concept in CDF, and don't forget that concepts in CDF also extend to CDE. If you open the developer tools in your browser and start inspecting some CDE dashboard code, you will see that the names of the components are always prepended with render_ with the name of the components that we set when editing the dashboard. This way, when you want to refer to a component using its name, you should use the complete name of the component such as:

this.dashboard.getComponentByName('${c:myComponentName}');

This would be the same as:

this.dashboard.getComponentByName('render_myComponentName');

When you use this code line inside a dashboard, where it is not valid for code in external files, you can refer to the component as ${c:myComponentName}. This is possible because, when using this syntax, it will be translated and replaced by the full name of the component, so you don't need to worry about the name that CDE may have given to it.

This is also valid for the parameters or elements created in the layout perspective. When you use a dashboard embedded in a dashboard, like we are going to see later, and you take a look at the code, you will see that the name will not be exactly the same as the one you gave it. You should worry about this, because you can use the syntax previously referred to.

Since this is also valid for components, parameters, and layout elements, we should have a way to distinguish them. This is done by changing the letter before :. You should use a p for parameters, a c for components, and an h (from HTML) for the layout elements, like the following:

· Components, which has already been covered:

this.dashboard.getComponentByName('${c:myComponentName}');

· Parameters:

this.dashboard.getParameterValue('${p:myParameterName}');

· Layout elements:

$('.${h:myLayoutLementName}').text('Hello World');

When you select the name of parameters, the elements of the layout, and the component's name using the drop-down, CDE will automatically create a reference to the proper object of the dashboard, using the correct syntax. It won't work, if you type the name yourself nor when writing your own code. For this reason, I always advise you to select the values using the drop-down, and select the value from the list, otherwise you might be creating issues.

The query and freeform components

The components we have covered will give you the freedom to do almost everything you need to do to build an amazing dashboard. However, sometimes we need to go further, and we might be able to do this just by using the available components. To build custom visualizations, you can also make use of the query and freeform components, but what's the difference between them? A very frequently asked question is where to use one or the other. So now let's answer this question and learn how to use these components.

The query component will trigger a query, but you want to display some custom content inside your dashboard. Here you can't avoid setting a valid query, so the freeform component is useful here. The freeform component will not trigger any query so you can use it as you want/need. You may be thinking, If it does not do anything, why would I use it? Well, it's because sometimes you have the need to execute some code that respects the lifecycle (a good example can be internationalization/localization, or just adding static HTML to the page/dashboard) of the dashboard; as with any other component, these two components have their own lifecycle. When using require, if you add HTML elements with text directly to the layout of the dashboard, you will see that text displayed in the first place, will give you a bad look and feel. You can use a text component to add the HTML, which will just be added to the page when the dashboard is being rendered.

For both, query and freeform components, you will be able to specify the priority of execution and the preExecution and postExecution functions, as well as the component that should be executed at the start of the dashboard, by setting true or false inexecuteAtStart. For the query component, we are also able to specify the properties datasource, parameters, and the postFetch function, which will be executed as soon as the component gets the results, just after the preExecution function and before thepostExecution function.

So the difference between the query and the freeform components, is that, for the query component, a data source will be used and, for the freeform, it will not. Just use them when you need to present content that you are not able to present with any other component provided by CDF and CDE.

The query component

As already said, we can use the query component to present custom content to the dashboard. The properties that are available along with the common ones are:

· Result var: This is the name of the dashboard parameter that is going to be used to store the result set from the query. This will not include the metadata, but just the resultset itself. It will store a multidimensional array with all the rows and columns returned. There is no default value—you should specify the name of a parameter. When you leave it blank, no parameter will be set. To access the parameter that could be named myResultset, you should use the same methods as for any other parameter in the dashboard, as in the following code:

var data = this.dashboard.getParameterValue('${p:myResultset}');

· Asynchronous mode: This tells us that the component should work in an asynchronous way. The default value is true and, when set to false, only after the component's rendering is complete, the components with a low priority (higher values) will be executed. My advice is to always use the default value. Pretty much, the property is there to retain compatibility with older versions of dashboards/components, so keep this property set to true.

You can also get more details on Pedro Alves' blog at the following links:

· http://pedroalves-bi.blogspot.co.uk/2012/11/making-cdf-calls-asynchronous.html

· http://pedroalves-bi.blogspot.co.uk/2013/01/cdf-async-support.html

Let's suppose that you wanted to display the product line to be displayed as an accordion, where for each product line item we display the products and sales for that same line. There is no component, out of the box, that would build what was described in last sentence. Anyhow, you can build a custom component if you want to use it multiple times in multiple dashboards; otherwise you can just use the query component. The reason for using the query component is because the results that are going to be displayed come from a query, and we still don't have them in the dashboard.

The query component

We could use the query component in one of two ways. The first way is to use the variable to create and write the elements to the page on the postExecution function, only after postFetch. The following code is an example of creating the accordion with the accordion from JQuery UI (more information is available at: https://jqueryui.com/accordion/):

function() {

var data = this.dashboard.getParameterValue('${p:myResultset}');

var $placeholder = this.placeholder(),

$accordion = $('<div id="accordion"></div>'),

productLines = _.groupBy(data, function(elem) {

return elem[0];

});

_.each(productLines, function(products, line) {

$accordion.append('<h3>'+line +'</h3>');

var $content = $('<div class="container"><p></p></div>');

_.each(products, function(product) {

var values = product[1] + ': ' +

Utils.numberFormat(product[2], '$#,###.0');

$content.find('p').append('<div>'+ values + '</div>');

});

$accordion.append($content);

});

$accordion.accordion({

heightStyle: "content"

});

$placeholder.empty().append($accordion);

}

In the previous code, we are getting the result of the query and iterating on each product line, and for each line we are also iterating on the products. This way, we can write the elements to the page. At the end of the code, we are using the accordion plugin and rendering it in the dashboard.

The other way is to use postFetch directly, making use of the argument that is passed. If that was the case, then you will not need to define the variable data, just specify it as argument of the function; where you have data, it will be data.resultset. Don't forget that, when in postFetch, we should return data to the component.

The freeform component

If you have a component that already has the result of a query that fits your need or you just want to render some content in the dashboard that does not depend on the result of a query, you can use the freeform component. The advantage is that the component has its own lifecycle and will be perfectly synced with the lifecycle of the dashboard. You can change the priority of execution, and add code for pre- or post execution.

A property that is different from the other components is:

· Custom script: This will accept a function with the code to be executed, just after preExecution and before postExecution. For this case, and supposing that you had a query component execution before this one, you could use the same code here in the custom script. This would grab the result from the parameters and the same code would be used to build the content for the dashboard.

Even simpler would be a case where you needed to have the freeform component performing fireChange to a parameter based on some actions that can be controlled by the lifecycle of the dashboard.

As the component will not make use of a query, you should already have figured out that it will not make a call to postFetch.

Creating add-ins

We have seen that both the table and template component can make use of add-ins. We can also extend CDF and create new add-ins that can be added to the dashboard. You should always be aware that when using it on a table component the add-in will be used for the cell of the table, so be cautious about what you are showing for each cell, so that the table does not become hard to read due to excessive information. Just because dashboard users get information from a dashboard does not mean you should present excessive information.

To add a new add-in you just need to write a few lines of code and some properties, create a new instance of an add-in, and register it to the dashboard. The definition of the add-ins will be set using a JSON structure with the following elements:

· name: This is the name/identifier given to the add-in, which will be used to reference it. This field is mandatory and accepts a string.

· label: This will be the description of the add-in. It accepts a string.

· defaults: Here we need to set another JSON structure with the default properties of the add-in. These properties are the ones that may be overwritten later when setting the properties of the add-in. For example, for the sparkline add-in, it may have the type set as line but later we may want to use a bar.

· init: This is used to execute some code once the add-in is requested. Some preparation code can be executed here. When defining an add-in that will be applied to a table, we also need to specify the sort functions, and the right place to do it. It accepts a function and accepts no arguments.

· implementation: This is a function where the code of the implementation will be added. The function receives three arguments:

· target or tgt: As you will see in the following example, it's a reference to the HTML element where we will be rendering the content to be displayed.

· status or st: As you will see in the following example, it's an object where we can find the value to be used. The information that will be available inside this argument will depend on the component that is using the add-in. For instance, when used in a table, we may have access to the index of the column (colIdx) and the index of the row index (rowIdx) being processed. Since in some cases we may be using a hierarchical structure, we will have access to the add-in identifier (id) that is being passed when the add-in is used in the template. For both cases, independently of the component, we will have access to the complete dataset or model (data when in the table component and model when in the template component).

· options or opt: Like you will see in the following example, these are the options set by the developer to customize the appearance and/or behavior of the add-in. We should use them if we want to extend the default options.

Let's see what the code would be for an add-in created to be used in a table and another for the template component. Let's suppose you want to create an add-in to format the values that are going to be displayed in the dashboard. For this, you need to use the numberFormat function under the CDF utilities, which was already referred to in this book as Utils. We should not forget to include the module.

To create and include a new add-in in your dashboard, you will need to add a new resource, using the layout perspective, and give it a name. After setting the name, you should place the code inside it, something like:

define(['../../../AddIn','../../../Dashboard',

'../../../dashboard/Utils','../../../Logger',

'../../../lib/jquery','amd!../../../lib/underscore',

'amd!../../../lib/datatables'],

function(AddIn, Dashboard, Utils, Logger, $, _) {

var formatted = {

name: "numberFormat",

label: " numberFormat",

defaults: {

formatMask: '#,#.#'

},

init: function() {

$.fn.dataTableExt.oSort[this.name+'-asc']=

$.fn.dataTableExt.oSort['string-asc'];

$.fn.dataTableExt.oSort[this.name+'-desc']=

$.fn.dataTableExt.oSort['string-desc'];

},

implementation: function(tgt, st, opt) {

var opts = $.extend(true, this.defaults, opt),

Value = Utils.numberFormat(st.value, opts.formatMask);

$(tgt).empty().text(Value);

}

};

var addin = new AddIn(numberFormat);

Dashboard.registerGlobalAddIn("Table", "colType", addin);

return formatted;

});

In the previous code, first we are using require to create the add-in, including all the modules (JavaScript and CSS). When all modules are there, we need to start creating the code for the add-in. We start by creating a JSON structure, the name, label, and defaults as well as the init and implementation functions. The name will be the identifier of the add-in, in this case the formatter. For the defaults, we are defining the format mask, which will be applied when no other format is passed to the add-in.

The init function is being used to define the sort functions of the jQuery Datatables plugin. This will only be used by the add-ins that we create and apply for the tables. It will not break if applied to the template; it will just not be used.

The implementation function has the code that will be executed every time it's called by the table or by the template components. On the first line of the implementation function, we are extending the default options with the options that may be passed to the add-in on the pre-execution of the component. On the second line, we are setting a variable with the format, which is defined in the options and applied to the value that we can grab from the status. Here, using jQuery, we are cleaning the actual content that might be there and writing the already formatted value.

After this is complete, and now that we have the formatted JSON with the definition of the add-in, it's time to create a new instance of the add-in. And last, we are registering the add-in in the dashboard and as available as a column type of the table.

When developing the add-in for the template component, there are two main differences between the add-in for the table component. One difference is that we would not need to define the sort function inside the init function, because that may not make sense when applying a template. That way, we would also not need the datatables module.

The other difference is when registering the add-in for the dashboard. As you can see in the following code line, we are registering it to be used for the template component. Just look at the first two arguments of the function. We are using Template and notTable, as we are also using templateType and not colType:

Dashboard.registerGlobalAddIn("Template", "templateType", addin);

The template add-in

There is one other add-in that was not covered, and it's available for the template component but also in the table component. The way it works is pretty much the same as the template component.

There are three ways for the add-in to use the data being processed. The first one is by working on the query to return a string with the JSON structure that will be parsed by the default modelHandler function. There is another way: overwriting the modelHandlerfunction by writing your own code and returning a custom and valid JSON structure as the model. If none of the earlier options return valid JSON, then the value will be treated as a string. Please refer to: http://www.json.org for more information.

Just use the method that you are most comfortable with, prepare everything in the query/backend, or use the available function to return a valid model that can be applied to the template being defined.

The way to apply options to the add-in is the same as already covered for the other add-ins, and the ones available are:

· templateType: You must select the template engine to use. Those currently available are underscore and mustache. The last one is the default value.

· template: This is the template to be used.

· rootElement: This is the name/key that will wrap the model/value being processed.

· formatters: This accepts a multidimensional array. Each formatter will be represented by an array that will have two elements. The first element will be a string with the name of the formatter and the second will be the function that accepts two arguments: the value to be formatted and the identifier of the add-in being executed. It should return the formatted value.

· events: This is similar to the last option, and here it accepts a multidimensional array. Each event will be an array with two elements. The first element is a string that has the event being handled and the selector of the element, separated by a comma (,). The second element is a function with the code to execute when the event is triggered in the selected element. The function accepts one argument that is a reference to the event itself, just like writing a function such as a regular JavaScript event.

· modelHandler: This is a function that accepts data being processed by the component. The function is used to return a valid model to be rendered in the template of the add-in.

· postProcess: This is a function where you can write some code after the elements are rendered on the page.

For any of these functions, please refer to the template component documentation to get more information:

var templateOpts = {

templateType: 'underscore',

template:

'<% _.each(items, function(value, idx) { %>' +

' <div class="row clickable productLine" data-id="<%=value[1]%>"> '+

' <div class="category"> <b> <%= value[1] %> : </b> </div>'+

' <div class="value"> <%= formatter(value[2], "abbreviation") %> </div>'+

' </div>' +

'<% }); %>',

formatters: [["abbreviation",function(value, id) {

return Utils.numberFormat(value, '$0.0A');

}]],

events: [["click, clickable.productLine", function(event){

alert('You clicked: '+$(this).data('id'));

}]],

modelHandler: function(data, opt) {

var model = {};

model.items = data;

return model;

},

};

this.setAddInOptions("templateType","template", templateOpts);

You may also apply the options and use this add-in in the template component so you just need to apply the options in the template and make a call to the add-in of the template using the following instruction:

this.setAddInOptions("templateType", "template", options);

In this code, you will see that we are setting the options to be used by the template add-in. First we are setting the template type, which for our case is underscore.

You will also see the template and, inside it, you will find a call to the formatter. We are passing two arguments to the formatter: the value to be formatted and the name of the function to be used. We could and should also specify another argument (a third one) that is the identifier and is useful when we want some conditional formatting.

We can have multiple formatters, so we need to define them as a multidimensional array. Each element of the parent array will have two elements: the first one is the name of the function and the second one is the function that is executed when the formatter is used inside the defined template. The second element, the function, accepts two arguments: the value to be formatted and one identifier. The identifier is really useful if we need to define a formatter that can be used multiple times with different options for the same template.

We can also see that we are specifying an event, and to do so we are specifying one array with one event and one Document Object Model (DOM) element. The events will also accept a multidimensional array and, once again, each element of the main/parent array will have another two elements. The first element is the event and the selector, which identify the target in the HTML, while the second one is the handler function triggered when the event is fired. The first and second elements of the array should be separated by a comma (,).

In the previous sample code, we are just showing an alert message with the product line where we are clicking. The last option is modelHandler, where we are returning the array wrapped in a JSON structure where the root element is items, used in the template. The last instruction registers the options for the add-in.

Take a look at the examples of dashboards provided with the book, and you can get a clear idea of how it works. Anyhow, it's the concept that we covered earlier for the template component, but applied to a table add-in.

Extending CDF and CDE with new components

When we covered the query component, you must have been asking: If I want to use this for multiple dashboards, do I need to apply my own code over and over again for every dashboard? The answer is, no. With the custom components, you are perfectly able to create components that can be reusable in CDF or in CDE.

What changes between CDE and CDF, is that, for CDE, you also need to include a XML file with the information about what should we see in the dashboard editor. Let's see how can you create your custom components.

Extending CDF

I would like you to consider two different kinds of components: those that allow filtering and those that allow you to display data in the dashboard. Of course, the ones that display information can also be used as selectors, but just add functionality to those that are really to allow visualizations of data on the dashboard.

You should remember from Chapter 3, Building the Dashboard Using CDF, that components can run asynchronously among those with the same priority of execution. If they have different priorities of execution, then they will run synchronously.

It's important to realize that, when I am talking about asynchronous execution, what I am really saying is that simultaneous AJAX requests will be executed, but the browser only allows a limited amount of concurrent calls, so they may be divided into multiple batches. That will depend on the number of components and the limit of simultaneous calls for the browser being used.

To be able to take advantage of some concepts of the Object-Oriented Programming (OOP) languages, CDF makes use of libraries that ease the pain of OOP in JavaScript. One of them is Base, which can be found athttp://dean.edwards.name/weblog/2006/03/base/. This is a simple class and extends the object Object by adding two instance methods and one class method.

When creating a new component, you should be sure to create it in a way that it can run synchronously or asynchronously. This can be achieved by extending from existing classes. The UnmanagedComponent class is one of those cases. It already inherits fromBaseComponent, which also inherits from Base class, so a lot of properties and methods/behaviors can be inherited. To take advantage of the existing base classes, we need to use UnmanagedComponent. This means that UnmanagedComponent is an advanced and more complete version of BaseComponent. It allows you to have control over the lifecycle when implementing components.

When using UnmanagedComponent, the class will make the calls to preExecution, postExecution, and even to postFetch when triggering a query. This will also make sure the listeners are handled and that the parameters are sent to the queries when the component is executed. This way, the calls to these functions are entirely the responsibility of CDF, and the component doesn't need to worry about them.

There are three functions that we can use when creating a component, and one of them needs be used. They are:

· synchronous: This implements a synchronous lifecycle identical to the core CDF lifecycle

· triggerQuery: This implements a simple interface to a lifecycle built around query objects

· triggerAjax: This implements a simple interface to a lifecycle built around AJAX calls

Creating a Hello World! component would be something like:

define(['cdf/components/UnmanagedComponent','amd!cdf/lib/underscore'],

function(UnmanagedComponent, _) {

HelloWorldComponent = UnmanagedComponent.extend({

update: function() {

var render = _.bind(this.render,this);

this.synchronous(render);

},

render: function(data) {

var message = this.message || 'Hello World!';

this.placeholder().empty().text(message);

}

});

return HelloWorldComponent;

});

This example code doesn't make use of a query, so this.synchronous(render) should be used. Managed automatically by the CDF lifecycle, the render function will be called just after preExecution and before postExecution. In the example, the render function is being called using the synchronous function. The render function will show the Hello World text if a property message is not defined in the component.

In the following example, we are making use of a query to show the result of the query. Here this.triggerQuery(this.queryDefinition, render) will be used, so postFetch will be also called, just before the render function, where we are making use of the data that we fetched. Inside the call of the query function, you can see that we are making use of the definitions of the data source/query for this component, so the definitions will be used to trigger the correct data source/query.

define(['cdf/components/UnmanagedComponent','amd!cdf/lib/underscore'],

function(UnmanagedComponent, _) {

ShowResultComponent = UnmanagedComponent.extend({

update: function() {

var render = _.bind(this.render,this);

this.triggerQuery(this.queryDefinition, render);

},

render: function(data) {

this.placeholder().empty().text(JSON.stringify(data));

}

});

return ShowResultComponent;

});

The queryDefiniton object should look like the following, where dataAccessId is the identifier of the CDA datasource defined, and file will by default point to the same name and folder as the dashboard:

{

dataAccessId: 'myQuery',

file: '/path/to/my/datasourceDefinition.cda'

}

Since the methods (synchronous, triggerQuery, and triggerAjax) expect a callback that handles the actual component rendering, the conventional style is to have that pointing to a redraw/render function. Also, to follow the standards we should use the bindfunction, as you can see in the two previous examples. This function will ensure that, inside the redraw/render callback, these point to the component itself.

Now let's suppose you want to create some kind of a select component and you may want developers to be able to make use of a values array and not a query. If that's the case, you can switch between static values and the result of a query:

define(['./UnmanagedComponent', 'amd!../lib/underscore'],

function(UnmanagedComponent, _) {

ShowResultComponent = UnmanagedComponent.extend({

update: function() {

var render = _.bind(this.render, this);

if(this.valuesArray && this.valuesArray.length > 0) {

this.synchronous(render, this.valuesArray);

} else {

this.triggerQuery(this.queryDefinition, render);

}

}

render: function(data) {

this.placeholder().text(JSON.stringify(data));

}

});

return ShowResultComponent;

});

In reality, we have been acting on modules where the components are defined. We can include the component in the dashboard, or we can make it available for CDF in such a way that all dashboards will have access to it. You can have your custom components inside the Pentaho Repository—just create a folder such as /Public/cdf/components, and place your components there. For instance, you should save the code of the first example provided for this section, Hello World, in a file calledHelloWorldComponent.js, so you can use it later in dashboards.

Now let's take a look at how you can make use of that same component, the custom Hello World. You will need to build a CDF dashboard like the one you can see here:

<div id="showTextHere"></div>

<script language="javascript" type="text/javascript">

require([

'cdf/Dashboard.Bootstrap',

CONTEXT_PATH + 'api/repo/files/public/cdf/components/HelloWorld/HelloWorldComponent.js'],

function(Dashboard, HelloWorldComponent) {

dashboard = new Dashboard();

dashboard.addComponent(new HelloWorldComponent({

name: "HelloWorld",

type: "HelloWorldComponent",

htmlObject: "showTextHere",

message: "Hello World",

executeAtStart: true

}));

dashboard.init();

});

</script>

You will see that we are requiring a resource using the Pentaho Repository API, and from there we just use any other component. Don't forget that , as it's a CDF component, you could also define and make use of the preExecution and postExecution functions, like you can for any other component.

When creating a component that acts like a filter, when an action is triggered (such as a click on the Apply button), it will change the parameter to store the selected values. We should make use of the following code:

this._value = selectedValues;

this.dashboard.processChange(this.name);

In this code, we can see the first line where we are setting the selected values of an internal variable _value, which will be used later to get the values that were selected.

The processChange function accepts an argument that is the name of the component making the call, and it's responsible for grabbing the selected values. This is done by calling the getValue function, so this function should also be defined in the component code.

Here is one example of the getValues function:

getValue: function() {

return this._value;

},

You can see that the getValue function is just returning the values that were selected, so that the processChange function can proceed with its work. The processChange function now knows the values that have been selected, so it will make a call to preChange from the component if it exists, do the fireChange function using the parameter defined for the component, and call the postChange function.

We don't need to worry about calling preChange, fireChange, and postChange; we just need to define the getValue function and call the processChange function. The dashboard lifecycle will take care of the rest for you.

Extending CDE

Now that we have a component working as a CDF component, we can also extend CDE to use that same component. We already saw that CDE is provided by a friendly interface that allows you to build the dashboard. But CDE needs to know what components are available, what properties may be used, and which types of values are accepted for each one of the properties.

The big difference between a CDF and a CDE component is that a CDE component is also provided with some kind of a metadata file that should be specified using XML. Reading this XML file is the way that the server knows how to build the editor HTML page that will be rendered by the browser.

You should give the same name to the file as for the component; that way, if you have multiple components inside the same folder, you will know which component belongs to which file. Another reason is because you can use different folders for the XML and JavaScript/CSS files. You have names that can identify them and will make your life easier, not only for you, but also for someone in your team who needs to understand them. For our example, we will show you the folder structure that we can have. But you should find out what the best approach is for you.

The XML file with the definition of the component should also be placed inside the folder /Public/cde/components. The main structure of the file will be similar to:

<?xml version="1.0"?>

<DesignerComponent>

<Header>

<Name>Hello World</Name>

<IName>HelloWorld</IName>

<Description>Hello </Description>

<Category>OTHERCOMPONENTS</Category>

<CatDescription>Others</CatDescription>

<Type>PalleteEntry</Type>

<Version>1.0</Version>

</Header>

<Contents>

<Model>

<Property>title</Property>

<Property>executeAtStart</Property>

<Property>htmlObject</Property>

<Property name="parameters">xActionArrayParameter</Property>

<Definition name="chartDefinition">

<Property type="query">dataSource</Property>

</Definition>

<Property>preExecution</Property>

<Property>postExecution</Property>

<Property>postFetch</Property>

<Property>parameter</Property>

<Property>tooltip</Property>

<Property>listeners</Property>

<!-- START: Template Component Properties -->

<Property>message</Property>

<!-- END: Template Component Properties -->

</Model>

<Implementation>

<Code src="HelloWorldComponent.js"/>

<Styles>

<Style src="style.css" version="1.0">Style</Style>

</Styles>

<Dependencies>

<Dependency src=" lib.js" version="1.0">Library</Dependency>

</Dependencies>

<CustomProperties>

<DesignerProperty>

<Header>

<Name>mesage</Name>

<Parent>BaseProperty</Parent>

<DefaultValue>Hello World</DefaultValue>

<Description>Model Handler</Description>

<Tooltip>Message to display.</Tooltip>

<InputType>String</InputType>

<OutputType>String</OutputType>

<Order>0</Order>

<Advanced>false</Advanced>

<Version>1.0</Version>

</Header>

</DesignerProperty>

</CustomProperties>

</Implementation>

</Contents>

</DesignerComponent>

You will get a lot of examples in your Pentaho solutions folder, under the CDE plugin folder, and inside the following folder: <your pentaho folder>/pentaho-solutions/system/pentaho-cdf-dd/resources/custom/components. This is where CDE components are placed, so you can have access to all the sources of all the CDE components.

Based on the knowledge that you have about using the lifecycle and the CDE components, the example code we just gave is almost self-explanatory. An important part is that we have a main tag <DesignerComponent> where we set all the other definitions. Inside it we will have headers and content.

In the <Headers> tag we are setting the name, description, and category/group for the component, where the following properties are being used:

· Name: The name of the component.

· Iname: The interface name of the component, which is very useful for a legacy dashboard. It should not have any special characters or blank spaces. It would be a good practice to set it with the same name as the RequireJS module.

· Description: This is just a description of the component.

· Category: This is the ID of the group where the component will become available. Another way to say this is to set the group of the layout perspective where we can select this component from.

· CatDescription: This is the description of the category where the component will be available.

· Version: This is just a number that you can use to specify the version of the component.

In the <Contents> tag, we are setting:

· Model: This is where we have defined the properties that will become visible when using the layout perspective and when the component has been selected. In the previous example, you will find the following properties that you already know: title,executeAtStart, htmlObject, parameters, preExecution, postExecution, postFetch, tooltip, listeners, and chartDefiniton, where you can find the dataSource to be used. If defining a select component, it would also make sense to have preChange and postChange.

A property that you don't really know is the message property, which we need to specify in the tag <CustomProperties> , which we will cover as follows:

· Implementation: Here you will find the definition of the component. The previous example code shows two properties in the <Implementation> tag; the first one supportsLegacy is used to make the component available for a legacy dashboard. Since it's not the purpose of this book to cover legacy but only a RequireJS dashboard, we have set this property to false. As the component should work in RequireJS dashboards, we have set supportsAMD to true. The renaming tags are:

· Code: This property is where we specify the path and filename of the JavaScript file where the code for the component will be placed.

· Styles: This is a tag where you will have all the styles (CSS files) that should be loaded with the dashboard. The <Style> tag will have two properties: the src attribute where you should specify the path and filename of the CSS file to load, andversion where you can set the version number (not mandatory). You can add as many <style> tags as you want pointing to CSS files.

· Dependencies: This is where you can specify the JavaScript files that should be loaded with the dashboard. If you need to use some third-party libraries/plugins, you need to specify them here. Like the styles tag, there are two properties: one to point to the file to be loaded and another to set the version. You can use multiple dependencies when setting multiple <Dependency> tags, as you can see in the previous example.

· Order: This is a number that will set the order that should be used when displaying the list of properties.

· Advanced: This property only becomes available when we click on Advanced Properties when setting the values of the properties of the component while using it in the layout perspective. It needs to be true or false. Setting it to true will make it available as an advanced property.

· Version: This property is a number inside double quotes where you can set the version.

· Custom properties: The definitions of properties in the model tag are already defined, but not the custom properties we are creating for this specific component.

These custom properties are enclosed inside the header tag, and the ones that you need to define are:

· name: This is used to set the name of the component.

· parent: This is the parent property. It should be set to BaseProperty, as it's part of the component.

· DefaultValue: This is the value that will be used and is visible by default. It may be changed.

· Description: This is the description to display in the property, like a label.

· Tooltip: This is a tooltip that a CDE Editor user will get when hovering over the name of a property.

· InputType and OutputType: These two properties are related to the property type:

· InputType is used by CDE to know what kinds of visual input element will be provided to the user. For instance, when you click to set a value on a postExecution property, you get a dialog where you can write your JavaScript code. When settingparameters, you get multiple pairs of input boxes where you can select a value or write one. This input type is also used for validation of the values that are being set for the property. You can see the Input type as the behavior that CDE should exhibit in order to allow the input of a value(s). Here, the number of options is big, but we can restrict them to just the most important.

· OutputType is the JavaScript data type that will be used to store the value(s) specified for the property. Here, the options to set are pretty much: String, Number, Boolean, Array, Function, and Object.

Now let's see what options for the input and output property types are available, and how they combine with each other:

Input Type

Output Type

Description

String

Integer

Float

Integer

Boolean

String

Number

Number

Number

Boolean

Basic input types and mapping. The input will be validated depending on InputType. The Boolean input type will also allow you to select between true and false.

JavaScript

Function

This will open a dialog where you can write the JavaScript code.

Extending CDE

ValuesArray

Array

Displays a dialog like the one used in parameters. It allows you to combine a pair of values:

Extending CDE

EditorValuesArray

Array

This is like the last one but, in the column to the right, it is possible to write JavaScript code, not just a string or number. Extension points, which we will cover later, are an example.

Extending CDE

Array

Array

This displays a dialog where you can add multiple values. Vertical input boxes will be displayed.

Extending CDE

ColSortableArray

Array

This is similar to the last one, but the values will be sortable. So you can change their order.

HTML

String

This allows you to write HTML code. This will be translated to a string, so the advantage is that you will get a friendly interface to do it.

You can find a lot of examples in the XML files under resourses/base/properties and resources/custom/properties. Just look under the system/pentaho-cdf-dd plugin folder.

Another way to load styles and dependencies is by setting them in the RequireJS dependencies. The HelloWorldComponent.js file; if you compare it with the code used in CDF, you will see the same, so a CDE component is pretty much a file that can be as follows:

define(['cdf/components/UnmanagedComponent','amd!cdf/lib/underscore'],

function(UnmanagedComponent, _) {

ShowResultComponent = UnmanagedComponent.extend({

update: function() {

var render = _.bind(this.render,this);

this.triggerQuery(this.queryDefinition, render);

},

render: function(data) {

this.placeholder().empty().text(JSON.stringify(data));

}

});

return ShowResultComponent;

});

Extending or creating new dashboard types

CDF also provides three types of dashboard that you can use when creating a CDF dashboard. It's also possible to extend the functionality of CDF and create new dashboard styles. The three dashboards types that are available out of the box are:

· Clean: When using this, the dashboard does not load any CSS—it's just an empty container. It might create some more work, but it also gives you more flexibility and enables a high level of customization. The way to use this dashboard is by setting the module in the modules dependency of RequireJS. The instruction should resemble: require(['cdf/Dashboard.Clean'],...)

· Blueprint: When using this, the dashboard loads the blueprint CSS framework, which you can find at http://www.blueprintcss.org/. You can use its classes easily without including any more resources. The way to use this is with an instruction, such as:require(['cdf/Dashboard.Blueprint'],...)

· Bootstrap: Last but not least. When you use this one, the dashboard loads the Bootstrap framework, which is my preferred framework as it provides more flexibility when applying styles to the layout of your dashboards. This is a very well-known and popular framework that you can find at http://getbootstrap.com/. The way to use it is by setting it as a dependency/module using RequireJS: require(['cdf/Dashboard.Bootstrap'],...)

The great advantage of having a dashboard type is that you or other developers can include some CSS and/or JavaScript frameworks and libraries that you might want to use in all the dashboards. Don't forget that you should be careful when doing this because, if you are loading resources that will not be used, you are just decreasing the load time and other resources.

But how do you create a new type? To create a new type, you should define a new require module. Just create a new JavaScript file and give it the name myCustomDashboardType.js. You just need also to include all the dependencies that you will need for yourdashboards. There is one module that is mandatory and that you can't avoid: the Dashboard module/dependency. You can include that dependency and all the others you need, as follows:

define(['./Dashboard'], function(Dashboard) {

return Dashboard;

});

The example just provided will produce the same result as the Clean dashboard type, because it does not include any other modules/frameworks/dependencies.

Let's suppose you also want to extend the Dashboard module with some methods, options, and so on. You can just do something like:

define(['./Dashboard'], function(Dashboard) {

return Dashboard.extend({

someCustomCode: function() {

//...

}

});

});

The dashboard will now have access to the function and can be used as: dashboard.someCustomCode().

You can require the dashboard type by providing the relative path to it. Let's suppose the dashboard type is in the same folder as the dashboard HTML file, and you can just use the name myCustomDashboardType when requiring. See the following example:

require([' myCustomDashboardType'], function(Dashboard) {

var dashboard = new Dashboard();

dashboard.init();

dashboard.someCustomCode ();

});

Tip

It is not possible to apply or extend these types to a CDE dashboard

At this time, creating a new dashboard type, it's only available to CDF dashboards, and cant' be applied to CDE dashboards. Inside CDE, you can choose the dashboard type from three options, but you cannot add more types there.

Creating a new dashboard style/template

Besides the dashboard type, we can also specify a dashboard style that will somehow work as a template wrapper for the dashboards. Here you can include some scripts or just HTML; you can define what is valid for an HTML file.

Extending styles for CDF dashboards

We saw earlier that when creating a CDF dashboard, we should specify the style in the XCDF file similar to <style>clean</style>. This will instruct CDF to make use of a particular template/style for our dashboard.

By default, the templates are inside a folder in the filesystem, in the plugin itself. The folder is <baserver>/pentaho-solutions/system/pentaho-cdf. When we create styles/templates, we need to have them in one place that is accessible for multiple projects, if needed; however, if we place them in the same folder as the default ones, they will be overwritten on the next update of the plugin. To avoid this, it's possible to place our own templates in a folder, inside the Pentaho Repository, that will not be lost when updating the plugins. It should be created in a folder as: /public/cdf/templates, and all the styles/templates will become available for the dashboards.

The name of the file should be: dashboard-template-myTemplate.html; when setting it in the XCDF file dashboard, we should exclude the prefix dashboard-template. The tag inside the XCDF will be <style>myTemplate</style>.

But how should we define the template? To answer that, let's look at the following example, which is exactly the same as the clean template:

<!DOCTYPE html PUBLIC

"-//W3C//DTD XHTML 1.0 Strict//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="content-type" content="text/html; charset=utf-8" />

<title>Ctools Book Samples</title>

<meta name="keywords" content="" />

<meta name="description" content="" />

</head>

<body>

{content}

</body>

</html>

You can see that this is pretty much an HTML page where you can also add CSS and JavaScript files, and CSS code—well, everything you can have on an HTML page you can also have here. The magic is the expression {content}, which represents the area where the specific code for each one of the dashboards will be placed. This expression will be replaced by the HTML, again including JavaScript and CSS code, from the file that is defined in the template element, such as<template>myFirstDashboard.html</template>. So the code inside myFirstDashboard.html will be replacing the referred expression, generating a complete HTML page for your dashboard, the file that will be used by the browser to render the web page with the dashboard.

So, when creating our first dashboard with the example code we saw previously, the browser would render similar to the following code:

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="content-type"

content="text/html; charset=utf-8" />

<title>Ctools Book Samples</title>

<meta name="keywords" content="" />

<meta name="description" content="" />

</head>

<body>

<style>

</style>

<div class="container-fluid">

<h1>My first dashboard!</h1>

</div>

<script language="javascript" type="text/javascript">

var dashboard;

require(['cdf/Dashboard.Clean'],

function(Dashboard) {

dashboard = new Dashboard();

dashboard.init();

}

);

</script>

</body>

</html>

Extending styles for CDE dashboards

It's also possible to extend CDE with new styles/templates. The way to create them is pretty much the same, except for the name and the folder where they should be saved. CDE templates should be saved inside /public/cdf/templates and the names are simple. As we can have legacy and require dashboards, we may need to specify two different files, one for the legacy and the other one to be used when building a require dashboard. The name should be appended by Require before the extension, for instance, myCustomStyleRequire.html. Otherwise on a legacy dashboard, which we are not covering in this book, it would just be myCustomStyle.html.

The tags that will be replaced also change a bit, so here we need to make use of:

· @header@: This is used to include some initialization scripts, such as the dashboard context. It should be part of the header.

· @content@: This is use to be replaced by the content of the dashboard. This is where the dashboard will be rendered. All the layout and code for the execution of the dashboard are placed here.

· @footer@: This is used to include some scripts. It can be part of the footer template.

The way to apply a style/template to a CDE dashboard is to just choose it from the setting dialog of the dashboard. You will see a dropdown where the available styles/templates will be selectable. You just need to select the one for your dashboard and save it. Next time the dashboard is rendered, it will make use of that style/template.

The CleanRequire.html style looks like the following code. You can see a clean file where the header, content, and footer will be placed:

<!DOCTYPE HTML>

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<meta name="viewport" content="width=device-width, initial-scale=1">

@HEADER@

</head>

<body>

@CONTENT@

@FOOTER@

</body>

</html>

Bookmarkable parameters

You have already seen that we are able to create a dashboard that uses filters to be interactive. But let's suppose you want a dashboard to jump to a particular state using some values that could be specified using the URL. That's also possible out of the box using the bookmarkable parameters.

A great usage of this is for you to share the status of a dashboard with someone else. When you are exploring data through the dashboard, you may find some insights or just some warnings that you want to send to someone else. You can even send an e-mail with that information to someone else in the company. So now let's see how you can get them working.

When you create parameters, by using the components perspective, you may create a parameter by expanding the Generic group, choosing the type of parameter (also covered in this book). Three properties will become available: one for the name, a second one for the default value, and a checkbox to make the parameter a bookmarkable one.

If you make a parameter available, and when you start using the dashboard in such a way the parameter value is changed, you will notice that the URL changes and some more information has been added. The changes that you will find in the decoded URL will look like the following, depending on the parameters and the values being sent to them:

bookmarkState={"params":{"filterParam":"[Product].[Trains]"}}

This line shows that the value that is selected is Trains. What this means is that, if you use the entire URL like we are getting in the browser, opening another tab or window for your browser and pasting the complete URL will filter your dashboards and show only trains. If you use the dashboard URL without bookmarkState in the URL, then you will get the dashboard filtered with the default value, which in the sample provided is Classic Cars.

You will find a short and simple example in the chapter samples of a dashboard that presents a button that will send an e-mail with the URL containing the selection applied. In that case, you could send a message to a particular department or person, providing some insights. The code in the button would resemble:

function() {

var dashboardPath = window.location.href;

// send email with path to dashboard

}

This just gets the current URL with the bookmark state and sends it by e-mail.

Tip

Bookmarkable parameters in CDF

The Bookmarkable parameters are a CDE functionality, so you can also use them in CDF dashboards as well, if you include the module, cdf/Dashboard.Bootstrap.

Internationalization and localization

We can have internationalized and localized dashboards, and these are based on the i18n jQuery plugin. When we need to translate a dashboard, we need to create multiple files in the same folder as the dashboard, all of the .properties files.

The first one is messages_supported_languages.properties, where we need to specify the languages that will be supported. This will dictate the files and languages that should be read. If we want to be able to provide transactions in Portuguese (pt) and English (en), we use:

pt

en

Here we should have <language> and/or <language>_<COUNTRY>, where <language> is the lowercase code for the language and <COUNTRY> is the uppercase country code.

The i18n properties files will be key/value pairs where the names will dictate the language that will be used. We can also make use of a fallback file, but the fallback file doesn't need to be defined in the supported languages file.

We can delegate i18n messages to three specific files, which need to be placed in the same folder as the dashboard. The standard in Pentaho is to have the names using the following rules:

messages_<language>_<COUNTRY>.properties

These files are the ones that will contain the translations for a particular country for a language, where <language> should be replaced by the lowercase language code and <COUNTRY> should be replaced by the uppercase country code (for instance:messages_en_US.properties, messages_en_UK.properties, messages_pt_PT.properties and messages_pt_BR.properties):

messages_<language>.properties

These files contain the translations for a particular language, not specifying the country, where <language> should be replaced by the lowercase language code (for instance: messages_en.properties and messages_pt.properties):

messages.properties

That's the fallback file, where no language or country is specified. Here we will not need to specify a language or country.

Messages or translations can and should be shared by the different files; whenever that happens, the following rule applies:

The message keys placed in messages_<language>_<COUNTRY>.properties will override similar ones placed in messages_<language>.properties, which will overwrite messages.properties.

A hierarchical structure of the messages properties files would be:

+ messages.properties

++ messages_en.properties

++++ messages_en_US.properties

++++ messages_en_UK.properties

++ messages_pt.properties

++++ messages_pt_PT.properties

++++ messages_pt_BR.properties

Each one of these files is a pair of key/value, where the key and value are separated by an equals sign (=). The key will represent the identifier for the transactions while the value will be the translation itself, the text that will be displayed on the dashboard.

Let's suppose that we are creating a dashboard that can translate English and Portuguese. The first step is to create the fallback properties file. From there, we need to create new files for language and country, and just include the keys that need translation.

The messages.properties file would look like:

DASH.TITLE: Internationalization and localization

DASH.DESC: Internationalization and localization, sample dashboard

The messages_en.properties file could be empty, because the translation will be the same. When there is no key/value for the requested key, the priority (as explained before) is taken into consideration, so the text from the fallback file would be shown.

The messages_pt.properties file would look like this:

DASH.TITLE: Internacionalização e localização

DASH.DESC: Exemplo de internacionalização e localização

To make use of the translations inside a dashboard, we need to make use of the CDF API, calling the prop function from i18nSupport. The following line of code grabs the translation from the correct language file and returning it. This is done through a line of code:

this.dashboard.i18nSupport.prop('DASH.TITLE');

The previous examples do not specify the messages for the countries but, as already covered, you should be able to do it; the content will also be based on key/value pairs. We can avoid repeating the key/value pairs that are similar and use the hierarchical priority rules to specify only the key/value pairs that are different from file to file.

To apply this to the DOM using postExecution of the components, you can use jQuery or a text component.

There are some places such as charts or tables where you can also use the function, but make sure you also have a way to return the transactions for the result of a query. This can be achieved using a metadata schema, a dynamic schema processor on Mondrian, or with Kettle (PDI), but it may be harder with a simple SQL query where we can't use parameters to change the column returned.

The dashboard component

Using RequireJS allows great flexibility for the integration of CDE and CDF dashboards in third-party applications. That said, you could start asking: Is there a way to have dashboards inside another dashboard? This way, we could develop mini-dashboardsthat we can reuse inside the same dashboard and/or for multiple dashboards.

Later, we will cover, in Chapter 10, Embed, Deploy, and Debug, how we can integrate/embed dashboards into third-party applications, because now reusing a CDE dashboard inside another CDE dashboard is really easy. We can use the dashboard component that you can find inside the Custom group of the Components panel. The component will only be available when building RequireJS and not a legacy dashboard and, as we said before, the CDF and CDE chapters of this book are really focused on building dashboards using RequireJS.

The dashboard component is really easy to understand and use, and in my opinion it's one of the most desired components for developers and teams who want to reuse code and build more complete and complex solutions with less effort.

One of its advantage is that you can make use of a mini-dashboard in multiple dashboards. If you find a problem or just want to make a change, you just need to apply it to the mini-dashboard, and instantly you will see those changes applied to all the dashboards making using of that mini-dashboard.

The available properties when using this component are:

· Dashboard path: This is the path to the dashboard you want to embed inside your main dashboard. Here you can point to a mini-dashboard that in reality is a dashboard. That mini-dashboard will be called and rendered inside the main dashboard.

· Parameter mapping: If you want to synchronize some of the parameters between both dashboards, you can do this using this property. You can choose the parameters from the list of available parameters; when entering the name, you just need to click on the down arrow. On the left, you will find the parameters of the main dashboard, and on the right are the parameters that are public on the dashboard that is being instantiated. The following screenshot shows the mapping between the parameters from the main instantiated dashboard:

The dashboard component

But you should now be asking, why am I referring to a public parameters? Well, it is possible to define the parameter mapping using the parameters of the dashboard being instantiated, and it's necessary that you make them public. When you are creating/editing a parameter, you need to change the Public property to True. This will make the parameter available to the outside world. The following screenshot is an example of this:

The dashboard component

You will see that the Public property is set to true, so the parameter will become available when instantiated in the dashboard component.

· Datasource mapping: Interesting? Yes, definitely interesting. This can be used to overwrite a data source being used on the instantiated dashboard. This means that we can replace the data source and use one defined in the main dashboard. When you create a mapping between dashboards, that data source will be replaced everywhere. So if you have multiple components using the same data source on the instantiated dashboard, this means that those components will use the data source from the main dashboard being mapped.

In the examples from this chapter that you can import to your repository, you will find inside the chapter 7 folder an example on the use of the dashboard component. There you will find two dashboards. On one main dashboard we use the mini-dashboard four times, displaying different titles, data, and even different chart types.

You can use the dashboard component to create navigation between dashboards, reuse dashboards, and create what in a more limited way CDE made available as widgets (with the use of RequireJS, this does not make sense anymore). I find the dashboard component much more flexible and easy to use, with the advantage that it is possible to define a dummy data source for the mini-dashboards and really use the data sources defined in the main-dashboard.

Summary

In this chapter, you learned some advanced features, tricks, and tips that you can use to build fancier dashboards, just save time developing dashboards, or extend the CDF and CDE capabilities by creating new components. We also covered some important features such as internationalization and localization.

You should also now know how to use the bookmarkable parameters to initiate a dashboard from a particular state or just call new dashboards, passing some values to replace the default values for the parameters.

If you create a new dashboard template that will be used to standardize the look and feel of all your dashboards, this topic is also covered in this chapter.

This may be one of the most complex chapters and hard to understand, but it is important when you start to get more complex and advanced requirements for the dashboards. This is really useful when you want to deliver advanced and custom features to your customers or developers. There is no need for everyone to know every single feature and possibility in CDF and CDE so, if you have a team that does not have that knowledge but you want them to be able to reuse some of your code and/or dashboards, you now have the knowledge to make it possible.

Don't forget that is fine to use the query component, but if you are using the same code twice, you may need to consider the development of a custom component and make it easier to reuse.

In the next chapter, you will come to understand the CCC properties and how they can be used for visualization.