Skip to content

Commit 25d3043

Browse files
committed
feat(vue): add flexible field support to FormField component
- Implement context-aware attribute resolution for Flexible fields - Add getFlexibleContextPrefix() to detect nested field context - Add extractBaseAttribute() to parse prefixed attribute names - Add getFlexibleAttributeFormats() for alternative pattern matching - Enhance findFieldByAttribute() with prefix-based lookups - Update getFieldValue() to check multiple attribute formats - Update handleFieldChanged() to cache both full and base attributes Resolves #2
1 parent 7aa9a83 commit 25d3043

1 file changed

Lines changed: 185 additions & 3 deletions

File tree

resources/js/components/FormField.vue

Lines changed: 185 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,41 @@ export default {
8383
},
8484
8585
handleFieldChanged(event) {
86-
this.dependentFieldValues[event.field.attribute] = event.value;
86+
const fullAttribute = event.field.attribute;
87+
this.dependentFieldValues[fullAttribute] = event.value;
88+
89+
// Also store by base attribute name (without Flexible prefix) for easier lookup
90+
const baseAttribute = this.extractBaseAttribute(fullAttribute);
91+
if (baseAttribute && baseAttribute !== fullAttribute) {
92+
this.dependentFieldValues[baseAttribute] = event.value;
93+
}
94+
8795
this.checkDependencies();
8896
},
8997
98+
/**
99+
* Extract the base attribute name from a potentially prefixed Flexible field attribute.
100+
* e.g., "overlay_items__0__type" -> "type"
101+
* "overlay_items[0][type]" -> "type"
102+
*/
103+
extractBaseAttribute(attribute) {
104+
if (!attribute) return null;
105+
106+
// Pattern 1: Double underscore format (e.g., "overlay_items__0__field_name")
107+
const underscoreMatch = attribute.match(/^.+__\d+__(.+)$/);
108+
if (underscoreMatch) {
109+
return underscoreMatch[1];
110+
}
111+
112+
// Pattern 2: Bracket format (e.g., "overlay_items[0][field_name]")
113+
const bracketMatch = attribute.match(/^.+\[\d+\]\[(.+)\]$/);
114+
if (bracketMatch) {
115+
return bracketMatch[1];
116+
}
117+
118+
return attribute;
119+
},
120+
90121
checkDependencies() {
91122
if (!this.field.dependencies || this.field.dependencies.length === 0) {
92123
this.isVisible = true;
@@ -154,10 +185,29 @@ export default {
154185
},
155186
156187
getFieldValue(fieldAttribute) {
188+
// First check exact match in cache
157189
if (this.dependentFieldValues.hasOwnProperty(fieldAttribute)) {
158190
return this.dependentFieldValues[fieldAttribute];
159191
}
160192
193+
// Check for prefixed version in cache (for Flexible field contexts)
194+
const contextPrefix = this.getFlexibleContextPrefix();
195+
if (contextPrefix) {
196+
const prefixedAttribute = `${contextPrefix}${fieldAttribute}`;
197+
if (this.dependentFieldValues.hasOwnProperty(prefixedAttribute)) {
198+
return this.dependentFieldValues[prefixedAttribute];
199+
}
200+
201+
// Try alternative formats
202+
const alternativeFormats = this.getFlexibleAttributeFormats(contextPrefix, fieldAttribute);
203+
for (const format of alternativeFormats) {
204+
if (this.dependentFieldValues.hasOwnProperty(format)) {
205+
return this.dependentFieldValues[format];
206+
}
207+
}
208+
}
209+
210+
// Fallback to finding field in DOM
161211
const field = this.findFieldByAttribute(fieldAttribute);
162212
if (field) {
163213
return field.value;
@@ -167,8 +217,140 @@ export default {
167217
},
168218
169219
findFieldByAttribute(attribute) {
170-
const allFields = Nova.$parent?.$refs?.fields || [];
171-
return allFields.find(f => f.field && f.field.attribute === attribute);
220+
const allFields = this.getAllFields();
221+
222+
// First try exact match
223+
const exactMatch = allFields.find(f => f.field && f.field.attribute === attribute);
224+
if (exactMatch) {
225+
return exactMatch;
226+
}
227+
228+
// For Flexible fields: resolve attribute relative to current context
229+
const contextPrefix = this.getFlexibleContextPrefix();
230+
if (contextPrefix) {
231+
// Try to find field with same prefix (within same Flexible group)
232+
const prefixedAttribute = `${contextPrefix}${attribute}`;
233+
const prefixedMatch = allFields.find(f => f.field && f.field.attribute === prefixedAttribute);
234+
if (prefixedMatch) {
235+
return prefixedMatch;
236+
}
237+
238+
// Also try alternative Flexible attribute formats
239+
const alternativeFormats = this.getFlexibleAttributeFormats(contextPrefix, attribute);
240+
for (const format of alternativeFormats) {
241+
const match = allFields.find(f => f.field && f.field.attribute === format);
242+
if (match) {
243+
return match;
244+
}
245+
}
246+
}
247+
248+
// Fallback: find any field that ends with the attribute name (for nested contexts)
249+
const suffixMatch = allFields.find(f => {
250+
if (!f.field || !f.field.attribute) return false;
251+
const attr = f.field.attribute;
252+
// Match patterns like: prefix__attribute, prefix[index][attribute]
253+
return attr.endsWith(`__${attribute}`) ||
254+
attr.endsWith(`][${attribute}]`) ||
255+
attr.endsWith(`[${attribute}]`);
256+
});
257+
258+
return suffixMatch || null;
259+
},
260+
261+
/**
262+
* Get all rendered fields from the Nova form.
263+
* Handles both standard Nova forms and nested Flexible field contexts.
264+
*/
265+
getAllFields() {
266+
// Try Nova's global field reference first
267+
if (Nova.$parent?.$refs?.fields) {
268+
return Nova.$parent.$refs.fields;
269+
}
270+
271+
// Walk up the component tree to find fields
272+
let parent = this.$parent;
273+
let maxDepth = 10;
274+
275+
while (parent && maxDepth-- > 0) {
276+
// Check for fields in parent's refs
277+
if (parent.$refs?.fields && Array.isArray(parent.$refs.fields)) {
278+
return parent.$refs.fields;
279+
}
280+
// Check for fields array directly on parent
281+
if (parent.fields && Array.isArray(parent.fields)) {
282+
return parent.fields.map(f => ({ field: f }));
283+
}
284+
parent = parent.$parent;
285+
}
286+
287+
return [];
288+
},
289+
290+
/**
291+
* Detect the Flexible field context prefix from the container's own field attribute.
292+
* Flexible fields use prefixes like: flexible_key__index__ or flexible_key[index]
293+
*/
294+
getFlexibleContextPrefix() {
295+
// Check if this container has a prefixed attribute (indicating it's inside a Flexible field)
296+
const ownAttribute = this.field?.attribute || '';
297+
298+
// Pattern 1: Double underscore format (e.g., "overlay_items__0__field_name")
299+
const underscoreMatch = ownAttribute.match(/^(.+__\d+__)/);
300+
if (underscoreMatch) {
301+
return underscoreMatch[1];
302+
}
303+
304+
// Pattern 2: Bracket format (e.g., "overlay_items[0][field_name]")
305+
const bracketMatch = ownAttribute.match(/^(.+\[\d+\]\[)/);
306+
if (bracketMatch) {
307+
return bracketMatch[1];
308+
}
309+
310+
// Try to detect from parent/sibling field attributes
311+
const siblingFields = this.field?.fields || [];
312+
for (const sibling of siblingFields) {
313+
if (sibling.attribute) {
314+
const siblingUnderscoreMatch = sibling.attribute.match(/^(.+__\d+__)/);
315+
if (siblingUnderscoreMatch) {
316+
return siblingUnderscoreMatch[1];
317+
}
318+
const siblingBracketMatch = sibling.attribute.match(/^(.+\[\d+\]\[)/);
319+
if (siblingBracketMatch) {
320+
return siblingBracketMatch[1];
321+
}
322+
}
323+
}
324+
325+
return null;
326+
},
327+
328+
/**
329+
* Generate alternative attribute formats for Flexible fields.
330+
*/
331+
getFlexibleAttributeFormats(prefix, attribute) {
332+
const formats = [];
333+
334+
// Extract the base key and index from the prefix
335+
const underscoreMatch = prefix.match(/^(.+)__(\d+)__$/);
336+
const bracketMatch = prefix.match(/^(.+)\[(\d+)\]\[$/);
337+
338+
if (underscoreMatch) {
339+
const [, key, index] = underscoreMatch;
340+
// Generate bracket format alternative
341+
formats.push(`${key}[${index}][${attribute}]`);
342+
// Single underscore variant
343+
formats.push(`${key}_${index}_${attribute}`);
344+
}
345+
346+
if (bracketMatch) {
347+
const [, key, index] = bracketMatch;
348+
// Generate underscore format alternative
349+
formats.push(`${key}__${index}__${attribute}`);
350+
formats.push(`${key}_${index}_${attribute}`);
351+
}
352+
353+
return formats;
172354
},
173355
174356
isEmpty(value) {

0 commit comments

Comments
 (0)