add icons for Character groups

This commit is contained in:
coderkun 2014-04-29 14:18:04 +02:00
commit 2d9a41a5fe
3461 changed files with 594457 additions and 0 deletions

View file

@ -0,0 +1,644 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* broadcast object is to help maintain a hash for link clicks and ajax calls
* so we can have back button and refresh button working.
*
* @type {object}
*/
var broadcast = {
/**
* Initialisation state
* @type {Boolean}
*/
_isInit: false,
/**
* Last known hash url without popover parameter
*/
currentHashUrl: false,
/**
* Last known popover parameter
*/
currentPopoverParameter: false,
/**
* Callbacks for popover parameter change
*/
popoverHandlers: [],
/**
* Force reload once
*/
forceReload: false,
/**
* Suppress content update on hash changing
*/
updateHashOnly: false,
/**
* Initializes broadcast object
* @return {void}
*/
init: function (noLoadingMessage) {
if (broadcast._isInit) {
return;
}
broadcast._isInit = true;
// Initialize history plugin.
// The callback is called at once by present location.hash
$.history.init(broadcast.pageload, {unescape: true});
if(noLoadingMessage != true) {
piwikHelper.showAjaxLoading();
}
},
/**
* ========== PageLoad function =================
* This function is called when:
* 1. after calling $.history.init();
* 2. after calling $.history.load(); //look at broadcast.changeParameter();
* 3. after pushing "Go Back" button of a browser
*
* * Note: the method is manipulated in Overlay/javascripts/Piwik_Overlay.js - keep this in mind when making changes.
*
* @param {string} hash to load page with
* @return {void}
*/
pageload: function (hash) {
broadcast.init();
// Unbind any previously attached resize handlers
$(window).off('resize');
// do not update content if it should be suppressed
if (broadcast.updateHashOnly) {
broadcast.updateHashOnly = false;
return;
}
// hash doesn't contain the first # character.
if (hash && 0 === (''+hash).indexOf('/')) {
hash = (''+hash).substr(1);
}
if (hash) {
if (/^popover=/.test(hash)) {
var hashParts = [
'',
hash.replace(/^popover=/, '')
];
} else {
var hashParts = hash.split('&popover=');
}
var hashUrl = hashParts[0];
var popoverParam = '';
if (hashParts.length > 1) {
popoverParam = hashParts[1];
// in case the $ was encoded (e.g. when using copy&paste on urls in some browsers)
popoverParam = decodeURIComponent(popoverParam);
// revert special encoding from broadcast.propagateNewPopoverParameter()
popoverParam = popoverParam.replace(/\$/g, '%');
popoverParam = decodeURIComponent(popoverParam);
}
var pageUrlUpdated = (popoverParam == '' ||
(broadcast.currentHashUrl !== false && broadcast.currentHashUrl != hashUrl));
var popoverParamUpdated = (popoverParam != '' && hashUrl == broadcast.currentHashUrl);
if (broadcast.currentHashUrl === false) {
// new page load
pageUrlUpdated = true;
popoverParamUpdated = (popoverParam != '');
}
if (pageUrlUpdated || broadcast.forceReload) {
Piwik_Popover.close();
if (hashUrl != broadcast.currentHashUrl || broadcast.forceReload) {
// restore ajax loaded state
broadcast.loadAjaxContent(hashUrl);
// make sure the "Widgets & Dashboard" is deleted on reload
$('.top_controls .dashboard-manager').hide();
$('#dashboardWidgetsArea').dashboard('destroy');
// remove unused controls
require('piwik/UI').UIControl.cleanupUnusedControls();
}
}
broadcast.forceReload = false;
broadcast.currentHashUrl = hashUrl;
broadcast.currentPopoverParameter = popoverParam;
if (popoverParamUpdated && popoverParam == '') {
Piwik_Popover.close();
} else if (popoverParamUpdated) {
var popoverParamParts = popoverParam.split(':');
var handlerName = popoverParamParts[0];
popoverParamParts.shift();
var param = popoverParamParts.join(':');
if (typeof broadcast.popoverHandlers[handlerName] != 'undefined') {
broadcast.popoverHandlers[handlerName](param);
}
}
} else {
// start page
Piwik_Popover.close();
$('.pageWrap #content:not(.admin)').empty();
}
},
/**
* propagateAjax -- update hash values then make ajax calls.
* example :
* 1) <a href="javascript:broadcast.propagateAjax('module=Referrers&action=getKeywords')">View keywords report</a>
* 2) Main menu li also goes through this function.
*
* Will propagate your new value into the current hash string and make ajax calls.
*
* NOTE: this method will only make ajax call and replacing main content.
*
* @param {string} ajaxUrl querystring with parameters to be updated
* @param {boolean} [disableHistory] the hash change won't be available in the browser history
* @return {void}
*/
propagateAjax: function (ajaxUrl, disableHistory) {
broadcast.init();
// abort all existing ajax requests
globalAjaxQueue.abort();
// available in global scope
var currentHashStr = broadcast.getHash();
ajaxUrl = ajaxUrl.replace(/^\?|&#/, '');
var params_vals = ajaxUrl.split("&");
for (var i = 0; i < params_vals.length; i++) {
currentHashStr = broadcast.updateParamValue(params_vals[i], currentHashStr);
}
// if the module is not 'Goals', we specifically unset the 'idGoal' parameter
// this is to ensure that the URLs are clean (and that clicks on graphs work as expected - they are broken with the extra parameter)
var action = broadcast.getParamValue('action', currentHashStr);
if (action != 'goalReport' && action != 'ecommerceReport') {
currentHashStr = broadcast.updateParamValue('idGoal=', currentHashStr);
}
// unset idDashboard if use doesn't display a dashboard
var module = broadcast.getParamValue('module', currentHashStr);
if (module != 'Dashboard') {
currentHashStr = broadcast.updateParamValue('idDashboard=', currentHashStr);
}
if (disableHistory) {
var newLocation = window.location.href.split('#')[0] + '#' + currentHashStr;
// window.location.replace changes the current url without pushing it on the browser's history stack
window.location.replace(newLocation);
}
else {
// Let history know about this new Hash and load it.
broadcast.forceReload = true;
$.history.load(currentHashStr);
}
},
/**
* propagateNewPage() -- update url value and load new page,
* Example:
* 1) We want to update idSite to both search query and hash then reload the page,
* 2) update period to both search query and hash then reload page.
*
* ** If you'd like to make ajax call with new values then use propagateAjax ** *
*
* Expecting:
* str = "param1=newVal1&param2=newVal2";
*
* NOTE: This method will refresh the page with new values.
*
* @param {string} str url with parameters to be updated
* @param {boolean} [showAjaxLoading] whether to show the ajax loading gif or not.
* @return {void}
*/
propagateNewPage: function (str, showAjaxLoading) {
// abort all existing ajax requests
globalAjaxQueue.abort();
if (typeof showAjaxLoading === 'undefined' || showAjaxLoading) {
piwikHelper.showAjaxLoading();
}
var params_vals = str.split("&");
// available in global scope
var currentSearchStr = window.location.search;
var currentHashStr = broadcast.getHashFromUrl();
var oldUrl = currentSearchStr + currentHashStr;
for (var i = 0; i < params_vals.length; i++) {
// update both the current search query and hash string
currentSearchStr = broadcast.updateParamValue(params_vals[i], currentSearchStr);
if (currentHashStr.length != 0) {
currentHashStr = broadcast.updateParamValue(params_vals[i], currentHashStr);
}
}
// Now load the new page.
var newUrl = currentSearchStr + currentHashStr;
if (oldUrl == newUrl) {
window.location.reload();
} else {
this.forceReload = true;
window.location.href = newUrl;
}
return false;
},
/*************************************************
*
* Broadcast Supporter Methods:
*
*************************************************/
/**
* updateParamValue(newParamValue,urlStr) -- Helping propagate functions to update value to url string.
* eg. I want to update date value to search query or hash query
*
* Expecting:
* urlStr : A Hash or search query string. e.g: module=whatever&action=index=date=yesterday
* newParamValue : A param value pair: e.g: date=2009-05-02
*
* Return module=whatever&action=index&date=2009-05-02
*
* @param {string} newParamValue param to be updated
* @param {string} urlStr url to be updated
* @return {string} urlStr with updated param
*/
updateParamValue: function (newParamValue, urlStr) {
var p_v = newParamValue.split("=");
var paramName = p_v[0];
var valFromUrl = broadcast.getParamValue(paramName, urlStr);
// if set 'idGoal=' then we remove the parameter from the URL automatically (rather than passing an empty value)
var paramValue = p_v[1];
if (paramValue == '') {
newParamValue = '';
}
var getQuotedRegex = function(str) {
return (str+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
};
if (valFromUrl != '') {
// replacing current param=value to newParamValue;
valFromUrl = getQuotedRegex(valFromUrl);
var regToBeReplace = new RegExp(paramName + '=' + valFromUrl, 'ig');
if (newParamValue == '') {
// if new value is empty remove leading &, aswell
regToBeReplace = new RegExp('[\&]?' + paramName + '=' + valFromUrl, 'ig');
}
urlStr = urlStr.replace(regToBeReplace, newParamValue);
} else if (newParamValue != '') {
urlStr += (urlStr == '') ? newParamValue : '&' + newParamValue;
}
return urlStr;
},
/**
* Loads a popover by adding a 'popover' query parameter to the current URL and
* indirectly executing the popover handler.
*
* This function should be called to open popovers that can be opened by URL alone.
* That is, if you want users to be able to copy-paste the URL displayed when a popover
* is open into a new browser window/tab and have the same popover open, you should
* call this function.
*
* In order for this function to open a popover, there must be a popover handler
* associated with handlerName. To associate one, call broadcast.addPopoverHandler.
*
* @param {String} handlerName The name of the popover handler.
* @param {String} value The String value that should be passed to the popover
* handler.
*/
propagateNewPopoverParameter: function (handlerName, value) {
// init broadcast if not already done (it is required to make popovers work in widgetize mode)
broadcast.init(true);
var hash = broadcast.getHashFromUrl(window.location.href);
var popover = '';
if (handlerName) {
popover = handlerName + ':' + value;
// between jquery.history and different browser bugs, it's impossible to ensure
// that the parameter is en- and decoded the same number of times. in order to
// make sure it doesn't change, we have to manipulate the url encoding a bit.
popover = encodeURIComponent(popover);
popover = popover.replace(/%/g, '\$');
}
if ('' == value || 'undefined' == typeof value) {
var newHash = hash.replace(/(&?popover=.*)/, '');
} else if (broadcast.getParamValue('popover', hash)) {
var newHash = broadcast.updateParamValue('popover='+popover, hash);
} else if (hash && hash != '#') {
var newHash = hash + '&popover=' + popover
} else {
var newHash = '#popover='+popover;
}
// never use an empty hash, as that might reload the page
if ('' == newHash) {
newHash = '#';
}
broadcast.forceReload = false;
$.history.load(newHash);
},
/**
* Adds a handler for the 'popover' query parameter.
*
* @see broadcast#propagateNewPopoverParameter
*
* @param {String} handlerName The handler name, eg, 'visitorProfile'. Should identify
* the popover that the callback will open up.
* @param {Function} callback This function should open the popover. It should take
* one string parameter.
*/
addPopoverHandler: function (handlerName, callback) {
broadcast.popoverHandlers[handlerName] = callback;
},
/**
* Loads the given url with ajax and replaces the content
*
* Note: the method is replaced in Overlay/javascripts/Piwik_Overlay.js - keep this in mind when making changes.
*
* @param {string} urlAjax url to load
* @return {Boolean}
*/
loadAjaxContent: function (urlAjax) {
if (typeof piwikMenu !== 'undefined') {
piwikMenu.activateMenu(
broadcast.getParamValue('module', urlAjax),
broadcast.getParamValue('action', urlAjax),
broadcast.getParamValue('idGoal', urlAjax) || broadcast.getParamValue('idDashboard', urlAjax)
);
}
piwikHelper.hideAjaxError('loadingError');
piwikHelper.showAjaxLoading();
$('#content').empty();
$("object").remove();
urlAjax = urlAjax.match(/^\?/) ? urlAjax : "?" + urlAjax;
broadcast.lastUrlRequested = urlAjax;
function sectionLoaded(content) {
// if content is whole HTML document, do not show it, otherwise recursive page load could occur
var htmlDocType = '<!DOCTYPE';
if (content.substring(0, htmlDocType.length) == htmlDocType) {
// if the content has an error message, display it
if ($(content).filter('title').text() == 'Piwik Error') {
content = $(content).filter('#contentsimple');
} else {
return;
}
}
if (urlAjax == broadcast.lastUrlRequested) {
$('#content').html(content).show();
$(broadcast).trigger('locationChangeSuccess', {element: $('#content'), content: content});
piwikHelper.hideAjaxLoading();
broadcast.lastUrlRequested = null;
piwikHelper.compileAngularComponents('#content');
}
initTopControls();
}
var ajax = new ajaxHelper();
ajax.setUrl(urlAjax);
ajax.setErrorCallback(broadcast.customAjaxHandleError);
ajax.setCallback(sectionLoaded);
ajax.setFormat('html');
ajax.send();
return false;
},
/**
* Method to handle ajax errors
* @param {XMLHttpRequest} deferred
* @param {string} status
* @return {void}
*/
customAjaxHandleError: function (deferred, status) {
broadcast.lastUrlRequested = null;
// do not display error message if request was aborted
if(status == 'abort') {
return;
}
$('#loadingError').show();
setTimeout( function(){
$('#loadingError').fadeOut('slow');
}, 2000);
},
/**
* Return hash string if hash exists on address bar.
* else return false;
*
* @return {string|boolean} current hash or false if it is empty
*/
isHashExists: function () {
var hashStr = broadcast.getHashFromUrl();
if (hashStr != "") {
return hashStr;
} else {
return false;
}
},
/**
* Get Hash from given url or from current location.
* return empty string if no hash present.
*
* @param {string} [url] url to get hash from (defaults to current location)
* @return {string} the hash part of the given url
*/
getHashFromUrl: function (url) {
var hashStr = "";
// If url provided, give back the hash from url, else get hash from current address.
if (url && url.match('#')) {
hashStr = url.substring(url.indexOf("#"), url.length);
}
else {
locationSplit = location.href.split('#');
if(typeof locationSplit[1] != 'undefined') {
hashStr = '#' + locationSplit[1];
}
}
return hashStr;
},
/**
* Get search query from given url or from current location.
* return empty string if no search query present.
*
* @param {string} url
* @return {string} the query part of the given url
*/
getSearchFromUrl: function (url) {
var searchStr = "";
// If url provided, give back the query string from url, else get query string from current address.
if (url && url.match(/\?/)) {
searchStr = url.substring(url.indexOf("?"), url.length);
} else {
searchStr = location.search;
}
return searchStr;
},
/**
* Extracts from a query strings, the request array
* @param queryString
* @returns {object}
*/
extractKeyValuePairsFromQueryString: function (queryString) {
var pairs = queryString.split('&');
var result = {};
for (var i = 0; i != pairs.length; ++i) {
// attn: split with regex has bugs in several browsers such as IE 8
// so we need to split, use the first part as key and rejoin the rest
var pair = pairs[i].split('=');
var key = pair.shift();
result[key] = pair.join('=');
}
return result;
},
/**
* Returns all key-value pairs in query string of url.
*
* @param {string} url url to check. if undefined, null or empty, current url is used.
* @return {object} key value pair describing query string parameters
*/
getValuesFromUrl: function (url) {
var searchString = this._removeHashFromUrl(url).split('?')[1] || '';
return this.extractKeyValuePairsFromQueryString(searchString);
},
/**
* help to get param value for any given url string with provided param name
* if no url is provided, it will get param from current address.
* return:
* Empty String if param is not found.
*
* @param {string} param parameter to search for
* @param {string} [url] url to check, defaults to current location
* @return {string} value of the given param within the given url
*/
getValueFromUrl: function (param, url) {
var searchString = this._removeHashFromUrl(url);
return broadcast.getParamValue(param, searchString);
},
/**
* NOTE: you should probably be using broadcast.getValueFromUrl instead!
*
* @param {string} param parameter to search for
* @param {string} [url] url to check
* @return {string} value of the given param within the hash part of the given url
*/
getValueFromHash: function (param, url) {
var hashStr = broadcast.getHashFromUrl(url);
if (hashStr.substr(0, 1) == '#') {
hashStr = hashStr.substr(1);
}
hashStr = hashStr.split('#')[0];
return broadcast.getParamValue(param, hashStr);
},
/**
* return value for the requested param, will return the first match.
* out side of this class should use getValueFromHash() or getValueFromUrl() instead.
* return:
* Empty String if param is not found.
*
* @param {string} param parameter to search for
* @param {string} url url to check
* @return {string} value of the given param within the given url
*/
getParamValue: function (param, url) {
var lookFor = param + '=';
var startStr = url.indexOf(lookFor);
if (startStr >= 0) {
var endStr = url.indexOf("&", startStr);
if (endStr == -1) {
endStr = url.length;
}
var value = url.substring(startStr + param.length + 1, endStr);
// we sanitize values to add a protection layer against XSS
// &segment= value is not sanitized, since segments are designed to accept any user input
if(param != 'segment') {
value = value.replace(/[^_%~\*\+\-\<\>!@\$\.()=,;0-9a-zA-Z]/gi, '');
}
return value;
} else {
return '';
}
},
/**
* Returns the hash without the starting #
* @return {string} hash part of the current url
*/
getHash: function () {
return broadcast.getHashFromUrl().replace(/^#/, '').split('#')[0];
},
/**
* Removes the hash portion of a URL and returns the rest.
*
* @param {string} url
* @return {string} url w/o hash
*/
_removeHashFromUrl: function (url) {
var searchString = '';
if (url) {
var urlParts = url.split('#');
searchString = urlParts[0];
} else {
searchString = location.search;
}
return searchString;
}
};

View file

@ -0,0 +1,545 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
Date.prototype.getWeek = function () {
var onejan = new Date(this.getFullYear(), 0, 1), // needed for getDay()
// use UTC times since getTime() can differ based on user's timezone
onejan_utc = Date.UTC(this.getFullYear(), 0, 1),
this_utc = Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()),
daysSinceYearStart = (this_utc - onejan_utc) / 86400000; // constant is millisecs in one day
return Math.ceil((daysSinceYearStart + onejan.getDay()) / 7);
};
var currentYear, currentMonth, currentDay, currentDate, currentWeek;
function setCurrentDate(dateStr) {
var splitDate = dateStr.split("-");
currentYear = splitDate[0];
currentMonth = splitDate[1] - 1;
currentDay = splitDate[2];
currentDate = new Date(currentYear, currentMonth, currentDay);
currentWeek = currentDate.getWeek();
}
if(!piwik.currentDateString) {
// eg. Login form
return;
}
setCurrentDate(piwik.currentDateString);
var todayDate = new Date;
var todayMonth = todayDate.getMonth();
var todayYear = todayDate.getFullYear();
var todayDay = todayDate.getDate();
// min/max date for picker
var piwikMinDate = new Date(piwik.minDateYear, piwik.minDateMonth - 1, piwik.minDateDay),
piwikMaxDate = new Date(piwik.maxDateYear, piwik.maxDateMonth - 1, piwik.maxDateDay);
// we start w/ the current period
var selectedPeriod = piwik.period;
function isDateInCurrentPeriod(date) {
// if the selected period isn't the current period, don't highlight any dates
if (selectedPeriod != piwik.period) {
return [true, ''];
}
var valid = false;
var dateMonth = date.getMonth();
var dateYear = date.getFullYear();
var dateDay = date.getDate();
// we don't color dates in the future
if (dateMonth == todayMonth
&& dateYear == todayYear
&& dateDay > todayDay
) {
return [true, ''];
}
// we don't color dates before the minimum date
if (dateYear < piwik.minDateYear
|| ( dateYear == piwik.minDateYear
&&
(
(dateMonth == piwik.minDateMonth - 1
&& dateDay < piwik.minDateDay)
|| (dateMonth < piwik.minDateMonth - 1)
)
)
) {
return [true, ''];
}
// we color all day of the month for the same year for the month period
if (piwik.period == "month"
&& dateMonth == currentMonth
&& dateYear == currentYear
) {
valid = true;
}
// we color all day of the year for the year period
else if (piwik.period == "year"
&& dateYear == currentYear
) {
valid = true;
}
else if (piwik.period == "week"
&& date.getWeek() == currentWeek
&& dateYear == currentYear
) {
valid = true;
}
else if (piwik.period == "day"
&& dateDay == currentDay
&& dateMonth == currentMonth
&& dateYear == currentYear
) {
valid = true;
}
if (valid) {
return [true, 'ui-datepicker-current-period'];
}
return [true, ''];
}
piwik.getBaseDatePickerOptions = function (defaultDate) {
return {
showOtherMonths: false,
dateFormat: 'yy-mm-dd',
firstDay: 1,
minDate: piwikMinDate,
maxDate: piwikMaxDate,
prevText: "",
nextText: "",
currentText: "",
defaultDate: defaultDate,
changeMonth: true,
changeYear: true,
stepMonths: 1,
// jquery-ui-i18n 1.7.2 lacks some translations, so we use our own
dayNamesMin: [
_pk_translate('General_DaySu'),
_pk_translate('General_DayMo'),
_pk_translate('General_DayTu'),
_pk_translate('General_DayWe'),
_pk_translate('General_DayTh'),
_pk_translate('General_DayFr'),
_pk_translate('General_DaySa')],
dayNamesShort: [
_pk_translate('General_ShortDay_7'), // start with sunday
_pk_translate('General_ShortDay_1'),
_pk_translate('General_ShortDay_2'),
_pk_translate('General_ShortDay_3'),
_pk_translate('General_ShortDay_4'),
_pk_translate('General_ShortDay_5'),
_pk_translate('General_ShortDay_6')],
dayNames: [
_pk_translate('General_LongDay_7'), // start with sunday
_pk_translate('General_LongDay_1'),
_pk_translate('General_LongDay_2'),
_pk_translate('General_LongDay_3'),
_pk_translate('General_LongDay_4'),
_pk_translate('General_LongDay_5'),
_pk_translate('General_LongDay_6')],
monthNamesShort: [
_pk_translate('General_ShortMonth_1'),
_pk_translate('General_ShortMonth_2'),
_pk_translate('General_ShortMonth_3'),
_pk_translate('General_ShortMonth_4'),
_pk_translate('General_ShortMonth_5'),
_pk_translate('General_ShortMonth_6'),
_pk_translate('General_ShortMonth_7'),
_pk_translate('General_ShortMonth_8'),
_pk_translate('General_ShortMonth_9'),
_pk_translate('General_ShortMonth_10'),
_pk_translate('General_ShortMonth_11'),
_pk_translate('General_ShortMonth_12')],
monthNames: [
_pk_translate('General_LongMonth_1'),
_pk_translate('General_LongMonth_2'),
_pk_translate('General_LongMonth_3'),
_pk_translate('General_LongMonth_4'),
_pk_translate('General_LongMonth_5'),
_pk_translate('General_LongMonth_6'),
_pk_translate('General_LongMonth_7'),
_pk_translate('General_LongMonth_8'),
_pk_translate('General_LongMonth_9'),
_pk_translate('General_LongMonth_10'),
_pk_translate('General_LongMonth_11'),
_pk_translate('General_LongMonth_12')]
};
};
var updateDate;
function getDatePickerOptions() {
var result = piwik.getBaseDatePickerOptions(currentDate);
result.beforeShowDay = isDateInCurrentPeriod;
result.stepMonths = selectedPeriod == 'year' ? 12 : 1;
result.onSelect = function () { updateDate.apply(this, arguments); };
return result;
}
$(function () {
var datepickerElem = $('#datepicker').datepicker(getDatePickerOptions()),
periodLabels = $('#periodString').find('.period-type label'),
periodTooltip = $('#periodString').find('.period-click-tooltip').html();
var toggleWhitespaceHighlighting = function (klass, toggleTop, toggleBottom) {
var viewedYear = $('.ui-datepicker-year', datepickerElem).val(),
viewedMonth = +$('.ui-datepicker-month', datepickerElem).val(), // convert to int w/ '+'
firstOfViewedMonth = new Date(viewedYear, viewedMonth, 1),
lastOfViewedMonth = new Date(viewedYear, viewedMonth + 1, 0);
// only highlight dates between piwik.minDate... & piwik.maxDate...
// we select the cells to highlight by checking whether the first & last of the
// currently viewed month are within the min/max dates.
if (firstOfViewedMonth >= piwikMinDate) {
$('tbody>tr:first-child td.ui-datepicker-other-month', datepickerElem).toggleClass(klass, toggleTop);
}
if (lastOfViewedMonth < piwikMaxDate) {
$('tbody>tr:last-child td.ui-datepicker-other-month', datepickerElem).toggleClass(klass, toggleBottom);
}
};
// 'this' is the table cell
var highlightCurrentPeriod = function () {
switch (selectedPeriod) {
case 'day':
// highlight this link
$('a', $(this)).addClass('ui-state-hover');
break;
case 'week':
var row = $(this).parent();
// highlight parent row (the week)
$('a', row).addClass('ui-state-hover');
// toggle whitespace if week goes into previous or next month. we check if week is on
// top or bottom row.
var toggleTop = row.is(':first-child'),
toggleBottom = row.is(':last-child');
toggleWhitespaceHighlighting('ui-state-hover', toggleTop, toggleBottom);
break;
case 'month':
// highlight all parent rows (the month)
$('a', $(this).parent().parent()).addClass('ui-state-hover');
break;
case 'year':
// highlight table (month + whitespace)
$('a', $(this).parent().parent()).addClass('ui-state-hover');
toggleWhitespaceHighlighting('ui-state-hover', true, true);
break;
}
};
var unhighlightAllDates = function () {
// make sure nothing is highlighted
$('.ui-state-active,.ui-state-hover', datepickerElem).removeClass('ui-state-active ui-state-hover');
// color whitespace
if (piwik.period == 'year') {
var viewedYear = $('.ui-datepicker-year', datepickerElem).val(),
toggle = selectedPeriod == 'year' && currentYear == viewedYear;
toggleWhitespaceHighlighting('ui-datepicker-current-period', toggle, toggle);
}
else if (piwik.period == 'week') {
var toggleTop = $('tr:first-child a', datepickerElem).parent().hasClass('ui-datepicker-current-period'),
toggleBottom = $('tr:last-child a', datepickerElem).parent().hasClass('ui-datepicker-current-period');
toggleWhitespaceHighlighting('ui-datepicker-current-period', toggleTop, toggleBottom);
}
};
updateDate = function (dateText) {
piwikHelper.showAjaxLoading('ajaxLoadingCalendar');
// select new dates in calendar
setCurrentDate(dateText);
piwik.period = selectedPeriod;
// make sure it's called after jquery-ui is done, otherwise everything we do will
// be undone.
setTimeout(unhighlightAllDates, 1);
datepickerElem.datepicker('refresh');
// Let broadcast do its job:
// It will replace date value to both search query and hash and load the new page.
broadcast.propagateNewPage('date=' + dateText + '&period=' + selectedPeriod);
};
var toggleMonthDropdown = function (disable) {
if (typeof disable === 'undefined') {
disable = selectedPeriod == 'year';
}
// enable/disable month dropdown based on period == year
$('.ui-datepicker-month', datepickerElem).attr('disabled', disable);
};
var togglePeriodPickers = function (showSingle) {
$('#periodString').find('.period-date').toggle(showSingle);
$('#periodString').find('.period-range').toggle(!showSingle);
$('#calendarRangeApply').toggle(!showSingle);
};
//
// setup datepicker
//
unhighlightAllDates();
//
// hook up event slots
//
// highlight current period when mouse enters date
datepickerElem.on('mouseenter', 'tbody td', function () {
if ($(this).hasClass('ui-state-hover')) // if already highlighted, do nothing
{
return;
}
// unhighlight if cell is disabled/blank, unless the period is year
if ($(this).hasClass('ui-state-disabled') && selectedPeriod != 'year') {
unhighlightAllDates();
// if period is week, then highlight the current week
if (selectedPeriod == 'week') {
highlightCurrentPeriod.call(this);
}
}
else {
highlightCurrentPeriod.call(this);
}
});
// make sure cell stays highlighted when mouse leaves cell (overrides jquery-ui behavior)
datepickerElem.on('mouseleave', 'tbody td', function () {
$('a', this).addClass('ui-state-hover');
});
// unhighlight everything when mouse leaves table body (can't do event on tbody, for some reason
// that fails, so we do two events, one on the table & one on thead)
datepickerElem.on('mouseleave', 'table', unhighlightAllDates)
.on('mouseenter', 'thead', unhighlightAllDates);
// make sure whitespace is clickable when the period makes it appropriate
datepickerElem.on('click', 'tbody td.ui-datepicker-other-month', function () {
if ($(this).hasClass('ui-state-hover')) {
var row = $(this).parent(), tbody = row.parent();
if (row.is(':first-child')) {
// click on first of the month
$('a', tbody).first().click();
}
else {
// click on last of month
$('a', tbody).last().click();
}
}
});
// Hack to get around firefox bug. When double clicking a label in firefox, the 'click'
// event of its associated input will not be fired twice. We want to change the period
// if clicking the select period's label OR input, so we catch the click event on the
// label & the input.
var reloading = false;
var changePeriodOnClick = function (periodInput) {
if (reloading) // if a click event resulted in reloading, don't reload again
{
return;
}
var url = periodInput.val(),
period = broadcast.getValueFromUrl('period', url);
// if clicking on the selected period, change the period but not the date
if (selectedPeriod == period && selectedPeriod != 'range') {
// only reload if current period is different from selected
if (piwik.period != selectedPeriod && !reloading) {
reloading = true;
selectedPeriod = period;
updateDate(piwik.currentDateString);
}
return true;
}
return false;
};
$("#otherPeriods").find("label").on('click', function (e) {
var id = $(e.target).attr('for');
changePeriodOnClick($('#' + id));
});
// when non-range period is clicked, change the period & refresh the date picker
$("#otherPeriods").find("input").on('click', function (e) {
var request_URL = $(e.target).val(),
period = broadcast.getValueFromUrl('period', request_URL),
lastPeriod = selectedPeriod;
if (changePeriodOnClick($(e.target))) {
return true;
}
// switch the selected period
selectedPeriod = period;
// remove tooltips from the period inputs
periodLabels.each(function () { $(this).attr('title', '').removeClass('selected-period-label'); });
// range periods are handled in an event handler below
if (period == 'range') {
return true;
}
// set the tooltip of the current period
if (period != piwik.period) // don't add tooltip for current period
{
$(this).parent().find('label[for=period_id_' + period + ']')
.attr('title', periodTooltip).addClass('selected-period-label');
}
// toggle the right selector controls (show period selector datepicker & hide 'apply range' button)
togglePeriodPickers(true);
// set months step to 12 for year period (or set back to 1 if leaving year period)
if (selectedPeriod == 'year' || lastPeriod == 'year') {
// setting stepMonths will change the month in view back to the selected date. to avoid
// we set the selected date to the month in view.
var currentMonth = $('.ui-datepicker-month', datepickerElem).val(),
currentYear = $('.ui-datepicker-year', datepickerElem).val();
datepickerElem
.datepicker('option', 'stepMonths', selectedPeriod == 'year' ? 12 : 1)
.datepicker('setDate', new Date(currentYear, currentMonth));
}
datepickerElem.datepicker('refresh'); // must be last datepicker call, otherwise cells get highlighted
unhighlightAllDates();
toggleMonthDropdown();
return true;
});
// clicking left/right re-enables the month dropdown, so we disable it again
$(datepickerElem).on('click', '.ui-datepicker-next,.ui-datepicker-prev', function () {
unhighlightAllDates(); // make sure today's date isn't highlighted & toggle extra year highlighting
toggleMonthDropdown(selectedPeriod == 'year');
});
// reset date/period when opening calendar
$("#periodString").on('click', "#date,.calendar-icon", function () {
var periodMore = $("#periodMore").toggle();
if (periodMore.is(":visible")) {
periodMore.find(".ui-state-highlight").removeClass('ui-state-highlight');
}
});
$('body').on('click', function(e) {
var target = $(e.target);
if (target.closest('html').length && !target.closest('#periodString').length && !target.is('option') && $("#periodMore").is(":visible")) {
$("#periodMore").hide();
}
});
function onDateRangeSelect(dateText, inst) {
var toOrFrom = inst.id == 'calendarFrom' ? 'From' : 'To';
$('#inputCalendar' + toOrFrom).val(dateText);
}
// this will trigger to change only the period value on search query and hash string.
$("#period_id_range").on('click', function (e) {
togglePeriodPickers(false);
var options = getDatePickerOptions();
// Custom Date range callback
options.onSelect = onDateRangeSelect;
// Do not highlight the period
options.beforeShowDay = '';
// Create both calendars
options.defaultDate = piwik.startDateString;
$('#calendarFrom').datepicker(options).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', piwik.startDateString));
// Technically we should trigger the onSelect event on the calendar, but I couldn't find how to do that
// So calling the onSelect bind function manually...
//$('#calendarFrom').trigger('dateSelected'); // or onSelect
onDateRangeSelect(piwik.startDateString, { "id": "calendarFrom" });
// Same code for the other calendar
options.defaultDate = piwik.endDateString;
$('#calendarTo').datepicker(options).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', piwik.endDateString));
onDateRangeSelect(piwik.endDateString, { "id": "calendarTo" });
// If not called, the first date appears light brown instead of dark brown
$('.ui-state-hover').removeClass('ui-state-hover');
// Apply date range button will reload the page with the selected range
$('#calendarRangeApply')
.on('click', function () {
var request_URL = $(e.target).val();
var dateFrom = $('#inputCalendarFrom').val(),
dateTo = $('#inputCalendarTo').val(),
oDateFrom = $.datepicker.parseDate('yy-mm-dd', dateFrom),
oDateTo = $.datepicker.parseDate('yy-mm-dd', dateTo);
if (!isValidDate(oDateFrom)
|| !isValidDate(oDateTo)
|| oDateFrom > oDateTo) {
$('#alert').find('h2').text(_pk_translate('General_InvalidDateRange'));
piwikHelper.modalConfirm('#alert', {});
return false;
}
piwikHelper.showAjaxLoading('ajaxLoadingCalendar');
broadcast.propagateNewPage('period=range&date=' + dateFrom + ',' + dateTo);
})
.show();
// Bind the input fields to update the calendar's date when date is manually changed
$('#inputCalendarFrom, #inputCalendarTo')
.keyup(function (e) {
var fromOrTo = this.id == 'inputCalendarFrom' ? 'From' : 'To';
var dateInput = $(this).val();
try {
var newDate = $.datepicker.parseDate('yy-mm-dd', dateInput);
} catch (e) {
return;
}
$("#calendar" + fromOrTo).datepicker("setDate", newDate);
if (e.keyCode == 13) {
$('#calendarRangeApply').click();
}
});
return true;
});
function isValidDate(d) {
if (Object.prototype.toString.call(d) !== "[object Date]")
return false;
return !isNaN(d.getTime());
}
if (piwik.period == 'range') {
$("#period_id_range").click();
}
});
}(jQuery));

