import { App } from './app' ; import { div } from './html' ; import { VElement } from './dom' ; import { transient , isTransient } from './util' ; import { Exclude , Type } from 'class-transformer' ; export abstract class Component { /** The app associated with the component; undefined if not yet attached - use judiciously - main purpose is internal use by update method */ @ transient app ? : App ; /** The parent component; undefined if the root component - use judiciously - main purpose is internal use by update method */ @ transient parent ? : Component ; @ transient private refreshQueue : Function [ ] = [ ] ; /** Called after construction, with a flag indicating if deserialization occured */ attached ( deserialized ) { } /** Override to return a virtual DOM element that visually represents the component's state */ view ( ) { return div ( ( < any > this . constructor ) . name ) ; } param payload updated ( payload ) { } private branchUpdated ( payload = { } ) { payload . source = this ; for ( var c of this . branch ( ) ) c . updated ( payload ) ; } private preUpdate ( payload = { } ) { if ( this . app ) this . app . activeUpdates ++ ; } private postUpdate ( ) { if ( this . app ) this . app . activeUpdates -- ; } private appRefresh ( ) { if ( this . app ) { this . app . snapshot ( ) ; this . app . refresh ( ) ; } } /** Call with action that updates the component's state, with optional payload object */ update ( updater , payload = { } ) { try { this . preUpdate ( payload ) ; updater ( ) ; this . branchUpdated ( payload ) ; } finally { this . postUpdate ( ) ; } this . appRefresh ( ) ; } root ( ) { return ! this . parent ? this : this . parent . root ( ) ; } /** Returns the branch, inclusively from this component to the root component */ branch ( ) { const branch = [ ] ; var current = this ; while ( current ) { branch . push ( current ) ; current = current . parent ; } return branch ; } /** Returns the properties that are components, appended with flattened out array properties of components. */ children ( ) { const children = [ ] ; const childrenFromArrayProperties = [ ] ; for ( var key of Object . keys ( this ) ) { var child = this [ key ] ; if ( child != this . parent && child instanceof Component ) children . push ( child ) ; else if ( child instanceof Array ) for ( var aChild of child ) if ( aChild != this . parent && aChild instanceof Component ) childrenFromArrayProperties . push ( aChild ) ; } return children . concat ( childrenFromArrayProperties ) ; } /** Returns the names of properties that are components */ childrenKeys ( ) { return Object . keys ( this ) . filter ( k => this [ k ] instanceof Component && this [ k ] != this . parent ) ; } /** Attaches a component to the component tree.
     * Called automatically on refresh but can be explicitly called to eagerly attach.
     */ attach ( app , parent ? , deserialize = false ) { const detached = this . parent == null && this . app == null ; this . parent = parent ; this . app = app ; for ( var child of this . children ( ) ) child . attach ( app , this , deserialize ) ; if ( detached ) this . attached ( deserialize ) ; } /** internal use only */ runRefreshesInternal ( ) { for ( var child of this . children ( ) ) child . runRefreshesInternal ( ) ; while ( this . refreshQueue . length ) this . refreshQueue . shift ( ) ! ( ) ; } /** Call to run an action after the view is rendered. Think of it like setTimeout but executed at exactly at the right time in the update cycle. */ onRefreshed ( action ) { this . refreshQueue . push ( action ) ; } /** Auto generates class-transformer's @Type decorators to properties that are components. */ initDecorators ( ) { for ( var propName of this . childrenKeys ( ) . filter ( k => ! isTransient ( this , k ) ) ) { const propValue = this [ propName ] ; propValue . initDecorators ( ) ; Type ( ( ) => propValue . constructor ) ( this , propName ) ; } } /** Returns properties not marked with the `@NonData()` decorator. Think of it like a customizable Object.getKeys method,
     * where you can black-list properties that aren't part of your domain. The properties on the Component base class are
     * marked with @NonData(). @NonData() is often used in conjuction with @Exclude() */ dataKeys ( ) { return Object . keys ( this ) . filter ( k => ! isTransient ( this , k ) ) ; } }