diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e649f7..691ed7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: notification: name: Slack notification - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest if: always() needs: [ci] @@ -36,12 +36,22 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} steps: - - name: Send notification - uses: edge/simple-slack-notify@master - with: - channel: "#ci" - username: CI - status: ${{ (contains(needs.*.result, 'cancelled') && 'cancelled') || (contains(needs.*.result, 'failure') && 'failure') || 'success' }} - success_text: ":octocat: <${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}|Build #${env.GITHUB_RUN_NUMBER}> of *${env.GITHUB_REPOSITORY}@${{ github.ref_name }}* by *${env.GITHUB_ACTOR}* completed successfully." - failure_text: ":octocat: <${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}|Build #${env.GITHUB_RUN_NUMBER}> of *${env.GITHUB_REPOSITORY}@${{ github.ref_name }}* by *${env.GITHUB_ACTOR}* failed." - cancelled_text: ":octocat: <${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}|Build #${env.GITHUB_RUN_NUMBER}> of *${env.GITHUB_REPOSITORY}@${{ github.ref_name }}* by *${env.GITHUB_ACTOR}* was cancelled." + - run: | + successText=":octocat: <${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|Versie ${{ inputs.version }}> uitgerold naar *${{ inputs.environment }}*." + failureText=":octocat: <${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|Versie ${{ inputs.version }}> niet uitgerold naar *${{ inputs.environment }}*." + cancelledText=":octocat: <${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}|Versie ${{ inputs.version }}> uitrol naar *${{ inputs.environment }}* geannuleerd." + status="${{ (contains(needs.*.result, 'cancelled') && 'cancelled') || (contains(needs.*.result, 'failure') && 'failure') || 'success' }}" + + if [ "$status" = 'success' ]; then + color='good' + text=$successText + elif [ "$status" = 'failure' ]; then + color='danger' + text=$failureText + elif [ "$status" = "cancelled" ]; then + color='warning' + text=$cancelledText + fi + + curl "${{ secrets.SLACK_WEBHOOK_URL }}" -X "POST" --header "Content-Type: application/json" \ + --data "{attachments: [{text: '$text', color: '$color'}]}" diff --git a/README.md b/README.md index 433eb10..f05534e 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,18 @@ ### JavaScript utility library -- No dependencies -- Choose between `checkbox` or `radio` inputs -- Customizable cookie types (identifiers, optional/required, pre-checked) -- Conditional script tags, iframes and elements based on cookie consent and type +- No dependencies +- Customizable cookie types (identifiers, optional/required, pre-checked) +- Conditional script tags, iframes and elements based on cookie consent and type -Screenshot of the GDPR proof cookie consent dialog from @grrr/cookie-consent with checkbox inputsScreenshot of the GDPR proof cookie consent dialog from @grrr/cookie-consent with radio inputs +Screenshot of the GDPR proof cookie consent dialog from @grrr/cookie-consent with checkbox inputs ### Developed with ❤️ by [GRRR](https://grrr.nl) -- GRRR is a [B Corp](https://grrr.nl/en/b-corp/) -- GRRR has a [tech blog](https://grrr.tech/) -- GRRR is [hiring](https://grrr.nl/en/jobs/) -- [@GRRRTech](https://twitter.com/grrrtech) tweets +- GRRR is a [B Corp](https://grrr.nl/en/b-corp/) +- GRRR has a [tech blog](https://grrr.tech/) +- GRRR is [hiring](https://grrr.nl/en/jobs/) +- [@GRRRTech](https://twitter.com/grrrtech) tweets ## Installation @@ -24,186 +23,123 @@ $ npm install @grrr/cookie-consent ``` -Note: depending on your setup [additional configuration might be needed](https://github.com/grrr-amsterdam/cookie-consent/wiki/Usage-with-build-tools). This package is published with untranspiled JavaScript, as EcmaScript Modules (ESM). +## Custom element + +This cookie-consent module is a [custom element](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements). This also means that the element is encapsulated in a [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM). Here follows some information of how to implement this custom element in your own project. ## Usage -Import the module and initialize it: +Import the module and register it as a custom element: ```js -import CookieConsent from '@grrr/cookie-consent'; +import CookieConsent from "@grrr/cookie-consent"; + +if (window.customElements.get("cookie-consent") === undefined) { + window.customElements.define("cookie-consent", cookieConsent); +} +``` + +Once registered, you can add the cookie-consent element to your HTML there's some optional data you can pass to the element but the only required attribute to pass along are the cookies: -const cookieConsent = CookieConsent({ - cookies: [ +```js +const cookies = [ { - id: 'functional', - label: 'Functional', - description: 'Lorem ipsum.', - required: true, - }, + id: "functional", // string + label: functionalCookiesLabel, // string + description: functionalCookiesDescription, // string + required: true, // boolean + }, { - id: 'marketing', - label: 'Marketing', - description: 'Lorem ipsum.', - checked: true, + id: "marketing", // string + label: marketingCookiesLabel, // string + description: marketingCookiesDescription, // string + checked: marketingCookiesAccepted, // boolean }, - ], -}); +]; +// in order to pass these as a data-attribute we'll need to transform them to a string first +const stringifiedCookies = JSON.stringify(cookies); ``` -### Conditional scripts - -Conditionally show `script` tags. Add the `data-cookie-consent`-attribute with the id of the required cookie type, and disable the script by setting the `type` to `text/plain`: - ```html -// External script. - - -// Inline script. - +; ``` -### Conditional iframe embeds +## Options -Conditionally show or hide `iframe` embed. Add the `data-cookie-consent`-attribute with the id of the required cookie consent type, and disable the iframe renaming the `src`-attribute to `data-src`: +As mentioned before there is some optional data you can pass to the element: -```html - -``` +- title `string` +- description `string` +- save button text `string` -### Conditional content +To use the options, add them as data attributes to the custom element: -Conditionally show or hide elements. Add the `data-cookie-consent-`-attribute with the id of the required cookie consent type. There are two types of state: `accepted` and `rejected`. +```js + -```html - - ``` -Notes: - -- When hiding, the module will add `aria-hidden="true"` and `style="display: none;"` to remove it from the DOM. -- When showing, the module will remove any inline set `display` style, along with any `hidden` or `aria-hidden` attributes. - -## Options - All options except `cookies` are optional. They will fall back to the defaults, which are listed here: ```js -{ - type: 'checkbox', // Can be `checkbox` or `radio`. - prefix: 'cookie-consent', // The prefix used for styling and identifiers. - append: true, // By default the dialog is appended before the `main` tag or - // as the first `body` child. Disable to append it yourself. - appendDelay: 500, // The delay after which the cookie consent should be appended. - acceptAllButton: false, // Nudge users to accept all cookies when nothing is selected. - // Will select all checkboxes, or the top radio button. - cookies: [ // Array with cookie types. - { - id: 'marketing', // The unique identifier of the cookie type. - label: 'Marketing', // The label used in the dialog. - description: '...', // The description used in the dialog. - required: false, // Mark a cookie required (ignored when type is `radio`). - checked: false, // The default checked state (only valid when not `required`). - }, - ], - // If you need to override the dialog template (default defined in renderDialog) - dialogTemplate: function(templateVars) { - return '...' - }, - // Labels to provide content for the dialog. - labels: { - title: 'Cookies & Privacy', - description: `

