Skip to content

Commit ef35992

Browse files
authored
fix: Improve Flexible field context detection (#4) (#5)
Merge fix for Flexible field context detection (#4)
1 parent 49b4f5a commit ef35992

5 files changed

Lines changed: 145 additions & 31 deletions

File tree

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22

33
All notable changes to `nova-dependency-container` will be documented in this file.
44

5+
## [1.0.6] - 2025-11-25
6+
7+
### Fixed
8+
- Fixed Flexible field context detection when container attribute is empty ([#4](https://github.com/iamgerwin/nova-dependency-container/issues/4))
9+
- Fixed cross-group event contamination in Flexible layouts
10+
11+
### Added
12+
- Added `cachedContextPrefix` for improved performance and reliability
13+
- Added `extractPrefixFromAttribute()` method for better prefix extraction
14+
- Added cross-group event filtering to prevent dependency interference between Flexible groups
15+
16+
### Changed
17+
- Enhanced `getFlexibleContextPrefix()` to check child field attributes when container attribute is empty
18+
- Enhanced `handleFieldChanged()` to filter events by Flexible group context
19+
- Updated documentation with improved context detection details
20+
21+
### Technical
22+
- Implemented context prefix caching from field-changed events
23+
- Implemented cross-group event filtering in FormField.vue
24+
- Updated both `FormField.vue` and `DetailField.vue` components
25+
526
## [1.0.5] - 2025-11-25
627

728
### Added

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "iamgerwin/nova-dependency-container",
3-
"version": "1.0.4",
3+
"version": "1.0.6",
44
"description": "A Laravel Nova 4 and 5 field container allowing to depend on other fields values",
55
"keywords": [
66
"laravel",

docs/flexible-field-support.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,25 @@ If a field remains hidden when it should be visible:
130130

131131
### Context Detection
132132

133-
The Flexible context is detected by examining:
133+
The Flexible context is detected by examining (in order of priority):
134134

135-
1. The container's own `attribute` property
136-
2. Sibling field `attribute` properties
137-
3. Parent component structure
135+
1. Cached context prefix from previous field-changed events
136+
2. The container's own `attribute` property
137+
3. Child field `attribute` properties (inside the dependency container)
138+
4. Cached dependent field values
139+
5. Parent component structure
140+
141+
The context prefix is cached once detected, improving performance and reliability for subsequent dependency checks.
142+
143+
### Cross-Group Event Filtering
144+
145+
When a field changes inside a Flexible layout, the event is broadcast globally. The dependency container automatically filters events to only process those from the same Flexible group:
146+
147+
- Events with a prefix matching the container's context are processed
148+
- Events from different Flexible groups (different index) are ignored
149+
- Non-prefixed events are processed normally (for non-Flexible contexts)
150+
151+
This ensures that changing a field in Overlay Item #1 doesn't affect the dependent fields in Overlay Item #2.
138152

139153
### Regex Patterns Used
140154

@@ -154,6 +168,7 @@ The Flexible context is detected by examining:
154168

155169
## Version History
156170

171+
- **1.0.6**: Improved Flexible field context detection and cross-group event filtering
157172
- **1.0.5**: Added Flexible field support (this feature)
158173
- **1.0.4**: Nova 4.35.x compatibility fixes
159174
- **1.0.0**: Initial release

resources/js/components/DetailField.vue

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default {
1919
return {
2020
dependentFieldValues: {},
2121
isVisible: false,
22+
cachedContextPrefix: null,
2223
};
2324
},
2425
@@ -177,37 +178,62 @@ export default {
177178
return [];
178179
},
179180
181+
/**
182+
* Extract the prefix (e.g., "overlay_items__0__") from a full attribute.
183+
*/
184+
extractPrefixFromAttribute(attribute) {
185+
if (!attribute) return null;
186+
187+
// Pattern 1: Double underscore format (e.g., "overlay_items__0__field_name")
188+
const underscoreMatch = attribute.match(/^(.+__\d+__)/);
189+
if (underscoreMatch) {
190+
return underscoreMatch[1];
191+
}
192+
193+
// Pattern 2: Bracket format (e.g., "overlay_items[0][field_name]")
194+
const bracketMatch = attribute.match(/^(.+\[\d+\]\[)/);
195+
if (bracketMatch) {
196+
return bracketMatch[1];
197+
}
198+
199+
return null;
200+
},
201+
180202
/**
181203
* Detect the Flexible field context prefix from the container's own field attribute.
182204
* Flexible fields use prefixes like: flexible_key__index__ or flexible_key[index]
183205
*/
184206
getFlexibleContextPrefix() {
207+
// Return cached prefix if available
208+
if (this.cachedContextPrefix) {
209+
return this.cachedContextPrefix;
210+
}
211+
185212
// Check if this container has a prefixed attribute (indicating it's inside a Flexible field)
186213
const ownAttribute = this.field?.attribute || '';
187214
188215
// Pattern 1: Double underscore format (e.g., "overlay_items__0__field_name")
189216
const underscoreMatch = ownAttribute.match(/^(.+__\d+__)/);
190217
if (underscoreMatch) {
191-
return underscoreMatch[1];
218+
this.cachedContextPrefix = underscoreMatch[1];
219+
return this.cachedContextPrefix;
192220
}
193221
194222
// Pattern 2: Bracket format (e.g., "overlay_items[0][field_name]")
195223
const bracketMatch = ownAttribute.match(/^(.+\[\d+\]\[)/);
196224
if (bracketMatch) {
197-
return bracketMatch[1];
225+
this.cachedContextPrefix = bracketMatch[1];
226+
return this.cachedContextPrefix;
198227
}
199228
200-
// Try to detect from parent/sibling field attributes
201-
const siblingFields = this.field?.fields || [];
202-
for (const sibling of siblingFields) {
203-
if (sibling.attribute) {
204-
const siblingUnderscoreMatch = sibling.attribute.match(/^(.+__\d+__)/);
205-
if (siblingUnderscoreMatch) {
206-
return siblingUnderscoreMatch[1];
207-
}
208-
const siblingBracketMatch = sibling.attribute.match(/^(.+\[\d+\]\[)/);
209-
if (siblingBracketMatch) {
210-
return siblingBracketMatch[1];
229+
// Try to detect from child field attributes (inside the container)
230+
const childFields = this.field?.fields || [];
231+
for (const child of childFields) {
232+
if (child.attribute) {
233+
const childPrefix = this.extractPrefixFromAttribute(child.attribute);
234+
if (childPrefix) {
235+
this.cachedContextPrefix = childPrefix;
236+
return this.cachedContextPrefix;
211237
}
212238
}
213239
}

resources/js/components/FormField.vue

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export default {
4343
return {
4444
dependentFieldValues: {},
4545
isVisible: false,
46+
cachedContextPrefix: null,
4647
};
4748
},
4849
@@ -84,17 +85,55 @@ export default {
8485
8586
handleFieldChanged(event) {
8687
const fullAttribute = event.field.attribute;
88+
const eventPrefix = this.extractPrefixFromAttribute(fullAttribute);
89+
const currentPrefix = this.getFlexibleContextPrefix();
90+
91+
// For Flexible fields: only process events from the same group context
92+
// If both have prefixes, they must match; if neither has prefix, process normally
93+
if (eventPrefix && currentPrefix) {
94+
if (eventPrefix !== currentPrefix) {
95+
// Event is from a different Flexible group, ignore it
96+
return;
97+
}
98+
}
99+
87100
this.dependentFieldValues[fullAttribute] = event.value;
88101
89102
// Also store by base attribute name (without Flexible prefix) for easier lookup
90103
const baseAttribute = this.extractBaseAttribute(fullAttribute);
91104
if (baseAttribute && baseAttribute !== fullAttribute) {
92105
this.dependentFieldValues[baseAttribute] = event.value;
106+
107+
// Cache the context prefix from incoming events for better Flexible field detection
108+
if (!this.cachedContextPrefix && eventPrefix) {
109+
this.cachedContextPrefix = eventPrefix;
110+
}
93111
}
94112
95113
this.checkDependencies();
96114
},
97115
116+
/**
117+
* Extract the prefix (e.g., "overlay_items__0__") from a full attribute.
118+
*/
119+
extractPrefixFromAttribute(attribute) {
120+
if (!attribute) return null;
121+
122+
// Pattern 1: Double underscore format (e.g., "overlay_items__0__field_name")
123+
const underscoreMatch = attribute.match(/^(.+__\d+__)/);
124+
if (underscoreMatch) {
125+
return underscoreMatch[1];
126+
}
127+
128+
// Pattern 2: Bracket format (e.g., "overlay_items[0][field_name]")
129+
const bracketMatch = attribute.match(/^(.+\[\d+\]\[)/);
130+
if (bracketMatch) {
131+
return bracketMatch[1];
132+
}
133+
134+
return null;
135+
},
136+
98137
/**
99138
* Extract the base attribute name from a potentially prefixed Flexible field attribute.
100139
* e.g., "overlay_items__0__type" -> "type"
@@ -292,36 +331,49 @@ export default {
292331
* Flexible fields use prefixes like: flexible_key__index__ or flexible_key[index]
293332
*/
294333
getFlexibleContextPrefix() {
334+
// Return cached prefix if available (detected from field-changed events)
335+
if (this.cachedContextPrefix) {
336+
return this.cachedContextPrefix;
337+
}
338+
295339
// Check if this container has a prefixed attribute (indicating it's inside a Flexible field)
296340
const ownAttribute = this.field?.attribute || '';
297341
298342
// Pattern 1: Double underscore format (e.g., "overlay_items__0__field_name")
299343
const underscoreMatch = ownAttribute.match(/^(.+__\d+__)/);
300344
if (underscoreMatch) {
301-
return underscoreMatch[1];
345+
this.cachedContextPrefix = underscoreMatch[1];
346+
return this.cachedContextPrefix;
302347
}
303348
304349
// Pattern 2: Bracket format (e.g., "overlay_items[0][field_name]")
305350
const bracketMatch = ownAttribute.match(/^(.+\[\d+\]\[)/);
306351
if (bracketMatch) {
307-
return bracketMatch[1];
352+
this.cachedContextPrefix = bracketMatch[1];
353+
return this.cachedContextPrefix;
308354
}
309355
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];
356+
// Try to detect from child field attributes (inside the container)
357+
const childFields = this.field?.fields || [];
358+
for (const child of childFields) {
359+
if (child.attribute) {
360+
const childPrefix = this.extractPrefixFromAttribute(child.attribute);
361+
if (childPrefix) {
362+
this.cachedContextPrefix = childPrefix;
363+
return this.cachedContextPrefix;
321364
}
322365
}
323366
}
324367
368+
// Try to detect from cached dependent field values
369+
for (const attr of Object.keys(this.dependentFieldValues)) {
370+
const prefix = this.extractPrefixFromAttribute(attr);
371+
if (prefix) {
372+
this.cachedContextPrefix = prefix;
373+
return this.cachedContextPrefix;
374+
}
375+
}
376+
325377
return null;
326378
},
327379

0 commit comments

Comments
 (0)