questlab/www/analytics/plugins/CoreVisualizations/javascripts/seriesPicker.js
coderkun 046a724272 merge
2015-04-27 16:42:05 +02:00

366 lines
13 KiB
JavaScript

/**
* Piwik - Web Analytics
*
* Series Picker control addition for DataTable visualizations.
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($, doc, require) {
/**
* This class creates and manages the Series Picker for certain DataTable visualizations.
*
* To add the series picker to your DataTable visualization, create a SeriesPicker instance
* and after your visualization has been rendered, call the 'init' method.
*
* To customize SeriesPicker placement and behavior, you can bind callbacks to the following
* events before calling 'init':
*
* 'placeSeriesPicker': Triggered after the DOM element for the series picker link is created.
* You must use this event to add the link to the dataTable. YOu can also
* use this event to position the link however you want.
*
* Callback Signature: function () {}
*
* 'seriesPicked': Triggered when the user selects one or more columns/rows.
*
* Callback Signature: function (eventInfo, columns, rows) {}
*
* Events are triggered via jQuery, so you bind callbacks to them like this:
*
* var picker = new SeriesPicker(dataTable);
* $(picker).bind('placeSeriesPicker', function () {
* $(this.domElem).doSomething(...);
* });
*
* @param {dataTable} dataTable The dataTable instance to add a series picker to.
* @constructor
*/
var SeriesPicker = function (dataTable) {
this.domElem = null;
this.dataTableId = dataTable.workingDivId;
// the columns that can be selected
this.selectableColumns = dataTable.props.selectable_columns;
// the rows that can be selected
this.selectableRows = dataTable.props.selectable_rows;
// render the picker?
this.show = !! dataTable.props.show_series_picker
&& (this.selectableColumns || this.selectableRows);
// can multiple rows we selected?
this.multiSelect = !! dataTable.props.allow_multi_select_series_picker;
// language strings
this.lang =
{
metricsToPlot: _pk_translate('General_MetricsToPlot'),
metricToPlot: _pk_translate('General_MetricToPlot'),
recordsToPlot: _pk_translate('General_RecordsToPlot')
};
this._pickerState = null;
this._pickerPopover = null;
};
SeriesPicker.prototype = {
/**
* Initializes the series picker by creating the element. Must be called when
* the datatable the picker is being attached to is ready for it to be drawn.
*/
init: function () {
if (!this.show) {
return;
}
var self = this;
// initialize dom element
this.domElem = $(doc.createElement('a'))
.addClass('jqplot-seriespicker')
.attr('href', '#')
.html('+')
// set opacity on 'hide'
.on('hide', function () {
$(this).css('opacity', .55);
})
.trigger('hide')
// show picker on hover
.hover(
function () {
var $this = $(this);
$this.css('opacity', 1);
if (!$this.hasClass('open')) {
$this.addClass('open');
self._showPicker();
}
},
function () {
// do nothing on mouseout because using this event doesn't work properly.
// instead, the timeout check beneath is used (_bindCheckPickerLeave()).
}
)
.click(function (e) {
e.preventDefault();
return false;
});
$(this).trigger('placeSeriesPicker');
},
/**
* Returns the translation of a metric that can be selected.
*
* @param {String} metric The name of the metric, ie, 'nb_visits' or 'nb_actions'.
* @return {String} The metric translation. If one cannot be found, the metric itself
* is returned.
*/
getMetricTranslation: function (metric) {
for (var i = 0; i != this.selectableColumns.length; ++i) {
if (this.selectableColumns[i].column == metric) {
return this.selectableColumns[i].translation;
}
}
return metric;
},
/**
* Creates the popover DOM element, binds event handlers to it, and then displays it.
*/
_showPicker: function () {
this._pickerState = {manipulated: false};
this._pickerPopover = this._createPopover();
this._positionPopover();
// hide and replot on mouse leave
var self = this;
this._bindCheckPickerLeave(function () {
var replot = self._pickerState.manipulated;
self._hidePicker(replot);
});
},
/**
* Creates a checkbox and related elements for a selectable column or selectable row.
*/
_createPickerPopupItem: function (config, type) {
var self = this;
if (type == 'column') {
var columnName = config.column,
columnLabel = config.translation,
cssClass = 'pickColumn';
} else {
var columnName = config.matcher,
columnLabel = config.label,
cssClass = 'pickRow';
}
var checkbox = $(document.createElement('input')).addClass('select')
.attr('type', this.multiSelect ? 'checkbox' : 'radio');
if (config.displayed && !(!this.multiSelect && this._pickerState.oneChecked)) {
checkbox.prop('checked', true);
this._pickerState.oneChecked = true;
}
// if we are rendering a column, remember the column name
// if it's a row, remember the string that can be used to match the row
checkbox.data('name', columnName);
var el = $(document.createElement('p'))
.append(checkbox)
.append($('<label/>').text(columnLabel))
.addClass(cssClass);
var replot = function () {
self._unbindPickerLeaveCheck();
self._hidePicker(true);
};
var checkBox = function (box) {
if (!self.multiSelect) {
self._pickerPopover.find('input.select:not(.current)').prop('checked', false);
}
box.prop('checked', true);
replot();
};
el.click(function (e) {
self._pickerState.manipulated = true;
var box = $(this).find('input.select');
if (!$(e.target).is('input.select')) {
if (box.is(':checked')) {
box.prop('checked', false);
} else {
checkBox(box);
}
} else {
if (box.is(':checked')) {
checkBox(box);
}
}
});
return el;
},
/**
* Binds an event to document that checks if the user has left the series picker.
*/
_bindCheckPickerLeave: function (onLeaveCallback) {
var offset = this._pickerPopover.offset();
var minX = offset.left;
var minY = offset.top;
var maxX = minX + this._pickerPopover.outerWidth();
var maxY = minY + this._pickerPopover.outerHeight();
var self = this;
this._onMouseMove = function (e) {
var currentX = e.pageX, currentY = e.pageY;
if (currentX < minX || currentX > maxX
|| currentY < minY || currentY > maxY
) {
self._unbindPickerLeaveCheck();
onLeaveCallback();
}
};
$(doc).mousemove(this._onMouseMove);
},
/**
* Unbinds the callback that was bound in _bindCheckPickerLeave.
*/
_unbindPickerLeaveCheck: function () {
$(doc).unbind('mousemove', this._onMouseMove);
},
/**
* Removes and destroys the popover dom element. If any columns/rows were selected, the
* 'seriesPicked' event is triggered.
*/
_hidePicker: function (replot) {
// hide picker
this._pickerPopover.hide();
this.domElem.trigger('hide').removeClass('open');
// replot
if (replot) {
var columns = [];
var rows = [];
this._pickerPopover.find('input:checked').each(function () {
if ($(this).closest('p').hasClass('pickRow')) {
rows.push($(this).data('name'));
} else {
columns.push($(this).data('name'));
}
});
var noRowSelected = this._pickerPopover.find('.pickRow').size() > 0
&& this._pickerPopover.find('.pickRow input:checked').size() == 0;
if (columns.length > 0 && !noRowSelected) {
$(this).trigger('seriesPicked', [columns, rows]);
// inform dashboard widget about changed parameters (to be restored on reload)
$('#' + this.dataTableId).closest('[widgetId]').trigger('setParameters', {columns: columns, rows: rows});
}
}
this._pickerPopover.remove();
},
/**
* Creates and returns the popover element. This element shows a list of checkboxes, one
* for each selectable column/row.
*/
_createPopover: function () {
var hasColumns = $.isArray(this.selectableColumns) && this.selectableColumns.length;
var hasRows = $.isArray(this.selectableRows) && this.selectableRows.length;
var popover = $('<div/>')
.addClass('jqplot-seriespicker-popover');
// create headline element
var title = this.multiSelect ? this.lang.metricsToPlot : this.lang.metricToPlot;
popover.append($('<p/>').addClass('headline').html(title));
// create selectable columns list
if (hasColumns) {
for (var i = 0; i < this.selectableColumns.length; i++) {
var column = this.selectableColumns[i];
popover.append(this._createPickerPopupItem(column, 'column'));
}
}
// create selectable rows list
if (hasRows) {
// "records to plot" subheadline
var header = $('<p/>').addClass('headline').addClass('recordsToPlot').html(this.lang.recordsToPlot);
popover.append(header);
// render the selectable rows
for (var i = 0; i < this.selectableRows.length; i++) {
var row = this.selectableRows[i];
popover.append(this._createPickerPopupItem(row, 'row'));
}
}
popover.hide();
return popover;
},
/**
* Positions the popover element.
*/
_positionPopover: function () {
var $body = $('body'),
popover = this._pickerPopover,
pickerLink = this.domElem,
pickerLinkLeft = pickerLink.offset().left,
bodyRight = $body.offset().left + $body.width()
;
$body.prepend(popover);
var neededSpace = popover.outerWidth() + 10;
var linkOffset = pickerLink.offset();
if (navigator.appVersion.indexOf("MSIE 7.") != -1) {
linkOffset.left -= 10;
}
// try to display popover to the right
var margin = parseInt(pickerLink.css('margin-left')) - 4;
var popoverRight = pickerLinkLeft + margin + neededSpace;
if (popoverRight < bodyRight
// make sure it's not too far to the left
|| popoverRight < 0
) {
popover.css('margin-left', (linkOffset.left - 4) + 'px').show();
} else {
// display to the left
popover.addClass('alignright')
.css('margin-left', (linkOffset.left - neededSpace + 38) + 'px')
.css('background-position', (popover.outerWidth() - 25) + 'px 4px')
.show();
}
popover.css('margin-top', (linkOffset.top - 5) + 'px').show();
}
};
var exports = require('piwik/DataTableVisualizations/Widgets');
exports.SeriesPicker = SeriesPicker;
})(jQuery, document, require);