mirror of
				https://github.com/NativeScript/NativeScript.git
				synced 2025-11-04 21:06:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			283 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			283 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { View, CSSType } from '../core/view';
 | 
						|
import { LayoutBase } from '../layouts/layout-base';
 | 
						|
import { Property } from '../core/properties';
 | 
						|
import { Trace } from '../../trace';
 | 
						|
 | 
						|
/**
 | 
						|
 * Proxy view container that adds all its native children directly to the parent.
 | 
						|
 * To be used as a logical grouping container of views.
 | 
						|
 *
 | 
						|
 * @nsView ProxyViewContainer
 | 
						|
 */
 | 
						|
// 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.
 | 
						|
// * 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.
 | 
						|
@CSSType('ProxyViewContainer')
 | 
						|
export class ProxyViewContainer extends LayoutBase {
 | 
						|
	private proxiedLayoutProperties = new Set<string>();
 | 
						|
 | 
						|
	constructor() {
 | 
						|
		super();
 | 
						|
		this.nativeViewProtected = undefined;
 | 
						|
	}
 | 
						|
 | 
						|
	// No native view for proxy container.
 | 
						|
	// @ts-ignore
 | 
						|
	get ios(): any {
 | 
						|
		return null;
 | 
						|
	}
 | 
						|
 | 
						|
	// @ts-ignore
 | 
						|
	get android(): any {
 | 
						|
		return null;
 | 
						|
	}
 | 
						|
 | 
						|
	// get nativeView(): any {
 | 
						|
	//     return null;
 | 
						|
	// }
 | 
						|
 | 
						|
	get isLayoutRequested(): boolean {
 | 
						|
		// Always return false so all layout requests from children bubble up.
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	public createNativeView() {
 | 
						|
		return undefined;
 | 
						|
	}
 | 
						|
 | 
						|
	public _getNativeViewsCount(): number {
 | 
						|
		let result = 0;
 | 
						|
		this.eachChildView((cv) => {
 | 
						|
			result += cv._getNativeViewsCount();
 | 
						|
 | 
						|
			return true;
 | 
						|
		});
 | 
						|
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	public _eachLayoutView(callback: (View) => void): void {
 | 
						|
		this.eachChildView((cv) => {
 | 
						|
			if (!cv.isCollapsed) {
 | 
						|
				cv._eachLayoutView(callback);
 | 
						|
			}
 | 
						|
 | 
						|
			return true;
 | 
						|
		});
 | 
						|
	}
 | 
						|
 | 
						|
	_setupUI(context: any, atIndex?: number, parentIsLoaded?: boolean) {
 | 
						|
		let processChildren = false;
 | 
						|
		if (this.reusable && this._context === context) {
 | 
						|
			processChildren = true;
 | 
						|
		}
 | 
						|
		super._setupUI(context, atIndex, parentIsLoaded);
 | 
						|
		if (this.reusable && processChildren) {
 | 
						|
			this.eachChild((child) => {
 | 
						|
				const oldReusable = child.reusable;
 | 
						|
				child.reusable = true;
 | 
						|
				child._setupUI(context);
 | 
						|
				child.reusable = oldReusable;
 | 
						|
				return true;
 | 
						|
			});
 | 
						|
		}
 | 
						|
	}
 | 
						|
	_tearDownUI(force?: boolean) {
 | 
						|
		super._tearDownUI(force);
 | 
						|
		if (this.reusable && !force) {
 | 
						|
			this.eachChild((child) => {
 | 
						|
				const oldReusable = child.reusable;
 | 
						|
				child.reusable = true;
 | 
						|
				child._tearDownUI();
 | 
						|
				child.reusable = oldReusable;
 | 
						|
				return true;
 | 
						|
			});
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public _addViewToNativeVisualTree(child: View, atIndex?: number): boolean {
 | 
						|
		if (Trace.isEnabled()) {
 | 
						|
			Trace.write('ProxyViewContainer._addViewToNativeVisualTree for a child ' + child + ' ViewContainer.parent: ' + this.parent, Trace.categories.ViewHierarchy);
 | 
						|
		}
 | 
						|
		super._addViewToNativeVisualTree(child);
 | 
						|
 | 
						|
		layoutProperties.forEach((propName) => {
 | 
						|
			const proxyPropName = makeProxyPropName(propName);
 | 
						|
			child[proxyPropName] = child[propName];
 | 
						|
 | 
						|
			if (this.proxiedLayoutProperties.has(propName)) {
 | 
						|
				this._applyLayoutPropertyToChild(child, propName, this[propName]);
 | 
						|
			}
 | 
						|
		});
 | 
						|
 | 
						|
		const parent = this.parent;
 | 
						|
		if (parent instanceof View) {
 | 
						|
			let baseIndex = 0;
 | 
						|
			let insideIndex = 0;
 | 
						|
			if (parent instanceof LayoutBase) {
 | 
						|
				// Get my index in parent and convert it to native index.
 | 
						|
				baseIndex = parent._childIndexToNativeChildIndex(parent.getChildIndex(this));
 | 
						|
			}
 | 
						|
 | 
						|
			if (atIndex !== undefined) {
 | 
						|
				insideIndex = this._childIndexToNativeChildIndex(atIndex);
 | 
						|
			} else {
 | 
						|
				// Add last;
 | 
						|
				insideIndex = this._getNativeViewsCount();
 | 
						|
			}
 | 
						|
			if (Trace.isEnabled()) {
 | 
						|
				Trace.write('ProxyViewContainer._addViewToNativeVisualTree at: ' + atIndex + ' base: ' + baseIndex + ' additional: ' + insideIndex, Trace.categories.ViewHierarchy);
 | 
						|
			}
 | 
						|
 | 
						|
			return parent._addViewToNativeVisualTree(child, baseIndex + insideIndex);
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	public _removeViewFromNativeVisualTree(child: View): void {
 | 
						|
		if (Trace.isEnabled()) {
 | 
						|
			Trace.write('ProxyViewContainer._removeViewFromNativeVisualTree for a child ' + child + ' ViewContainer.parent: ' + this.parent, Trace.categories.ViewHierarchy);
 | 
						|
		}
 | 
						|
		super._removeViewFromNativeVisualTree(child);
 | 
						|
 | 
						|
		const parent = this.parent;
 | 
						|
		if (parent instanceof View) {
 | 
						|
			return parent._removeViewFromNativeVisualTree(child);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * 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) {
 | 
						|
			parent._registerLayoutChild(child);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public _unregisterLayoutChild(child: View) {
 | 
						|
		const parent = this.parent;
 | 
						|
		if (parent instanceof LayoutBase) {
 | 
						|
			parent._unregisterLayoutChild(child);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * 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;
 | 
						|
		const oldLayout = <LayoutBase>oldParent;
 | 
						|
 | 
						|
		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) {
 | 
						|
		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);
 | 
						|
		}
 | 
						|
 | 
						|
		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.
 | 
						|
				if (Trace.isEnabled()) {
 | 
						|
					Trace.write('ProxyViewContainer._applyLayoutPropertyToChild child ' + child + ' has its own value [' + child[propName] + '] for [' + propName + ']', Trace.categories.ViewHierarchy);
 | 
						|
				}
 | 
						|
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		child[propName] = value;
 | 
						|
		child[proxyPropName] = value;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
for (const name of layoutProperties) {
 | 
						|
	const proxyProperty = new Property<ProxyViewContainer, string>({
 | 
						|
		name,
 | 
						|
		valueChanged(target, oldValue, value) {
 | 
						|
			target._changedLayoutProperty(name, value);
 | 
						|
		},
 | 
						|
	});
 | 
						|
 | 
						|
	proxyProperty.register(ProxyViewContainer);
 | 
						|
}
 | 
						|
 | 
						|
function makeProxyPropName(propName) {
 | 
						|
	return `_proxy:${propName}`;
 | 
						|
}
 |