Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 4 additions & 19 deletions glass-easel-shadow-sync/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -802,14 +802,13 @@ export class ShadowSyncBackendContext implements GlassEaselBackend.Context {
}

export const hookBuilderToSyncData = <TBuilder extends GeneralBehaviorBuilder>(
glassEasel: typeof import('glass-easel'),
builder: TBuilder,
getComponentFromMethodCaller?: (methodCaller: GeneralComponent) => GeneralComponent | undefined
): TBuilder => {
const properties: string[] = []
const methodCallerMap = new WeakMap<any, GeneralComponent>()

const getContextFromMethodCaller = (methodCaller: GeneralComponent) => {
const component: GeneralComponent = methodCallerMap.get(methodCaller) || methodCaller
const component: GeneralComponent = getComponentFromMethodCaller?.(methodCaller) || methodCaller
const context = component.getBackendContext() as ShadowSyncBackendContext
const backendElement = component.getBackendElement() as ShadowSyncElement
if (!(context instanceof ShadowSyncBackendContext)) {
Expand Down Expand Up @@ -867,30 +866,16 @@ export const hookBuilderToSyncData = <TBuilder extends GeneralBehaviorBuilder>(
return this
},
},
methodCallerInit: {
value(func: (this: GeneralComponent) => any) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
builderPrototype.methodCaller.call(this, function (this: GeneralComponent) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const methodCaller = func.call(this)
methodCallerMap.set(methodCaller, this)
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return methodCaller
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return this
},
},
}),
)