This site makes use of third-party cookies. Read more in our - privacy policy.

`, - // Button labels based on state and preferences. - button: { - // The default button label. - default: 'Save preferences', - // Shown when `acceptAllButton` is set, and no option is selected. - acceptAll: 'Accept all', - }, - // ARIA labels to improve accessibility. - aria: { - button: 'Confirm cookie settings', - tabList: 'List with cookie types', - tabToggle: 'Toggle cookie tab', +export const DEFAULTS = { + prefix: "cookie-consent", + append: true, + appendDelay: 500, + acceptAllButton: false, + labels: { + title: "Cookies & Privacy", + description: + '

This site makes use of third-party cookies. Read more in our privacy policy.

', + button: { + default: "Save preferences", + acceptAll: "Accept all", + }, + aria: { + button: "Confirm cookie settings", + tabList: "List with cookie types", + tabToggle: "Toggle cookie tab", + }, }, - }, -} +}; ``` ## API -- [CookieConsent()](#cookieconsentoptions-object) -- [getDialog()](#getdialog) -- [showDialog()](#showdialog) -- [hideDialog()](#hidedialog) -- [isAccepted()](#isacceptedid-string) -- [getPreferences()](#getpreferences) -- [on()](#on) -- [updatePreference()](#updatePreferencecookies-array) - -### CookieConsent(options: object) - -Will create a new instance. - -```js -const cookieConsent = CookieConsent({ - cookies: [ - // ... - ] -}); -``` - -To make the instance globally available (for instance to add event listeners elsewhere), add it as a global after the instance has been created: +- [show()](#show) +- [hide()](#hide) +- [getPreferences()](#getpreferences) +- [updatePreference()](#updatePreferencecookies-array) +- [on()](#on) -```js -const cookieConsent = CookieConsent(); - -window.CookieConsent = cookieConsent; -``` - -### getDialog() - -Will fetch the dialog element, for example to append it at a custom DOM position. - -```js -document.body.insertBefore(cookieConsent.getDialog(), document.body.firstElementChild); -``` - -### showDialog() +### show() Will show the dialog element, for example to show it when triggered to change settings. ```js -el.addEventListener('click', e => { - e.preventDefault(); - cookieConsent.showDialog(); +button.addEventListener("click", (e) => { + e.preventDefault(); + cookieConsent.show(); }); ``` -### hideDialog() +### hide() Will hide the dialog element. ```js -el.addEventListener('click', e => { - e.preventDefault(); - cookieConsent.hideDialog(); +button.addEventListener("click", (e) => { + e.preventDefault(); + cookieConsent.hide(); }); ``` -### isAccepted(id: string) - -Check if a certain cookie type has been accepted. Will return `true` when accepted, `false` when denied, and `undefined` when no action has been taken. - -```js -const acceptedMarketing = cookieConsent.isAccepted('marketing'); // => true, false, undefined -``` - ### getPreferences() Will return an array with preferences per cookie type. @@ -223,16 +159,6 @@ const preferences = cookieConsent.getPreferences(); // ] ``` -### on(event: string) - -Add listeners for events. Will fire when the event is dispatched from the CookieConsent module. -See available [events](#events). - -```js -cookieConsent.on('event', eventHandler); -``` - - ### updatePreference(cookies: array) Update cookies programmatically. @@ -242,29 +168,36 @@ By updating cookies programmatically, the event handler will receive an update m ```js const cookies = [ { - id: 'marketing', - label: 'Marketing', - description: '...', - required: false, - checked: true, + id: "marketing", + label: "Marketing", + description: "...", + required: false, + checked: true, }, { - id: 'simple', - label: 'Simple', - description: '...', - required: false, - checked: false, + id: "simple", + label: "Simple", + description: "...", + required: false, + checked: false, }, ]; +``` + +### on(event: string) -cookieConsent.updatePreference(cookies); +Add listeners for events. Will fire when the event is dispatched from the CookieConsent module. +See available [events](#events). + +```js +cookieConsent.on("event", eventHandler); ``` ## Events Events are bound by the [on](#onevent-string) method. -- [update](#update) +- [update](#update) ### update @@ -275,13 +208,15 @@ This event can be used to fire tag triggers for each cookie type, for example vi Example: ```js -cookieConsent.on('update', cookies => { - const accepted = cookies.filter(cookie => cookie.accepted); - const dataLayer = window.dataLayer || []; - accepted.forEach(cookie => dataLayer.push({ - event: 'cookieConsent', - cookieType: cookie.id, - })); +cookieConsent.on("update", (cookies) => { + const accepted = cookies.filter((cookie) => cookie.accepted); + const dataLayer = window.dataLayer || []; + accepted.forEach((cookie) => + dataLayer.push({ + event: "cookieConsent", + cookieType: cookie.id, + }) + ); }); ``` @@ -289,14 +224,80 @@ cookieConsent.on('update', cookies => { No styling is being applied by the JavaScript module. However, there is a default stylesheet in the form of a [Sass](https://sass-lang.com/) module which can easily be added and customized to your project and its needs. +You have to use the `::parts` pseudo-element to style the dialog and its elements due to the Shadow DOM encapsulation. You can style the dialog and its elements by using the following parts: + +```scss +cookie-consent::part(cookie-consent) { + // Styles for the cookie consent dialog +} + +/** + * Header + */ +cookie-consent::part(cookie-consent__header) { + // Styles for the cookie consent header +} +cookie-consent::part(cookie-consent__title) { + // Styles for the cookie consent title +} + +/** + * Tabs + */ +cookie-consent::part(cookie-consent__tab-list) { + // Styles for the cookie consent tab list +} +cookie-consent::part(cookie-consent__tab-list-item) { + // Styles for the cookie consent tab list item +} +cookie-consent::part(cookie-consent__tab) { + // Styles for the cookie consent tabs +} + +/** + * Tab option (label with input in it) & tab toggle + */ +cookie-consent::part(cookie-consent__option) { + // Styles for the tab option label +} +cookie-consent::part(cookie-consent__input) { + // Styles for the tab option input +} +cookie-consent::part(cookie-consent__tab-toggle) { + // Styles for the tab toggle +} +cookie-consent::part(cookie-consent__tab-toggle-icon) { + // Styles for the tab toggle icon +} + +/** + * Tab panel (with description) + */ +cookie-consent::part(cookie-consent__tab-panel) { + // Styles for the tab panel +} + +cookie-consent::part(cookie-consent__tab-description) { + // Styles for the tab description +} + +/** + * Button + */ +cookie-consent::part(cookie-consent__button) { + // styles for the consent button +} +cookie-consent::part(cookie-consent__button-text) { + // Styles for the consent button text +} +``` + ### Stylesheet View the [base stylesheet](https://github.com/grrr-amsterdam/cookie-consent/tree/master/styles/cookie-consent.scss). -Note: no vendor prefixes are applied. We recommend using something like [Autoprefixer](https://github.com/postcss/autoprefixer) to do that automatically. - ### Interface With the styling from the base module applied, the interface will look roughly like this (fonts, sizes and margins might differ): -Screenshot of the GDPR proof cookie consent dialog from @grrr/cookie-consent +Screenshot of the GDPR proof cookie consent dialog from @grrr/cookie-consent with checkbox inputs diff --git a/index.mjs b/index.mjs index 02e1bbb..673cf5f 100644 --- a/index.mjs +++ b/index.mjs @@ -1,3 +1,3 @@ -import CookieConsent from './src/cookie-consent.mjs'; +import CookieConsent from "./src/cookie-consent.mjs"; export default CookieConsent; diff --git a/src/config-defaults.mjs b/src/config-defaults.mjs index ed7673c..79a6a09 100644 --- a/src/config-defaults.mjs +++ b/src/config-defaults.mjs @@ -1,20 +1,20 @@ export const DEFAULTS = { - type: 'checkbox', - prefix: 'cookie-consent', + prefix: "cookie-consent", append: true, appendDelay: 500, acceptAllButton: false, labels: { - title: 'Cookies & Privacy', - description: '

