UNPKG

tns-core-modules

Version:

Telerik NativeScript Core Modules

529 lines (528 loc) • 22.6 kB
var observable = require("data/observable"); var dependencyObservable = require("ui/core/dependency-observable"); var weakEvents = require("ui/core/weak-event-listener"); var types = require("utils/types"); var trace = require("trace"); var bindingBuilder = require("../builder/binding-builder"); var viewModule = require("ui/core/view"); var utils = require("utils/utils"); var application; function ensureApplication() { if (!application) { application = require("application"); } } var expressions; function ensureExpressions() { if (!expressions) { expressions = require("js-libs/polymer-expressions"); } } var specialProperties; function ensureSpecialProperties() { if (!specialProperties) { specialProperties = require("ui/builder/special-properties"); } } var bindingContextProperty = new dependencyObservable.Property("bindingContext", "Bindable", new dependencyObservable.PropertyMetadata(undefined, dependencyObservable.PropertyMetadataSettings.Inheritable, onBindingContextChanged)); function onBindingContextChanged(data) { var bindable = data.object; bindable._onBindingContextChanged(data.oldValue, data.newValue); } var contextKey = "context"; var paramsRegex = /\[\s*(['"])*(\w*)\1\s*\]/; var bc = bindingBuilder.bindingConstants; var Bindable = (function (_super) { __extends(Bindable, _super); function Bindable() { _super.apply(this, arguments); } Object.defineProperty(Bindable.prototype, "bindings", { get: function () { if (!this._bindings) { this._bindings = {}; } return this._bindings; }, enumerable: true, configurable: true }); Object.defineProperty(Bindable.prototype, "bindingContext", { get: function () { return this._getValue(Bindable.bindingContextProperty); }, set: function (value) { this._setValue(Bindable.bindingContextProperty, value); }, enumerable: true, configurable: true }); Bindable.prototype.bind = function (options, source) { var binding = this.bindings[options.targetProperty]; if (binding) { binding.unbind(); } binding = new Binding(this, options); this.bindings[options.targetProperty] = binding; var bindingSource = source; if (!bindingSource) { bindingSource = this.bindingContext; binding.sourceIsBindingContext = true; } if (!types.isNullOrUndefined(bindingSource)) { binding.bind(bindingSource); } }; Bindable.prototype.unbind = function (property) { var binding = this.bindings[property]; if (binding) { binding.unbind(); delete this.bindings[property]; } }; Bindable.prototype._updateTwoWayBinding = function (propertyName, value) { var binding = this.bindings[propertyName]; if (binding) { binding.updateTwoWay(value); } }; Bindable.prototype._setCore = function (data) { _super.prototype._setCore.call(this, data); this._updateTwoWayBinding(data.propertyName, data.value); }; Bindable.prototype._onPropertyChanged = function (property, oldValue, newValue) { trace.write(this + "._onPropertyChanged(" + property.name + ", " + oldValue + ", " + newValue + ")", trace.categories.Binding); _super.prototype._onPropertyChanged.call(this, property, oldValue, newValue); if (this instanceof viewModule.View) { if (property.metadata.inheritable && this._isInheritedChange() === true) { return; } } var binding = this.bindings[property.name]; if (binding && !binding.updating) { if (binding.options.twoWay) { trace.write((this + "._updateTwoWayBinding(" + property.name + ", " + newValue + ");") + property.name, trace.categories.Binding); this._updateTwoWayBinding(property.name, newValue); } else { trace.write(this + ".unbind(" + property.name + ");", trace.categories.Binding); this.unbind(property.name); } } }; Bindable.prototype._onBindingContextChanged = function (oldValue, newValue) { var binding; for (var p in this.bindings) { binding = this.bindings[p]; if (binding.updating || !binding.sourceIsBindingContext) { continue; } trace.write("Binding " + binding.target.get() + "." + binding.options.targetProperty + " to new context " + newValue, trace.categories.Binding); binding.unbind(); if (!types.isNullOrUndefined(newValue)) { binding.bind(newValue); } } }; Bindable.bindingContextProperty = bindingContextProperty; return Bindable; }(dependencyObservable.DependencyObservable)); exports.Bindable = Bindable; var Binding = (function () { function Binding(target, options) { this.updating = false; this.propertyChangeListeners = {}; this.target = new WeakRef(target); this.options = options; } Binding.prototype.loadedHandlerVisualTreeBinding = function (args) { var targetInstance = args.object; targetInstance.off(viewModule.View.loadedEvent, this.loadedHandlerVisualTreeBinding, this); this.unbind(); if (!types.isNullOrUndefined(targetInstance.bindingContext)) { this.bind(targetInstance.bindingContext); } }; ; Binding.prototype.bind = function (obj) { if (types.isNullOrUndefined(obj)) { throw new Error("Expected valid object reference as a source in the Binding.bind method."); } if (typeof (obj) === "number") { obj = new Number(obj); } if (typeof (obj) === "boolean") { obj = new Boolean(obj); } if (typeof (obj) === "string") { obj = new String(obj); } this.source = new WeakRef(obj); this.updateTarget(this.getSourcePropertyValue()); if (!this.sourceOptions) { this.sourceOptions = this.resolveOptions(this.source, this.getSourceProperties()); } this.addPropertyChangeListeners(this.source, this.getSourceProperties()); }; Binding.prototype.getSourceProperties = function () { if (!this.sourcePropertiesArray) { this.sourcePropertiesArray = Binding.getProperties(this.options.sourceProperty); } return this.sourcePropertiesArray; }; Binding.getProperties = function (property) { var result; if (property) { var parentsMatches = property.match(bindingBuilder.parentsRegex); result = property.replace(bindingBuilder.parentsRegex, "parentsMatch") .replace(/\]/g, "") .split(/\.|\[/); var i; var resultLength = result.length; var parentsMatchesCounter = 0; for (i = 0; i < resultLength; i++) { if (result[i] === "parentsMatch") { result[i] = parentsMatches[parentsMatchesCounter]; parentsMatchesCounter++; } } return result; } else { return []; } }; Binding.prototype.resolveObjectsAndProperties = function (source, propsArray) { var result = []; var i; var propsArrayLength = propsArray.length; var currentObject = source; var objProp = ""; var currentObjectChanged = false; for (i = 0; i < propsArrayLength; i++) { objProp = propsArray[i]; if (propsArray[i] === bc.bindingValueKey) { currentObjectChanged = true; } if (propsArray[i] === bc.parentValueKey || propsArray[i].indexOf(bc.parentsValueKey) === 0) { var parentView = this.getParentView(this.target.get(), propsArray[i]).view; if (parentView) { currentObject = parentView.bindingContext; } else { var targetInstance = this.target.get(); targetInstance.off(viewModule.View.loadedEvent, this.loadedHandlerVisualTreeBinding, this); targetInstance.on(viewModule.View.loadedEvent, this.loadedHandlerVisualTreeBinding, this); } currentObjectChanged = true; } result.push({ instance: currentObject, property: objProp }); if (!currentObjectChanged && (i < propsArrayLength - 1)) { currentObject = currentObject ? currentObject[propsArray[i]] : null; } currentObjectChanged = false; } return result; }; Binding.prototype.addPropertyChangeListeners = function (source, sourceProperty) { var objectsAndProperties = this.resolveObjectsAndProperties(source.get(), sourceProperty); var objectsAndPropertiesLength = objectsAndProperties.length; if (objectsAndPropertiesLength > 0) { var i; for (i = 0; i < objectsAndPropertiesLength; i++) { var prop = objectsAndProperties[i].property; var currentObject = objectsAndProperties[i].instance; if (currentObject && !this.propertyChangeListeners[prop] && currentObject instanceof observable.Observable) { weakEvents.addWeakEventListener(currentObject, observable.Observable.propertyChangeEvent, this.onSourcePropertyChanged, this); this.propertyChangeListeners[prop] = currentObject; } } } }; Binding.prototype.unbind = function () { if (!this.source) { return; } var i; var propertyChangeListenersKeys = Object.keys(this.propertyChangeListeners); for (i = 0; i < propertyChangeListenersKeys.length; i++) { weakEvents.removeWeakEventListener(this.propertyChangeListeners[propertyChangeListenersKeys[i]], observable.Observable.propertyChangeEvent, this.onSourcePropertyChanged, this); delete this.propertyChangeListeners[propertyChangeListenersKeys[i]]; } if (this.source) { this.source.clear(); } if (this.sourceOptions) { this.sourceOptions.instance.clear(); this.sourceOptions = undefined; } if (this.targetOptions) { this.targetOptions = undefined; } this.sourcePropertiesArray = undefined; }; Binding.prototype.prepareExpressionForUpdate = function () { var escapedSourceProperty = utils.escapeRegexSymbols(this.options.sourceProperty); var expRegex = new RegExp(escapedSourceProperty, 'g'); var resultExp = this.options.expression.replace(expRegex, bc.newPropertyValueKey); return resultExp; }; Binding.prototype.updateTwoWay = function (value) { if (this.updating) { return; } if (this.options.twoWay) { if (this.options.expression) { var changedModel = {}; changedModel[bc.bindingValueKey] = value; changedModel[bc.newPropertyValueKey] = value; var 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; } var updateExpression = this.prepareExpressionForUpdate(); this.prepareContextForExpression(changedModel, updateExpression); var expressionValue = this._getExpressionValue(updateExpression, true, changedModel); if (expressionValue instanceof Error) { trace.write(expressionValue.message, trace.categories.Binding, trace.messageType.error); } else { this.updateSource(expressionValue); } } else { this.updateSource(value); } } }; Binding.prototype._getExpressionValue = function (expression, isBackConvert, changedModel) { try { ensureExpressions(); var exp = expressions.PolymerExpressions.getExpression(expression); if (exp) { var context = this.source && this.source.get && this.source.get() || global; var model = {}; ensureApplication(); for (var prop in application.resources) { if (application.resources.hasOwnProperty(prop) && !context.hasOwnProperty(prop)) { context[prop] = application.resources[prop]; } } this.prepareContextForExpression(context, expression); model[contextKey] = context; return exp.getValue(model, isBackConvert, changedModel ? changedModel : model); } return new Error(expression + " is not a valid expression."); } catch (e) { var errorMessage = "Run-time error occured in file: " + e.sourceURL + " at line: " + e.line + " and column: " + e.column; return new Error(errorMessage); } }; Binding.prototype.onSourcePropertyChanged = function (data) { if (this.options.expression) { var expressionValue = this._getExpressionValue(this.options.expression, false, undefined); if (expressionValue instanceof Error) { trace.write(expressionValue.message, trace.categories.Binding, trace.messageType.error); } else { this.updateTarget(expressionValue); } } else { var propIndex = this.getSourceProperties().indexOf(data.propertyName); if (propIndex > -1) { var props = this.getSourceProperties().slice(propIndex + 1); var propsLength = props.length; if (propsLength > 0) { var value = data.value; var i; for (i = 0; i < propsLength; i++) { value = value[props[i]]; } this.updateTarget(value); } else if (data.propertyName === this.sourceOptions.property) { this.updateTarget(data.value); } } } var sourceProps = Binding.getProperties(this.options.sourceProperty); var sourcePropsLength = sourceProps.length; var changedPropertyIndex = sourceProps.indexOf(data.propertyName); if (changedPropertyIndex > -1) { var probablyChangedObject = this.propertyChangeListeners[sourceProps[changedPropertyIndex + 1]]; if (probablyChangedObject && probablyChangedObject !== data.object[sourceProps[changedPropertyIndex]]) { for (i = sourcePropsLength - 1; i > changedPropertyIndex; i--) { weakEvents.removeWeakEventListener(this.propertyChangeListeners[sourceProps[i]], observable.Observable.propertyChangeEvent, this.onSourcePropertyChanged, this); delete this.propertyChangeListeners[sourceProps[i]]; } var newProps = sourceProps.slice(changedPropertyIndex + 1); this.addPropertyChangeListeners(new WeakRef(data.object[sourceProps[changedPropertyIndex]]), newProps); } } }; Binding.prototype.prepareContextForExpression = function (model, expression) { var parentViewAndIndex; var parentView; if (expression.indexOf(bc.bindingValueKey) > -1) { model[bc.bindingValueKey] = model; } if (expression.indexOf(bc.parentValueKey) > -1) { parentView = this.getParentView(this.target.get(), bc.parentValueKey).view; if (parentView) { model[bc.parentValueKey] = parentView.bindingContext; } } var parentsArray = expression.match(bindingBuilder.parentsRegex); if (parentsArray) { var i; for (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; } } } }; Binding.prototype.getSourcePropertyValue = function () { if (this.options.expression) { var changedModel = {}; changedModel[bc.bindingValueKey] = this.source.get(); var expressionValue = this._getExpressionValue(this.options.expression, false, changedModel); if (expressionValue instanceof Error) { trace.write(expressionValue.message, trace.categories.Binding, trace.messageType.error); } else { return expressionValue; } } if (!this.sourceOptions) { this.sourceOptions = this.resolveOptions(this.source, this.getSourceProperties()); } var value; if (this.sourceOptions) { var sourceOptionsInstance = this.sourceOptions.instance.get(); if (this.sourceOptions.property === bc.bindingValueKey) { value = sourceOptionsInstance; } else if (sourceOptionsInstance instanceof observable.Observable) { value = sourceOptionsInstance.get(this.sourceOptions.property); } else if (sourceOptionsInstance && this.sourceOptions.property && this.sourceOptions.property in sourceOptionsInstance) { value = sourceOptionsInstance[this.sourceOptions.property]; } } return value; }; Binding.prototype.updateTarget = function (value) { if (this.updating || (!this.target || !this.target.get())) { return; } if (!this.targetOptions) { this.targetOptions = this.resolveOptions(this.target, Binding.getProperties(this.options.targetProperty)); } this.updateOptions(this.targetOptions, value); }; Binding.prototype.updateSource = function (value) { if (this.updating || (!this.source || !this.source.get())) { return; } if (!this.sourceOptions) { this.sourceOptions = this.resolveOptions(this.source, this.getSourceProperties()); } this.updateOptions(this.sourceOptions, value); }; Binding.prototype.getParentView = function (target, property) { if (!target || !(target instanceof viewModule.View)) { return { view: null, index: null }; } var result; if (property === bc.parentValueKey) { result = target.parent; } if (property.indexOf(bc.parentsValueKey) === 0) { result = target.parent; var indexParams = paramsRegex.exec(property); var index; if (indexParams && indexParams.length > 1) { index = indexParams[2]; } if (!isNaN(index)) { var 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 }; }; Binding.prototype.resolveOptions = function (obj, properties) { var objectsAndProperties = this.resolveObjectsAndProperties(obj.get(), properties); if (objectsAndProperties.length > 0) { var resolvedObj = objectsAndProperties[objectsAndProperties.length - 1].instance; var prop = objectsAndProperties[objectsAndProperties.length - 1].property; if (resolvedObj) { return { instance: new WeakRef(resolvedObj), property: prop }; } } return null; }; Binding.prototype.updateOptions = function (options, value) { var optionsInstance; if (options && options.instance) { optionsInstance = options.instance.get(); } if (!optionsInstance) { return; } this.updating = true; try { if (optionsInstance instanceof Bindable && viewModule.isEventOrGesture(options.property, optionsInstance) && types.isFunction(value)) { optionsInstance.off(options.property, null, optionsInstance.bindingContext); optionsInstance.on(options.property, value, optionsInstance.bindingContext); } else { ensureSpecialProperties(); var specialSetter = specialProperties.getSpecialPropertySetter(options.property); if (specialSetter) { specialSetter(optionsInstance, value); } else { if (optionsInstance instanceof observable.Observable) { optionsInstance.set(options.property, value); } else { optionsInstance[options.property] = value; } } } } catch (ex) { trace.write("Binding error while setting property " + options.property + " of " + optionsInstance + ": " + ex, trace.categories.Binding, trace.messageType.error); } this.updating = false; }; return Binding; }()); exports.Binding = Binding;