diff --git a/src/components/organisms/Calendar/Calendar.js b/src/components/organisms/Calendar/Calendar.js index 2fee08cb..d4027c22 100644 --- a/src/components/organisms/Calendar/Calendar.js +++ b/src/components/organisms/Calendar/Calendar.js @@ -95,6 +95,7 @@ class Calendar extends HTMLElement { // eslint-disable-next-line no-console console.error(`Failed to parse list of events:\n${eventsJSON}`); } + events = this.applyEventPropertyRules(events); const eventSource = { id: 'eventsArray', events: events, @@ -102,6 +103,36 @@ class Calendar extends HTMLElement { return eventSource; } + /** + * Apply additional event properties as defined by event + * property rules supplied in the `event-property-rules` attribute. + * + * @param {Object[]} events - An array of pre-parsed + * event objects. See https://fullcalendar.io/docs/event-parsing. + * @returns {Object[]} An array of pre-parsed event objects. + */ + applyEventPropertyRules(events) { + const eventPropertyRules = this.getEventPropertyRules(); + for (const property in eventPropertyRules) { + events = events.map((event) => { + const eventPropertyValue = event[property]; + if ( + !Object.prototype.hasOwnProperty.call( + eventPropertyRules[property], + eventPropertyValue, + ) + ) { + return event; + } + return { + ...event, + ...eventPropertyRules[property][eventPropertyValue], + }; + }); + } + return events; + } + /** * Replaces the 'eventsArray' event source with a new event source. * @@ -132,7 +163,6 @@ class Calendar extends HTMLElement { * @param {string} [newFiltersJSON=null] - A JSON serialized array of event filter definitions. * If null, filters will be fetched from the 'event-filters' attribute on the * component instead. - * @returns void */ buildEventFilters(newFiltersJSON = null) { const calendarFilterElt = this.shadowRoot.getElementById('calendarFilters'); @@ -164,7 +194,6 @@ class Calendar extends HTMLElement { * @param {HTMLElement} calendarFilterElt - An HTML element to be used * as the container for the event filter created. * @param {Object} filter - A single event filter object. - * @returns void */ buildEventFilter(calendarFilterElt, filter) { switch (filter.type) { @@ -198,6 +227,8 @@ class Calendar extends HTMLElement { const radioButtonLabel = document.createElement('label'); radioButtonLabel.setAttribute('for', value); radioButtonLabel.classList.add('btn', 'btn-primary'); + const eventRule = this.getEventPropertyRule(filter.key, value); + this.applyFilterPropertyRule(radioButtonLabel, eventRule); radioButtonLabel.innerText = value; radioButtonContainer.appendChild(radioButtonLabel); radioFiltersContainer.appendChild(radioButtonContainer); @@ -214,6 +245,31 @@ class Calendar extends HTMLElement { } } + /** + * Applies necessary styles based on the event property rule supplied. + * + * @param {HTMLElement} filterElement - The HTML element where the filter + * property rule will be applied. + * @param {Object} eventPropertyRule - An event property rule to be used as + * the filter property. + */ + applyFilterPropertyRule(filterElement, eventPropertyRule) { + for (const property in eventPropertyRule) { + switch (property) { + case 'backgroundColor': { + filterElement.style.backgroundColor = eventPropertyRule[property]; + break; + } + default: { + // TODO: Introduce proper error logging. + // eslint-disable-next-line no-console + console.warn(`Ignoring unsupported filter property: ${property}`); + return; + } + } + } + } + /** * Handles filter element events by filter down events to the * user-selected criteria. @@ -237,6 +293,44 @@ class Calendar extends HTMLElement { this.updateEventArraySource(JSON.stringify(events)); } + /** + * Get an event/filter property and value for a given event/filter key and + * value. + * @param {string} eventKey - The event key used to filter down filters + * and events for rules. + * @param {string} eventKeyValue - The value at the sepcified event key + * used to filter down filters and events for rules. + * @returns Object - An event property rule consisting of + * property keys and values to be applied to events and filters. + */ + getEventPropertyRule(eventKey, eventKeyValue) { + const rules = this.getEventPropertyRules(); + return rules[eventKey][eventKeyValue] ?? {}; + } + + /** + * Get all the event property rules from the `event-property-rules` + * attribute. + * @returns Object - All event property rules. + */ + getEventPropertyRules() { + const eventPropertyRulesJSON = this.getAttribute('event-property-rules'); + if (!eventPropertyRulesJSON) { + return {}; + } + let eventPropertyRules = {}; + try { + eventPropertyRules = JSON.parse(eventPropertyRulesJSON ?? '{}'); + } catch (error) { + // TODO: Introduce proper error logging. + // eslint-disable-next-line no-console + console.error( + `Failed to parse event property rules:\n${eventPropertyRulesJSON}`, + ); + } + return eventPropertyRules; + } + attributeChangedCallback(name, oldValue, newValue) { if (name in Calendar.observedAttributeCbs) { this.handleObservedAttribute( diff --git a/src/stories/calendar.stories.js b/src/stories/calendar.stories.js index 6d03b2c8..19a87c87 100644 --- a/src/stories/calendar.stories.js +++ b/src/stories/calendar.stories.js @@ -25,6 +25,23 @@ export default { } }`, }, + eventPropertyRules: { + control: { type: 'text' }, + description: `A JSON object of event properties to be applied to events matching + a specific criteria. The keys and value of the object correspond to keys and + values of the event and filters where the properties are applied. The object + associated with each key is a list of event properties and their values + as defined in https://fullcalendar.io/docs/event-object. + The rules should take the following form: + { + event_field_key: { + event_field_key_value: { + backgroundColor: '#000', + } + } + } + `, + }, }, args: { events: JSON.stringify([ @@ -60,6 +77,19 @@ export default { ], }, }), + eventPropertyRules: JSON.stringify({ + location: { + 'Say Detroit Play Center': { + backgroundColor: '#ff6c37', + }, + 'Senior Facility': { + backgroundColor: '#1f71bf', + }, + 'Detroit Housing Commission': { + backgroundColor: '#1ed760', + }, + }, + }), }, }; @@ -70,6 +100,9 @@ const Template = (args) => { if (args.eventFilters) { calendarElt.setAttribute('event-filters', args.eventFilters); } + if (args.eventPropertyRules) { + calendarElt.setAttribute('event-property-rules', args.eventPropertyRules); + } return calendarElt; };