In previous posts in this series we discussed the business needs for
custom analytics report, I explained how to collect data, how to save them and
how to aggregate the data into reporting databases and how you can display them
as a SPEAK report, in this part I will show you how you can create new filter in
other words how you can extend the current existing filters.
If you missed the first two posts you can check them from here:
- - Building Custom Analytics Report – Part1 – Introduction
- - Build Custom Analytics Report - Part 2 – Storing and Retrieving Data
- - Building Custom Analytics Report – Part3 – SPEAK Report
- - Building Custom Analytics Report – Part 4 – Experience Analytics New Menu.
What are the default experience analytics filters components?
We already know that the default experience analytics filter come with date and subsite filters, to be able to extend the experience analytics filters we need to identify the related files for each component,
We already know that the default experience analytics filter come with date and subsite filters, to be able to extend the experience analytics filters we need to identify the related files for each component,
Mainly there are three main components, the
filters container view and its logic, the filters logic and the filter view and
logic, following are the default paths:
·
Filters Container and its logic
This view name is “ExperienceAnalyticsFilterscshtml” and
will be located in the following path:
\Website\sitecore\shell\client\Applications\ExperienceAnalytics\Common\Layouts\Renderings\ExperienceAnalyticsFilters.cshtml
Following is the content
of this file, and as you can see it include the default rendering “filters”,
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@using Sitecore | |
@using Sitecore.ExperienceAnalytics.Client | |
@using Sitecore.ExperienceAnalytics.Core.Extensions | |
@using Sitecore.Mvc | |
@using Sitecore.Web.UI.Controls.Common.Renderings | |
@using Sitecore.Web.UI.Controls.Common.UserControls | |
@using Globals = Sitecore.ExperienceAnalytics.Client.Globals | |
@using UserControl = Sitecore.Web.UI.Controls.Common.UserControls.UserControl | |
@model Sitecore.Mvc.Presentation.RenderingModel | |
@{ | |
var rendering = Html.Sitecore().Controls().GetUserControl(Model.Rendering); | |
rendering.Class = "sc-ExperienceAnalyticsFilters"; | |
rendering.Requires.Script("client", "ExperienceAnalyticsFilters.js"); | |
rendering.Attributes.Add("data-bind", "visible: isVisible"); | |
rendering.HasNestedComponents = true; | |
} | |
@EmbedFilters(rendering) | |
@helper EmbedFilters(UserControl rendering) | |
{ | |
var renderingId = rendering.ControlId; | |
var helper = new RenderingHelper(Html, renderingId); | |
var selectedSubsiteName = "{Binding " + renderingId + "SubsiteFilter.SelectedSubsiteName}"; | |
var fromDate = DateUtil.ToServerTime(DateTime.UtcNow).AddMonths(-1).ToDateRangeFormat(); | |
var toDate = DateUtil.ToServerTime(DateTime.UtcNow).ToDateRangeFormat(); | |
var dateRangeText = string.Concat(fromDate, " - ", toDate); | |
helper.MakeBorder("ContentWrapper", renderingId + "BarWrapper", contentWrapper => | |
{ | |
helper.MakeToggleButton("DateRangeToggleButton", contentWrapper, dateRangeText, true); | |
helper.MakeToggleButton("FilterToggleButton", contentWrapper, selectedSubsiteName, true); | |
}); | |
// Rendering | |
<div @rendering.HtmlAttributes> | |
@Html.Sitecore().Controls().Rendering(Html, Globals.Bcl.Renderings.Containers.Border, renderingId + "BarWrapper", "", new { }) | |
@Html.Sitecore().Controls().Rendering(Html, Globals.Layouts.Renderings.DateRangeFilter, renderingId + "DateRangeFilter", "", new | |
{ | |
IsVisible = false | |
}) | |
@Html.Sitecore().Controls().Rendering(Html, Globals.Layouts.Renderings.SubsiteFilter, renderingId + "SubsiteFilter", "", new | |
{ | |
IsVisible = false | |
}) | |
</div> | |
} |
The JS logic file name is “ExperienceAnalyticsFilters.js” and will be located in the following path:
\Website\sitecore\shell\client\Applications\ExperienceAnalytics\Common\Layouts\Renderings\ExperienceAnalyticsFilters.js
Following is the content
of this file:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(["sitecore"], function (Sitecore) { | |
var App = Backbone.Model.extend({ | |
cookiePrefix: "scExperienceAnalytics_", | |
illegalCharacters: /[/\\&%#?]/i, | |
attributes: { | |
"dateRange": null, | |
"subsite": null | |
}, | |
getSubsite: function() { | |
var subsite = "all", | |
subsiteFromUrl = this.getSubsiteFromUrl(), | |
sessionSubsite = this.getSessionValue("Subsite"); | |
if (subsiteFromUrl) { | |
subsite = subsiteFromUrl; | |
} else if (sessionSubsite) { | |
subsite = sessionSubsite; | |
} | |
return subsite; | |
}, | |
textTemplate: function(template, data) { | |
if (template && data) { | |
_.each(data, function (value, variableKey) { | |
var regexp = new RegExp("\{\{\\s*" + variableKey + "\\s*\}\}", "g"); | |
template = template.replace(regexp, value); | |
}); | |
} | |
return template; | |
}, | |
resolveTemplatesVariables: function (templates, variables) { | |
_.each(templates, _.bind(function (template, key) { | |
templates[key] = this.textTemplate(template, variables); | |
}, this)); | |
return templates; | |
}, | |
setSubsite: function(value) { | |
value = value || "all"; | |
this.setSessionValue("Subsite", value); | |
this.updateUrl({ | |
subsite: value | |
}); | |
this.set("subsite", value); | |
}, | |
getSubsiteFromUrl: function() { | |
var hash = window.location.hash.substring(1), | |
params = Sitecore.Helpers.url.getQueryParameters(hash); | |
return params.subsite; | |
}, | |
convertDateFormat: function(date) { | |
return $.datepicker.formatDate("dd-mm-yy", $.datepicker.parseDate("d M ''y", date)); | |
}, | |
reConvertDateFormat: function(date) { | |
return $.datepicker.formatDate("d M ''y", $.datepicker.parseDate("dd-mm-yy", date)); | |
}, | |
updateDateRangeInSession: function(from, to) { | |
this.setSessionValue("FromDate", this.convertDateFormat(from)); | |
this.setSessionValue("ToDate", this.convertDateFormat(to)); | |
}, | |
updateDateRangeInUrl: function(from, to) { | |
var hashObject = { | |
dateFrom: this.convertDateFormat(from), | |
dateTo: this.convertDateFormat(to) | |
}; | |
this.updateUrl(hashObject); | |
}, | |
setDateRange: function (from, to, persist) { | |
if (persist) { | |
this.updateDateRangeInSession(from, to); | |
} | |
this.updateDateRangeInUrl(from, to); | |
this.set("dateRange", { | |
dateFrom: from, | |
dateTo: to | |
}); | |
}, | |
getDateRange: function() { | |
var dateRangeFromUrl = this.getDateRangeFromUrl(), | |
sessionFromDate = this.getSessionValue("FromDate"), | |
sessionToDate = this.getSessionValue("ToDate"), | |
dateObject = null; | |
if (dateRangeFromUrl.dateFrom && dateRangeFromUrl.dateTo) { | |
if (this.validateDate(dateRangeFromUrl.dateFrom) && this.validateDate(dateRangeFromUrl.dateTo)) { | |
dateObject = dateRangeFromUrl; | |
dateObject.dateFrom = this.reConvertDateFormat(dateObject.dateFrom); | |
dateObject.dateTo = this.reConvertDateFormat(dateObject.dateTo); | |
} else { | |
return undefined; | |
} | |
} else if (sessionFromDate && sessionToDate) { | |
dateObject = { | |
dateFrom: this.reConvertDateFormat(sessionFromDate), | |
dateTo: this.reConvertDateFormat(sessionToDate) | |
}; | |
} | |
return dateObject; | |
}, | |
validateDate: function(dateString) { | |
return dateString.match(/^\d{2}[\-]\d{2}[\-]\d{4}$/); | |
}, | |
getDateRangeFromUrl: function() { | |
var hash = window.location.hash.substring(1), | |
params = Sitecore.Helpers.url.getQueryParameters(hash); | |
return { | |
dateFrom: params.dateFrom, | |
dateTo: params.dateTo | |
}; | |
}, | |
updateUrl: function (hashObject) { | |
var params = this.removeEmptyParams(window.location.hash.substring(1)); | |
params = params.replace(new RegExp("^&"), ""); | |
params = (params.length > 0) ? "?" + params : ""; | |
params = Sitecore.Helpers.url.addQueryParameters(params, hashObject).replace("?", ""); | |
if (window.history.replaceState) { | |
window.history.replaceState({ state: null }, null, window.location.pathname + window.location.search + "#" + params); | |
} else { | |
window.location.hash = params; | |
} | |
}, | |
removeEmptyParams: function(url) { | |
var params = Sitecore.Helpers.url.getQueryParameters(url); | |
for (var p in params) { | |
if (params[p] === "") | |
url = url.replace(new RegExp("&?" + p + "="), ""); | |
} | |
return url; | |
}, | |
getCookie: function(name) { | |
var cookie = document.cookie, | |
cookieArray = cookie.split(";"); | |
for (var i = 0; i < cookieArray.length; i++) { | |
var cookieItem = cookieArray[i].trim().split("="); | |
if (cookieItem[0] === name) { | |
return cookieItem[1]; | |
} | |
} | |
return null; | |
}, | |
getSessionValue: function (name) { | |
return this.getCookie(this.cookiePrefix + name); | |
}, | |
// TODO Make method private and insure that it will be executed just once on the start of application | |
setGlobalAjaxSettings: function () { | |
var ajaxSettings = {}, | |
langFromCookies = this.getCookie("shell#lang"); | |
// Needed to improve translation mechanism on the server | |
if (langFromCookies) { | |
ajaxSettings.headers = { "Accept-Language": langFromCookies }; | |
} | |
$.ajaxSetup(ajaxSettings); | |
}, | |
setSessionValue: function(name, value) { | |
document.cookie = this.cookiePrefix + name + "=" + value + ";path=/;"; | |
}, | |
/** | |
* Return first invalid string which matches with validator | |
* @param {array|string} list - array of strings which should be validated | |
* @param {RegExp} validator - regexp to find invalid sub-strings or characters | |
* @return {string|null} returns first invalid sub-strings or character if found or null if invalid character is not found | |
*/ | |
findInvalidString: function (list, validator) { | |
if (typeof list === 'string') { | |
list = [list]; | |
} | |
var invalidString = _.find(list, function (str) { | |
return validator.test(str); | |
}); | |
if (invalidString) { | |
return invalidString.match(validator)[0]; | |
} else { | |
return null; | |
} | |
} | |
}); | |
var app = new App; | |
app.setGlobalAjaxSettings(); | |
return app; | |
}); |
·
All filters logic
In this component you will find all the JS code that handle
the different type of operations, like selecting subsite, picking date, setting
values of filters and clearing them.
The file name is “ExperienceAnalytics.js” and can be found
in the following path:
Website\sitecore\shell\client\Applications\ExperienceAnalytics\Common\Layouts\Renderings\Shared\ExperienceAnalytics.js
Following is the content of this file:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(["sitecore"], function (Sitecore) { | |
var App = Backbone.Model.extend({ | |
cookiePrefix: "scExperienceAnalytics_", | |
illegalCharacters: /[/\\&%#?]/i, | |
attributes: { | |
"dateRange": null, | |
"subsite": null | |
}, | |
getSubsite: function() { | |
var subsite = "all", | |
subsiteFromUrl = this.getSubsiteFromUrl(), | |
sessionSubsite = this.getSessionValue("Subsite"); | |
if (subsiteFromUrl) { | |
subsite = subsiteFromUrl; | |
} else if (sessionSubsite) { | |
subsite = sessionSubsite; | |
} | |
return subsite; | |
}, | |
textTemplate: function(template, data) { | |
if (template && data) { | |
_.each(data, function (value, variableKey) { | |
var regexp = new RegExp("\{\{\\s*" + variableKey + "\\s*\}\}", "g"); | |
template = template.replace(regexp, value); | |
}); | |
} | |
return template; | |
}, | |
resolveTemplatesVariables: function (templates, variables) { | |
_.each(templates, _.bind(function (template, key) { | |
templates[key] = this.textTemplate(template, variables); | |
}, this)); | |
return templates; | |
}, | |
setSubsite: function(value) { | |
value = value || "all"; | |
this.setSessionValue("Subsite", value); | |
this.updateUrl({ | |
subsite: value | |
}); | |
this.set("subsite", value); | |
}, | |
getSubsiteFromUrl: function() { | |
var hash = window.location.hash.substring(1), | |
params = Sitecore.Helpers.url.getQueryParameters(hash); | |
return params.subsite; | |
}, | |
convertDateFormat: function(date) { | |
return $.datepicker.formatDate("dd-mm-yy", $.datepicker.parseDate("d M ''y", date)); | |
}, | |
reConvertDateFormat: function(date) { | |
return $.datepicker.formatDate("d M ''y", $.datepicker.parseDate("dd-mm-yy", date)); | |
}, | |
updateDateRangeInSession: function(from, to) { | |
this.setSessionValue("FromDate", this.convertDateFormat(from)); | |
this.setSessionValue("ToDate", this.convertDateFormat(to)); | |
}, | |
updateDateRangeInUrl: function(from, to) { | |
var hashObject = { | |
dateFrom: this.convertDateFormat(from), | |
dateTo: this.convertDateFormat(to) | |
}; | |
this.updateUrl(hashObject); | |
}, | |
setDateRange: function (from, to, persist) { | |
if (persist) { | |
this.updateDateRangeInSession(from, to); | |
} | |
this.updateDateRangeInUrl(from, to); | |
this.set("dateRange", { | |
dateFrom: from, | |
dateTo: to | |
}); | |
}, | |
getDateRange: function() { | |
var dateRangeFromUrl = this.getDateRangeFromUrl(), | |
sessionFromDate = this.getSessionValue("FromDate"), | |
sessionToDate = this.getSessionValue("ToDate"), | |
dateObject = null; | |
if (dateRangeFromUrl.dateFrom && dateRangeFromUrl.dateTo) { | |
if (this.validateDate(dateRangeFromUrl.dateFrom) && this.validateDate(dateRangeFromUrl.dateTo)) { | |
dateObject = dateRangeFromUrl; | |
dateObject.dateFrom = this.reConvertDateFormat(dateObject.dateFrom); | |
dateObject.dateTo = this.reConvertDateFormat(dateObject.dateTo); | |
} else { | |
return undefined; | |
} | |
} else if (sessionFromDate && sessionToDate) { | |
dateObject = { | |
dateFrom: this.reConvertDateFormat(sessionFromDate), | |
dateTo: this.reConvertDateFormat(sessionToDate) | |
}; | |
} | |
return dateObject; | |
}, | |
validateDate: function(dateString) { | |
return dateString.match(/^\d{2}[\-]\d{2}[\-]\d{4}$/); | |
}, | |
getDateRangeFromUrl: function() { | |
var hash = window.location.hash.substring(1), | |
params = Sitecore.Helpers.url.getQueryParameters(hash); | |
return { | |
dateFrom: params.dateFrom, | |
dateTo: params.dateTo | |
}; | |
}, | |
updateUrl: function (hashObject) { | |
var params = this.removeEmptyParams(window.location.hash.substring(1)); | |
params = params.replace(new RegExp("^&"), ""); | |
params = (params.length > 0) ? "?" + params : ""; | |
params = Sitecore.Helpers.url.addQueryParameters(params, hashObject).replace("?", ""); | |
if (window.history.replaceState) { | |
window.history.replaceState({ state: null }, null, window.location.pathname + window.location.search + "#" + params); | |
} else { | |
window.location.hash = params; | |
} | |
}, | |
removeEmptyParams: function(url) { | |
var params = Sitecore.Helpers.url.getQueryParameters(url); | |
for (var p in params) { | |
if (params[p] === "") | |
url = url.replace(new RegExp("&?" + p + "="), ""); | |
} | |
return url; | |
}, | |
getCookie: function(name) { | |
var cookie = document.cookie, | |
cookieArray = cookie.split(";"); | |
for (var i = 0; i < cookieArray.length; i++) { | |
var cookieItem = cookieArray[i].trim().split("="); | |
if (cookieItem[0] === name) { | |
return cookieItem[1]; | |
} | |
} | |
return null; | |
}, | |
getSessionValue: function (name) { | |
return this.getCookie(this.cookiePrefix + name); | |
}, | |
// TODO Make method private and insure that it will be executed just once on the start of application | |
setGlobalAjaxSettings: function () { | |
var ajaxSettings = {}, | |
langFromCookies = this.getCookie("shell#lang"); | |
// Needed to improve translation mechanism on the server | |
if (langFromCookies) { | |
ajaxSettings.headers = { "Accept-Language": langFromCookies }; | |
} | |
$.ajaxSetup(ajaxSettings); | |
}, | |
setSessionValue: function(name, value) { | |
document.cookie = this.cookiePrefix + name + "=" + value + ";path=/;"; | |
}, | |
/** | |
* Return first invalid string which matches with validator | |
* @param {array|string} list - array of strings which should be validated | |
* @param {RegExp} validator - regexp to find invalid sub-strings or characters | |
* @return {string|null} returns first invalid sub-strings or character if found or null if invalid character is not found | |
*/ | |
findInvalidString: function (list, validator) { | |
if (typeof list === 'string') { | |
list = [list]; | |
} | |
var invalidString = _.find(list, function (str) { | |
return validator.test(str); | |
}); | |
if (invalidString) { | |
return invalidString.match(validator)[0]; | |
} else { | |
return null; | |
} | |
} | |
}); | |
var app = new App; | |
app.setGlobalAjaxSettings(); | |
return app; | |
}); |
·
Filter view and logic
For each filter we will find a
view and JS file that handle the different operation, as example if we want to
take the subsite filter as example, following are files related:
View file name is “SubsiteFilter.cshtml”
and can be found in the following path:
Website\sitecore\shell\client\Applications\ExperienceAnalytics\Common\Layouts\Renderings\SubsiteFilter.cshtml
Log JS file name is “SubsiteFilter.js”
and can be found in the following path:
Website\sitecore\shell\client\Applications\ExperienceAnalytics\Common\Layouts\Renderings\SubsiteFilter.js
This filter is actually a
rendering that can be found in sitecore as an item, this can be found in the
core database in the following path:
/sitecore/client/Applications/ExperienceAnalytics/Common/Layouts/Renderings/SubsiteFilter
There you can find all the rendering
used in experience analytics.
Did you figure it out yet? J
I believe you should have a clear idea of what I will explain next to extend experience analytics to add a new filter.
Custom Experience Analytics filters extending Steps?
- Create new rendering component for the new filter.
- Create the following files as a copy from the original:
- ExperienceAnalyticsFiltersExtended.js
- ExperienceAnalyticsFiltersExtended.cshtml
- ExperienceAnalyticsExtended.js
- Update the above to include your new rendering “filter”
Example?
I want to
add a new dropdown filter called “Event Type”, see the updates in the files
below, I highlighted most of the files that I updated:
Event
type filter files:
Event Type
view
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@using Newtonsoft.Json
@using Sitecore.ExperienceAnalytics.Client
@using Sitecore.Mvc
@using Sitecore.Web.UI.Controls.Common.Renderings
@using Sitecore.Web.UI.Controls.Common.Texts
@using Sitecore.Web.UI.Controls.Common.UserControls
@using UserControl = Sitecore.Web.UI.Controls.Common.UserControls.UserControl
@model Sitecore.Mvc.Presentation.RenderingModel
@{
var rendering = Html.Sitecore().Controls().GetUserControl(Model.Rendering);
rendering.Class = "sc-EventTypeFilter";
rendering.Requires.Script("client", "EventTypeFilter.js");
rendering.Attributes.Add("data-bind", "visible: isVisible");
rendering.HasNestedComponents = true;
var errorMessages = new
{
InvalidSubsite = "Error message"
};
rendering.Attributes.Add("data-sc-errortexts", JsonConvert.SerializeObject(errorMessages));
}
@EmbedEventTypeFilter(rendering)
@helper EmbedEventTypeFilter(UserControl rendering)
{
var renderingId = rendering.ControlId;
var helper = new RenderingHelper(Html, renderingId);
List<ComboBoxItem> options = new List<ComboBoxItem>();
try
{
ComboBoxItem oAll = new ComboBoxItem()
{
DisplayName = "All Types",
ItemId = "all"
};
ComboBoxItem oSingle = new ComboBoxItem()
{
DisplayName = "Single",
ItemId = "single"
};
ComboBoxItem oRecurring = new ComboBoxItem()
{
DisplayName = "Recurring",
ItemId = "recurring"
};
options.Add(oAll);
options.Add(oSingle);
options.Add(oRecurring);
}
catch (Exception exception)
{
ClientContainer.GetLogger().Error("An error occured while rendering sites filter. Exception: " + exception.ToString(), this);
}
helper.MakeBorder("ContentWrapper", renderingId + "DropDownWrapper", contentWrapper =>
{
helper.MakeRow("TextBorder", contentWrapper, true, textBorder => helper.MakeText("Text", textBorder, Globals.System.Texts.DefineCustomFilter, TextType.Title));
helper.MakeRow("ControlsRow", contentWrapper, true, controlsRow =>
{
helper.MakeComboBox("EventTypeComboBox", controlsRow, options ?? new List<ComboBoxItem>()
{
new ComboBoxItem()
});
helper.MakeButton("SubmitButton", controlsRow, Globals.System.Texts.Apply.Guid.ToString(), "Primary");
helper.MakeButton("ResetButton", controlsRow, Globals.System.Texts.RevertFiltersToStandard.Guid.ToString());
});
});
// Rendering
<div @rendering.HtmlAttributes>
@Html.Sitecore().Controls().Rendering(Html, Globals.Bcl.Renderings.Containers.Border, renderingId + "DropDownWrapper", "", new
{
UsePadding = true
})
</div>
}
Event type JS file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require.config({
paths: {
ExperienceAnalyticsExtended: "/sitecore/shell/client/Applications/ExperienceAnalytics/Common/Layouts/Renderings/Shared/ExperienceAnalyticsExtended",
experienceAnalyticsBase: "/sitecore/shell/client/Applications/ExperienceAnalytics/Common/Layouts/Renderings/Shared/experienceAnalyticsBase"
}
});
define(["sitecore", "ExperienceAnalyticsExtended", "experienceAnalyticsBase"], function (Sitecore, ExperienceAnalyticsExtended, ExperienceAnalyticsBase) {
Sitecore.Factories.createBaseComponent({
name: "EventTypeFilter",
base: "ExperienceAnalyticsBase",
selector: ".sc-EventTypeFilter",
attributes: Sitecore.Definitions.Views.ExperienceAnalyticsBase.prototype._scAttrs.concat([
{ name: "selectedEventTypeValue", defaultValue: null },
{ name: "selectedEventTypeName", defaultValue: null },
{ name: "errorTexts", value: "$el.data:sc-errortexts" }
]),
extendModel: {
selectedEventTypeValue: function (value) {
this.set("selectedEventTypeValue", value);
ExperienceAnalyticsExtended.setEventType(value);
}
},
initialize: function () {
this._super();
$(window).off("hashchange." + this.model.get("name"));
$(window).on("hashchange." + this.model.get("name"), _.bind(this.onHashChange, this));
},
afterRender: function () {
var appName = this.model.get("name"),
submitButton = this.app[appName + "SubmitButton"],
resetButton = this.app[appName + "ResetButton"],
EventTypeComboBox = this.app[appName + "EventTypeComboBox"];
submitButton.on("click", this.setSelectedEventType, this);
resetButton.on("click", this.resetSelectedEventType, this);
this.model.on("change:selectedEventTypeValue", function (model, value) {
EventTypeComboBox.set("selectedValue", value ? value : EventTypeComboBox.get("items")[0].itemId);
var displayFieldName = EventTypeComboBox.get("selectedItem") ?
EventTypeComboBox.viewModel.getDisplayFieldName(EventTypeComboBox.get("selectedItem")) :
null;
this.model.set("selectedEventTypeName", displayFieldName);
}, this);
this.setSelectedEventType(ExperienceAnalyticsExtended.getEventType());
},
resetSelectedEventType: function () {
this.setSelectedEventType("all");
this.closeToggleButtons();
},
setSelectedEventType: function (value) {
var appName = this.model.get("name"),
EventTypeComboBox = this.app[appName + "EventTypeComboBox"],
eventtype = value || EventTypeComboBox.get("selectedValue"),
items = EventTypeComboBox.get("items");
if (items.length === 1 && eventtype === "all") {
eventtype = items[0].itemId;
}
if (_.contains(_.pluck(items, 'itemId'), eventtype)) {
this.model.selectedEventTypeValue(eventtype);
} else {
this.showMessage("notification", this.model.get("errorTexts").InvalidSubsite, { WEBSITE: ExperienceAnalyticsExtended.getEventType() });
this.resetSelectedEventType();
}
this.closeToggleButtons();
},
onHashChange: function () {
var eventTypeFromUrl = ExperienceAnalyticsExtended.getEventTypeFromUrl();
if (this.model.get("selectedEventTypeValue") && this.model.get("selectedEventTypeValue") !== eventTypeFromUrl) {
this.model.set("selectedEventTypeValue", eventTypeFromUrl);
this.setSelectedEventType(ExperienceAnalyticsExtended.getEventType());
}
},
closeToggleButtons: function () {
var filtersModel = this.app[this.model.get("name").replace(this.model.componentName, "")];
if (filtersModel) {
filtersModel.viewModel.closeToggleButtons();
}
}
});
});
Container view and logic
Container view file:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@using Sitecore
@using Sitecore.ExperienceAnalytics.Client
@using Sitecore.ExperienceAnalytics.Core.Extensions
@using Sitecore.Mvc
@using Sitecore.Web.UI.Controls.Common.Renderings
@using Sitecore.Web.UI.Controls.Common.UserControls
@using Globals = Sitecore.ExperienceAnalytics.Client.Globals
@using UserControl = Sitecore.Web.UI.Controls.Common.UserControls.UserControl
@model Sitecore.Mvc.Presentation.RenderingModel
@{
var rendering = Html.Sitecore().Controls().GetUserControl(Model.Rendering);
rendering.Class = "sc-ExperienceAnalyticsFiltersExtended";
rendering.Requires.Script("client", "ExperienceAnalyticsFiltersExtended.js");
rendering.Attributes.Add("data-bind", "visible: isVisible");
rendering.HasNestedComponents = true;
}
@EmbedFilters(rendering)
@helper EmbedFilters(UserControl rendering)
{
var renderingId = rendering.ControlId;
var helper = new RenderingHelper(Html, renderingId);
var selectedSubsiteName = "{Binding " + renderingId + "SubsiteFilter.SelectedSubsiteName}";
var selectedEventType = "{Binding " + renderingId + "EventTypeFilter.SelectedEventTypeName}";
var fromDate = DateUtil.ToServerTime(DateTime.UtcNow).AddMonths(-1).ToDateRangeFormat();
var toDate = DateUtil.ToServerTime(DateTime.UtcNow).ToDateRangeFormat();
var dateRangeText = string.Concat(fromDate, " - ", toDate);
helper.MakeBorder("ContentWrapper", renderingId + "BarWrapper", contentWrapper =>
{
helper.MakeToggleButton("DateRangeToggleButton", contentWrapper, dateRangeText, true);
helper.MakeToggleButton("FilterToggleButton", contentWrapper, selectedSubsiteName, true);
helper.MakeToggleButton("EventTypeFilterToggleButton", contentWrapper, selectedEventType, true);
});
// Rendering
<div @rendering.HtmlAttributes>
@Html.Sitecore().Controls().Rendering(Html, Globals.Bcl.Renderings.Containers.Border, renderingId + "BarWrapper", "", new { })
@Html.Sitecore().Controls().Rendering(Html, "{208A1340-B125-46D7-9286-C4642F5B5DD4}", renderingId + "DateRangeFilter", "", new
{
IsVisible = false
})
@*Globals.Layouts.Renderings.SubsiteFilter*@
@Html.Sitecore().Controls().Rendering(Html, "{3F76EA63-6BAB-4AA7-87EB-8F4E46F524E6}", renderingId + "SubsiteFilter", "", new
{
IsVisible = false
})
@Html.Sitecore().Controls().Rendering(Html, "{55D1DA76-AC50-4476-BF34-5B968F232C2F}", renderingId + "EventTypeFilter", "", new
{
IsVisible = false
})
</div>
}
Container JS File:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require.config({
paths: {
ExperienceAnalyticsExtended: "/sitecore/shell/client/Applications/ExperienceAnalytics/Common/Layouts/Renderings/Shared/ExperienceAnalyticsExtended",
experienceAnalyticsBase: "/sitecore/shell/client/Applications/ExperienceAnalytics/Common/Layouts/Renderings/Shared/experienceAnalyticsBase"
}
});
define(["sitecore", "ExperienceAnalyticsExtended", "experienceAnalyticsBase"], function (Sitecore, ExperienceAnalyticsExtended) {
Sitecore.Factories.createBaseComponent({
name: "ExperienceAnalyticsFiltersExtended",
base: "ExperienceAnalyticsBase",
selector: ".sc-ExperienceAnalyticsFiltersExtended",
attributes: Sitecore.Definitions.Views.ExperienceAnalyticsBase.prototype._scAttrs.concat([
]),
events: {
"click .sc-togglebutton[data-sc-id*='DateRangeToggleButton']": "toggleComponents",
"click .sc-togglebutton[data-sc-id*='FilterToggleButton']": "toggleComponents",
"click .sc-togglebutton[data-sc-id*='EventTypeFilterToggleButton']": "toggleComponents"
},
initialize: function () {
var renderingId = this.model.get("name"),
dateRangeFilter = this.app[renderingId + "DateRangeFilter"],
dateRangeToggleButton = this.app[renderingId + "DateRangeToggleButton"],
subsiteFilter = this.app[renderingId + "SubsiteFilter"],
eventTypeFilter = this.app[renderingId + "EventTypeFilter"],
filterToggleButton = this.app[renderingId + "FilterToggleButton"],
eventTypeFilterToggleButton = this.app[renderingId + "EventTypeFilterToggleButton"],
subsiteComboBox = this.app[renderingId + "SubsiteFilterSubsiteComboBox"],
eventTypeComboBox = this.app[renderingId + "EventTypeFilterEventTypeComboBox"];
this.setFilterButtonState(filterToggleButton, subsiteComboBox);
this.setFilterButtonState(eventTypeFilterToggleButton, eventTypeComboBox);
this.bindComponentVisibility(filterToggleButton, subsiteFilter);
this.bindComponentVisibility(eventTypeFilterToggleButton, eventTypeFilter);
this.bindComponentVisibility(dateRangeToggleButton, dateRangeFilter);
ExperienceAnalyticsExtended.on("change:dateRange", function (m, dateRange) {
dateRangeToggleButton.set("text", dateRange.dateFrom + " - " + dateRange.dateTo);
});
ExperienceAnalyticsExtended.on("change:subsite", function (model, value) {
subsiteFilter.set("selectedSubsiteValue", value);
});
ExperienceAnalyticsExtended.on("change:eventType", function (model, value) {
eventTypeFilter.set("selectedEventTypeValue", value);
});
},
setFilterButtonState: function (toggleButton, comboBox) {
toggleButton.set("isEnabled", comboBox.get("items").length > 1);
},
bindComponentVisibility: function (button, component) {
button.on("change:isOpen", function () {
component.set("isVisible", this.get("isOpen"));
});
},
toggleComponents: function (event) {
var renderingId = this.model.get("name"),
buttonClickedId = $(event.currentTarget).attr("data-sc-id"),
buttons = ["DateRangeToggleButton", "FilterToggleButton", "EventTypeFilterToggleButton"];
for (var i = 0; i < buttons.length; i++) {
var button = this.app[renderingId + buttons[i]];
if (button.get("name") !== buttonClickedId) {
button.viewModel.close();
}
}
},
closeToggleButtons: function () {
var renderingId = this.model.get("name"),
dateRangeToggleButton = this.app[renderingId + "DateRangeToggleButton"],
filterToggleButton = this.app[renderingId + "FilterToggleButton"],
EventTypeFilterToggleButton = this.app[renderingId + "EventTypeFilterToggleButton"];
dateRangeToggleButton.viewModel.close();
filterToggleButton.viewModel.close();
EventTypeFilterToggleButton.viewModel.close();
}
});
});
All filters logic
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(["sitecore"], function (Sitecore) {
var App = Backbone.Model.extend({
cookiePrefix: "scExperienceAnalyticsExtended_",
illegalCharacters: /[/\\&%#?]/i,
attributes: {
"dateRange": null,
"subsite": null,
"eventtype": null
},
getSubsite: function() {
var subsite = "all",
subsiteFromUrl = this.getSubsiteFromUrl(),
sessionSubsite = this.getSessionValue("Subsite");
if (subsiteFromUrl) {
subsite = subsiteFromUrl;
} else if (sessionSubsite) {
subsite = sessionSubsite;
}
return subsite;
},
getEventType: function () {
var eventtype = "all",
eventtypeFromUrl = this.getEventTypeFromUrl(),
sessionEventType = this.getSessionValue("EventType");
if (eventtypeFromUrl) {
eventtype = eventtypeFromUrl;
} else if (sessionEventType) {
eventtype = sessionEventType;
}
return eventtype;
},
textTemplate: function(template, data) {
if (template && data) {
_.each(data, function (value, variableKey) {
var regexp = new RegExp("\{\{\\s*" + variableKey + "\\s*\}\}", "g");
template = template.replace(regexp, value);
});
}
return template;
},
resolveTemplatesVariables: function (templates, variables) {
_.each(templates, _.bind(function (template, key) {
templates[key] = this.textTemplate(template, variables);
}, this));
return templates;
},
setSubsite: function (value) {
value = value || "all";
this.setSessionValue("Subsite", value);
this.updateUrl({
subsite: value
});
this.set("subsite", value);
},
setEventType: function (value) {
value = value || "all";
this.setSessionValue("EventType", value);
this.updateUrl({
eventtype: value
});
this.set("eventtype", value);
},
getSubsiteFromUrl: function() {
var hash = window.location.hash.substring(1),
params = Sitecore.Helpers.url.getQueryParameters(hash);
return params.subsite;
},
getEventTypeFromUrl: function () {
var hash = window.location.hash.substring(1),
params = Sitecore.Helpers.url.getQueryParameters(hash);
return params.eventtype;
},
convertDateFormat: function(date) {
return $.datepicker.formatDate("dd-mm-yy", $.datepicker.parseDate("d M ''y", date));
},
reConvertDateFormat: function(date) {
return $.datepicker.formatDate("d M ''y", $.datepicker.parseDate("dd-mm-yy", date));
},
updateDateRangeInSession: function(from, to) {
this.setSessionValue("FromDate", this.convertDateFormat(from));
this.setSessionValue("ToDate", this.convertDateFormat(to));
},
updateDateRangeInUrl: function(from, to) {
var hashObject = {
dateFrom: this.convertDateFormat(from),
dateTo: this.convertDateFormat(to)
};
this.updateUrl(hashObject);
},
setDateRange: function (from, to, persist) {
if (persist) {
this.updateDateRangeInSession(from, to);
}
this.updateDateRangeInUrl(from, to);
this.set("dateRange", {
dateFrom: from,
dateTo: to
});
},
getDateRange: function() {
var dateRangeFromUrl = this.getDateRangeFromUrl(),
sessionFromDate = this.getSessionValue("FromDate"),
sessionToDate = this.getSessionValue("ToDate"),
dateObject = null;
if (dateRangeFromUrl.dateFrom && dateRangeFromUrl.dateTo) {
if (this.validateDate(dateRangeFromUrl.dateFrom) && this.validateDate(dateRangeFromUrl.dateTo)) {
dateObject = dateRangeFromUrl;
dateObject.dateFrom = this.reConvertDateFormat(dateObject.dateFrom);
dateObject.dateTo = this.reConvertDateFormat(dateObject.dateTo);
} else {
return undefined;
}
} else if (sessionFromDate && sessionToDate) {
dateObject = {
dateFrom: this.reConvertDateFormat(sessionFromDate),
dateTo: this.reConvertDateFormat(sessionToDate)
};
}
return dateObject;
},
validateDate: function(dateString) {
return dateString.match(/^\d{2}[\-]\d{2}[\-]\d{4}$/);
},
getDateRangeFromUrl: function() {
var hash = window.location.hash.substring(1),
params = Sitecore.Helpers.url.getQueryParameters(hash);
return {
dateFrom: params.dateFrom,
dateTo: params.dateTo
};
},
updateUrl: function (hashObject) {
var params = this.removeEmptyParams(window.location.hash.substring(1));
params = params.replace(new RegExp("^&"), "");
params = (params.length > 0) ? "?" + params : "";
params = Sitecore.Helpers.url.addQueryParameters(params, hashObject).replace("?", "");
if (window.history.replaceState) {
window.history.replaceState({ state: null }, null, window.location.pathname + window.location.search + "#" + params);
} else {
window.location.hash = params;
}
},
removeEmptyParams: function(url) {
var params = Sitecore.Helpers.url.getQueryParameters(url);
for (var p in params) {
if (params[p] === "")
url = url.replace(new RegExp("&?" + p + "="), "");
}
return url;
},
getCookie: function(name) {
var cookie = document.cookie,
cookieArray = cookie.split(";");
for (var i = 0; i < cookieArray.length; i++) {
var cookieItem = cookieArray[i].trim().split("=");
if (cookieItem[0] === name) {
return cookieItem[1];
}
}
return null;
},
getSessionValue: function (name) {
return this.getCookie(this.cookiePrefix + name);
},
// TODO Make method private and insure that it will be executed just once on the start of application
setGlobalAjaxSettings: function () {
var ajaxSettings = {},
langFromCookies = this.getCookie("shell#lang");
// Needed to improve translation mechanism on the server
if (langFromCookies) {
ajaxSettings.headers = { "Accept-Language": langFromCookies };
}
$.ajaxSetup(ajaxSettings);
},
setSessionValue: function(name, value) {
document.cookie = this.cookiePrefix + name + "=" + value + ";path=/;";
},
/**
* Return first invalid string which matches with validator
* @param {array|string} list - array of strings which should be validated
* @param {RegExp} validator - regexp to find invalid sub-strings or characters
* @return {string|null} returns first invalid sub-strings or character if found or null if invalid character is not found
*/
findInvalidString: function (list, validator) {
if (typeof list === 'string') {
list = [list];
}
var invalidString = _.find(list, function (str) {
return validator.test(str);
});
if (invalidString) {
return invalidString.match(validator)[0];
} else {
return null;
}
}
});
var app = new App;
app.setGlobalAjaxSettings();
return app;
});
Hope the above gave a clear idea of how you can extend
sitecore experience analytics filters.
3 comments:
Thank you, it was helpful! One piece of information that is not clear to me - when the filter is set in place, where does the application respond to update the graph to make use of the new filter?
I have implemented this in my current project, however, it is not working(Filter is not getting appeared on Sitecore Analytics Reports Dashboard page). I have followed all above mentioned steps, but no luck. Can you please explain it more with actual screen shot of Sitecore Analytics Reports Dashboard.
Thanks for sharing this.,
Leanpitch provides online training in CSPO during this lockdown period everyone can use it wisely.
Join Leanpitch 2 Days CSPO Certification Workshop in different cities.
Product owner certification
Post a Comment