Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
178 lines
5.5 KiB
JavaScript
Executable file
178 lines
5.5 KiB
JavaScript
Executable file
define(function(require, exports, module) {
|
|
'use strict';
|
|
|
|
// this will be replaced later for a better logic (see Bug 992728) but it was
|
|
// too much to do in a single patch, so for now we do a simple double tap
|
|
// without any visual feedback (similar to the old day view behavior)
|
|
|
|
var QueryString = require('querystring');
|
|
var absoluteOffsetTop = require('utils/dom').absoluteOffsetTop;
|
|
var closest = require('utils/dom').closest;
|
|
var createDay = require('common/calc').createDay;
|
|
|
|
function HourDoubleTap(options) {
|
|
this.main = options.main;
|
|
this.daysHolder = options.daysHolder;
|
|
this.alldaysHolder = options.alldaysHolder;
|
|
this.hourHeight = options.hourHeight;
|
|
|
|
this._onDayTap = this._onDayTap.bind(this);
|
|
this._onAllDayTap = this._onAllDayTap.bind(this);
|
|
this.removeAddEventLink = this.removeAddEventLink.bind(this);
|
|
}
|
|
module.exports = HourDoubleTap;
|
|
|
|
HourDoubleTap.prototype = {
|
|
|
|
_isActive: false,
|
|
|
|
_addEventLink: null,
|
|
|
|
setup: function() {
|
|
this._mainOffset = absoluteOffsetTop(this.main);
|
|
this.daysHolder.addEventListener('click', this._onDayTap);
|
|
this.alldaysHolder.addEventListener('click', this._onAllDayTap);
|
|
},
|
|
|
|
destroy: function() {
|
|
this.removeAddEventLink();
|
|
this.daysHolder.removeEventListener('click', this._onDayTap);
|
|
this.alldaysHolder.removeEventListener('click', this._onAllDayTap);
|
|
},
|
|
|
|
/**
|
|
* If the event target is an existing event, the method finds whether the tap
|
|
* was _really_ on the existing event, to account for possible event
|
|
* retargeting that's not desirable in that case.
|
|
*
|
|
* @param {MouseEvent} evt This is the event to check.
|
|
* @returns {Boolean} True if we tapped on a child of a day.
|
|
*/
|
|
_isEventRightOnEventLink(evt) {
|
|
var target = evt.target;
|
|
if (!target.classList.contains('md__day')) {
|
|
// Could be aggressive event fuzzing registering a tap on an existing
|
|
// event or the "add event" UI, where the tap is really below that item.
|
|
// See bug 1210201.
|
|
|
|
// In this case, offsetY is the vertical space between the top of the
|
|
// target element and the actual tap, so if the tap is bigger than the
|
|
// height of the element, or smaller than 0, the tap really happened out
|
|
// of the element.
|
|
var tappedOnExistingEvent =
|
|
// intentionally keeping retargeting behavior for the `add` button.
|
|
target.classList.contains('md__add-event') ||
|
|
target.classList.contains('md__event') &&
|
|
evt.offsetY >= 0 &&
|
|
evt.offsetY <= target.getBoundingClientRect().height;
|
|
|
|
if (tappedOnExistingEvent) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_onDayTap: function(evt) {
|
|
if (this._isEventRightOnEventLink(evt)) {
|
|
return;
|
|
} else {
|
|
// Having this in the `else` block is a hint to the reader that we could
|
|
// remove it if the event retargeting gets smarter and we don't need this
|
|
// `if` anymore.
|
|
|
|
// It works because the event handler for the <a> is actually on `window`
|
|
// and checks if the event was defaultPrevented. Check js/ext/page.js.
|
|
|
|
// Until Bug 1091889 gets fixed we'll still get the :active
|
|
// styles because they're being applied with `touchstart` and we can't
|
|
// defaultPrevent this event without breaking everything.
|
|
evt.preventDefault();
|
|
}
|
|
|
|
var target = evt.target.closest('.md__day');
|
|
|
|
var y = evt.clientY + this.main.scrollTop - this._mainOffset;
|
|
var hour = Math.floor(y / this.hourHeight);
|
|
var baseDate = new Date(target.dataset.date);
|
|
|
|
this._onTap(target, {
|
|
startDate: addHours(baseDate, hour).toString(),
|
|
endDate: addHours(baseDate, hour + 1).toString()
|
|
}, hour);
|
|
},
|
|
|
|
_onAllDayTap: function(evt) {
|
|
var target = evt.target;
|
|
if (!target.classList.contains('md__allday-events')) {
|
|
return;
|
|
}
|
|
|
|
var startDate = new Date(closest(target, '.md__allday').dataset.date);
|
|
|
|
this._onTap(target, {
|
|
isAllDay: true,
|
|
startDate: startDate.toString(),
|
|
endDate: createDay(startDate, startDate.getDate() + 1).toString()
|
|
}, null, evt.mozInputSource);
|
|
},
|
|
|
|
_onTap: function(container, data, hour, source) {
|
|
hour = hour || 0;
|
|
|
|
if (this._addEventLink) {
|
|
this.removeAddEventLink();
|
|
return;
|
|
}
|
|
|
|
var link = document.createElement('a');
|
|
link.href = '/event/add/?' + QueryString.stringify(data);
|
|
link.className = 'md__add-event gaia-icon icon-newadd';
|
|
link.dataset.l10nId = 'multi-day-new-event-link';
|
|
link.style.top = (hour * this.hourHeight) + 'px';
|
|
link.style.opacity = 0;
|
|
|
|
link.addEventListener('click', this.removeAddEventLink);
|
|
|
|
container.appendChild(link);
|
|
this._addEventLink = link;
|
|
|
|
// Initiated by a screen reader on double tap.
|
|
if (source === 0) {
|
|
link.click();
|
|
return;
|
|
}
|
|
|
|
// opacity will trigger transition, needs to happen after nextTick
|
|
setTimeout(() => {
|
|
this._addEventLink && (this._addEventLink.style.opacity = 1);
|
|
});
|
|
},
|
|
|
|
removeAddEventLink: function() {
|
|
var link = this._addEventLink;
|
|
if (!link) {
|
|
return;
|
|
}
|
|
|
|
link.removeEventListener('click', this.removeAddEventLink);
|
|
|
|
link.addEventListener('transitionend', function onTransitionEnd() {
|
|
link.removeEventListener('transitionend', onTransitionEnd);
|
|
link.parentNode && link.parentNode.removeChild(link);
|
|
});
|
|
link.style.opacity = 0;
|
|
|
|
this._addEventLink = null;
|
|
}
|
|
|
|
};
|
|
|
|
function addHours(date, hourDiff) {
|
|
var result = new Date(date);
|
|
result.setHours(result.getHours() + hourDiff);
|
|
return result;
|
|
}
|
|
|
|
});
|