return builder.init(({ self, lifetime }) => {
lifetime('created', () => {
const { context, backendElement } = getContextFromMethodCaller(self)
const { component, context, backendElement } = getContextFromMethodCaller(self)
context.channel.initValues(
backendElement._id,
properties.reduce((initValues, property) => {
initValues[property] = self.data[property]
initValues[property] = component.data[property]
return initValues
}, {} as Record<string, unknown>),
)
Expand Down
44 changes: 43 additions & 1 deletion glass-easel-shadow-sync/tests/spec/backend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ describe('backend', () => {
expect(ops).toEqual([{ foo: 'foo' }])
})
test('hook to sync behavior builder', async () => {
const beh = hookBuilderToSyncData(glassEasel, componentSpace.define())
const beh = hookBuilderToSyncData(componentSpace.define())
.property('name', String)
.property('value', String)
.registerBehavior()
Expand All @@ -135,6 +135,48 @@ describe('backend', () => {
expect(viewRoot.data.name).toEqual('a')
expect(viewRoot.data.value).toEqual('b')
})
test('hook to sync behavior builder with method caller', async () => {
const methodCallerMap = new WeakMap<any, glassEasel.GeneralComponent>()

const beh = hookBuilderToSyncData(componentSpace.define(), (methodCaller) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
methodCallerMap.get(methodCaller),
)
.property('name', String)
.property('value', String)
.registerBehavior()

const compDef = componentSpace
.define()
.behavior(beh)
.template(
tmpl(`
<view>{{name}}-{{value}}</view>
`),
)
.methodCallerInit(function () {
const methodCaller = {}
methodCallerMap.set(methodCaller, this)
return methodCaller
})
.registerComponent()

const root = glassEasel.Component.createWithContext('root', compDef, shadowSyncBackend)

root.destroyBackendElementOnDetach()

const viewRoot = getViewNode(root) as glassEasel.GeneralComponent

expect(domHtml(root)).toEqual('<view>-</view>')
expect(viewRoot.data.name).toEqual('')
expect(viewRoot.data.value).toEqual('')

root.setData({ name: 'a', value: 'b' })
await Promise.resolve()
expect(domHtml(root)).toEqual('<view>a-b</view>')
expect(viewRoot.data.name).toEqual('a')
expect(viewRoot.data.value).toEqual('b')
})
test('hook template engine to sync', () => {
viewComponentSpace.setGlobalUsingComponent(
'wx-textarea',
Expand Down
1 change: 1 addition & 0 deletions glass-easel/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,7 @@ export class Component<
propertyPassingDeepCopy,
options.reflectToAttributes,
comp._$dataGroupObserverTree,
options.propertyComparer,
)
comp._$dataGroup = dataGroup

Expand Down
10 changes: 10 additions & 0 deletions glass-easel/src/component_space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ export const normalizeUrl = (
absPath,
}
}
// Hack for invalid relPath like `wx://the-comp`, privide some compatibility
const relProtoSep = relPath.indexOf('://')
if (relProtoSep > 0) {
const domain = relPath.slice(0, relProtoSep + 3)
const absPath = normalizePath(path, relPath.slice(relProtoSep + 3))
return {
domain,
absPath,
}
}
return {
domain: null,
absPath: normalizePath(path, relPath),
Expand Down
31 changes: 15 additions & 16 deletions glass-easel/src/data_proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,7 @@ export type DataObserver = (...values: unknown[]) => void

export type DataChange = DataReplace | DataSplice
// for replace
export type DataReplace = [DataPath, DataValue, RequireComparer, undefined]
export type RequireComparer = true | undefined
export type DataReplace = [DataPath, DataValue, undefined, undefined]
// for splice, numbers are index, removal count
export type DataSplice = [DataPath, DataValue[], number, number]

Expand Down Expand Up @@ -525,6 +524,8 @@ export class DataGroup<
} | null = null
/* @internal */
private _$recUpdateLevel = 0
/* @internal */
private _$propertyComparer: ((a: DataValue, b: DataValue) => boolean) | null

/* @internal */
private _$generateInnerData(data: { [key: string]: DataValue }) {
Expand Down Expand Up @@ -558,6 +559,7 @@ export class DataGroup<
propertyPassingDeepCopy: DeepCopyStrategy,
reflectToAttributes: boolean,
observerTree: DataGroupObserverTree,
propertyComparer: ((a: DataValue, b: DataValue) => boolean) | null,
) {
this._$comp = associatedComponent
this.data = data
Expand All @@ -568,6 +570,7 @@ export class DataGroup<
this._$propFields = observerTree.propFields
this._$observerTree = observerTree
this._$observerStatus = new Array(observerTree.observers.length) as boolean[]
this._$propertyComparer = propertyComparer
this.innerData = this._$generateInnerData(data)
}

Expand All @@ -581,6 +584,7 @@ export class DataGroup<
DeepCopyStrategy.None,
false,
new DataGroupObserverTree({}),
null,
)
}

Expand Down Expand Up @@ -637,7 +641,7 @@ export class DataGroup<
data = simpleDeepCopy(newData)
}
}
this._$pendingChanges.push([[propName], data, true, undefined])
this._$pendingChanges.push([[propName], data, undefined, undefined])
return true
}

Expand Down Expand Up @@ -794,19 +798,14 @@ export class DataGroup<

// run comparer for properties
let comparerResult: boolean
if (!isSplice && maybeSpliceIndex === true) {
if (prop.comparer) {
change[2] = undefined
comparerResult = !!safeCallback(
'Property Comparer',
prop.comparer,
comp!,
[newData, oldData],
comp?.general(),
)
} else {
comparerResult = oldData !== filteredData
}
if (!isSplice && (prop.comparer || this._$propertyComparer)) {
comparerResult = !!safeCallback(
'Property Comparer',
prop.comparer || this._$propertyComparer!,
comp!,
[newData, oldData],
comp?.general(),
)
changed = comparerResult
} else {
comparerResult = oldData !== filteredData
Expand Down
5 changes: 5 additions & 0 deletions glass-easel/src/global_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export type ComponentOptions = {
virtualHost?: boolean
/** Init component with property values or not */
propertyEarlyInit?: boolean
/** Property comparer function, return false if properties are equal */
propertyComparer?: ((a: any, b: any) => boolean) | null
}

export type NormalizedComponentOptions = {
Expand All @@ -102,6 +104,7 @@ export type NormalizedComponentOptions = {
listenerChangeLifetimes: boolean
virtualHost: boolean
propertyEarlyInit: boolean
propertyComparer: ((a: any, b: any) => boolean) | null
}

/**
Expand Down Expand Up @@ -143,6 +146,7 @@ export const globalOptions: NormalizedComponentOptions & EnvironmentOptions = {
listenerChangeLifetimes: false,
virtualHost: false,
propertyEarlyInit: false,
propertyComparer: null,
throwGlobalError: false,
writeExtraInfoToAttr: false,
backendContext: null,
Expand Down Expand Up @@ -191,5 +195,6 @@ export const normalizeComponentOptions = (
virtualHost: p.virtualHost !== undefined ? p.virtualHost : b.virtualHost,
propertyEarlyInit:
p.propertyEarlyInit !== undefined ? p.propertyEarlyInit : b.propertyEarlyInit,
propertyComparer: p.propertyComparer !== undefined ? p.propertyComparer : b.propertyComparer,
}
}
14 changes: 14 additions & 0 deletions glass-easel/tests/core/component_space.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,20 @@ describe('Component Space', () => {
expect((b.$$ as unknown as HTMLElement).tagName).toBe('SPAN')
})

test('normalizeUrl with backward capability', () => {
const cs = new glassEasel.ComponentSpace()
const behavior = cs.define('beh').registerBehavior()
cs.exportBehavior('beh', 'beh')
cs.importSpace('wx://', cs, false)
const component = cs.defineComponent({
is: 'wx://comp',
behaviors: ['beh'],
})
const comp = glassEasel.Component.createWithContext('root', component, domBackend)
expect(comp.asInstanceOf(component)).toEqual(comp)
expect(comp.hasBehavior(behavior)).toBe(true)
})

describe('Hooks', () => {
test('`createTextNode` hook', () => {
const cs = new glassEasel.ComponentSpace()
Expand Down
95 changes: 94 additions & 1 deletion glass-easel/tests/core/data_update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ describe('partial update', () => {
expect(execArr).toStrictEqual(['A', 'B', 'C'])
execArr = []
child.setData({ p: 'ghi' })
expect(execArr).toStrictEqual(['B'])
expect(execArr).toStrictEqual(['A', 'B', 'C'])
})

test('should not allow updates before init done', () => {
Expand Down Expand Up @@ -1052,4 +1052,97 @@ describe('partial update', () => {
expect(domHtml(comp)).toBe('<child>1-0</child>')
expect(execArr).toEqual(['update:1-0'])
})

test('should update data in nesting components', () => {
let execArr: string[] = []
const childCompDef = componentSpace
.define()
.options({ virtualHost: true })
.property('a', {
type: Boolean,
value: false,
observer(a) {
execArr.push(`child:property:${a}`)
},
})
.observer('a', function () {
execArr.push(`child:observer:${this.data.a}`)
})
.template(
tmpl(`
<block>{{a}}</block>
`),
)
.registerComponent()

const middleCompDef = componentSpace
.define()
.options({ virtualHost: true })
.usingComponents({
child: childCompDef,
})
.property('a', {
type: Boolean,
value: false,
observer(a) {
execArr.push(`middle:property:${a}`)
},
})
.observer('a', function () {
execArr.push(`middle:observer:${this.data.a}`)
})
.template(
tmpl(`
<child id="child" a="{{a}}"/>
`),
)
.registerComponent()

const compDef = componentSpace
.define()
.usingComponents({
middle: middleCompDef,
})
.data(() => ({
a: false,
}))
.template(
tmpl(`
<middle id="middle" a="{{a}}"/>
`),
)
.registerComponent()

const comp = glassEasel.Component.createWithContext('root', compDef, domBackend)
const middle = comp.$.middle as glassEasel.GeneralComponent
const child = middle.$.child as glassEasel.GeneralComponent
glassEasel.Element.pretendAttached(comp)

expect(domHtml(comp)).toBe('false')
expect(execArr).toEqual([
'child:observer:false',
'middle:observer:false',
'child:observer:false',
])

execArr = []
comp.setData({ a: true })
expect(domHtml(comp)).toBe('true')
expect(execArr).toEqual([
'middle:observer:true',
'child:observer:true',
'child:property:true',
'middle:property:true',
])

execArr = []
child.setData({ a: false })
expect(domHtml(comp)).toBe('false')
expect(execArr).toEqual(['child:observer:false', 'child:property:false'])

execArr = []
comp.setData({ a: true })
expect(domHtml(comp)).toBe('true')
expect(execArr).toEqual(['middle:observer:true', 'child:observer:true', 'child:property:true'])
})
})