Creating UI View (before v22)

Follow

Now let's discuss how you can build a UI for your plugin by adding new Countly views to the dashboard.

We know that some files are loaded automatically into Countly dashboard, and now using these files (basically, countly.models.js and countly.views.js) we can both get data from API and display them.

So what we need to achieve is:

  • Create a model which can fetch data from API
  • Create a Backbone view which would get the data from model and load it into template and append to a page
  • Add this view to App Router
  • Inject some Javascript to modify page, like create a menu item for our plugin

countly.models.js

Now let's define a simply model which fetched data from api on path /o?method=ourplugin

(function (countlyOurplugin, $) {

    //we will store our data here
    var _data = {};

    //Initializing model
    countlyOurplugin.initialize = function () {
  
        //returning promise
        return $.ajax({
            type:"GET",
            url:"/o",
            data:{
                //providing current user's api key
                "api_key":countlyGlobal.member.api_key,
                //providing current app's id
                "app_id":countlyCommon.ACTIVE_APP_ID,
                //specifying method param
                "method":"ourplugin"
            },
            success:function (json) {
                //got our data, let's store it
                _data = json;
            }
        });
    };
  
    //return data that we have
    countlyOurplugin.getData = function () {
        return _data;
    };
	
}(window.countlyOurplugin = window.countlyOurplugin || {}, jQuery));

Localization

There are two ways to provide localized string in Countly.

Through javascript by using global jQuery.i18n.map object which pre fetched values for you from your localized .properties files

alert(jQuery.i18n.map["ourplugin.hello"]);

Or through data attributes, which elements will have localized string added as contents of the element at render time

<!-- This will be changed  -->
<div data-localize="ourplugin.title"></div>

<!-- To this, automatically -->
<div data-localize="ourplugin.title">Our Plugin</div>

countly.views.js

Now let's create a basic Countly view, which will use our model to fetch data. More information on countlyView class and its properties and methods.

