/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; /** * Initialize the Calendar and generate nodes for week headers and days, and * attach event listeners. * * @param {Object} options * { * {Number} calViewSize: Number of days to appear on a calendar view * {Function} getDayString: Transform day number to string * {Function} getWeekHeaderString: Transform day of week number to string * {Function} setSelection: Set selection for dateKeeper * } * @param {Object} context * { * {DOMElement} weekHeader * {DOMElement} daysView * } */ function Calendar(options, context) { const DAYS_IN_A_WEEK = 7; this.context = context; this.state = { days: [], weekHeaders: [], setSelection: options.setSelection, getDayString: options.getDayString, getWeekHeaderString: options.getWeekHeaderString }; this.elements = { weekHeaders: this._generateNodes(DAYS_IN_A_WEEK, context.weekHeader), daysView: this._generateNodes(options.calViewSize, context.daysView) }; this._attachEventListeners(); } { Calendar.prototype = { /** * Set new properties and render them. * * @param {Object} props * { * {Boolean} isVisible: Whether or not the calendar is in view * {Array<Object>} days: Data for days * { * {Date} dateObj * {Number} content * {Array<String>} classNames * {Boolean} enabled * } * {Array<Object>} weekHeaders: Data for weekHeaders * { * {Number} content * {Array<String>} classNames * } * } */ setProps(props) { if (props.isVisible) { // Transform the days and weekHeaders array for rendering const days = props.days.map(({ dateObj, content, classNames, enabled }) => { return { dateObj, textContent: this.state.getDayString(content), className: classNames.join(" "), enabled }; }); const weekHeaders = props.weekHeaders.map(({ content, classNames }) => { return { textContent: this.state.getWeekHeaderString(content), className: classNames.join(" ") }; }); // Update the DOM nodes states this._render({ elements: this.elements.daysView, items: days, prevState: this.state.days }); this._render({ elements: this.elements.weekHeaders, items: weekHeaders, prevState: this.state.weekHeaders, }); // Update the state to current this.state.days = days; this.state.weekHeaders = weekHeaders; } }, /** * Render the items onto the DOM nodes * @param {Object} * { * {Array<DOMElement>} elements * {Array<Object>} items * {Array<Object>} prevState: state of items from last render * } */ _render({ elements, items, prevState }) { for (let i = 0, l = items.length; i < l; i++) { let el = elements[i]; // Check if state from last render has changed, if so, update the elements if (!prevState[i] || prevState[i].textContent != items[i].textContent) { el.textContent = items[i].textContent; } if (!prevState[i] || prevState[i].className != items[i].className) { el.className = items[i].className; } } }, /** * Generate DOM nodes * * @param {Number} size: Number of nodes to generate * @param {DOMElement} context: Element to append the nodes to * @return {Array<DOMElement>} */ _generateNodes(size, context) { let frag = document.createDocumentFragment(); let refs = []; for (let i = 0; i < size; i++) { let el = document.createElement("div"); el.dataset.id = i; refs.push(el); frag.appendChild(el); } context.appendChild(frag); return refs; }, /** * Handle events * @param {DOMEvent} event */ handleEvent(event) { switch (event.type) { case "click": { if (event.target.parentNode == this.context.daysView) { let targetId = event.target.dataset.id; let targetObj = this.state.days[targetId]; if (targetObj.enabled) { this.state.setSelection(targetObj.dateObj); } } break; } } }, /** * Attach event listener to daysView */ _attachEventListeners() { this.context.daysView.addEventListener("click", this); } }; }