• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16/**
17 * View (for full update)
18 *
19 * all definitions in this file are framework internal
20 */
21
22type ProvidedVarsMap = Map<string, ObservedPropertyAbstract<any>>;
23
24// Nativeview
25// implemented in C++  for release
26// and in utest/view_native_mock.ts for testing
27abstract class View extends NativeViewFullUpdate implements
28  IMultiPropertiesChangeSubscriber, IMultiPropertiesReadSubscriber {
29
30  private id_: number;
31  private propsUsedForRender: Set<string> = new Set<string>();
32  private isRenderingInProgress: boolean = false;
33
34  private watchedProps: Map<string, (propName: string) => void> = new Map<string, (propName: string) => void>();
35
36  // @Provide'd variables by this class and its ancestors
37  protected providedVars_: ProvidedVarsMap;
38
39  // my LocalStorge instance, shared with ancestor Views.
40  // create a default instance on demand if none is initialized
41  protected localStoragebackStore_: LocalStorage = undefined;
42  protected get localStorage_() {
43    if (!this.localStoragebackStore_) {
44      stateMgmtConsole.info(`${this.constructor.name} is accessing LocalStorage without being provided an instance. Creating a default instance.`);
45      this.localStoragebackStore_ = new LocalStorage({ /* emty */ });
46    }
47    return this.localStoragebackStore_;
48  }
49  protected set localStorage_(instance: LocalStorage) {
50    if (!instance) {
51      // setting to undefined not allowed
52      return;
53    }
54    if (this.localStoragebackStore_) {
55      stateMgmtConsole.error(`${this.constructor.name} is setting LocalStorage instance twice`);
56    }
57    this.localStoragebackStore_ = instance;
58  }
59
60  /**
61   * Create a View
62   *
63   * 1. option: top level View, specify
64   *    - compilerAssignedUniqueChildId must specify
65   *    - parent=undefined
66   *    - localStorage  must provide if @LocalSTorageLink/Prop variables are used
67   *      in this View or descendant Views.
68   *
69   * 2. option: not a top level View
70   *    - compilerAssignedUniqueChildId must specify
71   *    - parent must specify
72   *    - localStorage do not specify, will inherit from parent View.
73   *
74   * @param compilerAssignedUniqueChildId Tw
75   * @param parent
76   * @param localStorage
77   */
78
79  constructor(compilerAssignedUniqueChildId: string, parent: View, localStorage?: LocalStorage) {
80    super(compilerAssignedUniqueChildId, parent);
81    this.id_ = SubscriberManager.MakeId();
82    this.providedVars_ = parent ? new Map(parent.providedVars_)
83      : new Map<string, ObservedPropertyAbstract<any>>();
84
85    this.localStoragebackStore_ = undefined;
86    if (parent) {
87      // this View is not a top-level View
88      stateMgmtConsole.debug(`${this.constructor.name} constructor: Using LocalStorage instance of the parent View.`);
89      this.setCardId(parent.getCardId());
90      this.localStorage_ = parent.localStorage_;
91    } else if (localStorage) {
92      this.localStorage_ = localStorage;
93      stateMgmtConsole.debug(`${this.constructor.name} constructor: Using LocalStorage instance provided via @Entry.`);
94    }
95
96    SubscriberManager.Add(this);
97    stateMgmtConsole.debug(`${this.constructor.name}: constructor done`);
98  }
99
100  // globally unique id, this is different from compilerAssignedUniqueChildId!
101  id__(): number {
102    return this.id_;
103  }
104
105  // temporary function, do not use, it will be removed soon!
106  // prupsoe is to allow eDSL transpiler to fix a bug that
107  // relies on this method
108  id() {
109    return this.id__();
110  }
111
112  // inform the subscribed property
113  // that the View and thereby all properties
114  // are about to be deleted
115  abstract aboutToBeDeleted(): void;
116
117  abstract updateWithValueParams(params: Object): void;
118
119  propertyHasChanged(info?: PropertyInfo): void {
120    if (info) {
121      // need to sync container instanceId to switch instanceId in C++ side.
122      this.syncInstanceId();
123      if (this.propsUsedForRender.has(info)) {
124        stateMgmtConsole.debug(`${this.constructor.name}: propertyHasChanged ['${info || 'unknowm'}']. View needs update`);
125        this.markNeedUpdate();
126      } else {
127        stateMgmtConsole.debug(`${this.constructor.name}: propertyHasChanged ['${info || 'unknowm'}']. View does NOT need update`);
128      }
129      let cb = this.watchedProps.get(info);
130      if (cb) {
131        stateMgmtConsole.debug(`${this.constructor.name}: propertyHasChanged ['${info || 'unknowm'}']. calling @Watch function`);
132        cb.call(this, info);
133      }
134      this.restoreInstanceId();
135    } // if info avail.
136  }
137
138  propertyRead(info?: PropertyInfo): void {
139    stateMgmtConsole.debug(`${this.constructor.name}: propertyRead ['${info || 'unknowm'}'].`);
140    if (info && (info !== 'unknown') && this.isRenderingInProgress) {
141      this.propsUsedForRender.add(info);
142    }
143  }
144
145
146  // for test purposes
147  public propertiesNeededToRender(): Set<string> {
148    return this.propsUsedForRender;
149  }
150
151  public aboutToRender(): void {
152    stateMgmtConsole.debug(`${this.constructor.name}: aboutToRender`);
153    // reset
154    this.propsUsedForRender = new Set<string>();
155    this.isRenderingInProgress = true;
156  }
157
158  public aboutToContinueRender(): void {
159    // do not reset
160    this.isRenderingInProgress = true;
161  }
162
163  public onRenderDone(): void {
164    this.isRenderingInProgress = false;
165    stateMgmtConsole.debug(`${this.constructor.name}: onRenderDone: render performed get access to these properties: ${JSON.stringify(Array.from(this.propsUsedForRender))}.`);
166  }
167
168
169  /**
170   * Function to be called from the constructor of the sub component
171   * to register a @Watch varibale
172   * @param propStr name of the variable. Note from @Provide and @Consume this is
173   *      the variable name and not the alias!
174   * @param callback application defined member function of sub-class
175   */
176  protected declareWatch(propStr: string, callback: (propName: string) => void): void {
177    this.watchedProps.set(propStr, callback);
178  }
179
180  /**
181   * This View @Provide's a variable under given name
182   * Call this function from the constructor of the sub class
183   * @param providedPropName either the variable name or the alias defined as
184   *        decorator param
185   * @param store the backing store object for this variable (not the get/set variable!)
186   */
187  protected addProvidedVar<T>(providedPropName: string, store: ObservedPropertyAbstract<T>) {
188    if (this.providedVars_.has(providedPropName)) {
189      throw new ReferenceError(`${this.constructor.name}: duplicate @Provide property with name ${providedPropName}.
190      Property with this name is provided by one of the ancestor Views already.`);
191    }
192    this.providedVars_.set(providedPropName, store);
193  }
194
195  /**
196   * Method for the sub-class to call from its constructor for resolving
197   *       a @Consume variable and initializing its backing store
198   *       with the yncedPropertyTwoWay<T> object created from the
199   *       @Provide variable's backing store.
200   * @param providedPropName the name of the @Provide'd variable.
201   *     This is either the @Consume decortor parameter, or variable name.
202   * @param consumeVarName the @Consume variable name (not the
203   *            @Consume decortor parameter)
204   * @returns initiaizing value of the @Consume backing store
205   */
206  protected initializeConsume<T>(providedPropName: string,
207    consumeVarName: string): ObservedPropertyAbstract<T> {
208    let providedVarStore = this.providedVars_.get(providedPropName);
209    if (providedVarStore === undefined) {
210      throw new ReferenceError(`${this.constructor.name}: missing @Provide property with name ${providedPropName}.
211     Fail to resolve @Consume(${providedPropName}).`);
212    }
213
214    return providedVarStore.createLink(this, consumeVarName);
215  }
216}
217