{"__v":18,"_id":"54f860678dddbf2d003a27f9","category":{"_id":"54f6b81852174719008f610b","__v":10,"pages":["54f81a05172f2f0d00fc0908","54f81ca525a0b717001b753d","54f82860172f2f0d00fc0914","54f84a3a172f2f0d00fc092e","54f8587fdc0d020d008b9126","54f860678dddbf2d003a27f9","55a697d951457325000e4e15","55a6a87b89c9da1900e2a416","55bf45838b83a53700445003","561fb7b018d75b1700b2521a"],"project":"541c6d8251a68c3b45b9ada7","version":"541c6d8251a68c3b45b9adaa","sync":{"url":"","isSync":false},"reference":false,"createdAt":"2015-03-04T07:45:28.472Z","from_sync":false,"order":5,"slug":"plugin-development","title":"Plugin Development"},"parentDoc":null,"project":"541c6d8251a68c3b45b9ada7","user":"542e72631161420800d8353a","version":{"__v":16,"_id":"541c6d8251a68c3b45b9adaa","project":"541c6d8251a68c3b45b9ada7","createdAt":"2014-09-19T17:53:06.500Z","releaseDate":"2014-09-19T17:53:06.500Z","categories":["541c6d8251a68c3b45b9adab","541c6e1c51a68c3b45b9adae","542baa54e5bb3e2000801fec","5436794bb7cf0e1c0020d8cc","54367d76b7cf0e1c0020d8e9","54367dc7b7cf0e1c0020d8f4","54367defd0ffee0e00f18eb7","54368035b7cf0e1c0020d8fe","5436ad91b7cf0e1c0020da6c","5447a252a1024f14005a6dd7","547e0c858466c808005369a1","54f6b81852174719008f610b","5516d13c16a294230084a985","557971d4fdbdb717002fa6c1","55a964c102becf2d007aad11","55b8c26b1b56701900a14109"],"is_deprecated":false,"is_hidden":false,"is_beta":false,"is_stable":true,"codename":"","version_clean":"1.0.0","version":"1.0"},"updates":[],"next":{"pages":[],"description":""},"createdAt":"2015-03-05T13:55:51.573Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"auth":"required","params":[],"url":""},"isReference":false,"order":7,"body":"Now let's discuss how you can build a UI for your plugin by adding new Countly views to the dashboard.\n\nWe know that [some files are loaded automatically into Countly dashboard](http://resources.count.ly/v1.0/docs/plugins-frontend-browser-files), and now using these files (basically, countly.models.js and countly.views.js) we can both get data from API and display them.\n\nSo what we need to achieve is:\n\n  * Create a model which can fetch data from API\n  * Create a Backbone view which would get the data from model and load it into template and append to a page\n  * Add this view to App Router\n  * Inject some Javascript to modify page, like create a menu item for our plugin \n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"countly.models.js\"\n}\n[/block]\nNow let's define a simply model which fetched data from api on path /o?method=ourplugin\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"(function (countlyOurplugin, $) {\\n\\n\\t//we will store our data here\\n  var _data = {};\\n\\n  //Initializing model\\n  countlyOurplugin.initialize = function () {\\n  \\n  \\t//returning promise\\n\\t\\treturn $.ajax({\\n        type:\\\"GET\\\",\\n        url:\\\"/o\\\",\\n        data:{\\n          //providing current user's api key\\n        \\t\\\"api_key\\\":countlyGlobal.member.api_key,\\n          //providing current app's id\\n          \\\"app_id\\\":countlyCommon.ACTIVE_APP_ID,\\n          //specifying method param\\n          \\\"method\\\":\\\"ourplugin\\\"\\n        },\\n        success:function (json) {\\n           //got our data, let's store it\\n           _data = json;\\n        }\\n    });\\n \\t};\\n  \\n  //return data that we have\\n \\tcountlyOurplugin.getData = function () {\\n\\t\\treturn _data;\\n  };\\n\\t\\n}(window.countlyOurplugin = window.countlyOurplugin || {}, jQuery));\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Localization\"\n}\n[/block]\nThere are two ways to provide localized string in Countly.\n\nThrough javascript by using global jQuery.i18n.map object which pre fetched values for you from your localized .properties files\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"alert(jQuery.i18n.map[\\\"ourplugin.hello\\\"]);\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nOr through data attributes, which elements will have localized string added as contents of the element at render time\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"<!-- This will be changed  -->\\n<div data-localize=\\\"ourplugin.title\\\"></div>\\n\\n<!-- To this, automatically -->\\n<div data-localize=\\\"ourplugin.title\\\">Our Plugin</div>\",\n      \"language\": \"html\"\n    }\n  ]\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"countly.views.js\"\n}\n[/block]\nNow let's create a basic Countly view, which will use our model to fetch data\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"window.OurpluginView = countlyView.extend({\\n  \\n  //need to provide at least empty initialize function\\n  //to prevent using default template\\n\\tinitialize:function (){\\n\\t\\t//we can initialize stuff here\\n\\t},\\n  \\n  beforeRender: function() {\\n    \\n\\t\\t//check if we already have template\\n\\t\\tif(this.template)\\n      \\n\\t\\t\\t//then lets initialize our mode\\n\\t\\t\\treturn $.when(countlyOurplugin.initialize()).then(function () {});\\n\\t\\telse{\\n      \\n\\t\\t\\t//else let's fetch our template and initialize our mode in paralel\\n\\t\\t\\tvar self = this;\\n\\t\\t\\treturn $.when($.get(countlyGlobal[\\\"path\\\"]+'/ourplugin/templates/default.html', function(src){\\n        \\n\\t\\t\\t\\t//precompiled our template\\n\\t\\t\\t\\tself.template = Handlebars.compile(src);\\n\\t\\t\\t}), countlyOurplugin.initialize()).then(function () {});\\n\\t\\t}\\n  },\\n  \\n\\t//here we need to render our view\\n  renderCommon:function () {\\n\\t\\t\\n\\t\\t\\t\\t//provide template data\\n        this.templateData = {\\n            \\\"page-title\\\":\\\"OurPlugin\\\",\\n            \\\"logo-class\\\":\\\"\\\",\\n\\t\\t\\t\\t\\t\\t\\\"data\\\":countlyOurplugin.getData()\\n        };\\n\\t\\t\\n\\t\\t\\t\\t//populate template with data and attach it to page's content element\\n        $(this.el).html(this.template(this.templateData));\\n  },\\n  \\n\\t//here we need to refresh data\\n  refresh:function () {\\n      var self = this;\\n      $.when(countlyOurplugin.initialize()).then(function () {\\n\\t\\t\\n\\t\\t\\t\\t//our view is not active\\n        if (app.activeView != self) {\\n            return false;\\n        }\\n\\t\\t\\t\\n\\t\\t\\t\\t//here basically we want to do the same we did in renderCommon method\\n        self.renderCommon();\\n      });\\n    }\\n});\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nThen lets create instance of our view and add it to app router to some specific url\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"//create view\\napp.ourpluginView = new OurpluginView();\\n\\n//register route\\napp.route('/ourplugin', 'ourplugin', function () {\\n\\tthis.renderWhenReady(this.ourpluginView);\\n});\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nAfter that you should be able to view your view at `http://yourdomain.com/dashboard#/ourplugin`\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Injecting scripts and html\"\n}\n[/block]\nBut we do not want to type in our url, we want to get there by clicking on the menu button. To accomplish that we can inject the html to render our menu item.\n\nThis is a one time task which should be accomplished when webpage has loaded, so here we can simply wait for document to be ready and add our plugin item to menu.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"$( document ).ready(function() {\\n\\tvar menu = '<a href=\\\"#/ourplugin\\\" class=\\\"item\\\" \\\">'+\\n        '<div class=\\\"logo fa fa-plugin\\\" style=\\\"background-image:none; font-size:24px; text-align:center; width:35px; margin-left:14px; line-height:42px;\\\"></div>'+\\n        '<div class=\\\"text\\\" data-localize=\\\"ourplugin.title\\\"></div>'+\\n    '</a>';\\n\\t\\n\\tif($('#management-menu').length)\\n    //if there is management menu insert before it\\n\\t\\t$('#management-menu').before(menu);\\n\\telse\\n    //else inser in the end of menu\\n\\t\\t$('#sidebar-menu').append(menu);\\n});\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nOk, 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.\n\nWe 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\"\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"app.addPageScript(\\\"/analytics/sessions\\\", function(){\\n   //You can perform any dom manipulations here\\n   alert(\\\"You are viewing Sessions Analytics\\\");\\n});\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nBut what if we need to do something on every page view? Easy, we use # as a page script route\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"app.addPageScript(\\\"#\\\", function(){\\n   //You can perform any dom manipulations here\\n   console.log(\\\"new page view loaded\\\");\\n});\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nHow about dynamic URLs which you don't know upfront? Easy!\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"// All pages which URL starts with '/users/' and follows some string, for example: \\n// /users/c49ebdad8f39519af9e0bfbf79332f4ec50b6d0f\\napp.addPageScript(\\\"/users/#\\\", function(){\\n   console.log(\\\"new user profile view loaded\\\");\\n});\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nGreat, but sometimes we need to change something in the view, but then it refreshes and changes back, how to tackle that? \n\nWe can add a refresh page script to specific routes, same as add page scripts.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"app.addRefreshScript(\\\"/analytics/sessions\\\", function(){\\n  //You can perform any dom manipulations here\\n\\tconsole.log(\\\"sessions view refreshed\\\");\\n});\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\n\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Processing metric data\"\n}\n[/block]\nAs common usage for plugins are adding new metrics, let's view an example on how you can display your metric data.\n\nFirst 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\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"CountlyHelpers.createMetricModel(window.countlyOurmetric = window.countlyOurmetric || {}, \\\"ourmetric\\\", jQuery);\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nThen we create a view to use our model to fetch metric data and generate pie charts and datatable with data\n\nSince we will be using default template already used in the Countly, we don't need to load our own.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"window.OurmetricView = countlyView.extend({\\n  //initalize out model\\n\\tbeforeRender: function() {\\n  \\treturn $.when(countlyOurmetric.initialize()).then(function () {});\\n  },\\n  \\n  //render our data\\n  renderCommon:function (isRefresh) {\\n  \\tvar data = countlyOurmetric.getData();\\n\\n    //prepare template data\\n\\t\\tthis.templateData = {\\n    \\t\\\"page-title\\\":jQuery.i18n.map[\\\"ourmetric.title\\\"],\\n      \\\"logo-class\\\":\\\"\\\",\\n      \\\"graph-type-double-pie\\\":true,\\n      \\\"pie-titles\\\":{\\n      \\t\\\"left\\\":jQuery.i18n.map[\\\"common.total-users\\\"],\\n        \\\"right\\\":jQuery.i18n.map[\\\"common.new-users\\\"]\\n      }\\n    };\\n\\n    //if loading first time and not refershing\\n    if (!isRefresh) {\\n      \\n      //build template with data\\n    \\t$(this.el).html(this.template(this.templateData));\\n\\n      //create datatable with chart data\\n      this.dtable = $('.d-table').dataTable($.extend({}, $.fn.dataTable.defaults, {\\n        //provide data to datatables\\n      \\t\\\"aaData\\\": data.chartData,\\n        \\n        //specify which columns to show\\n        \\\"aoColumns\\\": [\\n        \\t{ \\\"mData\\\": \\\"ourmetric\\\", sType:\\\"session-duration\\\", \\\"sTitle\\\": jQuery.i18n.map[\\\"ourmetric.title\\\"] },\\n          { \\\"mData\\\": \\\"t\\\", sType:\\\"formatted-num\\\", \\\"mRender\\\":function(d) { return countlyCommon.formatNumber(d); }, \\\"sTitle\\\": jQuery.i18n.map[\\\"common.table.total-sessions\\\"] },\\n          { \\\"mData\\\": \\\"u\\\", sType:\\\"formatted-num\\\", \\\"mRender\\\":function(d) { return countlyCommon.formatNumber(d); }, \\\"sTitle\\\": jQuery.i18n.map[\\\"common.table.total-users\\\"] },\\n          { \\\"mData\\\": \\\"n\\\", sType:\\\"formatted-num\\\", \\\"mRender\\\":function(d) { return countlyCommon.formatNumber(d); }, \\\"sTitle\\\": jQuery.i18n.map[\\\"common.table.new-users\\\"] }\\n         ]\\n \\t\\t\\t}));\\n      \\n      //make table headers sticky\\n\\t\\t\\t$(\\\".d-table\\\").stickyTableHeaders();\\n       \\n      //draw chart with total data\\n      countlyCommon.drawGraph(data.chartDPTotal, \\\"#dashboard-graph\\\", \\\"pie\\\");\\n      \\n      //draw chart with new data\\n      countlyCommon.drawGraph(data.chartDPNew, \\\"#dashboard-graph2\\\", \\\"pie\\\");\\n    }\\n  },\\n  \\n  //refreshing out chart\\n  refresh:function () {\\n  \\tvar self = this;\\n    $.when(countlyOurmetric.refresh()).then(function () {\\n      \\n      //not our view\\n    \\tif (app.activeView != self) {\\n      \\treturn false;\\n      }\\n      \\n      //populate and regenerate template data\\n      self.renderCommon(true);\\n\\n      //replace existing elements in view with new data\\n      newPage = $(\\\"<div>\\\" + self.template(self.templateData) + \\\"</div>\\\");  \\n     \\t$(self.el).find(\\\".dashboard-summary\\\").replaceWith(newPage.find(\\\".dashboard-summary\\\"));\\n      \\n      var data = countlyOurmetric.getData();\\n      \\n      //refresh charts\\n      countlyCommon.drawGraph(data.chartDPTotal, \\\"#dashboard-graph\\\", \\\"pie\\\");\\n      countlyCommon.drawGraph(data.chartDPNew, \\\"#dashboard-graph2\\\", \\\"pie\\\");\\n      \\n      //refresh datatables\\n\\t\\t\\tCountlyHelpers.refreshTable(self.dtable, data.chartData);\\n    });\\n  }\\n});\\n\\n//create view\\napp.ourmetricView = new OurmetricView();\\n\\n//register route\\napp.route(\\\"/analytics/ourmetric\\\", 'ourmetric', function () {\\n\\tthis.renderWhenReady(this.ourmetricView);\\n});\\n\\n//add menu item\\n$( document ).ready(function() {\\n\\tvar menu = '<a href=\\\"#/analytics/ourmetric\\\" class=\\\"item\\\">'+\\n\\t\\t'<div class=\\\"logo\\\"></div>'+\\n\\t\\t'<div class=\\\"text\\\" data-localize=\\\"ourmetric.title\\\"></div>'+\\n\\t'</a>';\\n\\t$('#analytics-submenu').append(menu);\\n});\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nAnd we have added our new metric to Countly installation and additionally to API part example, we can share the plugin with everybody else.\n\nNow we only need to modify your Countly SDK installation to report our custom metrics to our server and we are done.","excerpt":"","slug":"plugins-frontend-client-side","type":"basic","title":"Frontend client side files"}

Frontend client side files


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](http://resources.count.ly/v1.0/docs/plugins-frontend-browser-files), 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 [block:api-header] { "type": "basic", "title": "countly.models.js" } [/block] Now let's define a simply model which fetched data from api on path /o?method=ourplugin [block:code] { "codes": [ { "code": "(function (countlyOurplugin, $) {\n\n\t//we will store our data here\n var _data = {};\n\n //Initializing model\n countlyOurplugin.initialize = function () {\n \n \t//returning promise\n\t\treturn $.ajax({\n type:\"GET\",\n url:\"/o\",\n data:{\n //providing current user's api key\n \t\"api_key\":countlyGlobal.member.api_key,\n //providing current app's id\n \"app_id\":countlyCommon.ACTIVE_APP_ID,\n //specifying method param\n \"method\":\"ourplugin\"\n },\n success:function (json) {\n //got our data, let's store it\n _data = json;\n }\n });\n \t};\n \n //return data that we have\n \tcountlyOurplugin.getData = function () {\n\t\treturn _data;\n };\n\t\n}(window.countlyOurplugin = window.countlyOurplugin || {}, jQuery));", "language": "javascript" } ] } [/block] [block:api-header] { "type": "basic", "title": "Localization" } [/block] 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 [block:code] { "codes": [ { "code": "alert(jQuery.i18n.map[\"ourplugin.hello\"]);", "language": "javascript" } ] } [/block] Or through data attributes, which elements will have localized string added as contents of the element at render time [block:code] { "codes": [ { "code": "<!-- This will be changed -->\n<div data-localize=\"ourplugin.title\"></div>\n\n<!-- To this, automatically -->\n<div data-localize=\"ourplugin.title\">Our Plugin</div>", "language": "html" } ] } [/block] [block:api-header] { "type": "basic", "title": "countly.views.js" } [/block] Now let's create a basic Countly view, which will use our model to fetch data [block:code] { "codes": [ { "code": "window.OurpluginView = countlyView.extend({\n \n //need to provide at least empty initialize function\n //to prevent using default template\n\tinitialize:function (){\n\t\t//we can initialize stuff here\n\t},\n \n beforeRender: function() {\n \n\t\t//check if we already have template\n\t\tif(this.template)\n \n\t\t\t//then lets initialize our mode\n\t\t\treturn $.when(countlyOurplugin.initialize()).then(function () {});\n\t\telse{\n \n\t\t\t//else let's fetch our template and initialize our mode in paralel\n\t\t\tvar self = this;\n\t\t\treturn $.when($.get(countlyGlobal[\"path\"]+'/ourplugin/templates/default.html', function(src){\n \n\t\t\t\t//precompiled our template\n\t\t\t\tself.template = Handlebars.compile(src);\n\t\t\t}), countlyOurplugin.initialize()).then(function () {});\n\t\t}\n },\n \n\t//here we need to render our view\n renderCommon:function () {\n\t\t\n\t\t\t\t//provide template data\n this.templateData = {\n \"page-title\":\"OurPlugin\",\n \"logo-class\":\"\",\n\t\t\t\t\t\t\"data\":countlyOurplugin.getData()\n };\n\t\t\n\t\t\t\t//populate template with data and attach it to page's content element\n $(this.el).html(this.template(this.templateData));\n },\n \n\t//here we need to refresh data\n refresh:function () {\n var self = this;\n $.when(countlyOurplugin.initialize()).then(function () {\n\t\t\n\t\t\t\t//our view is not active\n if (app.activeView != self) {\n return false;\n }\n\t\t\t\n\t\t\t\t//here basically we want to do the same we did in renderCommon method\n self.renderCommon();\n });\n }\n});", "language": "javascript" } ] } [/block] Then lets create instance of our view and add it to app router to some specific url [block:code] { "codes": [ { "code": "//create view\napp.ourpluginView = new OurpluginView();\n\n//register route\napp.route('/ourplugin', 'ourplugin', function () {\n\tthis.renderWhenReady(this.ourpluginView);\n});", "language": "javascript" } ] } [/block] After that you should be able to view your view at `http://yourdomain.com/dashboard#/ourplugin` [block:api-header] { "type": "basic", "title": "Injecting scripts and html" } [/block] 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 can inject the html to render our menu item. This is a one time task which should be accomplished when webpage has loaded, so here we can simply wait for document to be ready and add our plugin item to menu. [block:code] { "codes": [ { "code": "$( document ).ready(function() {\n\tvar menu = '<a href=\"#/ourplugin\" class=\"item\" \">'+\n '<div class=\"logo fa fa-plugin\" style=\"background-image:none; font-size:24px; text-align:center; width:35px; margin-left:14px; line-height:42px;\"></div>'+\n '<div class=\"text\" data-localize=\"ourplugin.title\"></div>'+\n '</a>';\n\t\n\tif($('#management-menu').length)\n //if there is management menu insert before it\n\t\t$('#management-menu').before(menu);\n\telse\n //else inser in the end of menu\n\t\t$('#sidebar-menu').append(menu);\n});", "language": "javascript" } ] } [/block] 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" [block:code] { "codes": [ { "code": "app.addPageScript(\"/analytics/sessions\", function(){\n //You can perform any dom manipulations here\n alert(\"You are viewing Sessions Analytics\");\n});", "language": "javascript" } ] } [/block] But what if we need to do something on every page view? Easy, we use # as a page script route [block:code] { "codes": [ { "code": "app.addPageScript(\"#\", function(){\n //You can perform any dom manipulations here\n console.log(\"new page view loaded\");\n});", "language": "javascript" } ] } [/block] How about dynamic URLs which you don't know upfront? Easy! [block:code] { "codes": [ { "code": "// All pages which URL starts with '/users/' and follows some string, for example: \n// /users/c49ebdad8f39519af9e0bfbf79332f4ec50b6d0f\napp.addPageScript(\"/users/#\", function(){\n console.log(\"new user profile view loaded\");\n});", "language": "javascript" } ] } [/block] 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. [block:code] { "codes": [ { "code": "app.addRefreshScript(\"/analytics/sessions\", function(){\n //You can perform any dom manipulations here\n\tconsole.log(\"sessions view refreshed\");\n});", "language": "javascript" } ] } [/block] [block:api-header] { "type": "basic", "title": "Processing metric data" } [/block] 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 [block:code] { "codes": [ { "code": "CountlyHelpers.createMetricModel(window.countlyOurmetric = window.countlyOurmetric || {}, \"ourmetric\", jQuery);", "language": "javascript" } ] } [/block] 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. [block:code] { "codes": [ { "code": "window.OurmetricView = countlyView.extend({\n //initalize out model\n\tbeforeRender: function() {\n \treturn $.when(countlyOurmetric.initialize()).then(function () {});\n },\n \n //render our data\n renderCommon:function (isRefresh) {\n \tvar data = countlyOurmetric.getData();\n\n //prepare template data\n\t\tthis.templateData = {\n \t\"page-title\":jQuery.i18n.map[\"ourmetric.title\"],\n \"logo-class\":\"\",\n \"graph-type-double-pie\":true,\n \"pie-titles\":{\n \t\"left\":jQuery.i18n.map[\"common.total-users\"],\n \"right\":jQuery.i18n.map[\"common.new-users\"]\n }\n };\n\n //if loading first time and not refershing\n if (!isRefresh) {\n \n //build template with data\n \t$(this.el).html(this.template(this.templateData));\n\n //create datatable with chart data\n this.dtable = $('.d-table').dataTable($.extend({}, $.fn.dataTable.defaults, {\n //provide data to datatables\n \t\"aaData\": data.chartData,\n \n //specify which columns to show\n \"aoColumns\": [\n \t{ \"mData\": \"ourmetric\", sType:\"session-duration\", \"sTitle\": jQuery.i18n.map[\"ourmetric.title\"] },\n { \"mData\": \"t\", sType:\"formatted-num\", \"mRender\":function(d) { return countlyCommon.formatNumber(d); }, \"sTitle\": jQuery.i18n.map[\"common.table.total-sessions\"] },\n { \"mData\": \"u\", sType:\"formatted-num\", \"mRender\":function(d) { return countlyCommon.formatNumber(d); }, \"sTitle\": jQuery.i18n.map[\"common.table.total-users\"] },\n { \"mData\": \"n\", sType:\"formatted-num\", \"mRender\":function(d) { return countlyCommon.formatNumber(d); }, \"sTitle\": jQuery.i18n.map[\"common.table.new-users\"] }\n ]\n \t\t\t}));\n \n //make table headers sticky\n\t\t\t$(\".d-table\").stickyTableHeaders();\n \n //draw chart with total data\n countlyCommon.drawGraph(data.chartDPTotal, \"#dashboard-graph\", \"pie\");\n \n //draw chart with new data\n countlyCommon.drawGraph(data.chartDPNew, \"#dashboard-graph2\", \"pie\");\n }\n },\n \n //refreshing out chart\n refresh:function () {\n \tvar self = this;\n $.when(countlyOurmetric.refresh()).then(function () {\n \n //not our view\n \tif (app.activeView != self) {\n \treturn false;\n }\n \n //populate and regenerate template data\n self.renderCommon(true);\n\n //replace existing elements in view with new data\n newPage = $(\"<div>\" + self.template(self.templateData) + \"</div>\"); \n \t$(self.el).find(\".dashboard-summary\").replaceWith(newPage.find(\".dashboard-summary\"));\n \n var data = countlyOurmetric.getData();\n \n //refresh charts\n countlyCommon.drawGraph(data.chartDPTotal, \"#dashboard-graph\", \"pie\");\n countlyCommon.drawGraph(data.chartDPNew, \"#dashboard-graph2\", \"pie\");\n \n //refresh datatables\n\t\t\tCountlyHelpers.refreshTable(self.dtable, data.chartData);\n });\n }\n});\n\n//create view\napp.ourmetricView = new OurmetricView();\n\n//register route\napp.route(\"/analytics/ourmetric\", 'ourmetric', function () {\n\tthis.renderWhenReady(this.ourmetricView);\n});\n\n//add menu item\n$( document ).ready(function() {\n\tvar menu = '<a href=\"#/analytics/ourmetric\" class=\"item\">'+\n\t\t'<div class=\"logo\"></div>'+\n\t\t'<div class=\"text\" data-localize=\"ourmetric.title\"></div>'+\n\t'</a>';\n\t$('#analytics-submenu').append(menu);\n});", "language": "javascript" } ] } [/block] 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.