window.OurpluginView = countlyView.extend({
  
    //need to provide at least empty initialize function
    //to prevent using default template
initialize:function (){ //we can initialize stuff here }, beforeRender: function() { //let's fetch our template and initialize our mode in paralel var self = this; return $.when(T.get('/ourplugin/templates/default.html', function(template){ //precompiled template self.template = template;
//initialize our model }), countlyOurplugin.initialize()).then(function () {}); }, //here we need to render our view renderCommon:function () {
//provide template data this.templateData = { "page-title":"OurPlugin", "logo-class":"", "data":countlyOurplugin.getData() }; //populate template with data and attach it to page's content element $(this.el).html(this.template(this.templateData)); }, //here we need to refresh data refresh:function () { var self = this; $.when(countlyOurplugin.initialize()).then(function () { //our view is not active if (app.activeView != self) { return false; } //here basically we want to do the same we did in renderCommon method self.renderCommon(); }); } });

Then lets create instance of our view and add it to app router to some specific url

//create view
app.ourpluginView = new OurpluginView();

//register route
app.route('/ourplugin', 'ourplugin', function () {
    this.renderWhenReady(this.ourpluginView);
});

After that you should be able to view your view at http://yourdomain.com/dashboard#/ourplugin

Injecting scripts and html

But we do not want to type in our URL, we want to get there by clicking on the menu button. To accomplish that we need to add a menu item.

This is a one time task which should be accomplished when webpage has loaded, so here we can simply wait for the document to be ready and add our plugin item to the menu (here is more information about addMenu method).

$( document ).ready(function() {
app.addMenu("understand", {
code: "ourplugin",
url: "#/ourplugin",
text: "ourplugin.title",
icon: '<div class="logo ion-pricetags"></div>',
priority: 50
}); });

Ok, but what if we need to inject html on specific view, which might not be available when page is loaded. How can we know if user accessed this specific view, so we could modify it.

We can add a page script which will be executed on specified route. So for example, this script will be executed every time user visits page "/dashboard#/analytics/sessions"

app.addPageScript("/analytics/sessions", function(){
   //You can perform any dom manipulations here
   alert("You are viewing Sessions Analytics");
});

But what if we need to do something on every page view? Easy, we use # as a page script route

app.addPageScript("#", function(){
   //You can perform any dom manipulations here
   console.log("new page view loaded");
});

How about dynamic URLs which you don't know upfront? Easy!

// All pages which URL starts with '/users/' and follows some string, for example: 
// /users/c49ebdad8f39519af9e0bfbf79332f4ec50b6d0f
app.addPageScript("/users/#", function(){
   console.log("new user profile view loaded");
});

Great, but sometimes we need to change something in the view, but then it refreshes and changes back, how to tackle that?

We can add a refresh page script to specific routes, same as add page scripts.

app.addRefreshScript("/analytics/sessions", function(){
  //You can perform any dom manipulations here
	console.log("sessions view refreshed");
});

Processing metric data

As common usage for plugins are adding new metrics, let's view an example on how you can display your metric data.

First again model. The data saved and displayed differs much, thus there is a difficult transformation performed on metric data, but you can use the helper method to transform it easily

CountlyHelpers.createMetricModel(window.countlyOurmetric = window.countlyOurmetric || {}, "ourmetric", jQuery);

Then we create a view to use our model to fetch metric data and generate pie charts and datatable with data

Since we will be using default template already used in the Countly, we don't need to load our own.

window.OurmetricView = countlyView.extend({
    //initalize out model
beforeRender: function() { return $.when(countlyOurmetric.initialize()).then(function () {}); }, //render our data renderCommon:function (isRefresh) { var data = countlyOurmetric.getData(); //prepare template data this.templateData = { "page-title":jQuery.i18n.map["ourmetric.title"], "logo-class":"", "graph-type-double-pie":true, "pie-titles":{ "left":jQuery.i18n.map["common.total-users"], "right":jQuery.i18n.map["common.new-users"] } }; //if loading first time and not refershing if (!isRefresh) { //build template with data $(this.el).html(this.template(this.templateData)); //create datatable with chart data this.dtable = $('.d-table').dataTable($.extend({}, $.fn.dataTable.defaults, { //provide data to datatables "aaData": data.chartData, //specify which columns to show "aoColumns": [ { "mData": "ourmetric", sType:"session-duration", "sTitle": jQuery.i18n.map["ourmetric.title"] }, { "mData": "t", sType:"formatted-num", "mRender":function(d) { return countlyCommon.formatNumber(d); }, "sTitle": jQuery.i18n.map["common.table.total-sessions"] }, { "mData": "u", sType:"formatted-num", "mRender":function(d) { return countlyCommon.formatNumber(d); }, "sTitle": jQuery.i18n.map["common.table.total-users"] }, { "mData": "n", sType:"formatted-num", "mRender":function(d) { return countlyCommon.formatNumber(d); }, "sTitle": jQuery.i18n.map["common.table.new-users"] } ] })); //make table headers sticky $(".d-table").stickyTableHeaders(); //draw chart with total data countlyCommon.drawGraph(data.chartDPTotal, "#dashboard-graph", "pie"); //draw chart with new data countlyCommon.drawGraph(data.chartDPNew, "#dashboard-graph2", "pie"); } }, //refreshing out chart refresh:function () { var self = this; $.when(countlyOurmetric.refresh()).then(function () { //populate and regenerate template data self.renderCommon(true); //replace existing elements in view with new data newPage = $("<div>" + self.template(self.templateData) + "</div>"); $(self.el).find(".dashboard-summary").replaceWith(newPage.find(".dashboard-summary")); var data = countlyOurmetric.getData(); //refresh charts countlyCommon.drawGraph(data.chartDPTotal, "#dashboard-graph", "pie"); countlyCommon.drawGraph(data.chartDPNew, "#dashboard-graph2", "pie"); //refresh datatables CountlyHelpers.refreshTable(self.dtable, data.chartData); }); } }); //create view app.ourmetricView = new OurmetricView(); //register route app.route("/analytics/ourmetric", 'ourmetric', function () { this.renderWhenReady(this.ourmetricView); }); //add menu item $( document ).ready(function() {
app.addSubMenu("analytics", {
code: "analytics-ourmetric",
url: "#/analytics/ourmetric",
text: "ourmetric.title",
priority: 90
}); });

And we have added our new metric to Countly installation and additionally to API part example, we can share the plugin with everybody else.

Now we only need to modify your Countly SDK installation to report our custom metrics to our server and we are done.

Looking for help?