View file

@ -0,0 +1,225 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
var colorNames = {"aliceblue":"#f0f8ff","antiquewhite":"#faebd7","aqua":"#00ffff","aquamarine":"#7fffd4","azure":"#f0ffff",
"beige":"#f5f5dc","bisque":"#ffe4c4","black":"#000000","blanchedalmond":"#ffebcd","blue":"#0000ff","blueviolet":"#8a2be2","brown":"#a52a2a","burlywood":"#deb887",
"cadetblue":"#5f9ea0","chartreuse":"#7fff00","chocolate":"#d2691e","coral":"#ff7f50","cornflowerblue":"#6495ed","cornsilk":"#fff8dc","crimson":"#dc143c","cyan":"#00ffff",
"darkblue":"#00008b","darkcyan":"#008b8b","darkgoldenrod":"#b8860b","darkgray":"#a9a9a9","darkgreen":"#006400","darkkhaki":"#bdb76b","darkmagenta":"#8b008b","darkolivegreen":"#556b2f",
"darkorange":"#ff8c00","darkorchid":"#9932cc","darkred":"#8b0000","darksalmon":"#e9967a","darkseagreen":"#8fbc8f","darkslateblue":"#483d8b","darkslategray":"#2f4f4f","darkturquoise":"#00ced1",
"darkviolet":"#9400d3","deeppink":"#ff1493","deepskyblue":"#00bfff","dimgray":"#696969","dodgerblue":"#1e90ff",
"firebrick":"#b22222","floralwhite":"#fffaf0","forestgreen":"#228b22","fuchsia":"#ff00ff","gainsboro":"#dcdcdc","ghostwhite":"#f8f8ff","gold":"#ffd700","goldenrod":"#daa520","gray":"#808080","green":"#008000","greenyellow":"#adff2f",
"honeydew":"#f0fff0","hotpink":"#ff69b4","indianred ":"#cd5c5c","indigo ":"#4b0082","ivory":"#fffff0","khaki":"#f0e68c",
"lavender":"#e6e6fa","lavenderblush":"#fff0f5","lawngreen":"#7cfc00","lemonchiffon":"#fffacd","lightblue":"#add8e6","lightcoral":"#f08080","lightcyan":"#e0ffff","lightgoldenrodyellow":"#fafad2",
"lightgrey":"#d3d3d3","lightgreen":"#90ee90","lightpink":"#ffb6c1","lightsalmon":"#ffa07a","lightseagreen":"#20b2aa","lightskyblue":"#87cefa","lightslategray":"#778899","lightsteelblue":"#b0c4de",
"lightyellow":"#ffffe0","lime":"#00ff00","limegreen":"#32cd32","linen":"#faf0e6","magenta":"#ff00ff","maroon":"#800000","mediumaquamarine":"#66cdaa","mediumblue":"#0000cd","mediumorchid":"#ba55d3","mediumpurple":"#9370d8","mediumseagreen":"#3cb371","mediumslateblue":"#7b68ee",
"mediumspringgreen":"#00fa9a","mediumturquoise":"#48d1cc","mediumvioletred":"#c71585","midnightblue":"#191970","mintcream":"#f5fffa","mistyrose":"#ffe4e1","moccasin":"#ffe4b5",
"navajowhite":"#ffdead","navy":"#000080","oldlace":"#fdf5e6","olive":"#808000","olivedrab":"#6b8e23","orange":"#ffa500","orangered":"#ff4500","orchid":"#da70d6",
"palegoldenrod":"#eee8aa","palegreen":"#98fb98","paleturquoise":"#afeeee","palevioletred":"#d87093","papayawhip":"#ffefd5","peachpuff":"#ffdab9","peru":"#cd853f","pink":"#ffc0cb","plum":"#dda0dd","powderblue":"#b0e0e6","purple":"#800080",
"red":"#ff0000","rosybrown":"#bc8f8f","royalblue":"#4169e1","saddlebrown":"#8b4513","salmon":"#fa8072","sandybrown":"#f4a460","seagreen":"#2e8b57","seashell":"#fff5ee","sienna":"#a0522d","silver":"#c0c0c0","skyblue":"#87ceeb","slateblue":"#6a5acd","slategray":"#708090","snow":"#fffafa","springgreen":"#00ff7f","steelblue":"#4682b4",
"tan":"#d2b48c","teal":"#008080","thistle":"#d8bfd8","tomato":"#ff6347","turquoise":"#40e0d0","violet":"#ee82ee","wheat":"#f5deb3","white":"#ffffff","whitesmoke":"#f5f5f5","yellow":"#ffff00","yellowgreen":"#9acd32"};
/**
* The ColorManager class allows JS code to grab colors defined in CSS for
* components that don't manage HTML (like jqPlot or sparklines). Such components
* can't use CSS colors directly since the colors are used to generate images
* or by <canvas> elements.
*
* Colors obtained via ColorManager are defined in CSS like this:
*
* .my-color-namespace[data-name=color-name] {
* color: #fff
* }
*
* and can be accessed in JavaScript like this:
*
* piwik.ColorManager.getColor("my-color-namespace", "color-name");
*
* The singleton instance of this class can be accessed via piwik.ColorManager.
*/
var ColorManager = function () {
// empty
};
ColorManager.prototype = {
/**
* Returns the color for a namespace and name.
*
* @param {String} namespace The string identifier that groups related colors
* together. For example, 'sparkline-colors'.
* @param {String} name The name of the color to retrieve. For example, 'lineColor'.
* @return {String} A hex color, eg, '#fff'.
*/
getColor: function (namespace, name) {
var element = this._getElement();
element.attr('class', 'color-manager ' + namespace).attr('data-name', name);
return this._normalizeColor(element.css('color'));
},
/**
* Returns the colors for a namespace and a list of names.
*
* @param {String} namespace The string identifier that groups related colors
* together. For example, 'sparkline-colors'.
* @param {Array} names An array of color names to retrieve.
* @param {Boolean} asArray Whether the result should be an array or an object.
* @return {Object|Array} An object mapping color names with color values or an
* array of colors.
*/
getColors: function (namespace, names, asArray) {
var colors = asArray ? [] : {};
for (var i = 0; i != names.length; ++i) {
var name = names[i],
color = this.getColor(namespace, name);
if (color) {
if (asArray) {
colors.push(color);
} else {
colors[name] = color;
}
}
}
return colors;
},
/**
* Returns a color that is N % between two other colors.
*
* @param {String|Array} spectrumStart The start color. If percentFromStart is 0, this color will
* be returned. Can be either a hex color or RGB array.
* It will be converted to an RGB array if a hex color is supplied.
* @param {String|Array} spectrumEnd The end color. If percentFromStart is 1, this color will be
* returned. Can be either a hex color or RGB array. It will be
* converted to an RGB array if a hex color is supplied.
* @param {Number} percentFromStart The percent from spectrumStart and twoard spectrumEnd that the
* result color should be. Must be a value between 0.0 & 1.0.
* @return {String} A hex color.
*/
getSingleColorFromGradient: function (spectrumStart, spectrumEnd, percentFromStart) {
if (!(spectrumStart instanceof Array)) {
spectrumStart = this.getRgb(spectrumStart);
}
if (!(spectrumEnd instanceof Array)) {
spectrumEnd = this.getRgb(spectrumEnd);
}
var result = [];
for (var channel = 0; channel != spectrumStart.length; ++channel) {
var delta = (spectrumEnd[channel] - spectrumStart[channel]) * percentFromStart;
result[channel] = Math.floor(spectrumStart[channel] + delta);
}
return this.getHexColor(result);
},
/**
* Utility function that converts a hex color (ie, #fff or #1a1a1a) to an array of
* RGB values.
*
* @param {String} hexColor The color to convert.
* @return {Array} An array with three integers between 0 and 255.
*/
getRgb: function (hexColor) {
if (hexColor[0] == '#') {
hexColor = hexColor.substring(1);
}
if (hexColor.length == 3) {
return [
parseInt(hexColor[0], 16),
parseInt(hexColor[1], 16),
parseInt(hexColor[2], 16)
];
} else {
return [
parseInt(hexColor.substring(0,2), 16),
parseInt(hexColor.substring(2,4), 16),
parseInt(hexColor.substring(4,6), 16)
];
}
},
/**
* Utility function that converts an RGB array to a hex color.
*
* @param {Array} rgbColor An array with three integers between 0 and 255.
* @return {String} The hex color, eg, #1a1a1a.
*/
getHexColor: function (rgbColor) {
// convert channels to hex with one leading 0
for (var i = 0; i != rgbColor.length; ++i) {
rgbColor[i] = ("00" + rgbColor[i].toString(16)).slice(-2);
}
// create hex string
return '#' + rgbColor.join('');
},
/**
* Turns a color string that might be an rgb value rgb(12, 34, 56) into
* a hex color string.
*/
_normalizeColor: function (color) {
if (color == this._getTransparentColor()) {
return null;
}
if (color && colorNames[color]) {
return colorNames[color];
}
if (color
&& color[0] != '#'
) {
// parse rgb(#, #, #) and get rgb numbers
var parts = color.split(/[()rgb,\s]+/);
parts = [+parts[1], +parts[2], +parts[3]];
// convert to hex
color = this.getHexColor(parts);
}
return color;
},
/**
* Returns the manufactured <div> element used to obtain color data. When
* getting color data the class and data-name attribute of this element are
* changed.
*/
_getElement: function () {
if (!this.$element) {
$('body').append('<div id="color-manager"></div>');
this.$element = $('#color-manager');
}
return this.$element;
},
/**
* Returns this browser's representation of the 'transparent' color. Used to
* compare against colors obtained in getColor. If a color is 'transparent'
* it means there's no color for that namespace/name combination.
*/
_getTransparentColor: function () {
if (!this.transparentColor) {
this.transparentColor =
$('<div style="color:transparent;display:none;"></div>').appendTo($('body')).css('color');
}
return this.transparentColor;
}
};
piwik.ColorManager = new ColorManager();
}(jQuery));

