11import { ObjectAny , ObjectHash } from '../../common' ;
22import DataResolverListener from '../../data_sources/model/DataResolverListener' ;
3+ import {
4+ DataBindingImportContext ,
5+ DataBindingImportPolicy ,
6+ DataBindingImportSource ,
7+ DataBindingKind ,
8+ } from '../../data_sources/types' ;
39import { getDataResolverInstance , getDataResolverInstanceValue , isDataResolverProps } from '../../data_sources/utils' ;
4- import StyleableModel from '../../domain_abstract/model/StyleableModel' ;
10+ import type StyleableModel from '../../domain_abstract/model/StyleableModel' ;
511import EditorModel from '../../editor/model/Editor' ;
12+ import { isFunction } from 'underscore' ;
613
714export interface DataWatchersOptions {
815 skipWatcherUpdates ?: boolean ;
916 fromDataSource ?: boolean ;
17+ parsedImportSource ?: DataBindingImportSource ;
18+ dataBindingImportPolicy ?: DataBindingImportPolicy ;
1019}
1120
1221export interface ModelResolverWatcherOptions {
@@ -23,6 +32,7 @@ export class ModelResolverWatcher<T extends ObjectHash> {
2332 constructor (
2433 private model : WatchableModel < T > ,
2534 private updateFn : UpdateFn < T > ,
35+ private kind : DataBindingKind ,
2636 options : ModelResolverWatcherOptions ,
2737 ) {
2838 this . em = options . em ;
@@ -33,6 +43,7 @@ export class ModelResolverWatcher<T extends ObjectHash> {
3343 }
3444
3545 setDataValues ( values : ObjectAny | undefined , options : DataWatchersOptions = { } ) {
46+ values = this . applyImportPolicy ( values , options ) ;
3647 const shouldSkipWatcherUpdates = options . skipWatcherUpdates || options . fromDataSource ;
3748 if ( ! shouldSkipWatcherUpdates ) {
3849 this . removeListeners ( ) ;
@@ -43,11 +54,13 @@ export class ModelResolverWatcher<T extends ObjectHash> {
4354
4455 addDataValues ( values : ObjectAny | undefined , options : DataWatchersOptions = { } ) {
4556 if ( ! values ) return { } ;
46- const evaluatedValues = this . evaluateValues ( values ) ;
57+ const nextValues = this . applyImportPolicy ( values , options ) ;
58+ if ( ! nextValues ) return { } ;
59+ const evaluatedValues = this . evaluateValues ( nextValues ) ;
4760
4861 const shouldSkipWatcherUpdates = options . skipWatcherUpdates || options . fromDataSource ;
4962 if ( ! shouldSkipWatcherUpdates ) {
50- this . updateListeners ( values ) ;
63+ this . updateListeners ( nextValues ) ;
5164 }
5265
5366 return evaluatedValues ;
@@ -111,6 +124,81 @@ export class ModelResolverWatcher<T extends ObjectHash> {
111124 return evaluatedValues ;
112125 }
113126
127+ private applyImportPolicy ( values : ObjectAny | undefined , options : DataWatchersOptions = { } ) {
128+ const { parsedImportSource } = options ;
129+ const dataBindingImportPolicy =
130+ options . dataBindingImportPolicy ?? this . em ?. DataSources . config . dataBindingImportPolicy ;
131+
132+ if ( ! values || ! parsedImportSource || dataBindingImportPolicy === 'overwrite' ) return values ;
133+
134+ const nextValues = { ...values } ;
135+ const source = parsedImportSource ;
136+
137+ Object . keys ( nextValues ) . forEach ( ( key ) => {
138+ const resolverListener = this . resolverListeners [ key ] ;
139+ const incomingValue = nextValues [ key ] ;
140+
141+ if ( ! resolverListener || isDataResolverProps ( incomingValue ) ) {
142+ return ;
143+ }
144+
145+ const resolver = resolverListener . resolver . toJSON ( ) ;
146+ const path = 'path' in resolver ? resolver . path : undefined ;
147+ const context : DataBindingImportContext = {
148+ target : this . model as StyleableModel ,
149+ kind : this . kind ,
150+ source,
151+ key,
152+ value : incomingValue ,
153+ resolvedValue : resolverListener . resolver . getDataValue ( ) ,
154+ resolver,
155+ path,
156+ } ;
157+ const action = this . resolveImportAction ( dataBindingImportPolicy , context ) ;
158+
159+ if ( action === 'overwrite' ) {
160+ return ;
161+ }
162+
163+ if ( action === 'update' ) {
164+ const updated = this . tryUpdateDataSource ( path , incomingValue ) ;
165+
166+ if ( ! updated ) {
167+ this . warnImportFallback ( key , source , path ) ;
168+ }
169+ }
170+
171+ nextValues [ key ] = resolver ;
172+ } ) ;
173+
174+ return nextValues ;
175+ }
176+
177+ private resolveImportAction ( handler : DataBindingImportPolicy | undefined , context : DataBindingImportContext ) {
178+ const action = isFunction ( handler ) ? handler ( context ) : handler ;
179+
180+ return action === 'skip' || action === 'update' || action === 'overwrite' ? action : 'overwrite' ;
181+ }
182+
183+ private tryUpdateDataSource ( path : string | undefined , value : any ) {
184+ if ( ! path ) {
185+ return false ;
186+ }
187+
188+ try {
189+ return this . em . DataSources . setValue ( path , value ) ;
190+ } catch ( error ) {
191+ return false ;
192+ }
193+ }
194+
195+ private warnImportFallback ( key : string , source : DataBindingImportSource , path ?: string ) {
196+ this . em . logWarning (
197+ `[DataSources]: Failed to update the data source bound to "${ key } " during ${ source } import; keeping the existing binding.` ,
198+ { key, source, path } ,
199+ ) ;
200+ }
201+
114202 /**
115203 * removes listeners to stop watching for changes,
116204 * if keys argument is omitted, remove all listeners
0 commit comments