77} from '../dom_utils' ;
88import { executeMorphdom } from '../morphdom' ;
99import UnsyncedInputsTracker from './UnsyncedInputsTracker' ;
10- import { ModelElementResolver } from './ModelElementResolver ' ;
10+ import { ElementDriver } from './ElementDriver ' ;
1111import HookManager from '../HookManager' ;
1212import PollingDirectory from '../PollingDirector' ;
1313
@@ -16,6 +16,7 @@ declare const Turbo: any;
1616export default class Component {
1717 readonly element : HTMLElement ;
1818 private readonly backend : BackendInterface ;
19+ private readonly elementDriver : ElementDriver ;
1920 id : string | null ;
2021
2122 /**
@@ -52,16 +53,17 @@ export default class Component {
5253 * @param fingerprint
5354 * @param id Some unique id to identify this component. Needed to be a child component
5455 * @param backend Backend instance for updating
55- * @param modelElementResolver Class to get "model" name from any element.
56+ * @param elementDriver Class to get "model" name from any element.
5657 */
57- constructor ( element : HTMLElement , props : any , data : any , fingerprint : string | null , id : string | null , backend : BackendInterface , modelElementResolver : ModelElementResolver ) {
58+ constructor ( element : HTMLElement , props : any , data : any , fingerprint : string | null , id : string | null , backend : BackendInterface , elementDriver : ElementDriver ) {
5859 this . element = element ;
5960 this . backend = backend ;
61+ this . elementDriver = elementDriver ;
6062 this . id = id ;
6163 this . fingerprint = fingerprint ;
6264
6365 this . valueStore = new ValueStore ( props , data ) ;
64- this . unsyncedInputsTracker = new UnsyncedInputsTracker ( this , modelElementResolver ) ;
66+ this . unsyncedInputsTracker = new UnsyncedInputsTracker ( this , elementDriver ) ;
6567 this . hooks = new HookManager ( ) ;
6668 this . pollingDirector = new PollingDirectory ( this ) ;
6769 }
@@ -80,10 +82,11 @@ export default class Component {
8082 /**
8183 * Add a named hook to the component. Available hooks are:
8284 *
83- * * render.started: (html: string, response: Response, controls: { shouldRender: boolean }) => {}
84- * * render.finished: (component: Component) => {}
85+ * * render:started (html: string, response: Response, controls: { shouldRender: boolean }) => {}
86+ * * render:finished (component: Component) => {}
8587 * * loading.state:started (element: HTMLElement, request: BackendRequest) => {}
8688 * * loading.state:finished (element: HTMLElement) => {}
89+ * * model:set (model, value) => {}
8790 */
8891 on ( hookName : string , callback : ( ...args : any [ ] ) => void ) : void {
8992 this . hooks . register ( hookName , callback ) ;
@@ -94,17 +97,7 @@ export default class Component {
9497 const modelName = normalizeModelName ( model ) ;
9598 this . valueStore . set ( modelName , value ) ;
9699
97- // if there is a "validatedFields" data, it means this component wants
98- // to track which fields have been / should be validated.
99- // in that case, when the model is updated, mark that it should be validated
100- // TODO: could this be done with a hook?
101- if ( this . valueStore . has ( 'validatedFields' ) ) {
102- const validatedFields = [ ...this . valueStore . get ( 'validatedFields' ) ] ;
103- if ( ! validatedFields . includes ( modelName ) ) {
104- validatedFields . push ( modelName ) ;
105- }
106- this . valueStore . set ( 'validatedFields' , validatedFields ) ;
107- }
100+ this . hooks . triggerHook ( 'model:set' , model , value ) ;
108101
109102 // the model's data is no longer unsynced
110103 this . unsyncedInputsTracker . markModelAsSynced ( modelName ) ;
@@ -179,17 +172,15 @@ export default class Component {
179172 }
180173
181174 updateFromNewElement ( toEl : HTMLElement ) : boolean {
182- // TODO: need a driver here to be agnostic of markup
183- const propsString = toEl . dataset . livePropsValue ;
175+ const props = this . elementDriver . getComponentProps ( toEl ) ;
184176
185177 // if no props are on the element, use the existing element completely
186178 // this means the parent is signaling that the child does not need to be re-rendered
187- if ( propsString === undefined ) {
179+ if ( props === null ) {
188180 return false ;
189181 }
190182
191183 // push props directly down onto the value store
192- const props = JSON . parse ( propsString ) ;
193184 const isChanged = this . valueStore . reinitializeProps ( props ) ;
194185
195186 const fingerprint = toEl . dataset . liveFingerprintValue ;
@@ -255,7 +246,7 @@ export default class Component {
255246
256247 private processRerender ( html : string , response : Response ) {
257248 const controls = { shouldRender : true } ;
258- this . hooks . triggerHook ( 'render. started' , html , response , controls ) ;
249+ this . hooks . triggerHook ( 'render: started' , html , response , controls ) ;
259250 // used to notify that the component doesn't live on the page anymore
260251 if ( ! controls . shouldRender ) {
261252 return ;
@@ -277,11 +268,6 @@ export default class Component {
277268 // elements to appear different unnecessarily
278269 this . hooks . triggerHook ( 'loading.state:finished' , this . element ) ;
279270
280- if ( ! this . dispatchEvent ( 'live:render' , html , true , true ) ) {
281- // preventDefault() was called
282- return ;
283- }
284-
285271 /**
286272 * For any models modified since the last request started, grab
287273 * their value now: we will re-set them after the new data from
@@ -296,32 +282,21 @@ export default class Component {
296282 // normalize new element into non-loading state before diff
297283 this . hooks . triggerHook ( 'loading.state:finished' , newElement ) ;
298284
299- // TODO: maybe abstract where the new data comes from
300- const newDataFromServer : any = JSON . parse ( newElement . dataset . liveDataValue as string ) ;
285+ this . valueStore . reinitializeData ( this . elementDriver . getComponentData ( newElement ) ) ;
301286 executeMorphdom (
302287 this . element ,
303288 newElement ,
304289 this . unsyncedInputsTracker . getUnsyncedInputs ( ) ,
305290 ( element : HTMLElement ) => getValueFromElement ( element , this . valueStore ) ,
306291 Array . from ( this . getChildren ( ) . values ( ) )
307292 ) ;
308- // TODO: could possibly do this by listening to the dataValue value change
309- this . valueStore . reinitializeData ( newDataFromServer ) ;
310293
311294 // reset the modified values back to their client-side version
312295 Object . keys ( modifiedModelValues ) . forEach ( ( modelName ) => {
313296 this . valueStore . set ( modelName , modifiedModelValues [ modelName ] ) ;
314297 } ) ;
315298
316- this . hooks . triggerHook ( 'render.finished' , this ) ;
317- }
318-
319- private dispatchEvent ( name : string , payload : any = null , canBubble = true , cancelable = false ) {
320- return this . element . dispatchEvent ( new CustomEvent ( name , {
321- bubbles : canBubble ,
322- cancelable,
323- detail : payload
324- } ) ) ;
299+ this . hooks . triggerHook ( 'render:finished' , this ) ;
325300 }
326301
327302 private calculateDebounce ( debounce : number | boolean ) : number {
0 commit comments