Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/CoreHome/CoreHome.php1
-rw-r--r--plugins/CoreHome/javascripts/broadcast.js3
-rw-r--r--plugins/CoreHome/javascripts/popover.js1
-rw-r--r--plugins/CoreHome/javascripts/uiControl.js75
-rwxr-xr-xplugins/Dashboard/javascripts/dashboardWidget.js1
-rw-r--r--plugins/Live/javascripts/visitorProfile.js12
-rw-r--r--plugins/UserCountryMap/Controller.php67
-rw-r--r--plugins/UserCountryMap/javascripts/realtime-map.js116
-rw-r--r--plugins/UserCountryMap/stylesheets/realtime-map.less4
-rw-r--r--plugins/UserCountryMap/templates/realtimeMap.twig61
10 files changed, 236 insertions, 105 deletions
diff --git a/plugins/CoreHome/CoreHome.php b/plugins/CoreHome/CoreHome.php
index 64fe4b56dd..5ed00a718a 100644
--- a/plugins/CoreHome/CoreHome.php
+++ b/plugins/CoreHome/CoreHome.php
@@ -72,6 +72,7 @@ class CoreHome extends \Piwik\Plugin
$jsFiles[] = "plugins/Zeitgeist/javascripts/piwikHelper.js";
$jsFiles[] = "plugins/Zeitgeist/javascripts/ajaxHelper.js";
$jsFiles[] = "plugins/CoreHome/javascripts/require.js";
+ $jsFiles[] = "plugins/CoreHome/javascripts/uiControl.js";
$jsFiles[] = "plugins/CoreHome/javascripts/dataTable.js";
$jsFiles[] = "plugins/CoreHome/javascripts/dataTable_rowactions.js";
$jsFiles[] = "plugins/CoreHome/javascripts/popover.js";
diff --git a/plugins/CoreHome/javascripts/broadcast.js b/plugins/CoreHome/javascripts/broadcast.js
index fe30534120..19aeb74bbc 100644
--- a/plugins/CoreHome/javascripts/broadcast.js
+++ b/plugins/CoreHome/javascripts/broadcast.js
@@ -130,6 +130,9 @@ var broadcast = {
// make sure the "Widgets & Dashboard" is deleted on reload
$('#dashboardSettings').remove();
$('#dashboardWidgetsArea').dashboard('destroy');
+
+ // remove unused controls
+ require('piwik/UI').UIControl.cleanupUnusedControls();
}
}
diff --git a/plugins/CoreHome/javascripts/popover.js b/plugins/CoreHome/javascripts/popover.js
index e3ff6d9326..22b23eab38 100644
--- a/plugins/CoreHome/javascripts/popover.js
+++ b/plugins/CoreHome/javascripts/popover.js
@@ -45,6 +45,7 @@ var Piwik_Popover = (function () {
$('.ui-widget-overlay').off('click.popover');
isOpen = false;
broadcast.propagateNewPopoverParameter(false);
+ require('piwik/UI').UIControl.cleanupUnusedControls();
if (typeof closeCallback == 'function') {
closeCallback();
closeCallback = false;
diff --git a/plugins/CoreHome/javascripts/uiControl.js b/plugins/CoreHome/javascripts/uiControl.js
new file mode 100644
index 0000000000..58d9662acb
--- /dev/null
+++ b/plugins/CoreHome/javascripts/uiControl.js
@@ -0,0 +1,75 @@
+/**
+ * Piwik - Web Analytics
+ *
+ * Visitor profile popup control.
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+(function ($, require) {
+
+ var exports = require('piwik/UI');
+
+ /**
+ * Base type for Piwik UI controls. Provides functionality that all controls need (such as
+ * cleanup on destruction).
+ *
+ * @param {Element} element The root element of the control.
+ */
+ var UIControl = function (element) {
+ this._controlIndex = UIControl._controls.length;
+ UIControl._controls.push(this);
+
+ var $element = this.$element = $(element);
+ $element.data('uiControlObject', this);
+ };
+
+ /**
+ * Contains all active control instances.
+ */
+ UIControl._controls = [];
+
+ /**
+ * Utility method that will clean up all piwik UI controls whose elements are not attached
+ * to the DOM.
+ *
+ * TODO: instead of having other pieces of the UI manually calling cleanupUnusedControls,
+ * MutationObservers should be called
+ */
+ UIControl.cleanupUnusedControls = function () {
+ var controls = UIControl._controls;
+
+ for (var i = 0; i != controls.length; ++i) {
+ var control = controls[i];
+ if (control.$element
+ && !$.contains(document.documentElement, control.$element[0])
+ ) {
+ controls[i] = null;
+ control._destroy();
+
+ if (!control._baseDestroyCalled) {
+ throw new Error("Error: " + control.constructor.name + "'s destroy method does not call " +
+ "UIControl.destroy. You may have a memory leak.");
+ }
+ }
+ }
+ };
+
+ UIControl.prototype = {
+
+ /**
+ * Perform cleanup. Called when the control has been removed from the DOM. Derived
+ * classes should overload this function to perform their own cleanup.
+ */
+ _destroy: function () {
+ this.$element.removeData('uiControlObject');
+ delete this.$element;
+
+ this._baseDestroyCalled = true;
+ },
+ };
+
+ exports.UIControl = UIControl;
+
+})(jQuery, require); \ No newline at end of file
diff --git a/plugins/Dashboard/javascripts/dashboardWidget.js b/plugins/Dashboard/javascripts/dashboardWidget.js
index ccbf68ba9c..a59ed325c4 100755
--- a/plugins/Dashboard/javascripts/dashboardWidget.js
+++ b/plugins/Dashboard/javascripts/dashboardWidget.js
@@ -68,6 +68,7 @@
}
$('*', this.element).off('.dashboardWidget'); // unbind all events
$('.widgetContent', this.element).trigger('widget:destroy');
+ require('piwik/UI').UIControl.cleanupUnusedControls();
return this;
},
diff --git a/plugins/Live/javascripts/visitorProfile.js b/plugins/Live/javascripts/visitorProfile.js
index 5b85211b0c..112f2c0781 100644
--- a/plugins/Live/javascripts/visitorProfile.js
+++ b/plugins/Live/javascripts/visitorProfile.js
@@ -10,7 +10,8 @@
(function ($, require) {
var piwik = require('piwik'),
- exports = require('piwik/UI');
+ exports = require('piwik/UI'),
+ UIControl = exports.UIControl;
/**
* Sets up and handles events for the visitor profile popup.
@@ -20,7 +21,7 @@
* @constructor
*/
var VisitorProfileControl = function (element) {
- this.$element = $(element).focus();
+ UIControl.call(this, element);
this._setupControl();
this._bindEventCallbacks();
};
@@ -53,9 +54,12 @@
Piwik_Popover.createPopupAndLoadUrl(url, '', 'visitor-profile-popup');
};
- VisitorProfileControl.prototype = {
+ $.extend(VisitorProfileControl.prototype, UIControl.prototype, {
_setupControl: function () {
+ // focus the popup so it will accept key events
+ this.$element.focus();
+
// highlight the first visit
$('.visitor-profile-visits>li:first-child', this.$element).addClass('visitor-profile-current-visit');
},
@@ -232,7 +236,7 @@
_inWidget: function () {
return !! this.$element.closest('.widget').length;
}
- };
+ });
exports.VisitorProfileControl = VisitorProfileControl;
diff --git a/plugins/UserCountryMap/Controller.php b/plugins/UserCountryMap/Controller.php
index 539e3dadb0..bf0dffd95d 100644
--- a/plugins/UserCountryMap/Controller.php
+++ b/plugins/UserCountryMap/Controller.php
@@ -131,40 +131,50 @@ class Controller extends \Piwik\Controller
$view->metrics = $this->getMetrics($idSite, 'range', self::REAL_TIME_WINDOW, $token_auth);
$view->defaultMetric = 'nb_visits';
- $view->liveRefreshAfterMs = (int)Config::getInstance()->General['live_widget_refresh_after_seconds'] * 1000;
+ $liveRefreshAfterMs = (int)Config::getInstance()->General['live_widget_refresh_after_seconds'] * 1000;
$goals = API::getInstance()->getGoals($idSite);
$site = new Site($idSite);
- $view->hasGoals = !empty($goals) || $site->isEcommerceEnabled() ? 'true' : 'false';
+ $hasGoals = !empty($goals) || $site->isEcommerceEnabled();
// maximum number of visits to be displayed in the map
- $view->maxVisits = Common::getRequestVar('format_limit', 100, 'int');
+ $maxVisits = Common::getRequestVar('format_limit', 100, 'int');
// some translations
- $view->localeJSON = json_encode(array(
- 'nb_actions' => Piwik_Translate('VisitsSummary_NbActionsDescription'),
- 'local_time' => Piwik_Translate('VisitTime_ColumnLocalTime'),
- 'from' => Piwik_Translate('General_FromReferrer'),
- 'seconds' => Piwik_Translate('UserCountryMap_Seconds'),
- 'seconds_ago' => Piwik_Translate('UserCountryMap_SecondsAgo'),
- 'minutes' => Piwik_Translate('UserCountryMap_Minutes'),
- 'minutes_ago' => Piwik_Translate('UserCountryMap_MinutesAgo'),
- 'hours' => Piwik_Translate('UserCountryMap_Hours'),
- 'hours_ago' => Piwik_Translate('UserCountryMap_HoursAgo'),
- 'days_ago' => Piwik_Translate('UserCountryMap_DaysAgo'),
- 'actions' => Piwik_Translate('VisitsSummary_NbPageviewsDescription'),
- 'searches' => Piwik_Translate('UserCountryMap_Searches'),
- 'goal_conversions' => Piwik_Translate('UserCountryMap_GoalConversions'),
- ));
+ $locale = array(
+ 'nb_actions' => Piwik_Translate('VisitsSummary_NbActionsDescription'),
+ 'local_time' => Piwik_Translate('VisitTime_ColumnLocalTime'),
+ 'from' => Piwik_Translate('General_FromReferrer'),
+ 'seconds' => Piwik_Translate('UserCountryMap_Seconds'),
+ 'seconds_ago' => Piwik_Translate('UserCountryMap_SecondsAgo'),
+ 'minutes' => Piwik_Translate('UserCountryMap_Minutes'),
+ 'minutes_ago' => Piwik_Translate('UserCountryMap_MinutesAgo'),
+ 'hours' => Piwik_Translate('UserCountryMap_Hours'),
+ 'hours_ago' => Piwik_Translate('UserCountryMap_HoursAgo'),
+ 'days_ago' => Piwik_Translate('UserCountryMap_DaysAgo'),
+ 'actions' => Piwik_Translate('VisitsSummary_NbPageviewsDescription'),
+ 'searches' => Piwik_Translate('UserCountryMap_Searches'),
+ 'goal_conversions' => Piwik_Translate('UserCountryMap_GoalConversions'),
+ );
$segment = $segmentOverride ?: Request::getRawSegmentFromRequest() ?: '';
- $view->reqParamsJSON = $this->getEnrichedRequest(array(
- 'period' => 'range',
- 'idSite' => $idSite,
- 'date' => self::REAL_TIME_WINDOW,
- 'segment' => $segment,
- 'token_auth' => $token_auth,
- ));
+ $reqParams = $this->getEnrichedRequest(array(
+ 'period' => 'range',
+ 'idSite' => $idSite,
+ 'date' => self::REAL_TIME_WINDOW,
+ 'segment' => $segment,
+ 'token_auth' => $token_auth,
+ ), $encode = false);
+
+ $view->config = array(
+ 'metrics' => array(),
+ 'svgBasePath' => $view->piwikUrl . 'plugins/UserCountryMap/svg/',
+ 'liveRefreshAfterMs' => $liveRefreshAfterMs,
+ '_' => $locale,
+ 'reqParams' => $reqParams,
+ 'siteHasGoals' => $hasGoals,
+ 'maxVisits' => $maxVisits
+ );
if ($fetch) {
return $view->render();
@@ -173,7 +183,7 @@ class Controller extends \Piwik\Controller
}
}
- private function getEnrichedRequest($params)
+ private function getEnrichedRequest($params, $encode = true)
{
$params['format'] = 'json';
$params['showRawMetrics'] = 1;
@@ -184,7 +194,10 @@ class Controller extends \Piwik\Controller
}
}
- return Common::json_encode($params);
+ if ($encode) {
+ $params = Common::json_encode($params);
+ }
+ return $params;
}
private function checkUserCountryPluginEnabled()
diff --git a/plugins/UserCountryMap/javascripts/realtime-map.js b/plugins/UserCountryMap/javascripts/realtime-map.js
index c851485a6b..e4e4eef09f 100644
--- a/plugins/UserCountryMap/javascripts/realtime-map.js
+++ b/plugins/UserCountryMap/javascripts/realtime-map.js
@@ -10,13 +10,65 @@
(function () {
- var RealtimeMap = window.UserCountryMap.RealtimeMap = function (config, theWidget) {
- this.config = config;
- this.theWidget = theWidget || false;
+ var UIControl = require('piwik/UI').UIControl;
+
+ var RealtimeMap = window.UserCountryMap.RealtimeMap = function (element) {
+ UIControl.call(this, element);
+ this._init();
this.run();
};
- $.extend(RealtimeMap.prototype, {
+ RealtimeMap.initElements = function () {
+ $('.RealTimeMap').each(function () {
+ if (!$(this).attr('data-inited')) {
+ var control = new RealtimeMap(this);
+
+ $(this).data('uiControlObject', control);
+ $(this).attr('data-inited', 1);
+ }
+ });
+ };
+
+ $.extend(RealtimeMap.prototype, UIControl.prototype, {
+
+ _init: function () {
+ var $element = this.$element;
+
+ this.config = JSON.parse($element.attr('data-config'));
+
+ // If the map is loaded from the menu, do a few tweaks to clean up the display
+ if ($element.attr('data-standalone') == 1) {
+ this._initStandaloneMap();
+ }
+
+ // handle widgetry
+ if ($('#dashboardWidgetsArea').length) {
+ var $widgetContent = $element.closest('.widgetContent');
+
+ var self = this;
+ $widgetContent.on('widget:maximise', function () {
+ self.resize();
+ }).on('widget:minimise', function () {
+ self.resize();
+ });
+ }
+
+ // set unique ID for kartograph map div
+ this.uniqueId = 'RealTimeMap_map-' + this._controlIndex;
+ $('.RealTimeMap_map', $element).attr('id', this.uniqueId);
+ },
+
+ _initStandaloneMap: function () {
+ $('.top_controls').hide();
+ $('.nav').on('piwikSwitchPage', function (event, item) {
+ var clickedMenuIsNotMap = ($(item).text() != "{{ 'UserCountryMap_RealTimeMap'|translate|e('js') }}");
+ if (clickedMenuIsNotMap) {
+ $('.top_controls').show();
+ }
+ });
+ $('.realTimeMap_overlay').css('top', '0px');
+ $('.realTimeMap_datetime').css('top', '20px');
+ },
run: function () {
var debug = 0;
@@ -24,8 +76,8 @@
var self = this,
config = self.config,
_ = config._,
- map = self.map = Kartograph.map('#RealTimeMap_map'),
- main = $('#RealTimeMap_container'),
+ map = self.map = Kartograph.map('#' + this.uniqueId),
+ main = $('.RealTimeMap_container', this.$element),
worldTotalVisits = 0,
maxVisits = config.maxVisits || 100,
width = main.width(),
@@ -75,7 +127,7 @@
'visitLocalTime', 'city', 'country', 'referrerType', 'referrerName',
'referrerTypeName', 'browserIcon', 'operatingSystemIcon',
'countryFlag', 'idVisit', 'actionDetails', 'continentCode',
- 'actions', 'searches', 'goalConversions'].join(','),
+ 'actions', 'searches', 'goalConversions', 'visitorId'].join(','),
minTimestamp: firstRun ? -1 : lastTimestamp
});
}
@@ -247,6 +299,25 @@
}
+ // default click behavior. if a visit is clicked, the visitor profile is launched,
+ // otherwise zoom in or out.
+ // TODO: visitor profile launching logic should probably be contained in
+ // visitorProfile.js. not sure how to do that, though...
+ this.$element.on('mapClick', function (e, visit, mapPath) {
+ var VisitorProfileControl = require('piwik/UI').VisitorProfileControl;
+ if (visit
+ && VisitorProfileControl
+ && !self.$element.closest('.visitor-profile').length
+ ) {
+ VisitorProfileControl.showPopover(visit.visitorId);
+ } else {
+ var cont = UserCountryMap.cont2cont[mapPath.data.continentCode];
+ if (cont && cont != currentMap) {
+ updateMap(cont);
+ }
+ }
+ });
+
/*
* this function requests new data from Live.getLastVisitsDetails
* and updates the symbols on the map. Then, it sets a timeout
@@ -259,6 +330,11 @@
* this is called after new visit reports came in
*/
function gotNewReport(report) {
+ // if the map has been destroyed, do nothing
+ if (!self.map) {
+ return;
+ }
+
// successful request, so set timeout for next API call
nextReqTimer = setTimeout(refreshVisits, config.liveRefreshAfterMs);
@@ -290,12 +366,9 @@
tooltip: visitTooltip,
mouseenter: highlightVisit,
mouseleave: unhighlightVisit,
- click: function (r, s, evt) {
+ click: function (visit, mapPath, evt) {
evt.stopPropagation();
- var cont = UserCountryMap.cont2cont[s.data.continentCode];
- if (cont && cont != currentMap) {
- updateMap(cont);
- }
+ self.$element.trigger('mapClick', [visit, mapPath]);
}
});
@@ -387,7 +460,7 @@
* the zoom behaviour is initialized.
*/
function initMap() {
- $('#widgetRealTimeMapliveMap .loadingPiwik, #RealTimeMap .loadingPiwik').hide();
+ $('#widgetRealTimeMapliveMap .loadingPiwik, .RealTimeMap .loadingPiwik').hide();
map.addLayer('countries', {
styles: {
fill: colorTheme[currentTheme].fill,
@@ -440,7 +513,7 @@
updateMap(location.hash && (location.hash == '#world' || location.hash.match(/^#[A-Z][A-Z]$/)) ? location.hash.substr(1) : 'world'); // TODO: restore last state
// clicking on map background zooms out
- $('#RealTimeMap_map').off('click').click(function () {
+ $('.RealTimeMap_map', this.$element).off('click').click(function () {
if (currentMap != 'world') updateMap('world');
});
@@ -456,7 +529,7 @@
}
function switchTheme() {
- $('#RealTimeMap').css({ background: colorTheme[currentTheme].bg });
+ self.$element.css({ background: colorTheme[currentTheme].bg });
if (isFullscreenWidget) {
$('body').css({ background: colorTheme[currentTheme].bg });
$('.widget').css({ 'border-width': 1 });
@@ -489,7 +562,7 @@
}
// setup automatic tooltip updates
- setInterval(function () {
+ this._tooltipUpdateInterval = setInterval(function () {
$('.qtip .rel-time').each(function (i, el) {
el = $(el);
var ds = new Date().getTime() / 1000 - el.data('actiontime');
@@ -523,11 +596,18 @@
else $('.tableIcon span').show();
},
- destroy: function () {
+ _destroy: function () {
+ UIControl.prototype._destroy.call(this);
+
+ if (this._tooltipUpdateInterval) {
+ clearInterval(this._tooltipUpdateInterval);
+ }
+
this.map.clear();
$(this.map.container).html('');
+ delete this.map;
}
});
-}());
+}()); \ No newline at end of file
diff --git a/plugins/UserCountryMap/stylesheets/realtime-map.less b/plugins/UserCountryMap/stylesheets/realtime-map.less
index c9d3aa47b0..2e1e57edaa 100644
--- a/plugins/UserCountryMap/stylesheets/realtime-map.less
+++ b/plugins/UserCountryMap/stylesheets/realtime-map.less
@@ -13,7 +13,7 @@
filter: alpha(opacity=3);
}
-#RealTimeMap-black {
+.RealTimeMap-black {
position: absolute;
right: 0;
left: 0;
@@ -23,7 +23,7 @@
background: #D5D3C8;
}
-#RealTimeMap .loadingPiwik {
+.RealTimeMap .loadingPiwik {
position: absolute !important;
top: 42% !important;
right: 10px !important;
diff --git a/plugins/UserCountryMap/templates/realtimeMap.twig b/plugins/UserCountryMap/templates/realtimeMap.twig
index dbc0f96c5d..8e7d90e51a 100644
--- a/plugins/UserCountryMap/templates/realtimeMap.twig
+++ b/plugins/UserCountryMap/templates/realtimeMap.twig
@@ -1,7 +1,9 @@
-<div id="RealTimeMap" style="position:relative; overflow:hidden;">
+<div class="RealTimeMap" style="position:relative; overflow:hidden;"
+ data-standalone="{{ mapIsStandaloneNotWidget|default(0) }}"
+ data-config="{{ config|json_encode }}">
- <div id="RealTimeMap_container">
- <div id="RealTimeMap_map" style="overflow:hidden;"></div>
+ <div class="RealTimeMap_container">
+ <div class="RealTimeMap_map" style="overflow:hidden;"></div>
<div class="realTimeMap_overlay">
<span class="showing_visits_of" style="display:none;">{{ 'UserCountryMap_ShowingVisits'|translate }}
<span class="realTimeMap_timeSpan" style="font-weight:bold;"></span>
@@ -12,60 +14,11 @@
</div>
<div class="realTimeMap_overlay realTimeMap_datetime"></div>
</div>
- <div id="RealTimeMap_meta">
+ <div class="RealTimeMap_meta">
<span class="loadingPiwik">
<img src="{{ piwikUrl }}plugins/Zeitgeist/images/loading-blue.gif"> {{ 'General_LoadingData'|translate }}...
</span>
</div>
</div>
-
-<!-- configure some piwik vars -->
-<script type="text/javascript">
- {# If the map is loaded from the menu, do a few tweaks to clean up the display #}
- {% if mapIsStandaloneNotWidget %}
- function initStandaloneMap() {
- $('.top_controls').hide();
- $('.nav').on('piwikSwitchPage', function (event, item) {
- var clickedMenuIsNotMap = ($(item).text() != "{{ 'UserCountryMap_RealTimeMap'|translate|e('js') }}");
- if (clickedMenuIsNotMap) {
- $('.top_controls').show();
- }
- });
- $('.realTimeMap_overlay').css('top', '0px');
- $('.realTimeMap_datetime').css('top', '20px');
- }
-
- initStandaloneMap();
- {% endif %}
-
- var config = { metrics: {} };
-
- config.svgBasePath = "{{ piwikUrl }}plugins/UserCountryMap/svg/";
- config.liveRefreshAfterMs = {{ liveRefreshAfterMs }};
-
- config._ = JSON.parse('{{ localeJSON|e('js') }}');
- config.reqParams = JSON.parse('{{ reqParamsJSON|e('js') }}');
- config.siteHasGoals = {{ hasGoals }};
- config.maxVisits = {{ maxVisits }};
-
- var realtimeMap;
-
- if ($('#dashboardWidgetsArea').length) {
- // dashboard mode
- var $widgetContent = $('#RealTimeMap').parents('.widgetContent');
-
- $widgetContent.on('widget:create',function (evt, widget) {
- realtimeMap = new UserCountryMap.RealtimeMap(config, widget);
- }).on('widget:maximise',function (evt) {
- realtimeMap.resize();
- }).on('widget:minimise',function (evt) {
- realtimeMap.resize();
- }).on('widget:destroy', function (evt) {
- realtimeMap.destroy();
- });
- } else {
- // stand-alone mode
- realtimeMap = new UserCountryMap.RealtimeMap(config);
- }
-</script>
+<script type="text/javascript">UserCountryMap.RealtimeMap.initElements();</script>