View file

@ -0,0 +1,160 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
$(function () {
//
// 'check for updates' behavior
//
var headerMessageParent = $('#header_message').parent();
// when 'check for updates...' link is clicked, force a check & display the result
headerMessageParent.on('click', '#updateCheckLinkContainer', function (e) {
e.preventDefault();
var headerMessage = $(this).closest('#header_message');
var ajaxRequest = new ajaxHelper();
ajaxRequest.setLoadingElement('#header_message .loadingPiwik');
ajaxRequest.addParams({
module: 'CoreHome',
action: 'checkForUpdates'
}, 'get');
ajaxRequest.setCallback(function (response) {
headerMessage.fadeOut('slow', function () {
response = $(response);
var newVersionAvailable = response.hasClass('header_alert');
if (newVersionAvailable) {
headerMessage.replaceWith(response);
}
else {
headerMessage.html(_pk_translate('CoreHome_YouAreUsingTheLatestVersion')).show();
setTimeout(function () {
headerMessage.fadeOut('slow', function () {
headerMessage.replaceWith(response);
});
}, 4000);
}
});
});
ajaxRequest.setFormat('html');
ajaxRequest.send(false);
return false;
});
// when clicking the header message, show the long message w/o needing to hover
headerMessageParent.on('click', '#header_message', function (e) {
if (e.target.tagName.toLowerCase() != 'a') {
$(this).toggleClass('active');
}
});
//
// section toggler behavior
//
var handleSectionToggle = function (self, showType, doHide) {
var sectionId = $(self).attr('data-section-id'),
section = $('#' + sectionId),
showText = _pk_translate('General_Show'),
hideText = _pk_translate('General_Hide');
if (typeof(doHide) == 'undefined') {
doHide = section.is(':visible');
}
if (doHide) {
var newText = $(self).text().replace(hideText, showText),
afterHide = function () { $(self).text(newText); };
if (showType == 'slide') {
section.slideUp(afterHide);
}
else if (showType == 'inline') {
section.hide();
afterHide();
}
else {
section.hide(afterHide);
}
}
else {
var newText = $(self).text().replace(showText, hideText);
$(self).text(newText);
if (showType == 'slide') {
section.slideDown();
}
else if (showType == 'inline') {
section.css('display', 'inline-block');
}
else {
section.show();
}
}
};
// when click section toggler link, toggle the visibility of the associated section
$('body').on('click', 'a.section-toggler-link', function (e) {
e.preventDefault();
handleSectionToggle(this, 'slide');
return false;
});
$('body').on('change', 'input.section-toggler-link', function (e) {
handleSectionToggle(this, 'inline', !$(this).is(':checked'));
});
//
// reports by dimension list behavior
//
// when a report dimension is clicked, load the appropriate report
var currentWidgetLoading = null;
$('body').on('click', '.reportDimension', function (e) {
var view = $(this).closest('.reportsByDimensionView'),
report = $('.dimensionReport', view),
loading = $('.loadingPiwik', view);
// make this dimension the active one
$('.activeDimension', view).removeClass('activeDimension');
$(this).addClass('activeDimension');
// hide the visible report & show the loading elem
report.hide();
loading.show();
// load the report using the data-url attribute (which holds the URL to the report)
var widgetParams = broadcast.getValuesFromUrl($(this).attr('data-url'));
for (var key in widgetParams) {
widgetParams[key] = decodeURIComponent(widgetParams[key]);
}
var widgetUniqueId = widgetParams.module + widgetParams.action;
currentWidgetLoading = widgetUniqueId;
widgetsHelper.loadWidgetAjax(widgetUniqueId, widgetParams, function (response) {
// if the widget that was loaded was not for the latest clicked link, do nothing w/ the response
if (widgetUniqueId != currentWidgetLoading) {
return;
}
loading.hide();
report.html($(response)).css('display', 'inline-block');
// scroll to report
piwikHelper.lazyScrollTo(report, 400);
});
});
});
}(jQuery));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,386 @@
/**
* Registry for row actions
*
* Plugins can call DataTable_RowActions_Registry.register() from their JS
* files in order to add new actions to arbitrary data tables. The register()
* method takes an object containing:
* - name: string identifying the action. must be short, no spaces.
* - dataTableIcon: path to the icon for the action
* - createInstance: a factory method to create an instance of the appropriate
* subclass of DataTable_RowAction
* - isAvailable: a method to determine whether the action is available in a
* given row of a data table
*/
var DataTable_RowActions_Registry = {
registry: [],
register: function (action) {
var createInstance = action.createInstance;
action.createInstance = function (dataTable, param) {
var instance = createInstance(dataTable, param);
instance.actionName = action.name;
return instance;
};
this.registry.push(action);
},
getAvailableActionsForReport: function (dataTableParams, tr) {
if (dataTableParams.disable_row_actions == '1') {
return [];
}
var available = [];
for (var i = 0; i < this.registry.length; i++) {
if (this.registry[i].isAvailableOnReport(dataTableParams, tr)) {
available.push(this.registry[i]);
}
}
available.sort(function (a, b) {
return b.order - a.order;
});
return available;
},
getActionByName: function (name) {
for (var i = 0; i < this.registry.length; i++) {
if (this.registry[i].name == name) {
return this.registry[i];
}
}
return false;
}
};
// Register Row Evolution (also servers as example)
DataTable_RowActions_Registry.register({
name: 'RowEvolution',
dataTableIcon: 'plugins/Zeitgeist/images/row_evolution.png',
dataTableIconHover: 'plugins/Zeitgeist/images/row_evolution_hover.png',
order: 50,
dataTableIconTooltip: [
_pk_translate('General_RowEvolutionRowActionTooltipTitle'),
_pk_translate('General_RowEvolutionRowActionTooltip')
],
createInstance: function (dataTable, param) {
if (dataTable !== null && typeof dataTable.rowEvolutionActionInstance != 'undefined') {
return dataTable.rowEvolutionActionInstance;
}
if (dataTable === null && param) {
// when row evolution is triggered from the url (not a click on the data table)
// we look for the data table instance in the dom
var report = param.split(':')[0];
var div = $(require('piwik/UI').DataTable.getDataTableByReport(report));
if (div.size() > 0 && div.data('uiControlObject')) {
dataTable = div.data('uiControlObject');
if (typeof dataTable.rowEvolutionActionInstance != 'undefined') {
return dataTable.rowEvolutionActionInstance;
}
}
}
var instance = new DataTable_RowActions_RowEvolution(dataTable);
if (dataTable !== null) {
dataTable.rowEvolutionActionInstance = instance;
}
return instance;
},
isAvailableOnReport: function (dataTableParams) {
return (
typeof dataTableParams.disable_row_evolution == 'undefined'
|| dataTableParams.disable_row_evolution == "0"
) && (
typeof dataTableParams.flat == 'undefined'
|| dataTableParams.flat == "0"
);
},
isAvailableOnRow: function (dataTableParams, tr) {
return true;
}
});
/**
* DataTable Row Actions
*
* The lifecycle of an action is as follows:
* - for each data table, a new instance of the action is created using the factory
* - when the table is loaded, initTr is called for each tr
* - when the action icon is clicked, trigger is called
* - the label is put together and performAction is called
* - performAction must call openPopover on the base class
* - openPopover calls back doOpenPopover after doing general stuff
*
* The two template methods are performAction and doOpenPopover
*/
//
// BASE CLASS
//
function DataTable_RowAction(dataTable) {
this.dataTable = dataTable;
// has to be overridden in subclasses
this.trEventName = 'piwikTriggerRowAction';
// set in registry
this.actionName = 'RowAction';
}
/** Initialize a row when the table is loaded */
DataTable_RowAction.prototype.initTr = function (tr) {
var self = this;
// For subtables, we need to make sure that the actions are always triggered on the
// action instance connected to the root table. Otherwise sharing data (e.g. for
// for multi-row evolution) wouldn't be possible. Also, sub-tables might have different
// API actions. For the label filter to work, we need to use the parent action.
// We use jQuery events to let subtables access their parents.
tr.bind(self.trEventName, function (e, params) {
self.trigger($(this), params.originalEvent, params.label);
});
};
/**
* This method is called from the click event and the tr event (see this.trEventName).
* It derives the label and calls performAction.
*/
DataTable_RowAction.prototype.trigger = function (tr, e, subTableLabel) {
var label = this.getLabelFromTr(tr);
label = label.trim();
// if we have received the event from the sub table, add the label
if (subTableLabel) {
var separator = ' > '; // LabelFilter::SEPARATOR_RECURSIVE_LABEL
label += separator + subTableLabel.trim();
}
// handle sub tables in nested reports: forward to parent
var subtable = tr.closest('table');
if (subtable.is('.subDataTable')) {
subtable.closest('tr').prev().trigger(this.trEventName, {
label: label,
originalEvent: e
});
return;
}
// ascend in action reports
if (subtable.closest('div.dataTableActions').length) {
var allClasses = tr.attr('class');
var matches = allClasses.match(/level[0-9]+/);
var level = parseInt(matches[0].substring(5, matches[0].length), 10);
if (level > 0) {
// .prev(.levelX) does not work for some reason => do it "by hand"
var findLevel = 'level' + (level - 1);
var ptr = tr;
while ((ptr = ptr.prev()).size() > 0) {
if (!ptr.hasClass(findLevel)) {
continue;
}
ptr.trigger(this.trEventName, {
label: label,
originalEvent: e
});
return;
}
}
}
this.performAction(label, tr, e);
};
/** Get the label string from a tr dom element */
DataTable_RowAction.prototype.getLabelFromTr = function (tr) {
var label = tr.find('span.label');
// handle truncation
var value = label.data('originalText');
if (!value) {
value = label.text();
}
value = value.trim();
return encodeURIComponent(value);
};
/**
* Base method for opening popovers.
* This method will remember the parameter in the url and call doOpenPopover().
*/
DataTable_RowAction.prototype.openPopover = function (parameter) {
broadcast.propagateNewPopoverParameter('RowAction', this.actionName + ':' + parameter);
};
broadcast.addPopoverHandler('RowAction', function (param) {
var paramParts = param.split(':');
var rowActionName = paramParts[0];
paramParts.shift();
param = paramParts.join(':');
var rowAction = DataTable_RowActions_Registry.getActionByName(rowActionName);
if (rowAction) {
rowAction.createInstance(null, param).doOpenPopover(param);
}
});
/** To be overridden */
DataTable_RowAction.prototype.performAction = function (label, tr, e) {
};
DataTable_RowAction.prototype.doOpenPopover = function (parameter) {
};
//
// ROW EVOLUTION
//
function DataTable_RowActions_RowEvolution(dataTable) {
this.dataTable = dataTable;
this.trEventName = 'piwikTriggerRowEvolution';
/** The rows to be compared in multi row evolution */
this.multiEvolutionRows = [];
}
/** Static helper method to launch row evolution from anywhere */
DataTable_RowActions_RowEvolution.launch = function (apiMethod, label) {
var param = 'RowEvolution:' + apiMethod + ':0:' + label;
broadcast.propagateNewPopoverParameter('RowAction', param);
};
DataTable_RowActions_RowEvolution.prototype = new DataTable_RowAction;
DataTable_RowActions_RowEvolution.prototype.performAction = function (label, tr, e) {
if (e.shiftKey) {
// only mark for multi row evolution if shift key is pressed
this.addMultiEvolutionRow(label);
return;
}
// check whether we have rows marked for multi row evolution
var isMultiRowEvolution = '0';
this.addMultiEvolutionRow(label);
if (this.multiEvolutionRows.length > 1) {
isMultiRowEvolution = '1';
label = this.multiEvolutionRows.join(',');
}
var apiMethod = this.dataTable.param.module + '.' + this.dataTable.param.action;
this.openPopover(apiMethod, isMultiRowEvolution, label);
};
DataTable_RowActions_RowEvolution.prototype.addMultiEvolutionRow = function (label) {
if ($.inArray(label, this.multiEvolutionRows) == -1) {
this.multiEvolutionRows.push(label);
}
};
DataTable_RowActions_RowEvolution.prototype.openPopover = function (apiMethod, multiRowEvolutionParam, label) {
var urlParam = apiMethod + ':' + multiRowEvolutionParam + ':' + label;
DataTable_RowAction.prototype.openPopover.apply(this, [urlParam]);
};
DataTable_RowActions_RowEvolution.prototype.doOpenPopover = function (urlParam) {
var urlParamParts = urlParam.split(':');
var apiMethod = urlParamParts[0];
urlParamParts.shift();
var multiRowEvolutionParam = urlParamParts[0];
urlParamParts.shift();
var label = urlParamParts.join(':');
this.showRowEvolution(apiMethod, label, multiRowEvolutionParam);
};
/** Open the row evolution popover */
DataTable_RowActions_RowEvolution.prototype.showRowEvolution = function (apiMethod, label, multiRowEvolutionParam) {
var self = this;
// open the popover
var box = Piwik_Popover.showLoading('Row Evolution');
box.addClass('rowEvolutionPopover');
// prepare loading the popover contents
var requestParams = {
apiMethod: apiMethod,
label: label,
disableLink: 1
};
// derive api action and requested column from multiRowEvolutionParam
var action;
if (multiRowEvolutionParam == '0') {
action = 'getRowEvolutionPopover';
} else if (multiRowEvolutionParam == '1') {
action = 'getMultiRowEvolutionPopover';
} else {
action = 'getMultiRowEvolutionPopover';
requestParams.column = multiRowEvolutionParam;
}
var callback = function (html) {
Piwik_Popover.setContent(html);
// use the popover title returned from the server
var title = box.find('div.popover-title');
if (title.size() > 0) {
Piwik_Popover.setTitle(title.html());
title.remove();
}
Piwik_Popover.onClose(function () {
// reset rows marked for multi row evolution on close
self.multiEvolutionRows = [];
});
if (self.dataTable !== null) {
// remember label for multi row evolution
box.find('a.rowevolution-startmulti').click(function () {
Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
Piwik_Popover.close();
return false;
});
} else {
// when the popover is launched by copy&pasting a url, we don't have the data table.
// in this case, we can't remember the row marked for multi row evolution so
// we disable the picker.
box.find('.compare-container, .rowevolution-startmulti').remove();
}
// switch metric in multi row evolution
box.find('select.multirowevoltion-metric').change(function () {
var metric = $(this).val();
Piwik_Popover.onClose(false); // unbind listener that resets multiEvolutionRows
self.openPopover(apiMethod, metric, label);
return true;
});
};
requestParams.module = 'CoreHome';
requestParams.action = action;
requestParams.colors = JSON.stringify(piwik.getSparklineColors());
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(requestParams, 'get');
ajaxRequest.setCallback(callback);
ajaxRequest.setFormat('html');
ajaxRequest.send(false);
};

