|
13 | 13 | customElements.define( |
14 | 14 | 'prpl-badge-progress-bar', |
15 | 15 | class extends HTMLElement { |
16 | | - constructor( badgeId, points, maxPoints, brandingId = 0 ) { |
17 | | - // Get parent class properties |
| 16 | + /** |
| 17 | + * Observed attributes, defined the attributes that will trigger the attributeChangedCallback. |
| 18 | + */ |
| 19 | + static get observedAttributes() { |
| 20 | + return [ 'data-badge-id', 'data-points', 'data-max-points' ]; |
| 21 | + } |
| 22 | + |
| 23 | + /** |
| 24 | + * Constructor, ran when the element is instantiated. |
| 25 | + */ |
| 26 | + constructor() { |
18 | 27 | super(); |
19 | | - badgeId = badgeId || this.getAttribute( 'data-badge-id' ); |
20 | | - points = points || this.getAttribute( 'data-points' ); |
21 | | - maxPoints = maxPoints || this.getAttribute( 'data-max-points' ); |
22 | | - brandingId = brandingId || this.getAttribute( 'data-branding-id' ); |
23 | | - const progress = ( points / maxPoints ) * 100; |
24 | | - |
25 | | - this.innerHTML = ` |
26 | | - <div class="prpl-badge-progress-bar-container" style="padding: 1rem 0;"> |
27 | | - <div |
28 | | - class="prpl-badge-progress-bar" |
29 | | - style=" |
30 | | - width: 100%; |
31 | | - height: 1rem; |
32 | | - background-color: var(--prpl-color-gauge-remain); |
33 | | - border-radius: 0.5rem; |
34 | | - position: relative;" |
35 | | - > |
36 | | - <div |
37 | | - class="prpl-badge-progress-bar-progress" |
38 | | - style=" |
39 | | - width: ${ progress }%; |
40 | | - height: 100%; |
41 | | - background-color: var(--prpl-color-monthly); |
42 | | - border-radius: 0.5rem;" |
43 | | - ></div> |
44 | | - <prpl-badge |
45 | | - badge-id="${ badgeId }" |
46 | | - style=" |
47 | | - display:flex; |
48 | | - width: 7.5rem; |
49 | | - height: auto; |
50 | | - position: absolute; |
51 | | - left: calc(${ progress }% - 3.75rem); |
52 | | - top: -2.5rem;" |
53 | | - branding-id="${ brandingId }" |
54 | | - ></prpl-badge> |
55 | | - </div> |
56 | | - </div> |
57 | | - `; |
| 28 | + this.attachShadow( { mode: 'open' } ); |
| 29 | + this.state = { |
| 30 | + badgeId: this.getAttribute( 'data-badge-id' ) || '', |
| 31 | + points: parseInt( this.getAttribute( 'data-points' ) || 0 ), |
| 32 | + maxPoints: parseInt( |
| 33 | + this.getAttribute( 'data-max-points' ) || 10 |
| 34 | + ), |
| 35 | + }; |
58 | 36 | } |
59 | | - } |
60 | | -); |
61 | 37 |
|
62 | | -/** |
63 | | - * Update the previous month badge progress bar. |
64 | | - * |
65 | | - * @param {number} pointsDiff The points difference. |
66 | | - * |
67 | | - * @return {void} |
68 | | - */ |
69 | | -// eslint-disable-next-line no-unused-vars |
70 | | -const prplUpdatePreviousMonthBadgeProgressBar = ( pointsDiff ) => { |
71 | | - const progressBars = document.querySelectorAll( |
72 | | - '.prpl-previous-month-badge-progress-bar-wrapper prpl-badge-progress-bar' |
73 | | - ); |
74 | | - |
75 | | - // Bail early if no badge progress bars are found. |
76 | | - if ( ! progressBars.length ) { |
77 | | - return; |
78 | | - } |
| 38 | + /** |
| 39 | + * Get the points. |
| 40 | + */ |
| 41 | + get points() { |
| 42 | + return parseInt( this.state.points ); |
| 43 | + } |
79 | 44 |
|
80 | | - // Get the 1st incomplete badge progress bar. |
81 | | - const progressBar = |
82 | | - parseInt( progressBars[ 0 ]?.getAttribute( 'data-points' ) ) >= |
83 | | - parseInt( progressBars[ 0 ]?.getAttribute( 'data-max-points' ) ) |
84 | | - ? progressBars[ 1 ] |
85 | | - : progressBars[ 0 ]; |
| 45 | + /** |
| 46 | + * Set the points. |
| 47 | + */ |
| 48 | + set points( v ) { |
| 49 | + v = Math.max( 0, Math.min( v, this.maxPoints ) ); |
| 50 | + this.state.points = v; |
| 51 | + this.setAttribute( 'data-points', v ); |
| 52 | + } |
86 | 53 |
|
87 | | - // Bail early if no badge progress bar is found. |
88 | | - if ( ! progressBar ) { |
89 | | - return; |
90 | | - } |
| 54 | + /** |
| 55 | + * Get the max points. |
| 56 | + */ |
| 57 | + get maxPoints() { |
| 58 | + return parseInt( this.state.maxPoints ); |
| 59 | + } |
91 | 60 |
|
92 | | - // Get the badge progress bar properties. |
93 | | - const badgeId = progressBar.getAttribute( 'data-badge-id' ); |
94 | | - const badgePoints = progressBar.getAttribute( 'data-points' ); |
95 | | - const badgeMaxPoints = progressBar.getAttribute( 'data-max-points' ); |
96 | | - const badgeProgress = customElements.get( 'prpl-badge-progress-bar' ); |
97 | | - const badgeNewPoints = parseInt( badgePoints ) + pointsDiff; |
98 | | - const brandingId = progressBar.getAttribute( 'data-branding-id' ); |
99 | | - |
100 | | - // Create a new badge progress bar. |
101 | | - const newProgressBar = new badgeProgress( |
102 | | - badgeId, |
103 | | - badgeNewPoints, |
104 | | - badgeMaxPoints, |
105 | | - brandingId |
106 | | - ); |
107 | | - newProgressBar.setAttribute( 'data-badge-id', badgeId ); |
108 | | - newProgressBar.setAttribute( 'data-points', badgeNewPoints ); |
109 | | - newProgressBar.setAttribute( 'data-max-points', badgeMaxPoints ); |
110 | | - newProgressBar.setAttribute( 'data-branding-id', brandingId ); |
111 | | - |
112 | | - // Replace the old badge progress bar with the new one. |
113 | | - progressBar.replaceWith( newProgressBar ); |
114 | | - |
115 | | - // Update the remaining points. |
116 | | - const remainingPointsEl = document.querySelector( |
117 | | - `.prpl-previous-month-badge-progress-bar-wrapper[data-badge-id="${ badgeId }"] .prpl-previous-month-badge-progress-bar-remaining` |
118 | | - ); |
119 | | - |
120 | | - if ( remainingPointsEl ) { |
121 | | - // The points in the remaining points element are updated in the prplUpdatePreviousMonthBadgeCounters function. |
122 | | - |
123 | | - remainingPointsEl.setAttribute( |
124 | | - 'data-remaining', |
125 | | - badgeMaxPoints - badgeNewPoints |
126 | | - ); |
127 | | - } |
| 61 | + /** |
| 62 | + * Set the max points. |
| 63 | + */ |
| 64 | + set maxPoints( v ) { |
| 65 | + this.state.maxPoints = v; |
| 66 | + this.setAttribute( 'data-max-points', v ); |
| 67 | + } |
128 | 68 |
|
129 | | - // Update the previous month badge points number. |
130 | | - const badgePointsNumberEl = document.querySelector( |
131 | | - `.prpl-previous-month-badge-progress-bar-wrapper[data-badge-id="${ badgeId }"] .prpl-widget-previous-ravi-points-number` |
132 | | - ); |
133 | | - if ( badgePointsNumberEl ) { |
134 | | - badgePointsNumberEl.textContent = badgeNewPoints + 'pt'; |
135 | | - } |
| 69 | + /** |
| 70 | + * Get the progress percent. |
| 71 | + */ |
| 72 | + get progressPercent() { |
| 73 | + return ( this.points / this.maxPoints ) * 100; |
| 74 | + } |
136 | 75 |
|
137 | | - // If the previous month badge is completed, update badge elements. |
138 | | - if ( badgeNewPoints >= parseInt( badgeMaxPoints ) ) { |
139 | | - document |
140 | | - .querySelectorAll( |
141 | | - `.prpl-badge-row-wrapper .prpl-badge prpl-badge[complete="false"][badge-id="${ badgeId }"]` |
142 | | - ) |
143 | | - ?.forEach( ( badge ) => { |
144 | | - badge.setAttribute( 'complete', 'true' ); |
145 | | - } ); |
146 | | - |
147 | | - // Remove the previous month badge progress bar. |
148 | | - document |
149 | | - .querySelector( |
150 | | - `.prpl-previous-month-badge-progress-bar-wrapper[data-badge-id="${ badgeId }"]` |
151 | | - ) |
152 | | - ?.remove(); |
153 | | - |
154 | | - // If there are no more progress bars, remove the previous month badge progress bar wrapper. |
155 | | - if ( |
156 | | - ! document.querySelector( |
157 | | - '.prpl-previous-month-badge-progress-bar-wrapper' |
158 | | - ) |
159 | | - ) { |
160 | | - document |
161 | | - .querySelector( |
162 | | - '.prpl-previous-month-badge-progress-bars-wrapper' |
163 | | - ) |
164 | | - ?.remove(); |
| 76 | + /** |
| 77 | + * Connected callback, ran after the element is connected to the DOM. |
| 78 | + */ |
| 79 | + connectedCallback() { |
| 80 | + this.render(); |
165 | 81 | } |
166 | | - } |
167 | | -}; |
168 | 82 |
|
169 | | -/** |
170 | | - * Update the previous month badge counters. |
171 | | - * |
172 | | - * @param {number} pointsDiff The points difference. |
173 | | - * |
174 | | - * @return {void} |
175 | | - */ |
176 | | -// eslint-disable-next-line no-unused-vars |
177 | | -const prplUpdatePreviousMonthBadgeCounters = ( pointsDiff ) => { |
178 | | - const remainingPointsEls = document.querySelectorAll( |
179 | | - `.prpl-previous-month-badge-progress-bar-wrapper .prpl-previous-month-badge-progress-bar-remaining` |
180 | | - ); |
181 | | - |
182 | | - if ( ! remainingPointsEls.length ) { |
183 | | - return; |
184 | | - } |
| 83 | + /** |
| 84 | + * Attribute changed callback, ran on page load and when an observed attribute is changed. |
| 85 | + * |
| 86 | + * @param {string} name The name of the attribute that was changed. |
| 87 | + * @param {string} oldVal The old value of the attribute. |
| 88 | + * @param {string} newVal The new value of the attribute. |
| 89 | + */ |
| 90 | + attributeChangedCallback( name, oldVal, newVal ) { |
| 91 | + if ( oldVal === newVal ) { |
| 92 | + return; |
| 93 | + } |
| 94 | + |
| 95 | + // Convert attribute name to camelCase, remove "data-" or "aria-" prefix if present. |
| 96 | + const camelCaseName = name |
| 97 | + .replace( /^(data|aria)-/, '' ) |
| 98 | + // Convert kebab-case to camelCase |
| 99 | + .replace( /-([a-z])/g, ( _, chr ) => chr.toUpperCase() ); |
| 100 | + |
| 101 | + // Update state. |
| 102 | + this.state[ camelCaseName ] = newVal; |
| 103 | + |
| 104 | + // Update progress. |
| 105 | + this.updateProgress(); |
| 106 | + |
| 107 | + // Dispatch event. |
| 108 | + this.dispatchEvent( |
| 109 | + new CustomEvent( 'prlp-badge-progress-bar-update', { |
| 110 | + detail: { |
| 111 | + points: this.state.points, |
| 112 | + maxPoints: this.state.maxPoints, |
| 113 | + badgeId: this.state.badgeId, |
| 114 | + element: this, |
| 115 | + }, |
| 116 | + bubbles: true, |
| 117 | + composed: true, |
| 118 | + } ) |
| 119 | + ); |
| 120 | + } |
| 121 | + |
| 122 | + /** |
| 123 | + * Render the gauge. |
| 124 | + */ |
| 125 | + render() { |
| 126 | + this.shadowRoot.innerHTML = ` |
| 127 | + <style> |
| 128 | + .container { |
| 129 | + padding: 1rem 0; |
| 130 | + } |
| 131 | + .bar { |
| 132 | + width: 100%; |
| 133 | + height: 1rem; |
| 134 | + background-color: var(--prpl-color-gauge-remain); |
| 135 | + border-radius: 0.5rem; |
| 136 | + position: relative; |
| 137 | + } |
| 138 | + .progress { |
| 139 | + height: 100%; |
| 140 | + background-color: var(--prpl-color-monthly); |
| 141 | + border-radius: 0.5rem; |
| 142 | + transition: width 0.4s ease; |
| 143 | + } |
| 144 | + prpl-badge { |
| 145 | + display: flex; |
| 146 | + width: 7.5rem; |
| 147 | + height: auto; |
| 148 | + position: absolute; |
| 149 | + top: -2.5rem; |
| 150 | + transition: left 0.4s ease; |
| 151 | +
|
| 152 | + &::after { |
| 153 | + content: "!"; |
| 154 | + display: flex; |
| 155 | + align-items: center; |
| 156 | + justify-content: center; |
| 157 | + width: 20px; |
| 158 | + height: 20px; |
| 159 | + background-color: var(--prpl-color-alert-error); |
| 160 | + border: 2px solid #fff; |
| 161 | + border-radius: 50%; |
| 162 | + position: absolute; |
| 163 | + top: 10%; |
| 164 | + right: 25%; |
| 165 | + color: #fff; |
| 166 | + } |
| 167 | + } |
| 168 | + </style> |
| 169 | + <div class="container"> |
| 170 | + <div class="bar"> |
| 171 | + <div class="progress"></div> |
| 172 | + <prpl-badge |
| 173 | + badge-id="${ this.state.badgeId }" |
| 174 | + branding-id="${ this.state.brandingId }"> |
| 175 | + </prpl-badge> |
| 176 | + </div> |
| 177 | + </div> |
| 178 | + `; |
185 | 179 |
|
186 | | - remainingPointsEls.forEach( ( pointsEl ) => { |
187 | | - const totalPoints = pointsEl.getAttribute( |
188 | | - 'data-remaining-total-points' |
189 | | - ); |
190 | | - pointsEl.setAttribute( |
191 | | - 'data-remaining-total-points', |
192 | | - totalPoints - pointsDiff |
193 | | - ); |
194 | | - |
195 | | - const numberEl = pointsEl.querySelector( '.number' ); |
196 | | - if ( numberEl ) { |
197 | | - numberEl.textContent = totalPoints - pointsDiff; |
| 180 | + this.progressEl = this.shadowRoot.querySelector( '.progress' ); |
| 181 | + this.badgeEl = this.shadowRoot.querySelector( 'prpl-badge' ); |
| 182 | + |
| 183 | + this.updateProgress(); |
198 | 184 | } |
199 | | - } ); |
200 | | -}; |
| 185 | + |
| 186 | + /** |
| 187 | + * Update the progress. |
| 188 | + */ |
| 189 | + updateProgress() { |
| 190 | + if ( ! this.progressEl || ! this.badgeEl ) { |
| 191 | + return; |
| 192 | + } |
| 193 | + |
| 194 | + this.progressEl.style.width = `${ this.progressPercent }%`; |
| 195 | + this.badgeEl.style.left = `calc(${ this.progressPercent }% - 3.75rem)`; |
| 196 | + } |
| 197 | + } |
| 198 | +); |
0 commit comments