mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-16 11:42:04 +08:00
feat: Scoped Packages (#7911)
* chore: move tns-core-modules to nativescript-core * chore: preparing compat generate script * chore: add missing definitions * chore: no need for http-request to be private * chore: packages chore * test: generate tests for tns-core-modules * chore: add anroid module for consistency * chore: add .npmignore * chore: added privateModulesWhitelist * chore(webpack): added bundle-entry-points * chore: scripts * chore: tests changed to use @ns/core * test: add scoped-packages test project * test: fix types * test: update test project * chore: build scripts * chore: update build script * chore: npm scripts cleanup * chore: make the compat pgk work with old wp config * test: generate diff friendly tests * chore: create barrel exports * chore: move files after rebase * chore: typedoc config * chore: compat mode * chore: review of barrels * chore: remove tns-core-modules import after rebase * chore: dev workflow setup * chore: update developer-workflow * docs: experiment with API extractor * chore: api-extractor and barrel exports * chore: api-extractor configs * chore: generate d.ts rollup with api-extractor * refactor: move methods inside Frame * chore: fic tests to use Frame static methods * refactor: create Builder class * refactor: use Builder class in tests * refactor: include Style in ui barrel * chore: separate compat build script * chore: fix tslint errors * chore: update NATIVESCRIPT_CORE_ARGS * chore: fix compat pack * chore: fix ui-test-app build with linked modules * chore: Application, ApplicationSettings, Connectivity and Http * chore: export Trace, Profiling and Utils * refactor: Static create methods for ImageSource * chore: fix deprecated usages of ImageSource * chore: move Span and FormattedString to ui * chore: add events-args and ImageSource to index files * chore: check for CLI >= 6.2 when building for IOS * chore: update travis build * chore: copy Pod file to compat package * chore: update error msg ui-tests-app * refactor: Apply suggestions from code review Co-Authored-By: Martin Yankov <m.i.yankov@gmail.com> * chore: typings and refs * chore: add missing d.ts files for public API * chore: adress code review FB * chore: update api-report * chore: dev-workflow for other apps * chore: api update * chore: update api-report
This commit is contained in:

committed by
GitHub

parent
6c7139477e
commit
cc97a16800
628
nativescript-core/ui/core/bindable/bindable.ts
Normal file
628
nativescript-core/ui/core/bindable/bindable.ts
Normal file
@ -0,0 +1,628 @@
|
||||
import { BindingOptions } from ".";
|
||||
import { ViewBase } from "../view-base";
|
||||
|
||||
import { unsetValue } from "../properties";
|
||||
import { Observable, WrappedValue, PropertyChangeData, EventData } from "../../../data/observable";
|
||||
import { addWeakEventListener, removeWeakEventListener } from "../weak-event-listener";
|
||||
import { bindingConstants, parentsRegex } from "../../builder/binding-builder";
|
||||
import { escapeRegexSymbols } from "../../../utils/utils";
|
||||
import {
|
||||
isEnabled as traceEnabled,
|
||||
write as traceWrite,
|
||||
error as traceError,
|
||||
categories as traceCategories,
|
||||
notifyEvent as traceNotifyEvent,
|
||||
isCategorySet,
|
||||
messageType as traceMessageType
|
||||
} from "../../../trace";
|
||||
import * as types from "../../../utils/types";
|
||||
|
||||
import * as applicationCommon from "../../../application/application-common";
|
||||
import * as polymerExpressions from "../../../js-libs/polymer-expressions";
|
||||
|
||||
export {
|
||||
Observable, WrappedValue, PropertyChangeData, EventData,
|
||||
traceEnabled, traceWrite, traceError, traceCategories, traceNotifyEvent, traceMessageType, isCategorySet
|
||||
};
|
||||
|
||||
const contextKey = "context";
|
||||
// this regex is used to get parameters inside [] for example:
|
||||
// from $parents['ListView'] will return 'ListView'
|
||||
// from $parents[1] will return 1
|
||||
const paramsRegex = /\[\s*(['"])*(\w*)\1\s*\]/;
|
||||
const bc = bindingConstants;
|
||||
const emptyArray = [];
|
||||
const propertiesCache = {};
|
||||
|
||||
function getProperties(property: string): Array<string> {
|
||||
if (!property) {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
let result: Array<string> = propertiesCache[property];
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// first replace all '$parents[..]' with a safe string
|
||||
// second removes all ] since they are not important for property access and not needed
|
||||
// then split properties either on '.' or '['
|
||||
const parentsMatches = property.match(parentsRegex);
|
||||
result = property.replace(parentsRegex, "parentsMatch")
|
||||
.replace(/\]/g, "")
|
||||
.split(/\.|\[/);
|
||||
|
||||
let parentsMatchesCounter = 0;
|
||||
for (let i = 0, resultLength = result.length; i < resultLength; i++) {
|
||||
if (result[i] === "parentsMatch") {
|
||||
result[i] = parentsMatches[parentsMatchesCounter++];
|
||||
}
|
||||
}
|
||||
propertiesCache[property] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getEventOrGestureName(name: string): string {
|
||||
return name.indexOf("on") === 0 ? name.substr(2, name.length - 2) : name;
|
||||
}
|
||||
|
||||
// NOTE: method fromString from "ui/gestures";
|
||||
export function isGesture(eventOrGestureName: string): boolean {
|
||||
let t = eventOrGestureName.trim().toLowerCase();
|
||||
|
||||
return t === "tap"
|
||||
|| t === "doubletap"
|
||||
|| t === "pinch"
|
||||
|| t === "pan"
|
||||
|| t === "swipe"
|
||||
|| t === "rotation"
|
||||
|| t === "longpress"
|
||||
|| t === "touch";
|
||||
}
|
||||
|
||||
// TODO: Make this instance function so that we dont need public statc tapEvent = "tap"
|
||||
// in controls. They will just override this one and provide their own event support.
|
||||
export function isEventOrGesture(name: string, view: ViewBase): boolean {
|
||||
if (typeof name === "string") {
|
||||
let eventOrGestureName = getEventOrGestureName(name);
|
||||
let evt = `${eventOrGestureName}Event`;
|
||||
|
||||
return view.constructor && evt in view.constructor || isGesture(eventOrGestureName.toLowerCase());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export class Binding {
|
||||
private source: WeakRef<Object>;
|
||||
// TODO: target should be hard reference!
|
||||
public target: WeakRef<ViewBase>;
|
||||
|
||||
private sourceOptions: { instance: WeakRef<any>; property: string };
|
||||
private targetOptions: { instance: WeakRef<Object>; property: string };
|
||||
|
||||
private sourceProperties: Array<string>;
|
||||
private propertyChangeListeners: Map<string, Observable> = new Map<string, Observable>();
|
||||
|
||||
public updating: boolean;
|
||||
public sourceIsBindingContext: boolean;
|
||||
public options: BindingOptions;
|
||||
|
||||
constructor(target: ViewBase, options: BindingOptions) {
|
||||
this.target = new WeakRef(target);
|
||||
this.options = options;
|
||||
this.sourceProperties = getProperties(options.sourceProperty);
|
||||
this.targetOptions = this.resolveOptions(target, getProperties(options.targetProperty));
|
||||
if (!this.targetOptions) {
|
||||
throw new Error(`Invalid property: ${options.targetProperty} for target: ${target}`);
|
||||
}
|
||||
|
||||
if (options.twoWay) {
|
||||
const target = this.targetOptions.instance.get();
|
||||
if (target instanceof Observable) {
|
||||
target.on(`${this.targetOptions.property}Change`, this.onTargetPropertyChanged, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onTargetPropertyChanged(data: PropertyChangeData): void {
|
||||
this.updateTwoWay(data.value);
|
||||
}
|
||||
|
||||
public loadedHandlerVisualTreeBinding(args) {
|
||||
let target = args.object;
|
||||
target.off("loaded", this.loadedHandlerVisualTreeBinding, this);
|
||||
const context = target.bindingContext;
|
||||
if (context !== undefined && context !== null) {
|
||||
this.update(context);
|
||||
}
|
||||
}
|
||||
|
||||
public clearSource(): void {
|
||||
this.propertyChangeListeners.forEach((observable, index, map) => {
|
||||
removeWeakEventListener(
|
||||
observable,
|
||||
Observable.propertyChangeEvent,
|
||||
this.onSourcePropertyChanged,
|
||||
this);
|
||||
});
|
||||
|
||||
this.propertyChangeListeners.clear();
|
||||
|
||||
if (this.source) {
|
||||
this.source.clear();
|
||||
}
|
||||
|
||||
if (this.sourceOptions) {
|
||||
this.sourceOptions.instance.clear();
|
||||
this.sourceOptions = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private sourceAsObject(source: any): any {
|
||||
/* tslint:disable */
|
||||
let objectType = typeof source;
|
||||
if (objectType === "number") {
|
||||
source = new Number(source);
|
||||
}
|
||||
else if (objectType === "boolean") {
|
||||
source = new Boolean(source);
|
||||
}
|
||||
else if (objectType === "string") {
|
||||
source = new String(source);
|
||||
}
|
||||
|
||||
/* tslint:enable */
|
||||
return source;
|
||||
}
|
||||
|
||||
private bindingContextChanged(data: PropertyChangeData): void {
|
||||
const target = this.targetOptions.instance.get();
|
||||
if (!target) {
|
||||
this.unbind();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const value = data.value;
|
||||
if (value !== null && value !== undefined) {
|
||||
this.update(value);
|
||||
} else {
|
||||
// TODO: Is this correct?
|
||||
// What should happen when bindingContext is null/undefined?
|
||||
this.clearBinding();
|
||||
}
|
||||
|
||||
// TODO: if oneWay - call target.unbind();
|
||||
}
|
||||
|
||||
public bind(source: any): void {
|
||||
const target = this.targetOptions.instance.get();
|
||||
if (this.sourceIsBindingContext && target instanceof Observable && this.targetOptions.property !== "bindingContext") {
|
||||
target.on("bindingContextChange", this.bindingContextChanged, this);
|
||||
}
|
||||
|
||||
this.update(source);
|
||||
}
|
||||
|
||||
private update(source: any): void {
|
||||
this.clearSource();
|
||||
|
||||
source = this.sourceAsObject(source);
|
||||
|
||||
if (!types.isNullOrUndefined(source)) {
|
||||
// TODO: if oneWay - call target.unbind();
|
||||
this.source = new WeakRef(source);
|
||||
this.sourceOptions = this.resolveOptions(source, this.sourceProperties);
|
||||
|
||||
let sourceValue = this.getSourcePropertyValue();
|
||||
this.updateTarget(sourceValue);
|
||||
this.addPropertyChangeListeners(this.source, this.sourceProperties);
|
||||
} else if (!this.sourceIsBindingContext) {
|
||||
// TODO: if oneWay - call target.unbind();
|
||||
let sourceValue = this.getSourcePropertyValue();
|
||||
this.updateTarget(sourceValue ? sourceValue : source);
|
||||
}
|
||||
}
|
||||
|
||||
public unbind() {
|
||||
const target = this.targetOptions.instance.get();
|
||||
if (target instanceof Observable) {
|
||||
if (this.options.twoWay) {
|
||||
target.off(`${this.targetOptions.property}Change`, this.onTargetPropertyChanged, this);
|
||||
}
|
||||
|
||||
if (this.sourceIsBindingContext && this.targetOptions.property !== "bindingContext") {
|
||||
target.off("bindingContextChange", this.bindingContextChanged, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.targetOptions) {
|
||||
this.targetOptions = undefined;
|
||||
}
|
||||
|
||||
this.sourceProperties = undefined;
|
||||
if (!this.source) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearSource();
|
||||
}
|
||||
|
||||
// Consider returning single {} instead of array for performance.
|
||||
private resolveObjectsAndProperties(source: Object, properties: Array<string>): Array<{ instance: Object; property: string }> {
|
||||
let result = [];
|
||||
let currentObject = source;
|
||||
let currentObjectChanged = false;
|
||||
for (let i = 0, propsArrayLength = properties.length; i < propsArrayLength; i++) {
|
||||
let property = properties[i];
|
||||
if (property === bc.bindingValueKey) {
|
||||
currentObjectChanged = true;
|
||||
}
|
||||
|
||||
if (property === bc.parentValueKey || property.indexOf(bc.parentsValueKey) === 0) {
|
||||
let parentView = this.getParentView(this.target.get(), property).view;
|
||||
if (parentView) {
|
||||
currentObject = parentView.bindingContext;
|
||||
} else {
|
||||
let targetInstance = this.target.get();
|
||||
targetInstance.off("loaded", this.loadedHandlerVisualTreeBinding, this);
|
||||
targetInstance.on("loaded", this.loadedHandlerVisualTreeBinding, this);
|
||||
}
|
||||
|
||||
currentObjectChanged = true;
|
||||
}
|
||||
|
||||
if (currentObject) {
|
||||
result.push({ instance: currentObject, property: property });
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
// do not need to dive into last object property getter on binding stage will handle it
|
||||
if (!currentObjectChanged && (i < propsArrayLength - 1)) {
|
||||
currentObject = currentObject ? currentObject[properties[i]] : null;
|
||||
}
|
||||
|
||||
currentObjectChanged = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private addPropertyChangeListeners(source: WeakRef<Object>, sourceProperty: Array<string>, parentProperies?: string) {
|
||||
let objectsAndProperties = this.resolveObjectsAndProperties(source.get(), sourceProperty);
|
||||
let prop = parentProperies || "";
|
||||
|
||||
for (let i = 0, length = objectsAndProperties.length; i < length; i++) {
|
||||
const propName = objectsAndProperties[i].property;
|
||||
prop += "$" + propName;
|
||||
let currentObject = objectsAndProperties[i].instance;
|
||||
if (!this.propertyChangeListeners.has(prop) && currentObject instanceof Observable && currentObject._isViewBase) {
|
||||
// Add listener for properties created with after 3.0 version
|
||||
addWeakEventListener(currentObject, `${propName}Change`, this.onSourcePropertyChanged, this);
|
||||
addWeakEventListener(currentObject, Observable.propertyChangeEvent, this.onSourcePropertyChanged, this);
|
||||
this.propertyChangeListeners.set(prop, currentObject);
|
||||
} else if (!this.propertyChangeListeners.has(prop) && currentObject instanceof Observable) {
|
||||
addWeakEventListener(currentObject, Observable.propertyChangeEvent, this.onSourcePropertyChanged, this);
|
||||
this.propertyChangeListeners.set(prop, currentObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private prepareExpressionForUpdate(): string {
|
||||
// this regex is used to create a valid RegExp object from a string that has some special regex symbols like [,(,$ and so on.
|
||||
// Basically this method replaces all matches of 'source property' in expression with '$newPropertyValue'.
|
||||
// For example: with an expression similar to:
|
||||
// text="{{ sourceProperty = $parents['ListView'].test, expression = $parents['ListView'].test + 2}}"
|
||||
// update expression will be '$newPropertyValue + 2'
|
||||
// then on expression execution the new value will be taken and target property will be updated with the value of the expression.
|
||||
let escapedSourceProperty = escapeRegexSymbols(this.options.sourceProperty);
|
||||
let expRegex = new RegExp(escapedSourceProperty, "g");
|
||||
let resultExp = this.options.expression.replace(expRegex, bc.newPropertyValueKey);
|
||||
|
||||
return resultExp;
|
||||
}
|
||||
|
||||
private updateTwoWay(value: any) {
|
||||
if (this.updating || !this.options.twoWay) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newValue = value;
|
||||
if (this.options.expression) {
|
||||
let changedModel = {};
|
||||
changedModel[bc.bindingValueKey] = value;
|
||||
changedModel[bc.newPropertyValueKey] = value;
|
||||
let sourcePropertyName = "";
|
||||
if (this.sourceOptions) {
|
||||
sourcePropertyName = this.sourceOptions.property;
|
||||
}
|
||||
else if (typeof this.options.sourceProperty === "string" && this.options.sourceProperty.indexOf(".") === -1) {
|
||||
sourcePropertyName = this.options.sourceProperty;
|
||||
}
|
||||
|
||||
if (sourcePropertyName !== "") {
|
||||
changedModel[sourcePropertyName] = value;
|
||||
}
|
||||
|
||||
let updateExpression = this.prepareExpressionForUpdate();
|
||||
this.prepareContextForExpression(changedModel, updateExpression, undefined);
|
||||
|
||||
let expressionValue = this._getExpressionValue(updateExpression, true, changedModel);
|
||||
if (expressionValue instanceof Error) {
|
||||
traceWrite((<Error>expressionValue).message, traceCategories.Binding, traceMessageType.error);
|
||||
}
|
||||
|
||||
newValue = expressionValue;
|
||||
}
|
||||
|
||||
this.updateSource(newValue);
|
||||
}
|
||||
|
||||
private _getExpressionValue(expression: string, isBackConvert: boolean, changedModel: any): any {
|
||||
try {
|
||||
let exp = polymerExpressions.PolymerExpressions.getExpression(expression);
|
||||
if (exp) {
|
||||
let context = this.source && this.source.get && this.source.get() || global;
|
||||
let model = {};
|
||||
let addedProps = [];
|
||||
const resources = applicationCommon.getResources();
|
||||
for (let prop in resources) {
|
||||
if (resources.hasOwnProperty(prop) && !context.hasOwnProperty(prop)) {
|
||||
context[prop] = resources[prop];
|
||||
addedProps.push(prop);
|
||||
}
|
||||
}
|
||||
|
||||
this.prepareContextForExpression(context, expression, addedProps);
|
||||
model[contextKey] = context;
|
||||
let result = exp.getValue(model, isBackConvert, changedModel ? changedModel : model);
|
||||
// clear added props
|
||||
let addedPropsLength = addedProps.length;
|
||||
for (let i = 0; i < addedPropsLength; i++) {
|
||||
delete context[addedProps[i]];
|
||||
}
|
||||
addedProps.length = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return new Error(expression + " is not a valid expression.");
|
||||
}
|
||||
catch (e) {
|
||||
let errorMessage = "Run-time error occured in file: " + e.sourceURL + " at line: " + e.line + " and column: " + e.column;
|
||||
|
||||
return new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public onSourcePropertyChanged(data: PropertyChangeData) {
|
||||
const sourceProps = this.sourceProperties;
|
||||
const sourcePropsLength = sourceProps.length;
|
||||
let changedPropertyIndex = sourceProps.indexOf(data.propertyName);
|
||||
let parentProps = "";
|
||||
if (changedPropertyIndex > -1) {
|
||||
parentProps = "$" + sourceProps.slice(0, changedPropertyIndex + 1).join("$");
|
||||
while (this.propertyChangeListeners.get(parentProps) !== data.object) {
|
||||
changedPropertyIndex += sourceProps.slice(changedPropertyIndex + 1).indexOf(data.propertyName) + 1;
|
||||
parentProps = "$" + sourceProps.slice(0, changedPropertyIndex + 1).join("$");
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.expression) {
|
||||
const expressionValue = this._getExpressionValue(this.options.expression, false, undefined);
|
||||
if (expressionValue instanceof Error) {
|
||||
traceWrite(expressionValue.message, traceCategories.Binding, traceMessageType.error);
|
||||
} else {
|
||||
this.updateTarget(expressionValue);
|
||||
}
|
||||
} else {
|
||||
if (changedPropertyIndex > -1) {
|
||||
const props = sourceProps.slice(changedPropertyIndex + 1);
|
||||
const propsLength = props.length;
|
||||
if (propsLength > 0) {
|
||||
let value = data.value;
|
||||
for (let i = 0; i < propsLength; i++) {
|
||||
value = value[props[i]];
|
||||
}
|
||||
|
||||
this.updateTarget(value);
|
||||
} else if (data.propertyName === this.sourceOptions.property) {
|
||||
this.updateTarget(data.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we need to do this only if nested objects are used as source and some middle object has changed.
|
||||
if (changedPropertyIndex > -1 && changedPropertyIndex < sourcePropsLength - 1) {
|
||||
const probablyChangedObject = this.propertyChangeListeners.get(parentProps);
|
||||
if (probablyChangedObject &&
|
||||
probablyChangedObject !== data.object[sourceProps[changedPropertyIndex]]) {
|
||||
// remove all weakevent listeners after change, because changed object replaces object that is hooked for
|
||||
// propertyChange event
|
||||
for (let i = sourcePropsLength - 1; i > changedPropertyIndex; i--) {
|
||||
const prop = "$" + sourceProps.slice(0, i + 1).join("$");
|
||||
if (this.propertyChangeListeners.has(prop)) {
|
||||
removeWeakEventListener(
|
||||
this.propertyChangeListeners.get(prop),
|
||||
Observable.propertyChangeEvent,
|
||||
this.onSourcePropertyChanged,
|
||||
this);
|
||||
this.propertyChangeListeners.delete(prop);
|
||||
}
|
||||
}
|
||||
|
||||
const newProps = sourceProps.slice(changedPropertyIndex + 1);
|
||||
// add new weak event listeners
|
||||
const newObject = data.object[sourceProps[changedPropertyIndex]];
|
||||
if (!types.isNullOrUndefined(newObject) && typeof newObject === "object") {
|
||||
this.addPropertyChangeListeners(new WeakRef(newObject), newProps, parentProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private prepareContextForExpression(model: Object, expression: string, newProps: Array<string>) {
|
||||
let parentViewAndIndex: { view: ViewBase, index: number };
|
||||
let parentView;
|
||||
let addedProps = newProps || [];
|
||||
if (expression.indexOf(bc.bindingValueKey) > -1) {
|
||||
model[bc.bindingValueKey] = model;
|
||||
addedProps.push(bc.bindingValueKey);
|
||||
}
|
||||
|
||||
if (expression.indexOf(bc.parentValueKey) > -1) {
|
||||
parentView = this.getParentView(this.target.get(), bc.parentValueKey).view;
|
||||
if (parentView) {
|
||||
model[bc.parentValueKey] = parentView.bindingContext;
|
||||
addedProps.push(bc.parentValueKey);
|
||||
}
|
||||
}
|
||||
|
||||
let parentsArray = expression.match(parentsRegex);
|
||||
if (parentsArray) {
|
||||
for (let i = 0; i < parentsArray.length; i++) {
|
||||
parentViewAndIndex = this.getParentView(this.target.get(), parentsArray[i]);
|
||||
if (parentViewAndIndex.view) {
|
||||
model[bc.parentsValueKey] = model[bc.parentsValueKey] || {};
|
||||
model[bc.parentsValueKey][parentViewAndIndex.index] = parentViewAndIndex.view.bindingContext;
|
||||
addedProps.push(bc.parentsValueKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getSourcePropertyValue() {
|
||||
if (this.options.expression) {
|
||||
let changedModel = {};
|
||||
changedModel[bc.bindingValueKey] = this.source ? this.source.get() : undefined;
|
||||
let expressionValue = this._getExpressionValue(this.options.expression, false, changedModel);
|
||||
if (expressionValue instanceof Error) {
|
||||
traceWrite((<Error>expressionValue).message, traceCategories.Binding, traceMessageType.error);
|
||||
}
|
||||
else {
|
||||
return expressionValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.sourceOptions) {
|
||||
let sourceOptionsInstance = this.sourceOptions.instance.get();
|
||||
if (this.sourceOptions.property === bc.bindingValueKey) {
|
||||
return sourceOptionsInstance;
|
||||
} else if ((sourceOptionsInstance instanceof Observable) && (this.sourceOptions.property && this.sourceOptions.property !== "")) {
|
||||
return sourceOptionsInstance.get(this.sourceOptions.property);
|
||||
} else if (sourceOptionsInstance && this.sourceOptions.property && this.sourceOptions.property !== "" &&
|
||||
this.sourceOptions.property in sourceOptionsInstance) {
|
||||
return sourceOptionsInstance[this.sourceOptions.property];
|
||||
} else {
|
||||
traceWrite("Property: '" + this.sourceOptions.property + "' is invalid or does not exist. SourceProperty: '" + this.options.sourceProperty + "'", traceCategories.Binding, traceMessageType.error);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public clearBinding() {
|
||||
this.clearSource();
|
||||
this.updateTarget(unsetValue);
|
||||
}
|
||||
|
||||
private updateTarget(value: any) {
|
||||
if (this.updating) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateOptions(this.targetOptions, types.isNullOrUndefined(value) ? unsetValue : value);
|
||||
}
|
||||
|
||||
private updateSource(value: any) {
|
||||
if (this.updating || !this.source || !this.source.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateOptions(this.sourceOptions, value);
|
||||
}
|
||||
|
||||
private getParentView(target: any, property: string): { view: ViewBase, index: number } {
|
||||
if (!target) {
|
||||
return { view: null, index: null };
|
||||
}
|
||||
|
||||
let result: ViewBase;
|
||||
if (property === bc.parentValueKey) {
|
||||
result = target.parent;
|
||||
}
|
||||
|
||||
let index = null;
|
||||
if (property.indexOf(bc.parentsValueKey) === 0) {
|
||||
result = target.parent;
|
||||
let indexParams = paramsRegex.exec(property);
|
||||
if (indexParams && indexParams.length > 1) {
|
||||
index = indexParams[2];
|
||||
}
|
||||
|
||||
if (!isNaN(index)) {
|
||||
let indexAsInt = parseInt(index);
|
||||
while (indexAsInt > 0) {
|
||||
result = result.parent;
|
||||
indexAsInt--;
|
||||
}
|
||||
} else if (types.isString(index)) {
|
||||
while (result && result.typeName !== index) {
|
||||
result = result.parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { view: result, index: index };
|
||||
}
|
||||
|
||||
private resolveOptions(obj: Object, properties: Array<string>): { instance: WeakRef<Object>; property: any } {
|
||||
let objectsAndProperties = this.resolveObjectsAndProperties(obj, properties);
|
||||
if (objectsAndProperties.length > 0) {
|
||||
let resolvedObj = objectsAndProperties[objectsAndProperties.length - 1].instance;
|
||||
let prop = objectsAndProperties[objectsAndProperties.length - 1].property;
|
||||
|
||||
return {
|
||||
instance: new WeakRef(this.sourceAsObject(resolvedObj)),
|
||||
property: prop
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private updateOptions(options: { instance: WeakRef<any>; property: string }, value: any) {
|
||||
let optionsInstance;
|
||||
if (options && options.instance) {
|
||||
optionsInstance = options.instance.get();
|
||||
}
|
||||
|
||||
if (!optionsInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updating = true;
|
||||
|
||||
try {
|
||||
if (isEventOrGesture(options.property, <any>optionsInstance) &&
|
||||
types.isFunction(value)) {
|
||||
// calling off method with null as handler will remove all handlers for options.property event
|
||||
optionsInstance.off(options.property, null, optionsInstance.bindingContext);
|
||||
optionsInstance.on(options.property, value, optionsInstance.bindingContext);
|
||||
} else if (optionsInstance instanceof Observable) {
|
||||
optionsInstance.set(options.property, value);
|
||||
} else {
|
||||
optionsInstance[options.property] = value;
|
||||
}
|
||||
} catch (ex) {
|
||||
traceWrite("Binding error while setting property " + options.property + " of " + optionsInstance + ": " + ex,
|
||||
traceCategories.Binding,
|
||||
traceMessageType.error);
|
||||
}
|
||||
|
||||
this.updating = false;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user