11import { BehaviorSubject , combineLatest } from 'https://cdn.jsdelivr.net/npm/rxjs@7.8.1/+esm' ;
22
3+ const STREAM = 'https://common.tools/stream-binding.schema.json'
4+ const CELL = 'https://common.tools/cell-binding.schema.json'
5+
6+ function createElement ( node , context ) {
7+ if ( typeof node === 'string' ) {
8+ const textNode = document . createTextNode ( node ) ;
9+ return textNode ;
10+ }
11+
12+ if ( ! node || typeof node !== 'object' ) return null ;
13+
14+ // Handle text nodes
15+ if ( ! node . tag && node . $id && node . name ) {
16+ // Bind the reactive source to update the text node if it changes
17+ if ( context [ node . name ] && context [ node . name ] . subscribe ) {
18+ if ( node . type == 'slot' ) {
19+ const uiNode = createElement ( context [ node . name ] . getValue ( ) , context )
20+ context [ node . name ] . subscribe ( newValue => {
21+ uiNode . innerHTML = '' ;
22+ uiNode . appendChild ( createElement ( newValue , context ) ) ;
23+ } ) ;
24+ return uiNode ;
25+ } else {
26+ const textNode = document . createTextNode ( context [ node . name ] || '' ) ;
27+ context [ node . name ] . subscribe ( newValue => {
28+ textNode . textContent = newValue ;
29+ } ) ;
30+ return textNode ;
31+ }
32+ }
33+ }
34+
35+ // Handle element nodes
36+ if ( ! node . tag && node . type == 'repeat' ) {
37+ const container = document . createElement ( 'div' ) ;
38+ const items = context [ node . binding ] || [ ] ;
39+ items . forEach ( item => {
40+ container . appendChild ( createElement ( node . template , item ) ) ;
41+ } ) ;
42+ return container
43+ }
44+
45+ const element = document . createElement ( node . tag ) ;
46+
47+ // Set properties
48+ for ( const [ key , value ] of Object . entries ( node . props || { } ) ) {
49+ if ( typeof value === 'object' && value . type ) {
50+ // Handle specific types and bind reactive sources from context
51+ if ( value . type && value [ "$id" ] && value [ "$id" ] === CELL ) {
52+ let name = value . name || key ;
53+ if ( ! context [ name ] ) continue ;
54+ element [ key ] = context [ name ] . getValue ( ) ;
55+ context [ name ] . subscribe ( newValue => element [ key ] = newValue ) ;
56+ } else {
57+ if ( value . binding ) {
58+ element [ key ] = context [ value . binding ] ;
59+ }
60+ }
61+ } else if ( value [ "$id" ] && value [ "$id" ] === STREAM && value . name ) {
62+ // Handle event binding to a stream
63+ if ( context [ value . name ] ) {
64+ element . addEventListener ( key , context [ value . name ] ) ;
65+ }
66+ } else {
67+ element [ key ] = value ;
68+ }
69+ }
70+
71+ let children = node . children || [ ] ;
72+ if ( ! Array . isArray ( children ) ) {
73+ children = [ children ] ;
74+ }
75+
76+ // Recursively create and append child elements
77+ children . forEach ( childNode => {
78+ if ( childNode . binding && childNode . type == 'literal' ) {
79+ const node = document . createTextNode ( context [ childNode . binding ] )
80+ element . appendChild ( node ) ;
81+ return
82+ }
83+
84+ const childElement = createElement ( childNode , context ) ;
85+ if ( childElement ) {
86+ element . appendChild ( childElement ) ;
87+ }
88+ } ) ;
89+
90+ return element ;
91+ }
92+
393// Example usage with a simplified system.get function
494const system = {
595 get : ( key ) => {
@@ -16,28 +106,31 @@ const system = {
16106
17107// Function to create the RxJS network from the new JSON graph format
18108function createRxJSNetworkFromJson ( graph ) {
19- const context = { } ;
109+ const context = {
110+ inputs : { } ,
111+ outputs : { }
112+ } ;
20113
21114 // Create subjects for each node
22115 graph . nodes . forEach ( node => {
23116 const nodeName = node . definition . name ;
24- context [ nodeName ] = { } ;
25- context [ nodeName ] [ 'out' ] = new BehaviorSubject ( null ) ;
117+ context . outputs [ nodeName ] = new BehaviorSubject ( null ) ;
26118
27119 // foreach input in the signature, create a subject
28120 if ( node . definition . signature ) {
29121 const { inputs } = node . definition . signature ;
122+ context . inputs [ nodeName ] = { } ;
30123 for ( const inputName in inputs ) {
31- context [ nodeName ] [ inputName ] = new BehaviorSubject ( null ) ;
124+ context . inputs [ nodeName ] [ inputName ] = new BehaviorSubject ( null ) ;
32125 }
33126 }
34127 } ) ;
35128
36129 // Set up reactive bindings based on edges
37130 graph . edges . forEach ( edge => {
38131 const [ source , target ] = Object . entries ( edge ) [ 0 ] ;
39- const sourceSubject = context [ source ] [ 'out' ] ;
40- const targetSubject = context [ target [ 0 ] ] [ target [ 1 ] ] ;
132+ const sourceSubject = context . outputs [ source ] ;
133+ const targetSubject = context . inputs [ target [ 0 ] ] [ target [ 1 ] ] ;
41134
42135 sourceSubject . subscribe ( value => {
43136 targetSubject . next ( value ) ;
@@ -53,18 +146,18 @@ function createRxJSNetworkFromJson(graph) {
53146 // Evaluate the JavaScript content and bind it to the subject
54147 const func = new Function ( 'system' , body ) ;
55148 const result = func ( system , {
56- get : ( key ) => context [ key ] [ 'out' ] . getValue ( ) ,
57- set : ( key , value ) => context [ key ] [ 'out' ] . next ( value )
149+ get : ( key ) => context . outputs [ nodeName ] . getValue ( ) ,
150+ set : ( key , value ) => context . outputs [ nodeName ] . next ( value )
58151 } ) ;
59- context [ nodeName ] [ 'out' ] . next ( result ) ;
152+ context . outputs [ nodeName ] . next ( result ) ;
60153 } else if ( contentType === 'application/json+vnd.common.ui' ) {
61154 // Set up template rendering
62155 const { inputs } = signature ;
63156 const inputObservables = [ ] ;
64157
65158 for ( const inputName in inputs ) {
66- if ( context [ inputName ] ) {
67- inputObservables . push ( context [ inputName ] . out ) ;
159+ if ( context . outputs [ inputName ] ) {
160+ inputObservables . push ( context . outputs [ inputName ] ) ;
68161 }
69162 }
70163
@@ -75,8 +168,8 @@ function createRxJSNetworkFromJson(graph) {
75168 return acc ;
76169 } , { } ) ;
77170
78- const renderedTemplate = renderTemplate ( node . definition . body , inputValues ) ;
79- context [ nodeName ] [ 'out' ] . next ( renderedTemplate ) ;
171+ const renderedTemplate = createElement ( node . definition . body , inputValues ) ;
172+ context . outputs [ nodeName ] . next ( renderedTemplate ) ;
80173 } ) ;
81174 }
82175 } ) ;
@@ -162,25 +255,35 @@ const jsonDocument = {
162255 }
163256 } ,
164257 "body" : {
165- "tag" : "todos " ,
258+ "tag" : "ul " ,
166259 "props" : {
167260 "className" : "todo"
168261 } ,
169262 "children" : {
170263 "type" : "repeat" ,
171264 "binding" : "todos" ,
172- "template" : [
173- {
174- "tag" : "li" ,
175- "props" : {
176- "todo" : {
177- "$id" : "https://common.tools/cell.json" ,
178- "type" : "todo"
265+ "template" : {
266+ "tag" : "li" ,
267+ "props" : { } ,
268+ "children" : [
269+ {
270+ "tag" : "input" ,
271+ "props" : {
272+ "type" : "checkbox" ,
273+ "checked" : { type : 'boolean' , binding : 'checked' }
179274 }
180275 } ,
181- "children" : [ ]
182- }
183- ]
276+ {
277+ "tag" : "span" ,
278+ "props" : {
279+ "className" : "todo-label"
280+ } ,
281+ "children" : [
282+ { type : 'literal' , binding : 'label' }
283+ ]
284+ }
285+ ]
286+ }
184287 }
185288 }
186289 }
@@ -198,8 +301,42 @@ const jsonDocument = {
198301// Create the RxJS network
199302const context = createRxJSNetworkFromJson ( jsonDocument ) ;
200303
304+ function debug ( ) {
305+ document . querySelector ( '#tree' ) . innerHTML = JSON . stringify ( jsonDocument , null , 2 ) ;
306+ document . querySelector ( '#ctx' ) . innerHTML = JSON . stringify ( snapshot ( context ) , null , 2 ) ;
307+ document . querySelector ( '#system' ) . innerHTML = JSON . stringify ( system . get ( 'todos' ) , null , 2 ) ;
308+ }
309+
310+ function snapshot ( ctx ) {
311+ // grab values of behavior subjects
312+ // preserve literals
313+
314+ const snapshot = {
315+ inputs : { } ,
316+ outputs : { }
317+ }
318+ for ( const key in ctx . outputs ) {
319+ const value = ctx . outputs [ key ] . getValue ( )
320+ snapshot . outputs [ key ] = value
321+ }
322+
323+ for ( const key in ctx . inputs ) {
324+ snapshot . inputs [ key ] = { }
325+ for ( const inputKey in ctx . inputs [ key ] ) {
326+ const value = ctx . inputs [ key ] [ inputKey ] . getValue ( )
327+ snapshot . inputs [ key ] [ inputKey ] = value
328+ }
329+ }
330+
331+ return snapshot
332+
333+ }
334+
201335// Example output for the UI component
202- context [ 'ui' ] . out . subscribe ( renderedTemplate => {
336+ context . outputs . ui . subscribe ( renderedTemplate => {
203337 console . log ( renderedTemplate ) ;
204- document . getElementById ( 'app' ) . innerHTML = renderedTemplate ;
338+ document . getElementById ( 'app' ) . replaceChildren ( renderedTemplate )
339+ debug ( )
205340} ) ;
341+
342+ debug ( )
0 commit comments