/* global IntlHelper */ define(function(require, exports, module) { 'use strict'; var Overlap = require('utils/overlap'); var buildElement = require('utils/dom').buildElement; var core = require('core'); var isSameDate = require('common/calc').isSameDate; var relativeDuration = require('common/calc').relativeDuration; var relativeOffset = require('common/calc').relativeOffset; var spanOfDay = require('common/calc').spanOfDay; var template = require('templates/multi_day'); var _id = 0; IntlHelper.define('longDateFormat', 'datetime', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); function SingleDay(config) { this.date = config.date; this._hourHeight = config.hourHeight; this._daysHolder = config.daysHolder; this._allDayIcon = config.allDayIcon; this._alldaysHolder = config.alldaysHolder; this._oneDayLabelFormat = config.oneDayLabelFormat; this._render = this._render.bind(this); this._instanceID = _id++; this.overlaps = new Overlap(); } module.exports = SingleDay; SingleDay.prototype = { _isActive: false, _borderWidth: 0.1, _attached: false, _observer: null, setup: function() { this.day = buildElement(template.day.render({ date: this.date })); this.allday = buildElement(template.allday.render({ date: this.date, id: this._instanceID })); this._dayName = this.allday.querySelector('.md__day-name'); this._alldayEvents = this.allday.querySelector('.md__allday-events'); this._updateDayName(); this.onactive(); }, _updateDayName: function() { // we can't use [data-l10n-date-format] because format might change based // on locale this._dayName.textContent = this.date.toLocaleString(navigator.languages, { weekday: 'short', day: 'numeric' }); }, handleEvent: function(evt) { switch(evt.type) { case 'localized': this._updateDayName(); break; } }, append: function() { this._daysHolder.appendChild(this.day); this._alldaysHolder.appendChild(this.allday); this._attached = true; }, onactive: function() { if (this._isActive) { return; } this._observer = core.bridge.observeDay(this.date); this._observer.listen(this._render); window.addEventListener('localized', this); this._isActive = true; }, _render: function(records) { this._alldayEvents.innerHTML = ''; records.allday.forEach(this._renderAlldayEvent, this); this.overlaps.reset(); this.day.innerHTML = ''; records.basic.forEach(this._renderEvent, this); if (this._alldayEvents.children.length > 0) { // If there are all day events, the section acts as a listbox. this._alldayEvents.setAttribute('role', 'listbox'); this._alldayEvents.setAttribute('aria-labelledby', this._allDayIcon.id + ' ' + this._dayName.id); } else { // If there are no all day events, the section acts as a create new all // day event button. this._alldayEvents.setAttribute('role', 'button'); this._alldayEvents.setAttribute('data-l10n-id', 'create-all-day-event'); this._alldayEvents.setAttribute('aria-describedby', this._dayName.id); } }, _renderEvent: function(record) { var el = this._buildEventElement(record); var {busytime} = record; var {startDate, endDate} = busytime; // we don't set the height on the template because we use the same markup // for all day events as well var duration = relativeDuration(this.date, startDate, endDate); // we subtract border to keep a margin between consecutive events var hei = duration * this._hourHeight - this._borderWidth; // It doesn't make sense to set height less than 1 px, so we set it to 1 px // in order to show events correctly. hei = Math.max(1, hei); el.style.height = hei + 'px'; if (duration < 1) { el.classList.add('is-partial'); var size = ''; // we need to toggle layout if event lasts less than 20, 30 and 45min if (duration < 0.3) { size = 'micro'; } else if (duration < 0.5) { size = 'tiny'; } else if (duration < 0.75) { size = 'small'; } if (size) { el.classList.add('is-partial-' + size); } } var offset = relativeOffset(this.date, startDate); el.style.top = (offset * this._hourHeight) + 'px'; this.overlaps.add(busytime, el); this.day.appendChild(el); }, _buildEventElement: function(record) { var {title, location, alarms} = record.event.remote; var {startDate, endDate, _id} = record.busytime; // screen reader should be aware if the event spans multiple dates and also // know the event duration without having the open it var labelFormat = isSameDate(startDate, endDate) ? this._oneDayLabelFormat : 'event-multiple-day-duration'; var longDateFormat = IntlHelper.get('longDateFormat'); var shortTimeFormat = IntlHelper.get('shortTimeFormat'); var labelFormatArgs = JSON.stringify({ startDate: longDateFormat.format(startDate), startTime: shortTimeFormat.format(startDate), endDate: longDateFormat.format(endDate), endTime: shortTimeFormat.format(endDate) }); return buildElement(template.event.render({ id: _id, instance: this._instanceID, title: title, location: location, hasAlarms: alarms && alarms.length, color: record.color, labelFormat: labelFormat, labelFormatArgs: labelFormatArgs })); }, _renderAlldayEvent: function(record) { var el = this._buildEventElement(record); el.classList.add('is-allday'); el.setAttribute('role', 'option'); this._alldayEvents.appendChild(el); }, destroy: function() { this.oninactive(); this._detach(); }, _detach: function() { if (this._attached) { this._daysHolder.removeChild(this.day); this._alldaysHolder.removeChild(this.allday); this._attached = false; } }, oninactive: function() { if (!this._isActive) { return; } this._observer.cancel(); window.removeEventListener('localized', this); this._isActive = false; }, setVisibleForScreenReader: function(visibleRange) { var visible = visibleRange.contains(spanOfDay(this.date)); this.day.setAttribute('aria-hidden', !visible); this.allday.setAttribute('aria-hidden', !visible); } }; });