diff options
author | Benaka <diosmosis@users.noreply.github.com> | 2017-10-16 07:54:55 +0300 |
---|---|---|
committer | Matthieu Aubry <mattab@users.noreply.github.com> | 2017-10-16 07:54:55 +0300 |
commit | d4e57274c765698e82e476de4ad6d1a81b65cc91 (patch) | |
tree | 3a36a9eb3d4e58d19123aa352908b60906c48115 /plugins/CoreHome/javascripts/calendar.js | |
parent | c1422b533fef97b63b434befe57856ee348c1e46 (diff) |
Convert period selector to angular & allow plugins to add periods to the frontend (#11873)
* Add generate:angular-component command to generate an angular component.
* Do not modify Date prototype.
* Move period selector code from calendar.js to new angular directive (just move, no refactoring).
* Extract date picker code from period selector code and put into new directive.
* Extract range picking code into separate component than period selector.
* Extract single period calendar to separate component & extract period specific functionality to new extendable periods service.
* Fixing regressions in period selector behavior.
* Move bulk of period selector code from directive to controller, & fix variable name in date range picker template.
* Fix issue w/ yesterday date value, remove need to give period selector directive translations and make sure periods can be extended in the frontend.
* Make sure period selector still works outside of an angular routing context (ie, in embedded dashboard).
* In period selector UI test, hide ajaxLoadingCalendar using CSS since it is now managed by angular.
* Make sure selected period highlighting changes immediately after selecting, even if loading a new page.
* Put period selector top level element ID & classes on correct elements to ensure certain styles work properly.
* Make sure selected period text changes immediately after selecing period, even if loading a new page or changing the URL.
* Make sure range start/end changes immediately when a period is selected & selected period date range stops being highlighted immediately when a range period is selected, even if loading a new page.
* Updating expected screenshots.
* Updating screenshots.
* Assorted fixes for period selector refactor.
- Filter out invalid period labels (can happen if INI config for allowed periods is incorrect).
- When determining display text for range, don't try to format the startRangeDate/endRangeDate vars, they're both strings.
- Use correct selector when closing period selector.
* Set global piwik date/period values on location change, outside of period selector component.
* Do not skip parsing date if it does not start with an int (since the JS can handle today/yesterday/now).
* Assorted fixes for period selector refactor:
- use $onChanges instead of watches in datepicker (watches get triggered every time, $onChanges doesn't)
- don't use arrays for selected/highlighted dates (for some weird reason, changing one of these arrays results in angular thinking it changes 3 times instead of once)
- don't redraw on triggered mouseover events (something triggers mouseover when a date is selected, probably jquery datepicker)
- draw after a setTimeout when a date is selected so our drawing occurs after jquery datepicker draws
* Achieving smoother rendering for period selector by removing click handlers jquery datepicker adds.
Also fixed bug where selecting the current period type reset the view date for the date picker.
* Bound range date in period selector by piwik min/max date, so inferred dates will always be within allowed pickable dates in picker.
* Removing ES6 used by accident + fix for issue when switching from non-year to year period (ui-datepicker-current-day class does not get removed).
* Fix for angularjs one way binding quirk: initial property value is set before $onInit not during construction.
* Avoid an exception when a date input in the date range picker is empty.
* Split up change/keyup event to solve strange race condition in IE 10 on browserstack.
* Change period selector "click again" tooltip to "double click".
* Remove tabindexes > 1 so period selector control can be tabbed through.
* Show visual cue for invalid dates in date range picker.
* Only hide period option tooltip if period is active period, not if period is selected period.
* In period selector, disable apply button if range is invalid. Also fix case when \$.datepicker.parseDate returns null instead of throwing.
Diffstat (limited to 'plugins/CoreHome/javascripts/calendar.js')
-rw-r--r-- | plugins/CoreHome/javascripts/calendar.js | 621 |
1 files changed, 0 insertions, 621 deletions
diff --git a/plugins/CoreHome/javascripts/calendar.js b/plugins/CoreHome/javascripts/calendar.js index c026bc8da5..a1b89ae12d 100644 --- a/plugins/CoreHome/javascripts/calendar.js +++ b/plugins/CoreHome/javascripts/calendar.js @@ -5,224 +5,10 @@ * @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 formatDateString(date) { - var month = date.getMonth() + 1; - var day = date.getDate(); - if (month < 10) { - month = '0' + month; - } - if (day < 10) { - day = '0' + day; - } - - return date.getFullYear() + '-' + month + '-' + day; - } - - function updateDisplayDate(selectedPeriod, dateText) - { - piwik.period = selectedPeriod; - - if (dateText && dateText.indexOf(',') > -1) { - var dateParts = dateText.split(','); - if (dateParts[1]) { - piwik.currentDateString = dateParts[1]; - } else if (dateParts[0]) { - piwik.currentDateString = dateParts[0]; - } - } else { - piwik.currentDateString = dateText; - } - - if (selectedPeriod === 'week') { - var millisecondsPerDay = 24 * 60 * 60 * 1000; - var currentTimeSelected = currentDate.getTime(); - // we fix currentDayOfWeek as our week starts on Monday. - var currentDayOfWeek = currentDate.getDay(); - if (currentDayOfWeek === 0) { - // Usually Sunday === 0, we set Sunday to the end of the week. - currentDayOfWeek = 6; - } else { - // we move every day one day forward, so Monday which is usually 1 becomes 0 - currentDayOfWeek = currentDayOfWeek - 1; - } - - var firstDayOfWeek = new Date(); - var lastDayOfWeek = new Date(); - firstDayOfWeek.setTime(currentTimeSelected - (millisecondsPerDay * currentDayOfWeek)); - lastDayOfWeek.setTime(firstDayOfWeek.getTime() + (millisecondsPerDay * 6)); - - piwik.startDateString = formatDateString(firstDayOfWeek); - piwik.endDateString = formatDateString(lastDayOfWeek); - } else if (dateText.indexOf(',') !== -1) { - var dateParts = dateText.split(','); - piwik.startDateString = dateParts[0]; - piwik.endDateString = dateParts[1]; - } else { - piwik.startDateString = dateText; - piwik.endDateString = dateText; - } - - var displayDate = dateText; - if (selectedPeriod === 'month') { - displayDate = _pk_translate('Intl_Month_Long_StandAlone_' + (currentMonth+1)) + ' ' + currentYear; - } else if (selectedPeriod === 'year') { - displayDate = currentYear; - } else if (selectedPeriod === 'range' || selectedPeriod === 'week') { - displayDate = _pk_translate('General_DateRangeFromTo', [piwik.startDateString, piwik.endDateString]); - } - - var $title = $('#date.title'); - $title.text(displayDate); - $title.attr('title', _pk_translate('General_ChooseDate', [piwikHelper.escape(displayDate)])); - - if (typeof initTopControls !== 'undefined' && initTopControls) { - initTopControls(); - } - } - - function setCurrentDate(dateStr) { - if (dateStr && dateStr.indexOf(',') !== -1) { - var parts = dateStr.split(','); - dateStr = parts[0]; - } - - 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, '']; - } - - function propagateNewUrlParams(date, period) - { - if (piwikHelper.isAngularRenderingThePage()) { - - $('#periodString').removeClass('expanded'); - piwikHelper.hideAjaxLoading('ajaxLoadingCalendar'); - - angular.element(document).injector().invoke(function ($location) { - var $search = $location.search(); - - if (date !== $search.date || period !== $search.period) { - // eg when using back button the date might be actually already changed in the URL and we do not - // want to change the URL again - $search.date = date; - $search.period = period; - $location.search($search); - } - - }); - - } else { - // Let broadcast do its job: - // It will replace date value to both search query and hash and load the new page. - broadcast.propagateNewPage('date=' + date + '&period=' + period); - } - } - piwik.getBaseDatePickerOptions = function (defaultDate) { return { showOtherMonths: false, @@ -291,413 +77,6 @@ }; }; - 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 reloading = false; - - 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'); - $('.ui-datepicker-current-day', datepickerElem).removeClass('ui-datepicker-current-day'); - - // 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); - updateDisplayDate(selectedPeriod, dateText); - - // make sure it's called after jquery-ui is done, otherwise everything we do will - // be undone. - setTimeout(unhighlightAllDates, 1); - - datepickerElem.datepicker('refresh'); - - propagateNewUrlParams(dateText, selectedPeriod); - initTopControls(); - reloading = false; - }; - - 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); - }; - - // - // 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(); - } - } - }); - - var changePeriodWithPageReload = function (periodInput) { - - var url = periodInput.val(), - period = broadcast.getValueFromUrl('period', url); - - // if clicking on the selected period, change the period but not the date - if (period != 'range' && !reloading) { - // only reload if current period is different from selected - reloading = true; - selectedPeriod = period; - updateDate(piwik.currentDateString); - return true; - } - - return false; - }; - - var changePeriodOnClickIfPeriodChanged = 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) { - return changePeriodWithPageReload(periodInput); - } - - return true; - } - - return false; - }; - - $("#otherPeriods").find("label,input").on('dblclick', function (e) { - var id = $(e.target).attr('for'); - changePeriodOnClickIfPeriodChanged($('#' + id)); - }); - - $("#otherPeriods").find("label,input").on('dblclick', function (e) { - var id = $(e.target).attr('for'); - changePeriodOnClickIfPeriodChanged($('#' + id)); - }); - - // Apply date range button will reload the page with the selected range - $('#calendarApply') - .on("click", function() { - var $selectedPeriod = $('#periodMore [name=period]:checked'); - - if (!$selectedPeriod.is('#period_id_range')) { - changePeriodWithPageReload($selectedPeriod); - return true; - } - - 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'); - propagateNewUrlParams(dateFrom + ',' + dateTo, 'range'); - }) - .show(); - - - - // 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 (changePeriodOnClickIfPeriodChanged($(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'); - }); - - 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'); - - // 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) { - $('#calendarApply').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(); - } - - function updatePeriodPickerFromHash() - { - var dateHash = broadcast.getValueFromHash('date'); - var periodHash = broadcast.getValueFromHash('period'); - - if (!dateHash || dateHash.length > 30 || !periodHash || periodHash.length > 7) { - // invalid data in URL - return; - } - - if (!/^(\d){4}/.test(dateHash)) { - // it's not an actual date, it is 'yesterday' or so meaning date was not changed in calendar - return; - } - - if (piwik.period === periodHash && piwik.currentDateString === dateHash) { - // this period / date is already loaded - return; - } - - $('input[id=period_id_' + periodHash + ']').click(); - - updateDate(dateHash); - - $('input[id=period_id_' + periodHash + ']').click(); - } - - if (piwikHelper.isAngularRenderingThePage()) { - // when using back button etc we need to update the date under circumstances - angular.element(document).injector().invoke(function ($rootScope) { - $rootScope.$on('$locationChangeSuccess', updatePeriodPickerFromHash); - }); - - // on initial page load the date/period in hash might be different to the one in the URL - updatePeriodPickerFromHash(); - } - - initTopControls(); - }); - Mousetrap.bind('d', function(event) { if (event.altKey) { return; |