View file

@ -0,0 +1,142 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
$(document).ready(function () {
var donateAmounts = [0, 30, 60, 90, 120];
// returns the space between each donation amount in the donation slider
var getTickWidth = function (slider) {
var effectiveSliderWidth = $('.slider-range', slider).width() - $('.slider-position', slider).width();
return effectiveSliderWidth / (donateAmounts.length - 1);
};
// returns the position index on a slider based on a x coordinate value
var getPositionFromPageCoord = function (slider, pageX) {
return Math.round((pageX - $('.slider-range', slider).offset().left) / getTickWidth(slider));
};
// set's the correct amount text & smiley face image based on the position of the slider
var setSmileyFaceAndAmount = function (slider, pos) {
// set text yearly amount
$('.slider-donate-amount', slider).text('$' + donateAmounts[pos] + '/' + _pk_translate('General_YearShort'));
// set the right smiley face
$('.slider-smiley-face').attr('src', 'plugins/Zeitgeist/images/smileyprog_' + pos + '.png');
// set the hidden option input for paypal
var option = Math.max(1, pos);
$('.piwik-donate-call input[name=os0]').val("Option " + option);
};
// move's a slider's position to a specific spot
var moveSliderPosition = function (slider, to) {
// make sure 'to' is valid
if (to < 0) {
to = 0;
}
else if (to >= donateAmounts.length) {
to = donateAmounts.length - 1;
}
// set the slider position
var left = to * getTickWidth(slider);
if (left == 0) {
left = -1; // at position 0 we move one pixel left to cover up some of slider bar
}
$('.slider-position', slider).css({
left: left + 'px'
});
// reset the smiley face & amount based on the new position
setSmileyFaceAndAmount(slider, to);
};
// when a slider is clicked, set the amount & smiley face appropriately
$('body').on('click', '.piwik-donate-slider>.slider-range', function (e) {
var slider = $(this).parent(),
currentPageX = $('.slider-position', this).offset().left,
currentPos = getPositionFromPageCoord(slider, currentPageX),
pos = getPositionFromPageCoord(slider, e.pageX);
// if the closest position is the current one, use the other position since
// the user obviously wants to move the slider.
if (currentPos == pos) {
// if click is to right, go forward one, else backwards one
if (e.pageX > currentPageX) {
++pos;
}
else {
--pos;
}
}
moveSliderPosition(slider, pos);
});
// when the smiley icon is clicked, move the position up one to demonstrate how to use the slider
$('body').on('click', '.piwik-donate-slider .slider-smiley-face,.piwik-donate-slider .slider-donate-amount',
function (e) {
var slider = $(this).closest('.piwik-donate-slider'),
currentPageX = $('.slider-position', slider).offset().left,
currentPos = getPositionFromPageCoord(slider, currentPageX);
moveSliderPosition(slider, currentPos + 1);
}
);
// stores the current slider being dragged
var draggingSlider = false;
// start dragging on mousedown for a slider's position bar
$('body').on('mousedown', '.piwik-donate-slider .slider-position', function () {
draggingSlider = $(this).parent().parent();
});
// move the slider position if currently dragging when the mouse moves anywhere over the entire page
$('body').on('mousemove', function (e) {
if (draggingSlider) {
var slider = draggingSlider.find('.slider-range'),
sliderPos = slider.find('.slider-position'),
left = e.pageX - slider.offset().left;
// only move slider if the mouse x-coord is still on the slider (w/ some padding for borders)
if (left <= slider.width() - sliderPos.width() + 2
&& left >= -2) {
sliderPos.css({left: left + 'px'});
var closestPos = Math.round(left / getTickWidth(draggingSlider));
setSmileyFaceAndAmount(draggingSlider, closestPos);
}
}
});
// stop dragging and normalize a slider's position on mouseup over the entire page
$('body').on('mouseup', function () {
if (draggingSlider) {
var sliderPos = $('.slider-position', draggingSlider),
slider = sliderPos.parent();
if (sliderPos.length) {
// move the slider to the nearest donation amount position
var pos = getPositionFromPageCoord(draggingSlider, sliderPos.offset().left);
moveSliderPosition(draggingSlider, pos);
}
draggingSlider = false; // stop dragging
}
});
// event for programatically changing the position
$('body').on('piwik:changePosition', '.piwik-donate-slider', function (e, data) {
moveSliderPosition(this, data.position);
});
});
}(jQuery));

