Skip to content
125 changes: 37 additions & 88 deletions packages/core/ui/proxy-view-container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { Trace } from '../../trace';
*/
// Cases to cover:
// * Child is added to the attached proxy. Handled in _addViewToNativeVisualTree.
// * Proxy (with children) is added to the DOM. In _addViewToNativeVisualTree _addViewToNativeVisualTree recursively when the proxy is added to the parent.
// * Proxy (with children) is added to the DOM. In _addViewToNativeVisualTree recursively when the proxy is added to the parent.
// * Child is removed from attached proxy. Handled in _removeViewFromNativeVisualTree.
// * Proxy (with children) is removed form the DOM. In _removeViewFromNativeVisualTree recursively when the proxy is removed from its parent.
// * Proxy (with children) is removed from the DOM. In _removeViewFromNativeVisualTree recursively when the proxy is removed from its parent.
@CSSType('ProxyViewContainer')
export class ProxyViewContainer extends LayoutBase {
private proxiedLayoutProperties = new Set<string>();
Expand All @@ -24,46 +24,37 @@ export class ProxyViewContainer extends LayoutBase {
}

// No native view for proxy container.
// @ts-ignore
get ios(): any {
public override get ios(): any {
return null;
}

// @ts-ignore
get android(): any {
public override get android(): any {
return null;
}

// get nativeView(): any {
// return null;
// }

get isLayoutRequested(): boolean {
public get isLayoutRequested(): boolean {
// Always return false so all layout requests from children bubble up.
return false;
}

public createNativeView() {
public createNativeView(): any {
return undefined;
}

public _getNativeViewsCount(): number {
let result = 0;
this.eachChildView((cv) => {
result += cv._getNativeViewsCount();

this.eachChildView((cv: View) => {
result += (cv as any)._getNativeViewsCount();
return true;
});

return result;
}

public _eachLayoutView(callback: (View) => void): void {
this.eachChildView((cv) => {
public _eachLayoutView(callback: (view: View) => void): void {
this.eachChildView((cv: View) => {
if (!cv.isCollapsed) {
cv._eachLayoutView(callback);
(cv as any)._eachLayoutView(callback);
}

return true;
});
}
Expand All @@ -84,6 +75,7 @@ export class ProxyViewContainer extends LayoutBase {
});
}
}

_tearDownUI(force?: boolean) {
super._tearDownUI(force);
if (this.reusable && !force) {
Expand All @@ -105,10 +97,10 @@ export class ProxyViewContainer extends LayoutBase {

layoutProperties.forEach((propName) => {
const proxyPropName = makeProxyPropName(propName);
child[proxyPropName] = child[propName];
(child as any)[proxyPropName] = (child as any)[propName];

if (this.proxiedLayoutProperties.has(propName)) {
this._applyLayoutPropertyToChild(child, propName, this[propName]);
this._applyLayoutPropertyToChild(child, propName, (this as any)[propName]);
}
});

Expand All @@ -124,7 +116,6 @@ export class ProxyViewContainer extends LayoutBase {
if (atIndex !== undefined) {
insideIndex = this._childIndexToNativeChildIndex(atIndex);
} else {
// Add last;
insideIndex = this._getNativeViewsCount();
}
if (Trace.isEnabled()) {
Expand All @@ -145,16 +136,11 @@ export class ProxyViewContainer extends LayoutBase {

const parent = this.parent;
if (parent instanceof View) {
return parent._removeViewFromNativeVisualTree(child);
parent._removeViewFromNativeVisualTree(child);
return;
}
}

/*
* Some layouts (e.g. GridLayout) need to get notified when adding and
* removing children, so that they can update private measure data.
*
* We register our children with the parent to avoid breakage.
*/
public _registerLayoutChild(child: View) {
const parent = this.parent;
if (parent instanceof LayoutBase) {
Expand All @@ -169,11 +155,7 @@ export class ProxyViewContainer extends LayoutBase {
}
}

/*
* Register/unregister existing children with the parent layout.
*/
public _parentChanged(oldParent: View): void {
// call super in order to execute base logic like clear inherited properties, etc.
super._parentChanged(oldParent);
const addingToParent = this.parent && !oldParent;
const newLayout = <LayoutBase>this.parent;
Expand All @@ -182,101 +164,68 @@ export class ProxyViewContainer extends LayoutBase {
if (addingToParent && newLayout instanceof LayoutBase) {
this.eachLayoutChild((child) => {
newLayout._registerLayoutChild(child);

return true;
});
} else if (oldLayout instanceof LayoutBase) {
this.eachLayoutChild((child) => {
oldLayout._unregisterLayoutChild(child);

return true;
});
}
}

/**
* Layout property changed, proxy the new value to the child view(s)
*/
public _changedLayoutProperty(propName: string, value: string) {
public _changedLayoutProperty(propName: string, value: any) {
const numChildren = this._getNativeViewsCount();
if (numChildren > 1) {
Trace.write("ProxyViewContainer._changeLayoutProperty - you're setting '" + propName + "' for " + this + ' with more than one child. Probably this is not what you want, consider wrapping it in a StackLayout ', Trace.categories.ViewHierarchy, Trace.messageType.error);
Trace.write("ProxyViewContainer._changeLayoutProperty - you're setting '" + propName + "' for " + this + ' with more than one child. Consider wrapping it in a StackLayout ', Trace.categories.ViewHierarchy, Trace.messageType.error);
}

this.eachLayoutChild((child) => {
this._applyLayoutPropertyToChild(child, propName, value);

return true;
});

this.proxiedLayoutProperties.add(propName);
}

/**
* Apply the layout property to the child view.
*/
private _applyLayoutPropertyToChild(child: View, propName: string, value: any) {
const proxyPropName = makeProxyPropName(propName);
if (proxyPropName in child) {
if (child[propName] !== child[proxyPropName]) {
// Value was set directly on the child view, don't override.
const childAny = child as any;

if (proxyPropName in childAny) {
if (childAny[propName] !== childAny[proxyPropName]) {
if (Trace.isEnabled()) {
Trace.write('ProxyViewContainer._applyLayoutPropertyToChild child ' + child + ' has its own value [' + child[propName] + '] for [' + propName + ']', Trace.categories.ViewHierarchy);
Trace.write('ProxyViewContainer._applyLayoutPropertyToChild child ' + child + ' has its own value [' + childAny[propName] + '] for [' + propName + ']', Trace.categories.ViewHierarchy);
}

return;
}
}

child[propName] = value;
child[proxyPropName] = value;
childAny[propName] = value;
childAny[proxyPropName] = value;
}

public override set hidden(value: boolean): void {
this.eachChildView((child: View) => {
(child as any).hidden = value;
return true;
});
}
}

// Layout propeties to be proxyed to the child views
const layoutProperties = [
// AbsoluteLayout
'left',
'top',

// DockLayout
'dock',

// FlexLayout
'flexDirection',
'flexWrap',
'justifyContent',
'alignItems',
'alignContent',
'order',
'flexGrow',
'flexShrink',
'flexWrapBefore',
'alignSelf',
'flexFlow',
'flex',

// GridLayout
'column',
'columnSpan',
'col',
'colSpan',
'row',
'rowSpan',
];

// Override the inherited layout properties
// Layout properties to be proxied to the child views
const layoutProperties = ['left', 'top', 'dock', 'flexDirection', 'flexWrap', 'justifyContent', 'alignItems', 'alignContent', 'order', 'flexGrow', 'flexShrink', 'flexWrapBefore', 'alignSelf', 'flexFlow', 'flex', 'column', 'columnSpan', 'col', 'colSpan', 'row', 'rowSpan'];

for (const name of layoutProperties) {
const proxyProperty = new Property<ProxyViewContainer, string>({
const proxyProperty = new Property<ProxyViewContainer, any>({
name,
valueChanged(target, oldValue, value) {
target._changedLayoutProperty(name, value);
},
});

proxyProperty.register(ProxyViewContainer);
}

function makeProxyPropName(propName) {
function makeProxyPropName(propName: string) {
return `_proxy:${propName}`;
}
Loading