This site makes use of third-party cookies. Read more in our privacy policy.

', + title: "Cookies & Privacy", + description: + '

This site makes use of third-party cookies. Read more in our privacy policy.

', button: { - default: 'Save preferences', - acceptAll: 'Accept all', + default: "Save preferences", + acceptAll: "Accept all", }, aria: { - button: 'Confirm cookie settings', - tabList: 'List with cookie types', - tabToggle: 'Toggle cookie tab', + button: "Confirm cookie settings", + tabList: "List with cookie types", + tabToggle: "Toggle cookie tab", }, }, }; diff --git a/src/config.mjs b/src/config.mjs index eb77611..a417ce6 100644 --- a/src/config.mjs +++ b/src/config.mjs @@ -1,10 +1,10 @@ -import { DEFAULTS } from './config-defaults.mjs'; -import { getEntryByDotString } from './utils.mjs'; +import { DEFAULTS } from "./config-defaults.mjs"; +import { getEntryByDotString } from "./utils.mjs"; /** * Config getter with defaults fallback and warning when required values are missing. */ -const Config = settings => { +const Config = (settings) => { return { get: (entryString, required = false) => { const value = getEntryByDotString(settings, entryString); @@ -12,7 +12,9 @@ const Config = settings => { console.warn(`Required setting '${entryString}' is missing.`); return undefined; } - return value === undefined ? getEntryByDotString(DEFAULTS, entryString) : value; + return value === undefined + ? getEntryByDotString(DEFAULTS, entryString) + : value; }, }; }; diff --git a/src/cookie-consent.mjs b/src/cookie-consent.mjs index 2959b73..41c9fcd 100644 --- a/src/cookie-consent.mjs +++ b/src/cookie-consent.mjs @@ -1,65 +1,264 @@ -import Config from './config.mjs'; -import Dialog from './dialog.mjs'; -import DomToggler from './dom-toggler.mjs'; -import EventDispatcher from './event-dispatcher.mjs'; -import Preferences from './preferences.mjs'; +/* eslint class-methods-use-this:[ + "error", + { + "exceptMethods": + [ + "getConfig", + "initEventDispatcher", + "getPreferences", + "initDomToggler" + ] + } +] */ + +import { htmlToElement, preventingDefault } from "@grrr/utils"; +import EventDispatcher from "./event-dispatcher.mjs"; +import DialogTabList from "./dialog-tablist.mjs"; +import DomToggler from "./dom-toggler.mjs"; + +import Config from "./config.mjs"; +import Preferences from "./preferences.mjs"; /** - * Main constructor, which provides the API to the outside. + * Dialog which is shown to update cookie preferences. */ -const CookieConsent = settings => { - - // Show warning when settings are missing. - if (typeof settings !== 'object' || !Object.keys(settings).length) { - console.warn(`No settings specified.`); - } - - // Construct 'classes'. - const config = Config(settings); - const preferences = Preferences(config.get('prefix')); - const dialog = Dialog({ config, preferences }); - const domToggler = DomToggler(config); - const events = EventDispatcher(); - - // Update initial content. - domToggler.toggle(preferences); - - const updatePreference = (cookies) => { - preferences.store(cookies); - events.dispatch('update', preferences.getAll()); - domToggler.toggle(preferences); - }; - - // Initialize dialog and bind `submit` event. - dialog.init(); - dialog.on('submit', updatePreference); - - // Append dialog to the DOM, if this is not explicitly prevented. - if (config.get('append') !== false) { - const appendEl = document.querySelector('main') || document.body.firstElementChild; - const container = appendEl ? appendEl.parentNode : document.body; - container.insertBefore(dialog.element, appendEl); - } - - // Show the dialog when no preferences are found. If found, fire the `update` event. - if (preferences.hasPreferences()) { - events.dispatch('update', preferences.getAll()); - } else { - // Show the dialog. Invoked via a timeout, to ensure it's added in the next cycle - // to cater for possible transitions. - window.setTimeout(() => dialog.show(), config.get('appendDelay')); - } - - return { - getDialog: () => dialog.element, - hideDialog: dialog.hide, - showDialog: dialog.show, - isAccepted: preferences.getState, - getPreferences: preferences.getAll, - on: events.add, - updatePreference, - }; - -}; - -export default CookieConsent; +export default class Dialog extends HTMLElement { + constructor() { + // Always call super first in constructor + super(); + // sets and returns 'this.shadowRoot' + this.attachShadow({ mode: "open" }); + // get data + this.data = this.getData(); + // get config + this.config = this.getConfig(); + // initialize event dispatcher + this.events = this.initEventDispatcher(); + // initialize teblist + this.tabList = this.initTabList(); + // get cookies + this.cookies = this.data.cookies; + // generate dialog element + this.dialogElement = this.generateDialogElement(); + // append dialog to shadowRoot + this.shadowRoot.append(this.dialogElement); + // get preferences + this.preferences = this.getPreferences(); + // initialize domtoggler + this.domToggler = this.initDomToggler(); + // initialize show and hide + this.show = this.show(); + this.hide = this.hide(); + // get all preferences + this.preferences.getAll(); + + this.domToggler.toggle(this.preferences); + + // if cookie prefs already selected dispatch update event and hide + if (this.preferences.hasPreferences()) { + this.events.dispatch("update", this.preferences.getAll()); + } + + // add submit event + this.events.add("submit", this.updatePreference.bind(this)); + } + + getData() { + // fallback content from config + const fallbackContent = { + title: Config().get("labels.title"), + description: Config().get("labels.description"), + saveButtonText: Config().get("labels.aria.button"), + defaultButtonLabel: Config().get("labels.button.default"), + acceptAllButton: + Config().get("acceptAllButton") + && !Preferences().hasPreferences(), + }; + // custom content from data-attributes + const customContent = { + title: this.getAttribute("data-title"), + description: this.getAttribute("data-description"), + saveButtonText: this.getAttribute("data-saveButtonText"), + }; + // parse cookies to json + const cookies = JSON.parse(this.getAttribute("data-cookies")); + + return { + title: + customContent.title === null + ? fallbackContent.title + : customContent.title, + description: + customContent.description === null + ? fallbackContent.description + : customContent.description, + saveButtonText: + customContent.saveButtonText === null + ? fallbackContent.defaultButtonLabel + : customContent.saveButtonText, + acceptAllButton: fallbackContent.acceptAllButton, + cookies, + }; + } + + getConfig() { + return { + type: Config().get("type"), + prefix: Config().get("prefix"), + dialogTemplate: Config().get("dialogTemplate"), + }; + } + + initEventDispatcher() { + return EventDispatcher(); + } + + initTabList() { + return DialogTabList(this.data.cookies); + } + + generateDialogElement() { + // Initialize tab list and append it to the form. + this.tabList.init(); + + const template = ` + `; + + const dialogElement = htmlToElement(template); + + dialogElement.insertAdjacentHTML( + "afterbegin", + ` + ` + ); + + const formElement = dialogElement.lastElementChild; + + formElement.addEventListener( + "submit", + preventingDefault(this.submitHandler.bind(this)) + ); + + dialogElement.insertBefore(this.tabList.element, formElement); + + return dialogElement; + } + + submitHandler(e) { + e.preventDefault(); + // Get values based on the rules defined in `composeValues`. + const values = this.composeValues(this.tabList.getValues()); + + if (!values) { + return; + } + + // Dispatch values and hide the dialog. + this.events.dispatch("submit", values); + // toggleDialogVisibility(this.firstElementChild).hide(); + this.hide(); + } + + composeValues(values) { + // Checkbox with `acceptAllButton` and no user-choosable option is checked. + // We compare amount of required options against checked options. + + const requiredCount = this.data.cookies.filter((c) => c.required).length; + const checkedCount = values.filter((v) => v.accepted).length; + const userOptionsChecked = checkedCount >= requiredCount; + if ( + this.data.acceptAllButton + && this.config.type === "checkbox" + && !userOptionsChecked + ) { + return values.map((value) => ({ + ...value, + accepted: true, + })); + } + + // Return the values untouched. Happens for: + // - Checkbox with or without checked option, except the `acceptAllButton` case above. + return values; + } + + getPreferences() { + const preferences = Preferences(Config().get("prefix")); + + return preferences; + } + + updatePreference(selectedCookies) { + this.preferences.store(selectedCookies); + this.events.dispatch("update", this.preferences.getAll()); + this.domToggler.toggle(this.preferences); + } + + initDomToggler() { + return DomToggler(Config()); + } + + show() { + return () => + this.shadowRoot + .querySelector(".cookie-consent") + .setAttribute("aria-hidden", "false"); + } + + hide() { + return () => + this.shadowRoot + .querySelector(".cookie-consent") + .setAttribute("aria-hidden", "true"); + } + + on(type, payload) { + return this.events.add(type, payload); + } + + static get observedAttributes() { + return ["data-cookies"]; + } + + attributeChangedCallback(attrName, oldValue, newValue) { + // Set this.cookies to the updated value + this.cookies = JSON.parse(newValue); + // Transform NodeList to array + const arrayfiedTabList = Array.from(this.tabList.element.children); + // Filter out all li elements + const tabListChildren = arrayfiedTabList.filter( + (item) => item.nodeName === "LI" + ); + // Loop through arrayfiedTabListChildren + tabListChildren.forEach((input) => { + // Find all input elements + const inputElement = input.firstElementChild.firstElementChild.firstElementChild; + // Loop through updated cookies + this.cookies.forEach((cookie) => { + // set the checked state to the updated cookie state + if (inputElement.value === cookie.id && cookie.checked) { + inputElement.checked = true; + } + }); + }); + } +} diff --git a/src/dialog-tablist.mjs b/src/dialog-tablist.mjs index 41e1355..c84a178 100644 --- a/src/dialog-tablist.mjs +++ b/src/dialog-tablist.mjs @@ -1,21 +1,24 @@ -import { htmlToElement } from '@grrr/utils'; -import EventDispatcher from './event-dispatcher.mjs'; +import { htmlToElement } from "@grrr/utils"; +import EventDispatcher from "./event-dispatcher.mjs"; + +import Config from "./config.mjs"; +import Preferences from "./preferences.mjs"; /** * Dialog tab list with cookie tabs. */ -const DialogTabList = ({ config, preferences }) => { - +const DialogTabList = (cookieInformation) => { const events = EventDispatcher(); - const TYPE = config.get('type'); - const PREFIX = config.get('prefix'); + const PREFIX = Config().get("prefix"); /** * Render cookie tabs. */ - const renderTab = ({ id, label, description, required, checked, accepted }, index) => { - + const renderTab = ( + { id, label, description, required, checked, accepted }, + index + ) => { /** * Check if the checkbox should be checked: * @@ -24,37 +27,54 @@ const DialogTabList = ({ config, preferences }) => { * `required: false`, because of #3) * 3. Use the `checked` setting. */ - const shouldBeChecked = typeof accepted !== 'undefined' + const shouldBeChecked = typeof accepted !== "undefined" ? accepted : required === true ? required : checked; return ` -
  • -
    -