View file

@ -0,0 +1,110 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* @constructor
*/
function menu() {
this.param = {};
}
menu.prototype =
{
resetTimer: null,
adaptSubMenuHeight: function() {
var subNavHeight = $('.sfHover > ul').outerHeight();
$('.nav_sep').height(subNavHeight);
},
overMainLI: function () {
var $this = $(this);
$this.siblings().removeClass('sfHover');
$this.addClass('sfHover');
menu.prototype.adaptSubMenuHeight();
clearTimeout(menu.prototype.resetTimer);
},
outMainLI: function () {
clearTimeout(menu.prototype.resetTimer);
menu.prototype.resetTimer = setTimeout(function() {
$('.Menu-tabList > .sfHover', this.menuNode).removeClass('sfHover');
$('.Menu-tabList > .sfActive', this.menuNode).addClass('sfHover');
menu.prototype.adaptSubMenuHeight();
}, 2000);
},
onItemClick: function (item) {
$('.Menu--dashboard').trigger('piwikSwitchPage', item);
broadcast.propagateAjax( $(item).attr('href').substr(1) );
return false;
},
init: function () {
this.menuNode = $('.Menu--dashboard');
this.menuNode.find("li:has(ul)").hover(this.overMainLI, this.outMainLI);
// add id to all li menu to support menu identification.
// for all sub menu we want to have a unique id based on their module and action
// for main menu we want to add just the module as its id.
this.menuNode.find('li').each(function () {
var url = $(this).find('a').attr('href').substr(1);
var module = broadcast.getValueFromUrl("module", url);
var action = broadcast.getValueFromUrl("action", url);
var moduleId = broadcast.getValueFromUrl("idGoal", url) || broadcast.getValueFromUrl("idDashboard", url);
var main_menu = $(this).parent().hasClass('Menu-tabList') ? true : false;
if (main_menu) {
$(this).attr({id: module});
}
// if there's a idGoal or idDashboard, use this in the ID
else if (moduleId != '') {
$(this).attr({id: module + '_' + action + '_' + moduleId});
}
else {
$(this).attr({id: module + '_' + action});
}
});
menu.prototype.adaptSubMenuHeight();
},
activateMenu: function (module, action, id) {
this.menuNode.find('li').removeClass('sfHover').removeClass('sfActive');
var $li = this.getSubmenuID(module, id, action);
var mainLi = $("#" + module);
if (!mainLi.length) {
mainLi = $li.parents('li');
}
mainLi.addClass('sfActive').addClass('sfHover');
$li.addClass('sfHover');
},
// getting the right li is a little tricky since goals uses idGoal, and overview is index.
getSubmenuID: function (module, id, action) {
var $li = '';
// So, if module is Goals, id is present, and action is not Index, must be one of the goals
if (module == 'Goals' && id != '' && (action != 'index')) {
$li = $("#" + module + "_" + action + "_" + id);
// if module is Dashboard and id is present, must be one of the dashboards
} else if (module == 'Dashboard') {
if (!id) id = 1;
$li = $("#" + module + "_" + action + "_" + id);
} else {
$li = $("#" + module + "_" + action);
}
return $li;
},
loadFirstSection: function () {
if (broadcast.isHashExists() == false) {
$('li:first a:first', this.menuNode).click().addClass('sfHover').addClass('sfActive');
}
}
};

