|
1 | | -import observer from "@cocreate/observer"; |
2 | | -import { queryElements } from "@cocreate/utils"; |
3 | | -// import { renderValue } from '@cocreate/render'; |
4 | | -import "@cocreate/element-prototype"; |
| 1 | +import observer from "@cocreate/observer"; // Module for observing DOM mutations. |
| 2 | +import { queryElements } from "@cocreate/utils"; // Utility for querying DOM elements. |
| 3 | +import "@cocreate/element-prototype"; // Include custom element prototype extensions. |
5 | 4 |
|
| 5 | +// Initializes the calculation elements within the document. |
6 | 6 | function init() { |
| 7 | + // Select all elements in the document with a "calculate" attribute. |
7 | 8 | let calculateElements = document.querySelectorAll("[calculate]"); |
| 9 | + // Initialize each of the selected elements. |
8 | 10 | initElements(calculateElements); |
9 | 11 | } |
10 | 12 |
|
| 13 | +// Initialize multiple elements by invoking initElement for each. |
11 | 14 | function initElements(elements) { |
| 15 | + // Iterate through the collection of elements and initialize each one. |
12 | 16 | for (let el of elements) initElement(el); |
13 | 17 | } |
14 | 18 |
|
| 19 | +// Asynchronously initializes an individual element with setup for calculations. |
15 | 20 | async function initElement(element) { |
| 21 | + // Fetch the calculate string from the element's attribute. |
16 | 22 | let calculate = element.getAttribute("calculate"); |
| 23 | + // Return early if the calculate string contains placeholders or template syntax. |
17 | 24 | if (calculate.includes("{{") || calculate.includes("{[")) return; |
18 | 25 |
|
| 26 | + // Extract selectors from the calculate attribute value. |
19 | 27 | let selectors = getSelectors(element.attributes["calculate"].value); |
20 | 28 |
|
| 29 | + // Iterate through each selector and set up elements impacted by them. |
21 | 30 | for (let i = 0; i < selectors.length; i++) { |
| 31 | + // Find input elements based on the selector criteria. |
22 | 32 | let inputs = queryElements({ |
23 | 33 | element, |
24 | 34 | selector: selectors[i], |
25 | 35 | type: "selector" |
26 | 36 | }); |
27 | 37 |
|
| 38 | + // Set up events for each found input element. |
28 | 39 | for (let input of inputs) { |
29 | 40 | initEvent(element, input); |
30 | 41 | } |
31 | 42 |
|
| 43 | + // Initialize an observer to monitor newly added nodes that match the selector. |
32 | 44 | observer.init({ |
33 | 45 | name: "calculateSelectorInit", |
34 | 46 | types: ["addedNodes"], |
35 | 47 | selector: selectors[i], |
| 48 | + // Callback function to run when nodes matching the selector are added. |
36 | 49 | callback(mutation) { |
| 50 | + // Initialize events for the new element and update calculation. |
37 | 51 | initEvent(element, mutation.target); |
38 | 52 | setCalcationValue(element); |
39 | 53 | } |
40 | 54 | }); |
41 | 55 | } |
| 56 | + // Set initial calculation value when an element is being initialized. |
42 | 57 | setCalcationValue(element); |
43 | 58 | } |
44 | 59 |
|
@@ -110,63 +125,77 @@ function initEvent(element, input) { |
110 | 125 | }); |
111 | 126 | } |
112 | 127 |
|
| 128 | +// Asynchronously set the calculated value for the given element. |
113 | 129 | async function setCalcationValue(element) { |
| 130 | + // Get the expression or formula from the element's "calculate" attribute. |
114 | 131 | let calString = await getValues(element); |
| 132 | + // Evaluate the formula and set the calculated value back to the element. |
115 | 133 | element.setValue(calculate(calString)); |
116 | 134 | } |
117 | 135 |
|
| 136 | +// Asynchronously retrieve values necessary for computing the calculation attribute of an element. |
118 | 137 | async function getValues(element) { |
| 138 | + // Get the expression that needs to be evaluated from the "calculate" attribute. |
119 | 139 | let calculate = element.getAttribute("calculate"); |
120 | 140 |
|
| 141 | + // Parse the expression to extract any selectors which values need to contribute to calculation. |
121 | 142 | let selectors = getSelectors(element.attributes["calculate"].value); |
122 | 143 |
|
| 144 | + // For each selector, retrieve and calculate the respective value. |
123 | 145 | for (let i = 0; i < selectors.length; i++) { |
124 | | - let value = 0; // Default to 0 for missing inputs |
| 146 | + let value = 0; // Default value in case no input is found for the selector. |
| 147 | + |
| 148 | + // Query DOM elements based on selector. |
125 | 149 | let inputs = queryElements({ |
126 | 150 | element, |
127 | 151 | selector: selectors[i], |
128 | 152 | type: "selector" |
129 | 153 | }); |
130 | 154 |
|
| 155 | + // Iterate through inputs/elements matched by the selector. |
131 | 156 | for (let input of inputs) { |
| 157 | + // Initialize event listeners on inputs so that changes can update the calculation. |
132 | 158 | initEvent(element, input); |
133 | 159 | let val = null; |
| 160 | + |
| 161 | + // Attempt to get the value from the input element, if it can provide it. |
134 | 162 | if (input.getValue) { |
135 | 163 | val = Number(await input.getValue()); |
136 | 164 | } |
137 | 165 |
|
| 166 | + // Only accumulate valid numeric values. |
138 | 167 | if (!Number.isNaN(val)) { |
139 | | - value += val; // Accumulate valid numeric values |
| 168 | + value += val; |
140 | 169 | } else { |
141 | 170 | console.warn( |
142 | 171 | `Invalid value for selector "${selectors[i]}". Defaulting to 0.` |
143 | 172 | ); |
144 | 173 | } |
145 | 174 | } |
146 | 175 |
|
| 176 | + // Replace the placeholder in the calculation expression with the accumulated value. |
147 | 177 | calculate = calculate.replaceAll(`(${selectors[i]})`, value); |
148 | 178 | } |
149 | | - |
150 | | - return calculate; |
| 179 | + return calculate; // Return the resolved calculation expression. |
151 | 180 | } |
152 | 181 |
|
153 | 182 | // Defines mathematical constants available in expressions. |
154 | 183 | const constants = { PI: Math.PI, E: Math.E }; |
155 | 184 |
|
156 | | -// Defines allowed mathematical functions and maps them to their JavaScript Math counterparts. |
| 185 | +// Defines allowed mathematical functions and maps them to their respective JavaScript Math counterparts. |
157 | 186 | const functions = { |
158 | | - abs: Math.abs, |
159 | | - ceil: Math.ceil, |
160 | | - floor: Math.floor, |
161 | | - round: Math.round, |
162 | | - max: Math.max, // Note: RPN evaluator assumes arity 2 for max/min |
163 | | - min: Math.min, // Note: RPN evaluator assumes arity 2 for max/min |
164 | | - pow: Math.pow, |
165 | | - sqrt: Math.sqrt, |
| 187 | + abs: Math.abs, // Absolute value |
| 188 | + ceil: Math.ceil, // Ceiling function |
| 189 | + floor: Math.floor, // Floor function |
| 190 | + round: Math.round, // Round to nearest integer |
| 191 | + max: Math.max, // Maximum value (assumes arity 2 in RPN) |
| 192 | + min: Math.min, // Minimum value (assumes arity 2 in RPN) |
| 193 | + pow: Math.pow, // Exponentiation |
| 194 | + sqrt: Math.sqrt, // Square root |
166 | 195 | log: Math.log, // Natural logarithm |
167 | | - sin: Math.sin, |
168 | | - cos: Math.cos, |
169 | | - tan: Math.tan |
| 196 | + sin: Math.sin, // Sine function |
| 197 | + cos: Math.cos, // Cosine function |
| 198 | + tan: Math.tan // Tangent function |
170 | 199 | }; |
171 | 200 |
|
172 | 201 | /** |
|
0 commit comments