Skip to content

Commit bb5d0fe

Browse files
authored
Merge pull request #218 from mrigsby/Dot-Notation-Data-Props
Dot notation data props
2 parents cac62ba + eaa80e6 commit bb5d0fe

File tree

12 files changed

+322
-34
lines changed

12 files changed

+322
-34
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ test-harness/testbox/**
1414
test-harness/tests/results/**
1515
test-harness/logs/**
1616
test-harness/modules/**
17+
views/tmp/**
1718

1819
# log files
1920
logs/**
@@ -23,4 +24,6 @@ node_modules/**
2324

2425
livewire/**
2526

26-
models/tmp/**
27+
models/tmp/**
28+
29+
.DS_Store

models/Component.cfc

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ component output="true" accessors="true" {
533533
if ( !variables._event.privateValueExists( "_cbwire_stream" ) ) {
534534
cfcontent( reset=true );
535535
variables._event.setPrivateValue( "_cbwire_stream", true );
536-
cfheader( statusCode=200, statustext="OK" );
536+
cfheader( statusCode=200 );
537537
cfheader( name="Cache-Control", value="no-cache, private" );
538538
cfheader( name="Host", value=cgi.http_host );
539539
cfheader( name="Content-Type", value="text/event-stream" );
@@ -793,24 +793,31 @@ component output="true" accessors="true" {
793793
local.regexMatch = reFindNoCase( "(.+)\.([0-9]+)", arguments.key, 1, true );
794794
local.propertyName = local.regexMatch.match[ 2 ];
795795
local.arrayIndex = local.regexMatch.match[ 3 ];
796-
variables.data[ local.propertyName][ local.arrayIndex + 1 ] = isNumeric( arguments.value ) ? val( arguments.value ) : arguments.value;
797-
// Track that we updated an array property
796+
local.currentArray = structGet( "variables.data." & local.propertyName );
797+
local.currentArray[ local.arrayIndex + 1 ] = isNumeric( arguments.value ) ? val( arguments.value ) : arguments.value;
798+
_updateDataValue( local.propertyName, local.currentArray );
799+
// Track that we updated an array property
798800
if ( !arrayFindNoCase( updatedArrayProps, local.propertyName ) ) {
799801
updatedArrayProps.append( local.propertyName );
800802
}
801803
} else {
802-
local.oldValue = variables.data[ key ];
803-
variables.data[ key ] = arguments.value;
804-
if ( structKeyExists( this, "onUpdate#key#") ) {
805-
invoke( this, "onUpdate#key#", { value: arguments.value, oldValue: local.oldValue });
804+
local.oldValue = structGet( "variables.data." & key );
805+
_updateDataValue( key, arguments.value );
806+
var onUpdateFunctionName = "onUpdate" & key.replace( ".", "_", "all" );
807+
if ( structKeyExists( this, onUpdateFunctionName) ) {
808+
invoke( this, onUpdateFunctionName, { value: arguments.value, oldValue: local.oldValue });
806809
}
807810
}
808811
} );
809812

810813
local.updatedArrayProps.each( function( prop ) {
811-
variables.data[ arguments.prop ] = variables.data[ arguments.prop ].filter( function( value ) {
812-
return arguments.value != "__rm__";
813-
} );
814+
var currentArray = structGet( "variables.data." & prop );
815+
_updateDataValue(
816+
prop,
817+
currentArray.filter( function( value ) {
818+
return value != "__rm__";
819+
} )
820+
);
814821
} );
815822

816823
// Call onUpdate passing newValues and oldValues
@@ -819,6 +826,30 @@ component output="true" accessors="true" {
819826
}
820827
}
821828

829+
/**
830+
* update a key value in variables.data structure.
831+
*
832+
* @keyPath string | the data property key being updated. Supports dot notation for nested structures (e.g., "user.address.street").
833+
* @value any | the value to set.
834+
*
835+
* @return void
836+
*/
837+
public void function _updateDataValue( required string keyPath, required any value ) {
838+
var keys = ListToArray( arguments.keyPath, "." );
839+
var current = variables.data;
840+
// Loop through keys except the last one to create/traverse nested structure
841+
for ( var i = 1; i < keys.Len(); i++ ) {
842+
var key = keys[ i ];
843+
// Create nested struct if it doesn't exist
844+
if ( !current.KeyExists( key ) ) {
845+
current[ key ] = {};
846+
}
847+
// Move to the next level
848+
current = current[ key ];
849+
}
850+
current[ keys[ keys.Len() ] ] = arguments.value;
851+
}
852+
822853
/**
823854
* Validate if key being updated is a locked property.
824855
*
@@ -948,7 +979,7 @@ component output="true" accessors="true" {
948979
);
949980
// Check if the component has an onUploadError method and invoke it
950981
if ( structKeyExists( this, "onUploadError" ) ) {
951-
invoke( this, "onUploadError", {
982+
invoke( this, "onUploadError", {
952983
property: arguments.prop,
953984
errors: arguments.errors,
954985
multiple: arguments.multiple

test-harness/handlers/Workshop.cfc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ component {
1111
function signupForm() {}
1212

1313
function taskList() {}
14+
15+
function nestedDataKeys() {}
1416
}

test-harness/layouts/workshop.cfm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<li><a href="/workshop/Counter">Counter</a></li>
2525
<li><a href="/workshop/SignupForm">Signup Form</a></li>
2626
<li><a href="/workshop/TaskList">Task List</a></li>
27+
<li><a href="/workshop/nestedDataKeys">Dot Notation Data</a></li>
2728
</ul>
2829
</div>
2930
<div class="col-9 right-content">
@@ -44,7 +45,7 @@
4445
// });
4546
});
4647
document.addEventListener("livewire:navigated", () => {
47-
48+
4849
});
4950
</script>
5051
</body>

test-harness/server-adobe@2021.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@
1515
"/modules/cbwire":"../"
1616
}
1717
},
18-
"openBrowser":"false"
18+
"openBrowser":"false",
19+
"scripts":{
20+
"onServerInstall":"cfpm install zip"
21+
}
1922
}
Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
{
2-
"name": "cbwire-adobe@2023",
3-
"app": {
4-
"serverHomeDirectory": ".engine/adobe2023",
5-
"cfengine": "adobe@2023"
2+
"name":"cbwire-adobe@2023",
3+
"app":{
4+
"serverHomeDirectory":".engine/adobe2023",
5+
"cfengine":"adobe@2023"
66
},
7-
"web": {
8-
"http": {
9-
"port": "60299"
7+
"web":{
8+
"http":{
9+
"port":"60299"
1010
},
11-
"rewrites": {
12-
"enable": "true"
11+
"rewrites":{
12+
"enable":"true"
1313
},
14-
"aliases": {
15-
"/modules/cbwire": "../"
14+
"aliases":{
15+
"/modules/cbwire":"../"
1616
}
1717
},
18-
"openBrowser": "false"
18+
"openBrowser":"false",
19+
"scripts":{
20+
"onServerInstall":"cfpm install zip"
21+
}
1922
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name":"cbwire-adobe@2025",
3+
"app":{
4+
"serverHomeDirectory":".engine/adobe2025",
5+
"cfengine":"adobe@2025"
6+
},
7+
"web":{
8+
"http":{
9+
"port":"60299"
10+
},
11+
"rewrites":{
12+
"enable":"true"
13+
},
14+
"aliases":{
15+
"/modules/cbwire":"../"
16+
}
17+
},
18+
"JVM":{
19+
"javaVersion":"openjdk21"
20+
},
21+
"openBrowser":"false",
22+
"scripts":{
23+
"onServerInstall":"cfpm install zip"
24+
}
25+
}

test-harness/tests/specs/CBWIRESpec.cfc

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ component extends="coldbox.system.testing.BaseTestCase" {
110110
var CBWIREController = getInstance( "CBWIREController@cbwire" );
111111
var settings = getInstance( "coldbox:modulesettings:cbwire" );
112112
settings.updateEndpoint = "/index.bxm/cbwire/update";
113-
113+
114114
var uploadURL = CBWIREController.generateSignedUploadURL();
115-
115+
116116
// The URL should contain the custom path
117117
expect( uploadURL ).toInclude( "/index.bxm/cbwire/upload" );
118118
// It should also contain expires and signature parameters
@@ -842,6 +842,73 @@ component extends="coldbox.system.testing.BaseTestCase" {
842842
expect( response.components[1].effects.html ).toInclude( "CBWIRE Slaps!" );
843843
} );
844844

845+
it( "should provide updates to data properties using dot notation", function() {
846+
var payload = incomingRequest(
847+
memo = {
848+
"name": "test.test_component_with_dot_notation_data",
849+
"id": "Z1Ruz1tGMPXSfw7osBW2",
850+
"children": []
851+
},
852+
data = {
853+
"title": {
854+
"label" : "CBWIRE Rocks!"
855+
}
856+
},
857+
calls = [],
858+
updates = {
859+
"title.label": "CBWIRE Slaps!"
860+
}
861+
);
862+
var response = cbwireController.handleRequest( payload, event );
863+
expect( response.components[1].effects.html ).toInclude( "CBWIRE Slaps!" );
864+
} );
865+
866+
it( "should support incoming array values in dot notation referenced array", function() {
867+
var payload = incomingRequest(
868+
memo = {
869+
"name": "test.test_component_with_dot_notation_data",
870+
"id": "Z1Ruz1tGMPXSfw7osBW2",
871+
"children": []
872+
},
873+
data = {},
874+
calls = [],
875+
updates = [
876+
"modules.names.0": "CBWIRE",
877+
"modules.names.1": "CBORM",
878+
"modules.names.2": "__rm__"
879+
]
880+
);
881+
var response = cbwireController.handleRequest( payload, event );
882+
var snapshot = deserializeJson( response.components[ 1 ].snapshot );
883+
expect( snapshot.data.modules.names ).toBeArray();
884+
expect( snapshot.data.modules.names.len() ).toBe( 2 );
885+
expect( snapshot.data.modules.names[ 1 ] ).toBe( "CBWIRE" );
886+
expect( snapshot.data.modules.names[ 2 ] ).toBe( "CBORM" );
887+
} );
888+
889+
it( "should call onUpdate[Property_Dot_Notation] if it exists", function() {
890+
var payload = incomingRequest(
891+
memo = {
892+
"name": "test.test_component_with_dot_notation_data",
893+
"id": "Z1Ruz1tGMPXSfw7osBW2",
894+
"children": []
895+
},
896+
data = {
897+
"title": {
898+
"label" : "CBWIRE Rocks!"
899+
}
900+
},
901+
calls = [],
902+
updates = {
903+
"title.label": "CBWIRE Slaps!"
904+
}
905+
);
906+
var response = cbwireController.handleRequest( payload, event );
907+
expect( response.components[1].effects.html ).toInclude( "CBWIRE Slaps!" );
908+
expect( response.components[1].effects.html ).toInclude( "New Value: CBWIRE Slaps!" );
909+
expect( response.components[1].effects.html ).toInclude( "Old Value: CBWIRE Rocks!" );
910+
} );
911+
845912
it( "should dispatch an event without params", function() {
846913
var payload = incomingRequest(
847914
memo = {
@@ -1853,9 +1920,9 @@ component extends="coldbox.system.testing.BaseTestCase" {
18531920
*
18541921
* @html string | The rendered HTML containing the component.
18551922
* @index numeric | The index of the component if multiple match (usually 1).
1856-
*
1923+
*
18571924
* @return struct The deserialized snapshot struct.
1858-
*
1925+
*
18591926
* @throws Error if parsing or deserialization fails.
18601927
*/
18611928
private function parseSnapshot( required string html, numeric index = 1 ) {
@@ -1893,9 +1960,9 @@ component extends="coldbox.system.testing.BaseTestCase" {
18931960
*
18941961
* @html The rendered HTML containing the component.
18951962
* @index The index of the component if multiple match (usually 1).
1896-
*
1963+
*
18971964
* @return any The deserialized effects (usually struct or array).
1898-
*
1965+
*
18991966
* @throws Error if parsing or deserialization fails.
19001967
*/
19011968
private function parseEffects( required string html, numeric index = 1 ) {
@@ -1931,9 +1998,9 @@ component extends="coldbox.system.testing.BaseTestCase" {
19311998
}
19321999
}
19332000

1934-
/**
2001+
/**
19352002
* Check if the current environment is a BoxLang environment
1936-
*
2003+
*
19372004
* @return boolean
19382005
*/
19392006
private function isBoxLang() {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<cfoutput>#wire( "workshop.NestedDataKeys" )#</cfoutput>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<cfscript>
2+
// @startWire
3+
data = {
4+
"title": {
5+
"label" : "CBWIRE Rocks!"
6+
},
7+
"newValue": "",
8+
"oldValue": "",
9+
"modules": {
10+
"names" : [
11+
"Module 1",
12+
"Module 2",
13+
"Module 3"
14+
]
15+
}
16+
};
17+
18+
function onUpdatetitle_label( value, oldValue ) {
19+
data.title.label = arguments.value;
20+
data.newValue = arguments.value;
21+
data.oldValue = arguments.oldValue;
22+
}
23+
// @endWire
24+
</cfscript>
25+
26+
<cfoutput>
27+
<div>
28+
<h1>Title: #title.label#</h1>
29+
<p>New Value: #newValue#</p>
30+
<p>Old Value: #oldValue#</p>
31+
<cfif modules.names.len()>
32+
<ul>
33+
<cfloop array="#modules.names#" index="module">
34+
<li>#module#</li>
35+
</cfloop>
36+
</ul>
37+
</cfif>
38+
39+
</div>
40+
</cfoutput>

0 commit comments

Comments
 (0)