View file

@ -0,0 +1,19 @@
$(function () {
var isPageHasMenu = $('.Menu--dashboard').size();
var isPageIsAdmin = $('#content.admin').size();
if (isPageHasMenu) {
piwikMenu = new menu();
piwikMenu.init();
piwikMenu.loadFirstSection();
}
if(isPageIsAdmin) {
// don't use broadcast in admin pages
return;
}
if(isPageHasMenu) {
broadcast.init();
} else {
broadcast.init(true);
}
});

View file

@ -0,0 +1,198 @@
/**
* Piwik - Web Analytics
*
* @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');
/**
* Creates a new notifications.
*
* Example:
* var UI = require('piwik/UI');
* var notification = new UI.Notification();
* notification.show('My Notification Message', {title: 'Low space', context: 'warning'});
*/
var Notification = function () {
this.$node = null;
};
/**
* Makes the notification visible.
*
* @param {string} message The actual message that will be displayed. Must be set.
* @param {Object} [options]
* @param {string} [options.id] Only needed for persistent notifications. The id will be sent to the
* frontend once the user closes the notifications. The notification has to
* be registered/notified under this name
* @param {string} [options.title] The title of the notification. For instance the plugin name.
* @param {bool} [options.animate=true] If enabled, the notification will be faded in.
* @param {string} [options.context=warning] Context of the notification: 'info', 'warning', 'success' or
* 'error'
* @param {string} [options.type=transient] The type of the notification: Either 'toast' or 'transitent'
* @param {bool} [options.noclear=false] If set, the close icon is not displayed.
* @param {object} [options.style] Optional style/css dictionary. For instance {'display': 'inline-block'}
* @param {string} [options.placeat] By default, the notification will be displayed in the "stats bar".
* You can specify any other CSS selector to place the notifications
* whereever you want.
*/
Notification.prototype.show = function (message, options) {
if (!message) {
throw new Error('No message given, cannot display notification');
}
if (options && !$.isPlainObject(options)) {
throw new Error('Options has the wrong format, cannot display notification');
} else if (!options) {
options = {};
}
if ('persistent' == options.type) {
// otherwise it is never possible to dismiss the notification
options.noclear = false;
}
closeExistingNotificationHavingSameIdIfNeeded(options);
var template = generateNotificationHtmlMarkup(options, message);
var $notificationNode = placeNotification(template, options);
this.$node = $notificationNode;
if ('persistent' == options.type) {
addPersistentEvent($notificationNode);
} else if ('toast' == options.type) {
addToastEvent($notificationNode);
}
if (!options.noclear) {
addCloseEvent($notificationNode);
}
};
Notification.prototype.scrollToNotification = function () {
if (this.$node) {
piwikHelper.lazyScrollTo(this.$node, 250);
}
};
exports.Notification = Notification;
function closeExistingNotificationHavingSameIdIfNeeded(options)
{
if (!options.id) {
return;
}
var $existingNode = $('.system.notification[data-id=' + options.id + ']');
if ($existingNode && $existingNode.length) {
$existingNode.remove();
}
}
function generateNotificationHtmlMarkup(options, message) {
var template = buildNotificationStart(options);
if (!options.noclear) {
template += buildClearButton();
}
if (options.title) {
template += buildTitle(options);
}
template += message;
template += buildNotificationEnd();
return template;
}
function buildNotificationStart(options) {
var template = '<div class="notification system';
if (options.context) {
template += ' notification-' + options.context;
}
template += '"';
if (options.id) {
template += ' data-id="' + options.id + '"';
}
template += '>';
return template;
}
function buildNotificationEnd() {
return '</div>';
}
function buildClearButton() {
return '<button type="button" class="close" data-dismiss="alert">&times;</button>';
}
function buildTitle(options) {
return '<strong>' + options.title + '</strong> ';
}
function placeNotification(template, options) {
var $notificationNode = $(template);
if (options.style) {
$notificationNode.css(options.style);
}
$notificationNode = $notificationNode.hide();
$(options.placeat || '#notificationContainer').append($notificationNode);
if (false === options.animate) {
$notificationNode.show();
} else {
$notificationNode.fadeIn(1000);
}
return $notificationNode;
}
function addToastEvent($notificationNode)
{
setTimeout(function () {
$notificationNode.fadeOut( 'slow', function() {
$notificationNode.remove();
$notificationNode = null;
});
}, 12 * 1000);
}
function addCloseEvent($notificationNode) {
$notificationNode.on('click', '.close', function (event) {
if (event && event.delegateTarget) {
$(event.delegateTarget).remove();
}
});
};
function addPersistentEvent($notificationNode) {
var notificationId = $notificationNode.data('id');
if (!notificationId) {
return;
}
$notificationNode.on('click', '.close', function (event) {
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
module: 'CoreHome',
action: 'markNotificationAsRead'
}, 'GET');
ajaxHandler.addParams({notificationId: notificationId}, 'POST');
ajaxHandler.send(true);
});
};
})(jQuery, require);

View file

@ -0,0 +1,31 @@
/**
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
$(document).ready((function ($, require) {
return function () {
var UI = require('piwik/UI');
var $notificationNodes = $('[data-role="notification"]');
$notificationNodes.each(function (index, notificationNode) {
$notificationNode = $(notificationNode);
var attributes = $notificationNode.data();
var message = $notificationNode.html();
if (message) {
var notification = new UI.Notification();
attributes.animate = false;
notification.show(message, attributes);
}
$notificationNodes.remove();
});
}
})(jQuery, require));

View file

@ -0,0 +1,254 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
var Piwik_Popover = (function () {
var container = false;
var isOpen = false;
var closeCallback = false;
var createContainer = function () {
if (container === false) {
container = $(document.createElement('div')).attr('id', 'Piwik_Popover');
}
};
var openPopover = function (title, dialogClass) {
createContainer();
var options =
{
title: title,
modal: true,
width: '950px',
position: ['center', 'center'],
resizable: false,
autoOpen: true,
open: function (event, ui) {
if (dialogClass) {
$(this).parent().addClass(dialogClass).attr('style', '');
}
$('.ui-widget-overlay').on('click.popover', function () {
container.dialog('close');
});
},
close: function (event, ui) {
container.find('div.jqplot-target').trigger('piwikDestroyPlot');
container[0].innerHTML = ''; // IE8 fix
container.dialog('destroy').remove();
globalAjaxQueue.abort();
$('.ui-widget-overlay').off('click.popover');
isOpen = false;
broadcast.propagateNewPopoverParameter(false);
require('piwik/UI').UIControl.cleanupUnusedControls();
if (typeof closeCallback == 'function') {
closeCallback();
closeCallback = false;
}
}
};
container.dialog(options);
// override the undocumented _title function to ensure that the title attribute is not escaped (according to jQueryUI bug #6016)
container.data( "uiDialog" )._title = function(title) {
title.html( this.options.title );
};
isOpen = true;
};
var centerPopover = function () {
if (container !== false) {
container.dialog({position: ['center', 'center']});
}
};
return {
/**
* Open the popover with a loading message
*
* @param {string} popoverName name of the popover
* @param {string} [popoverSubject] subject of the popover (e.g. url, optional)
* @param {int} [height] height of the popover in px (optional)
* @param {string} [dialogClass] css class to add to dialog
*/
showLoading: function (popoverName, popoverSubject, height, dialogClass) {
var loading = $(document.createElement('div')).addClass('Piwik_Popover_Loading');
var loadingMessage = popoverSubject ? translations.General_LoadingPopoverFor :
translations.General_LoadingPopover;
loadingMessage = loadingMessage.replace(/%s/, popoverName);
var p1 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Name');
loading.append(p1.text(loadingMessage));
var p2;
if (popoverSubject) {
popoverSubject = piwikHelper.addBreakpointsToUrl(popoverSubject);
p1.addClass('Piwik_Popover_Loading_NameWithSubject');
p2 = $(document.createElement('p')).addClass('Piwik_Popover_Loading_Subject');
loading.append(p2.html(popoverSubject));
}
if (height) {
loading.height(height);
}
if (!isOpen) {
openPopover(null, dialogClass);
}
this.setContent(loading);
this.setTitle('');
if (height) {
var offset = loading.height() - p1.outerHeight();
if (popoverSubject) {
offset -= p2.outerHeight();
}
var spacingEl = $(document.createElement('div'));
spacingEl.height(Math.round(offset / 2));
loading.prepend(spacingEl);
}
return container;
},
/**
* Add a help button to the current popover
*
* @param {string} helpUrl
*/
addHelpButton: function (helpUrl) {
if (!isOpen) {
return;
}
var titlebar = container.parent().find('.ui-dialog-titlebar');
var button = $(document.createElement('a')).addClass('ui-dialog-titlebar-help');
button.attr({href: helpUrl, target: '_blank'});
titlebar.append(button);
},
/** Set the title of the popover */
setTitle: function (titleHtml) {
container.dialog('option', 'title', titleHtml);
},
/** Set inner HTML of the popover */
setContent: function (html) {
if (typeof closeCallback == 'function') {
closeCallback();
closeCallback = false;
}
container[0].innerHTML = ''; // IE8 fix
container.html(html);
centerPopover();
},
/**
* Show an error message. All params are HTML!
*
* @param {string} title
* @param {string} [message]
* @param {string} [backLabel]
*/
showError: function (title, message, backLabel) {
var error = $(document.createElement('div')).addClass('Piwik_Popover_Error');
var p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Title');
error.append(p.html(title));
if (message) {
p = $(document.createElement('p')).addClass('Piwik_Popover_Error_Message');
error.append(p.html(message));
}
if (backLabel) {
var back = $(document.createElement('a')).addClass('Piwik_Popover_Error_Back');
back.attr('href', '#').click(function () {
history.back();
return false;
});
error.append(back.html(backLabel));
}
if (!isOpen) {
openPopover();
}
this.setContent(error);
},
/**
* Add a callback for the next time the popover is closed or the content changes
*
* @param {function} callback
*/
onClose: function (callback) {
closeCallback = callback;
},
/** Close the popover */
close: function () {
if (isOpen) {
container.dialog('close');
}
},
/**
* Create a Popover and load the specified URL in it.
*
* Note: If you want the popover to be persisted in the URL (so if the URL is copy/pasted
* to a new window/tab it will be opened there), use broadcast.propagateNewPopoverParameter
* with a popover handler function that calls this one.
*
* @param {string} url
* @param {string} loadingName
* @param {string} [dialogClass] css class to add to dialog
*/
createPopupAndLoadUrl: function (url, loadingName, dialogClass) {
// make sure the minimum top position of the popover is 15px
var ensureMinimumTop = function () {
var popoverContainer = $('#Piwik_Popover').parent();
if (popoverContainer.position().top < 106) {
popoverContainer.css('top', '15px');
}
};
// open the popover
var box = Piwik_Popover.showLoading(loadingName, null, null, dialogClass);
ensureMinimumTop();
var callback = function (html) {
function setPopoverTitleIfOneFoundInContainer() {
var title = $('h1,h2', container);
if (title.length == 1) {
Piwik_Popover.setTitle(title.text());
$(title).hide();
}
}
Piwik_Popover.setContent(html);
setPopoverTitleIfOneFoundInContainer();
ensureMinimumTop();
};
var ajaxRequest = new ajaxHelper();
ajaxRequest.addParams(piwikHelper.getArrayFromQueryString(url), 'get');
ajaxRequest.setCallback(callback);
ajaxRequest.setFormat('html');
ajaxRequest.send(false);
}
};
})();

View file

@ -0,0 +1,12 @@
$(function () {
$('#piwik-promo-thumbnail').click(function () {
var promoEmbed = $('#piwik-promo-embed'),
widgetWidth = $(this).closest('.widgetContent').width(),
height = (266 * widgetWidth) / 421,
embedHtml = '<iframe width="100%" height="' + height + '" src="http://www.youtube.com/embed/OslfF_EH81g?autoplay=1&vq=hd720&wmode=transparent" frameborder="0" wmode="Opaque"></iframe>';
$(this).hide();
promoEmbed.height(height).html(embedHtml);
promoEmbed.show();
});
});

View file

@ -0,0 +1,39 @@
/**
* Piwik - Web Analytics
*
* Module creation & inclusion for Piwik.
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function (window) {
var MODULE_SPLIT_REGEX = /[\/.\\]/;
/**
* Returns a module for its ID. Empty modules are created if they does not exist.
*
* Modules are currently stored in the window object.
*
* @param {String} moduleId e.g. 'piwik/UserCountryMap' or 'myPlugin/Widgets/FancySchmancyThing'.
* The following characters can be used to separate individual modules:
* '/', '.' or '\'.
* @return {Object} The module object.
*/
window.require = function (moduleId) {
var parts = moduleId.split(MODULE_SPLIT_REGEX);
// TODO: we use window objects for backwards compatibility. when rest of Piwik is rewritten to use
// require, we can switch simply holding the modules in a private variable.
var currentModule = window;
for (var i = 0; i != parts.length; ++i) {
var part = parts[i];
currentModule[part] = currentModule[part] || {};
currentModule = currentModule[part];
}
return currentModule;
};
})(window);

View file

@ -0,0 +1,83 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
(function ($) {
var sparklineColorNames = ['backgroundColor', 'lineColor', 'minPointColor', 'maxPointColor', 'lastPointColor'];
piwik.getSparklineColors = function () {
return piwik.ColorManager.getColors('sparkline-colors', sparklineColorNames);
};
// initializes each sparkline so they use colors defined in CSS
piwik.initSparklines = function() {
$('.sparkline > img').each(function () {
var $self = $(this);
if ($self.attr('src')) {
return;
}
var colors = JSON.stringify(piwik.getSparklineColors());
var appendToSparklineUrl = '&colors=' + encodeURIComponent(colors);
// Append the token_auth to the URL if it was set (eg. embed dashboard)
var token_auth = broadcast.getValueFromUrl('token_auth');
if (token_auth.length) {
appendToSparklineUrl += '&token_auth=' + token_auth;
}
$self.attr('src', $self.attr('data-src') + appendToSparklineUrl);
});
};
window.initializeSparklines = function () {
var sparklineUrlParamsToIgnore = ['module', 'action', 'idSite', 'period', 'date', 'viewDataTable'];
$("[data-graph-id]").each(function () {
var graph = $(this);
// try to find sparklines and add them clickable behaviour
graph.parent().find('div.sparkline').each(function () {
// find the sparkline and get it's src attribute
var sparklineUrl = $('img', this).attr('data-src');
if (sparklineUrl != "") {
var params = broadcast.getValuesFromUrl(sparklineUrl);
for (var i = 0; i != sparklineUrlParamsToIgnore.length; ++i) {
delete params[sparklineUrlParamsToIgnore[i]];
}
for (var key in params) {
if (typeof params[key] == 'undefined') {
// this happens for example with an empty segment parameter
delete params[key];
} else {
params[key] = decodeURIComponent(params[key]);
}
}
// on click, reload the graph with the new url
$(this).click(function () {
var reportId = graph.attr('data-graph-id'),
dataTable = $(require('piwik/UI').DataTable.getDataTableByReport(reportId));
// when the metrics picker is used, the id of the data table might be updated (which is correct behavior).
// for example, in goal reports it might change from GoalsgetEvolutionGraph to GoalsgetEvolutionGraph1.
// if this happens, we can't find the graph using $('#'+idDataTable+"Chart");
// instead, we just use the first evolution graph we can find.
if (dataTable.length == 0) {
dataTable = $('div.dataTableVizEvolution');
}
// reload the datatable w/ a new column & scroll to the graph
dataTable.trigger('reload', params);
});
}
});
});
};
}(jQuery));

View file

@ -0,0 +1,27 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
function initTopControls() {
var $topControlsContainer = $('.top_controls'),
left = 0;
if ($topControlsContainer.length) {
$('.piwikTopControl').each(function () {
var $control = $(this);
if ($control.css('display') == 'none') {
return;
}
$control.css('left', left);
if (!$.contains($topControlsContainer[0], this)) {
$control.detach().appendTo($topControlsContainer);
}
left += $control.outerWidth(true);
});
}
}

View file

@ -0,0 +1,113 @@
/**
* 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) {
if (!element) {
throw new Error("no element passed to UIControl constructor");
}
this._controlId = UIControl._controls.length;
UIControl._controls.push(this);
var $element = this.$element = $(element);
$element.data('uiControlObject', this);
var params = JSON.parse($element.attr('data-params') || '{}');
for (var key in params) { // convert values in params that are arrays to comma separated string lists
if (params[key] instanceof Array) {
params[key] = params[key].join(',');
}
}
this.param = params;
this.props = JSON.parse($element.attr('data-props') || '{}');
};
/**
* 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 used
*/
UIControl.cleanupUnusedControls = function () {
var controls = UIControl._controls;
for (var i = 0; i != controls.length; ++i) {
var control = controls[i];
if (control
&& 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.initElements = function (klass, selector) {
$(selector).each(function () {
if (!$(this).attr('data-inited')) {
var control = new klass(this);
$(this).attr('data-inited', 1);
}
});
};
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;
},
/**
* Handle the widget resize event, if we're currently in a widget.
*
* TODO: should use proper resize detection (see
* http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/ )
* with timeouts (since resizing widgets can be expensive)
*/
onWidgetResize: function (handler) {
var $widget = this.$element.closest('.widgetContent');
$widget.on('widget:maximise', handler)
.on('widget:minimise', handler)
.on('widget:resize', handler);
}
};
exports.UIControl = UIControl;
})(jQuery, require);