mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +08:00
feat: new expression parser for xml bindings (#9729)
This commit is contained in:

committed by
Nathan Walker

parent
a518249958
commit
90ceed15d3
@ -659,17 +659,19 @@ export function test_BindingConverterCalledEvenWithNullValue() {
|
||||
const testPropertyValue = null;
|
||||
const expectedValue = 'collapsed';
|
||||
pageViewModel.set('testProperty', testPropertyValue);
|
||||
appModule.getResources()['converter'] = function (value) {
|
||||
if (value) {
|
||||
return 'visible';
|
||||
} else {
|
||||
return 'collapsed';
|
||||
}
|
||||
appModule.getResources()['converter'] = {
|
||||
toView: function (value) {
|
||||
if (value) {
|
||||
return 'visible';
|
||||
} else {
|
||||
return 'collapsed';
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const testFunc = function (views: Array<View>) {
|
||||
const testLabel = <Label>views[0];
|
||||
testLabel.bind({ sourceProperty: 'testProperty', targetProperty: 'text', expression: 'testProperty | converter' });
|
||||
testLabel.bind({ sourceProperty: 'testProperty', targetProperty: 'text', expression: 'testProperty | converter()' });
|
||||
|
||||
const page = <Page>views[1];
|
||||
page.bindingContext = pageViewModel;
|
||||
|
@ -437,15 +437,17 @@ export class ListViewTest extends UITest<ListView> {
|
||||
public test_usingAppLevelConvertersInListViewItems() {
|
||||
var listView = this.testView;
|
||||
|
||||
var dateConverter = function (value, format) {
|
||||
var result = format;
|
||||
var day = value.getDate();
|
||||
result = result.replace('DD', day < 10 ? '0' + day : day);
|
||||
var month = value.getMonth() + 1;
|
||||
result = result.replace('MM', month < 10 ? '0' + month : month);
|
||||
result = result.replace('YYYY', value.getFullYear());
|
||||
var dateConverter = {
|
||||
toView: function (value, format) {
|
||||
var result = format;
|
||||
var day = value.getDate();
|
||||
result = result.replace('DD', day < 10 ? '0' + day : day);
|
||||
var month = value.getMonth() + 1;
|
||||
result = result.replace('MM', month < 10 ? '0' + month : month);
|
||||
result = result.replace('YYYY', value.getFullYear());
|
||||
|
||||
return result;
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
Application.getResources()['dateConverter'] = dateConverter;
|
||||
@ -565,10 +567,12 @@ export class ListViewTest extends UITest<ListView> {
|
||||
var listView = this.testView;
|
||||
var converterCalledCounter = 0;
|
||||
|
||||
var testConverter = function (value) {
|
||||
converterCalledCounter++;
|
||||
var testConverter = {
|
||||
toView: function (value) {
|
||||
converterCalledCounter++;
|
||||
|
||||
return value;
|
||||
return value;
|
||||
},
|
||||
};
|
||||
|
||||
Application.getResources()['testConverter'] = testConverter;
|
||||
@ -578,7 +582,7 @@ export class ListViewTest extends UITest<ListView> {
|
||||
listView.bindingContext = listViewModel;
|
||||
|
||||
listView.bind({ sourceProperty: 'items', targetProperty: 'items' });
|
||||
listView.itemTemplate = '<Label id="testLabel" text="{{ $value, $value | testConverter }}" />';
|
||||
listView.itemTemplate = '<Label id="testLabel" text="{{ $value, $value | testConverter() }}" />';
|
||||
|
||||
this.waitUntilListViewReady();
|
||||
|
||||
@ -589,10 +593,12 @@ export class ListViewTest extends UITest<ListView> {
|
||||
var listView = this.testView;
|
||||
var converterCalledCounter = 0;
|
||||
|
||||
var testConverter = function (value) {
|
||||
converterCalledCounter++;
|
||||
var testConverter = {
|
||||
toView: function (value) {
|
||||
converterCalledCounter++;
|
||||
|
||||
return value;
|
||||
return value;
|
||||
},
|
||||
};
|
||||
|
||||
Application.getResources()['testConverter'] = testConverter;
|
||||
@ -602,7 +608,7 @@ export class ListViewTest extends UITest<ListView> {
|
||||
listView.bindingContext = listViewModel;
|
||||
|
||||
listView.bind({ sourceProperty: 'items', targetProperty: 'items' });
|
||||
listView.itemTemplate = '<StackLayout><Label id="testLabel" text="{{ $value, $value | testConverter }}" /></StackLayout>';
|
||||
listView.itemTemplate = '<StackLayout><Label id="testLabel" text="{{ $value, $value | testConverter() }}" /></StackLayout>';
|
||||
|
||||
this.waitUntilListViewReady();
|
||||
|
||||
|
@ -252,15 +252,17 @@ export function test_splice_observable_array_refreshes_the_Repeater() {
|
||||
export function test_usingAppLevelConvertersInRepeaterItems() {
|
||||
var repeater = new Repeater();
|
||||
|
||||
var dateConverter = function (value, format) {
|
||||
var result = format;
|
||||
var day = value.getDate();
|
||||
result = result.replace('DD', month < 10 ? '0' + day : day);
|
||||
var month = value.getMonth() + 1;
|
||||
result = result.replace('MM', month < 10 ? '0' + month : month);
|
||||
result = result.replace('YYYY', value.getFullYear());
|
||||
var dateConverter = {
|
||||
toView: function (value, format) {
|
||||
var result = format;
|
||||
var day = value.getDate();
|
||||
result = result.replace('DD', month < 10 ? '0' + day : day);
|
||||
var month = value.getMonth() + 1;
|
||||
result = result.replace('MM', month < 10 ? '0' + month : month);
|
||||
result = result.replace('YYYY', value.getFullYear());
|
||||
|
||||
return result;
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
Application.getResources()['dateConverter'] = dateConverter;
|
||||
@ -275,7 +277,7 @@ export function test_usingAppLevelConvertersInRepeaterItems() {
|
||||
|
||||
TKUnit.waitUntilReady(() => repeater.isLayoutValid);
|
||||
|
||||
TKUnit.assertEqual(getChildAtText(repeater, 0), dateConverter(new Date(), 'DD.MM.YYYY'), 'element');
|
||||
TKUnit.assertEqual(getChildAtText(repeater, 0), dateConverter.toView(new Date(), 'DD.MM.YYYY'), 'element');
|
||||
}
|
||||
|
||||
helper.buildUIAndRunTest(repeater, testAction);
|
||||
|
@ -1,19 +0,0 @@
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,24 +0,0 @@
|
||||
**Esprima** ([esprima.org](http://esprima.org), BSD license) is a high performance,
|
||||
standard-compliant [ECMAScript](http://www.ecma-international.org/publications/standards/Ecma-262.htm)
|
||||
parser written in ECMAScript (also popularly known as
|
||||
[JavaScript](http://en.wikipedia.org/wiki/JavaScript).
|
||||
Esprima is created and maintained by [Ariya Hidayat](http://twitter.com/ariyahidayat),
|
||||
with the help of [many contributors](https://github.com/ariya/esprima/contributors).
|
||||
|
||||
### Features
|
||||
|
||||
- Full support for ECMAScript 5.1 ([ECMA-262](http://www.ecma-international.org/publications/standards/Ecma-262.htm))
|
||||
- Sensible [syntax tree format](http://esprima.org/doc/index.html#ast) compatible with Mozilla
|
||||
[Parser AST](https://web.archive.org/web/20201119095346/https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API)
|
||||
- Optional tracking of syntax node location (index-based and line-column)
|
||||
- Heavily tested (> 700 [unit tests](http://esprima.org/test/) with [full code coverage](http://esprima.org/test/coverage.html))
|
||||
- [Partial support](http://esprima.org/doc/es6.html) for ECMAScript 6
|
||||
|
||||
Esprima serves as a **building block** for some JavaScript
|
||||
language tools, from [code instrumentation](http://esprima.org/demo/functiontrace.html)
|
||||
to [editor autocompletion](http://esprima.org/demo/autocomplete.html).
|
||||
|
||||
Esprima runs on many popular web browsers, as well as other ECMAScript platforms such as
|
||||
[Rhino](https://github.com/mozilla/rhino), [Nashorn](http://openjdk.java.net/projects/nashorn/), and [Node.js](https://npmjs.org/package/esprima).
|
||||
|
||||
For more information, check the web site [esprima.org](http://esprima.org).
|
266
packages/core/js-libs/esprima/esprima.d.ts
vendored
266
packages/core/js-libs/esprima/esprima.d.ts
vendored
@ -1,266 +0,0 @@
|
||||
/* tslint:disable */
|
||||
|
||||
// Type definitions for Esprima v1.2.0
|
||||
// Project: http://esprima.org
|
||||
// Definitions by: teppeis <https://github.com/teppeis/>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
export const version: string;
|
||||
export function parse(code: string, options?: Options): Syntax.Program;
|
||||
export function tokenize(code: string, options?: Options): Array<Token>;
|
||||
|
||||
export interface Token {
|
||||
type: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
loc?: boolean
|
||||
range?: boolean
|
||||
raw?: boolean
|
||||
tokens?: boolean
|
||||
comment?: boolean
|
||||
attachComment?: boolean
|
||||
tolerant?: boolean
|
||||
source?: boolean
|
||||
}
|
||||
|
||||
export namespace Syntax {
|
||||
// Node
|
||||
interface Node {
|
||||
type: string
|
||||
loc?: LineLocation
|
||||
range?: number[]
|
||||
leadingComments?: Comment[]
|
||||
trailingComments?: Comment[]
|
||||
}
|
||||
interface LineLocation {
|
||||
start: Position
|
||||
end: Position
|
||||
}
|
||||
interface Position {
|
||||
line: number
|
||||
column: number
|
||||
}
|
||||
|
||||
// Comment
|
||||
interface Comment extends Node {
|
||||
value: string
|
||||
}
|
||||
|
||||
// Program
|
||||
interface Program extends Node {
|
||||
body: SomeStatement[]
|
||||
comments?: Comment[]
|
||||
}
|
||||
|
||||
// Function
|
||||
interface Function extends Node {
|
||||
id: Identifier // | null
|
||||
params: Identifier[]
|
||||
defaults: SomeExpression[]
|
||||
rest: Identifier // | null
|
||||
body: BlockStatementOrExpression
|
||||
generator: boolean
|
||||
expression: boolean
|
||||
}
|
||||
interface BlockStatementOrExpression extends Array<SomeStatement>, BlockStatement, SomeExpression {
|
||||
body: BlockStatementOrExpression
|
||||
}
|
||||
|
||||
// Statement
|
||||
type Statement = Node
|
||||
type EmptyStatement = Statement
|
||||
interface BlockStatement extends Statement {
|
||||
body: SomeStatement[]
|
||||
}
|
||||
interface ExpressionStatement extends Statement {
|
||||
expression: SomeExpression
|
||||
}
|
||||
interface IfStatement extends Statement {
|
||||
test: SomeExpression
|
||||
consequent: SomeStatement
|
||||
alternate: SomeStatement
|
||||
}
|
||||
interface LabeledStatement extends Statement {
|
||||
label: Identifier
|
||||
body: SomeStatement
|
||||
}
|
||||
interface BreakStatement extends Statement {
|
||||
label: Identifier // | null
|
||||
}
|
||||
interface ContinueStatement extends Statement {
|
||||
label: Identifier // | null
|
||||
}
|
||||
interface WithStatement extends Statement {
|
||||
object: SomeExpression
|
||||
body: SomeStatement
|
||||
}
|
||||
interface SwitchStatement extends Statement {
|
||||
discriminant: SomeExpression
|
||||
cases: SwitchCase[]
|
||||
lexical: boolean
|
||||
}
|
||||
interface ReturnStatement extends Statement {
|
||||
argument: SomeExpression // | null
|
||||
}
|
||||
interface ThrowStatement extends Statement {
|
||||
argument: SomeExpression
|
||||
}
|
||||
interface TryStatement extends Statement {
|
||||
block: BlockStatement
|
||||
handler: CatchClause // | null
|
||||
guardedHandlers: CatchClause[]
|
||||
finalizer: BlockStatement // | null
|
||||
}
|
||||
interface WhileStatement extends Statement {
|
||||
test: SomeExpression
|
||||
body: SomeStatement
|
||||
}
|
||||
interface DoWhileStatement extends Statement {
|
||||
body: SomeStatement
|
||||
test: SomeExpression
|
||||
}
|
||||
interface ForStatement extends Statement {
|
||||
init: VariableDeclaratorOrExpression // | null
|
||||
test: SomeExpression // | null
|
||||
update: SomeExpression // | null
|
||||
body: SomeStatement
|
||||
}
|
||||
interface ForInStatement extends Statement {
|
||||
left: VariableDeclaratorOrExpression
|
||||
right: SomeExpression
|
||||
body: SomeStatement
|
||||
each: boolean
|
||||
}
|
||||
interface VariableDeclaratorOrExpression extends VariableDeclarator, SomeExpression {
|
||||
}
|
||||
type DebuggerStatement = Statement
|
||||
interface SomeStatement extends
|
||||
EmptyStatement, ExpressionStatement, BlockStatement, IfStatement,
|
||||
LabeledStatement, BreakStatement, ContinueStatement, WithStatement,
|
||||
SwitchStatement, ReturnStatement, ThrowStatement, TryStatement,
|
||||
WhileStatement, DoWhileStatement, ForStatement, ForInStatement, DebuggerStatement {
|
||||
body: SomeStatementOrList
|
||||
}
|
||||
interface SomeStatementOrList extends Array<SomeStatement>, SomeStatement {
|
||||
}
|
||||
|
||||
// Declration
|
||||
type Declration = Statement
|
||||
interface FunctionDeclration extends Declration {
|
||||
id: Identifier
|
||||
params: Identifier[] // Pattern
|
||||
defaults: SomeExpression[]
|
||||
rest: Identifier
|
||||
body: BlockStatementOrExpression
|
||||
generator: boolean
|
||||
expression: boolean
|
||||
}
|
||||
interface VariableDeclaration extends Declration {
|
||||
declarations: VariableDeclarator[]
|
||||
kind: string // "var" | "let" | "const"
|
||||
}
|
||||
interface VariableDeclarator extends Node {
|
||||
id: Identifier // Pattern
|
||||
init: SomeExpression
|
||||
}
|
||||
|
||||
// Expression
|
||||
type Expression = Node
|
||||
interface SomeExpression extends
|
||||
ThisExpression, ArrayExpression, ObjectExpression, FunctionExpression,
|
||||
ArrowFunctionExpression, SequenceExpression, UnaryExpression, BinaryExpression,
|
||||
AssignmentExpression, UpdateExpression, LogicalExpression, ConditionalExpression,
|
||||
NewExpression, CallExpression, MemberExpression {
|
||||
}
|
||||
type ThisExpression = Expression
|
||||
interface ArrayExpression extends Expression {
|
||||
elements: SomeExpression[] // [ Expression | null ]
|
||||
}
|
||||
interface ObjectExpression extends Expression {
|
||||
properties: Property[]
|
||||
}
|
||||
interface Property extends Node {
|
||||
key: LiteralOrIdentifier // Literal | Identifier
|
||||
value: SomeExpression
|
||||
kind: string // "init" | "get" | "set"
|
||||
}
|
||||
interface LiteralOrIdentifier extends Literal, Identifier {
|
||||
}
|
||||
interface FunctionExpression extends Function, Expression {
|
||||
}
|
||||
interface ArrowFunctionExpression extends Function, Expression {
|
||||
}
|
||||
interface SequenceExpression extends Expression {
|
||||
expressions: SomeExpression[]
|
||||
}
|
||||
interface UnaryExpression extends Expression {
|
||||
operator: string // UnaryOperator
|
||||
prefix: boolean
|
||||
argument: SomeExpression
|
||||
}
|
||||
interface BinaryExpression extends Expression {
|
||||
operator: string // BinaryOperator
|
||||
left: SomeExpression
|
||||
right: SomeExpression
|
||||
}
|
||||
interface AssignmentExpression extends Expression {
|
||||
operator: string // AssignmentOperator
|
||||
left: SomeExpression
|
||||
right: SomeExpression
|
||||
}
|
||||
interface UpdateExpression extends Expression {
|
||||
operator: string // UpdateOperator
|
||||
argument: SomeExpression
|
||||
prefix: boolean
|
||||
}
|
||||
interface LogicalExpression extends Expression {
|
||||
operator: string // LogicalOperator
|
||||
left: SomeExpression
|
||||
right: SomeExpression
|
||||
}
|
||||
interface ConditionalExpression extends Expression {
|
||||
test: SomeExpression
|
||||
alternate: SomeExpression
|
||||
consequent: SomeExpression
|
||||
}
|
||||
interface NewExpression extends Expression {
|
||||
callee: SomeExpression
|
||||
arguments: SomeExpression[]
|
||||
}
|
||||
interface CallExpression extends Expression {
|
||||
callee: SomeExpression
|
||||
arguments: SomeExpression[]
|
||||
}
|
||||
interface MemberExpression extends Expression {
|
||||
object: SomeExpression
|
||||
property: IdentifierOrExpression // Identifier | Expression
|
||||
computed: boolean
|
||||
}
|
||||
interface IdentifierOrExpression extends Identifier, SomeExpression {
|
||||
}
|
||||
|
||||
// Pattern
|
||||
// interface Pattern extends Node {
|
||||
// }
|
||||
|
||||
// Clauses
|
||||
interface SwitchCase extends Node {
|
||||
test: SomeExpression
|
||||
consequent: SomeStatement[]
|
||||
}
|
||||
interface CatchClause extends Node {
|
||||
param: Identifier // Pattern
|
||||
guard: SomeExpression
|
||||
body: BlockStatement
|
||||
}
|
||||
|
||||
// Misc
|
||||
interface Identifier extends Node, Expression { // | Pattern
|
||||
name: string
|
||||
}
|
||||
interface Literal extends Node, Expression {
|
||||
value: any // string | boolean | null | number | RegExp
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,75 +0,0 @@
|
||||
{
|
||||
"name": "esprima",
|
||||
"description": "ECMAScript parsing infrastructure for multipurpose analysis",
|
||||
"homepage": "http://esprima.org",
|
||||
"main": "esprima",
|
||||
"sideEffects": false,
|
||||
"types": "esprima.d.ts",
|
||||
"bin": {
|
||||
"esparse": "./bin/esparse.js",
|
||||
"esvalidate": "./bin/esvalidate.js"
|
||||
},
|
||||
"version": "2.0.0-dev",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
},
|
||||
"author": {
|
||||
"name": "Ariya Hidayat",
|
||||
"email": "ariya.hidayat@gmail.com"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Ariya Hidayat",
|
||||
"email": "ariya.hidayat@gmail.com",
|
||||
"web": "http://ariya.ofilabs.com"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ariya/esprima.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://issues.esprima.org"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "BSD",
|
||||
"url": "https://github.com/ariya/esprima/raw/master/LICENSE.BSD"
|
||||
}
|
||||
],
|
||||
"devDependencies": {
|
||||
"jslint": "~0.1.9",
|
||||
"eslint": "~5.16.0",
|
||||
"jscs": "~1.2.4",
|
||||
"istanbul": "~0.2.6",
|
||||
"complexity-report": "~0.6.1",
|
||||
"regenerate": "~0.6.2",
|
||||
"unicode-7.0.0": "~0.1.5",
|
||||
"json-diff": "~0.3.1",
|
||||
"optimist": "~0.6.0"
|
||||
},
|
||||
"keywords": [
|
||||
"ast",
|
||||
"ecmascript",
|
||||
"javascript",
|
||||
"parser",
|
||||
"syntax"
|
||||
],
|
||||
"scripts": {
|
||||
"generate-regex": "node tools/generate-identifier-regex.js",
|
||||
"test": "npm run-script lint && node test/run.js && npm run-script coverage && npm run-script complexity",
|
||||
"lint": "npm run-script check-version && npm run-script eslint && npm run-script jscs && npm run-script jslint",
|
||||
"check-version": "node tools/check-version.js",
|
||||
"eslint": "node node_modules/eslint/bin/eslint.js esprima.js",
|
||||
"jscs": "node node_modules/jscs/bin/jscs esprima.js",
|
||||
"jslint": "node node_modules/jslint/bin/jslint.js esprima.js",
|
||||
"coverage": "npm run-script analyze-coverage && npm run-script check-coverage",
|
||||
"analyze-coverage": "node node_modules/istanbul/lib/cli.js cover test/runner.js",
|
||||
"check-coverage": "node node_modules/istanbul/lib/cli.js check-coverage --statement 100 --branch 100 --function 100",
|
||||
"complexity": "npm run-script analyze-complexity && npm run-script check-complexity",
|
||||
"analyze-complexity": "node tools/list-complexity.js",
|
||||
"check-complexity": "node node_modules/complexity-report/src/cli.js --maxcc 15 --silent -l -w esprima.js",
|
||||
"benchmark": "node test/benchmarks.js",
|
||||
"benchmark-quick": "node test/benchmarks.js quick"
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
// Copyright (c) 2014 The Polymer Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"sideEffects": false,
|
||||
"name": "polymer-expressions",
|
||||
"main": "polymer-expressions",
|
||||
"types": "polymer-expressions.d.ts"
|
||||
}
|
@ -1,393 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function detectEval() {
|
||||
// Don't test for eval if we're running in a Chrome App environment.
|
||||
// We check for APIs set that only exist in a Chrome App context.
|
||||
if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Firefox OS Apps do not allow eval. This feature detection is very hacky
|
||||
// but even if some other platform adds support for this function this code
|
||||
// will continue to work.
|
||||
if (typeof navigator != 'undefined' && navigator.getDeviceStorage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var f = new Function('', 'return true;');
|
||||
return f();
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var hasEval = detectEval();
|
||||
|
||||
function isIndex(s) {
|
||||
return +s === s >>> 0 && s !== '';
|
||||
}
|
||||
|
||||
function toNumber(s) {
|
||||
return +s;
|
||||
}
|
||||
|
||||
function isObject(obj) {
|
||||
return obj === Object(obj);
|
||||
}
|
||||
|
||||
var numberIsNaN = Number.isNaN || function (value) {
|
||||
return typeof value === 'number' && isNaN(value);
|
||||
}
|
||||
|
||||
function areSameValue(left, right) {
|
||||
if (left === right)
|
||||
return left !== 0 || 1 / left === 1 / right;
|
||||
if (numberIsNaN(left) && numberIsNaN(right))
|
||||
return true;
|
||||
|
||||
return left !== left && right !== right;
|
||||
}
|
||||
|
||||
var createObject = ('__proto__' in {}) ?
|
||||
function (obj) { return obj; } :
|
||||
function (obj) {
|
||||
var proto = obj.__proto__;
|
||||
if (!proto)
|
||||
return obj;
|
||||
var newObject = Object.create(proto);
|
||||
Object.getOwnPropertyNames(obj).forEach(function (name) {
|
||||
Object.defineProperty(newObject, name,
|
||||
Object.getOwnPropertyDescriptor(obj, name));
|
||||
});
|
||||
return newObject;
|
||||
};
|
||||
|
||||
var identStart = '[\$_a-zA-Z]';
|
||||
var identPart = '[\$_a-zA-Z0-9]';
|
||||
var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$');
|
||||
|
||||
function getPathCharType(char) {
|
||||
if (char === undefined)
|
||||
return 'eof';
|
||||
|
||||
var code = char.charCodeAt(0);
|
||||
|
||||
switch (code) {
|
||||
case 0x5B: // [
|
||||
case 0x5D: // ]
|
||||
case 0x2E: // .
|
||||
case 0x22: // "
|
||||
case 0x27: // '
|
||||
case 0x30: // 0
|
||||
return char;
|
||||
|
||||
case 0x5F: // _
|
||||
case 0x24: // $
|
||||
return 'ident';
|
||||
|
||||
case 0x20: // Space
|
||||
case 0x09: // Tab
|
||||
case 0x0A: // Newline
|
||||
case 0x0D: // Return
|
||||
case 0xA0: // No-break space
|
||||
case 0xFEFF: // Byte Order Mark
|
||||
case 0x2028: // Line Separator
|
||||
case 0x2029: // Paragraph Separator
|
||||
return 'ws';
|
||||
}
|
||||
|
||||
// a-z, A-Z
|
||||
if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A))
|
||||
return 'ident';
|
||||
|
||||
// 1-9
|
||||
if (0x31 <= code && code <= 0x39)
|
||||
return 'number';
|
||||
|
||||
return 'else';
|
||||
}
|
||||
|
||||
var pathStateMachine = {
|
||||
'beforePath': {
|
||||
'ws': ['beforePath'],
|
||||
'ident': ['inIdent', 'append'],
|
||||
'[': ['beforeElement'],
|
||||
'eof': ['afterPath']
|
||||
},
|
||||
|
||||
'inPath': {
|
||||
'ws': ['inPath'],
|
||||
'.': ['beforeIdent'],
|
||||
'[': ['beforeElement'],
|
||||
'eof': ['afterPath']
|
||||
},
|
||||
|
||||
'beforeIdent': {
|
||||
'ws': ['beforeIdent'],
|
||||
'ident': ['inIdent', 'append']
|
||||
},
|
||||
|
||||
'inIdent': {
|
||||
'ident': ['inIdent', 'append'],
|
||||
'0': ['inIdent', 'append'],
|
||||
'number': ['inIdent', 'append'],
|
||||
'ws': ['inPath', 'push'],
|
||||
'.': ['beforeIdent', 'push'],
|
||||
'[': ['beforeElement', 'push'],
|
||||
'eof': ['afterPath', 'push']
|
||||
},
|
||||
|
||||
'beforeElement': {
|
||||
'ws': ['beforeElement'],
|
||||
'0': ['afterZero', 'append'],
|
||||
'number': ['inIndex', 'append'],
|
||||
"'": ['inSingleQuote', 'append', ''],
|
||||
'"': ['inDoubleQuote', 'append', '']
|
||||
},
|
||||
|
||||
'afterZero': {
|
||||
'ws': ['afterElement', 'push'],
|
||||
']': ['inPath', 'push']
|
||||
},
|
||||
|
||||
'inIndex': {
|
||||
'0': ['inIndex', 'append'],
|
||||
'number': ['inIndex', 'append'],
|
||||
'ws': ['afterElement'],
|
||||
']': ['inPath', 'push']
|
||||
},
|
||||
|
||||
'inSingleQuote': {
|
||||
"'": ['afterElement'],
|
||||
'eof': ['error'],
|
||||
'else': ['inSingleQuote', 'append']
|
||||
},
|
||||
|
||||
'inDoubleQuote': {
|
||||
'"': ['afterElement'],
|
||||
'eof': ['error'],
|
||||
'else': ['inDoubleQuote', 'append']
|
||||
},
|
||||
|
||||
'afterElement': {
|
||||
'ws': ['afterElement'],
|
||||
']': ['inPath', 'push']
|
||||
}
|
||||
}
|
||||
|
||||
function noop() { }
|
||||
|
||||
function parsePath(path) {
|
||||
var keys = [];
|
||||
var index = -1;
|
||||
var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath';
|
||||
|
||||
var actions = {
|
||||
push: function () {
|
||||
if (key === undefined)
|
||||
return;
|
||||
|
||||
keys.push(key);
|
||||
key = undefined;
|
||||
},
|
||||
|
||||
append: function () {
|
||||
if (key === undefined)
|
||||
key = newChar
|
||||
else
|
||||
key += newChar;
|
||||
}
|
||||
};
|
||||
|
||||
function maybeUnescapeQuote() {
|
||||
if (index >= path.length)
|
||||
return;
|
||||
|
||||
var nextChar = path[index + 1];
|
||||
if ((mode == 'inSingleQuote' && nextChar == "'") ||
|
||||
(mode == 'inDoubleQuote' && nextChar == '"')) {
|
||||
index++;
|
||||
newChar = nextChar;
|
||||
actions.append();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while (mode) {
|
||||
index++;
|
||||
c = path[index];
|
||||
|
||||
if (c == '\\' && maybeUnescapeQuote(mode))
|
||||
continue;
|
||||
|
||||
type = getPathCharType(c);
|
||||
typeMap = pathStateMachine[mode];
|
||||
transition = typeMap[type] || typeMap['else'] || 'error';
|
||||
|
||||
if (transition == 'error')
|
||||
return; // parse error;
|
||||
|
||||
mode = transition[0];
|
||||
action = actions[transition[1]] || noop;
|
||||
newChar = transition[2] === undefined ? c : transition[2];
|
||||
action();
|
||||
|
||||
if (mode === 'afterPath') {
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
return; // parse error
|
||||
}
|
||||
|
||||
function isIdent(s) {
|
||||
return identRegExp.test(s);
|
||||
}
|
||||
|
||||
var constructorIsPrivate = {};
|
||||
|
||||
function Path(parts, privateToken) {
|
||||
if (privateToken !== constructorIsPrivate)
|
||||
throw Error('Use Path.get to retrieve path objects');
|
||||
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
this.push(String(parts[i]));
|
||||
}
|
||||
|
||||
if (hasEval && this.length) {
|
||||
this.getValueFrom = this.compiledGetValueFromFn();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(rafaelw): Make simple LRU cache
|
||||
var pathCache = {};
|
||||
|
||||
function getPath(pathString) {
|
||||
if (pathString instanceof Path)
|
||||
return pathString;
|
||||
|
||||
if (pathString == null || pathString.length == 0)
|
||||
pathString = '';
|
||||
|
||||
if (typeof pathString != 'string') {
|
||||
if (isIndex(pathString.length)) {
|
||||
// Constructed with array-like (pre-parsed) keys
|
||||
return new Path(pathString, constructorIsPrivate);
|
||||
}
|
||||
|
||||
pathString = String(pathString);
|
||||
}
|
||||
|
||||
var path = pathCache[pathString];
|
||||
if (path)
|
||||
return path;
|
||||
|
||||
var parts = parsePath(pathString);
|
||||
if (!parts)
|
||||
return invalidPath;
|
||||
|
||||
var path = new Path(parts, constructorIsPrivate);
|
||||
pathCache[pathString] = path;
|
||||
return path;
|
||||
}
|
||||
|
||||
Path.get = getPath;
|
||||
|
||||
function formatAccessor(key) {
|
||||
if (isIndex(key)) {
|
||||
return '[' + key + ']';
|
||||
} else {
|
||||
return '["' + key.replace(/"/g, '\\"') + '"]';
|
||||
}
|
||||
}
|
||||
|
||||
Path.prototype = createObject({
|
||||
__proto__: [],
|
||||
valid: true,
|
||||
|
||||
toString: function () {
|
||||
var pathString = '';
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
var key = this[i];
|
||||
if (isIdent(key)) {
|
||||
pathString += i ? '.' + key : key;
|
||||
} else {
|
||||
pathString += formatAccessor(key);
|
||||
}
|
||||
}
|
||||
|
||||
return pathString;
|
||||
},
|
||||
|
||||
getValueFrom: function (obj, directObserver) {
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (obj == null)
|
||||
return;
|
||||
obj = obj[this[i]];
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
iterateObjects: function (obj, observe) {
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (i)
|
||||
obj = obj[this[i - 1]];
|
||||
if (!isObject(obj))
|
||||
return;
|
||||
observe(obj, this[i]);
|
||||
}
|
||||
},
|
||||
|
||||
compiledGetValueFromFn: function () {
|
||||
var str = '';
|
||||
var pathString = 'obj';
|
||||
str += 'if (obj != null';
|
||||
var i = 0;
|
||||
var key;
|
||||
for (; i < (this.length - 1) ; i++) {
|
||||
key = this[i];
|
||||
pathString += isIdent(key) ? '.' + key : formatAccessor(key);
|
||||
str += ' &&\n ' + pathString + ' != null';
|
||||
}
|
||||
str += ')\n';
|
||||
|
||||
var key = this[i];
|
||||
pathString += isIdent(key) ? '.' + key : formatAccessor(key);
|
||||
|
||||
str += ' return ' + pathString + ';\nelse\n return undefined;';
|
||||
return new Function('obj', str);
|
||||
},
|
||||
|
||||
setValueFrom: function (obj, value) {
|
||||
if (!this.length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < this.length - 1; i++) {
|
||||
if (!isObject(obj))
|
||||
return false;
|
||||
obj = obj[this[i]];
|
||||
}
|
||||
|
||||
if (!isObject(obj))
|
||||
return false;
|
||||
|
||||
obj[this[i]] = value;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
var invalidPath = new Path('', constructorIsPrivate);
|
||||
invalidPath.valid = false;
|
||||
invalidPath.getValueFrom = invalidPath.setValueFrom = function () { };
|
||||
|
||||
exports.Path = Path;
|
@ -1,14 +0,0 @@
|
||||
//@private
|
||||
export class PolymerExpressions {
|
||||
static getExpression(expression: string): Expression;
|
||||
}
|
||||
|
||||
export class Expression {
|
||||
/**
|
||||
* Evaluates a value for an expression.
|
||||
* @param model - Context of the expression.
|
||||
* @param isBackConvert - Denotes if the convertion is forward (from model to ui) or back (ui to model).
|
||||
* @param changedModel - A property bag which contains all changed properties (in case of two way binding).
|
||||
*/
|
||||
getValue(model, isBackConvert, changedModel);
|
||||
}
|
@ -1,541 +0,0 @@
|
||||
// Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
// This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
// The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
// The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
// Code distributed by Google as part of the polymer project is also
|
||||
// subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
|
||||
// Hack to resolve https://github.com/webpack/enhanced-resolve/issues/197 .
|
||||
// This issue causes an require like this (`../esprima`) to be resolved to (`esprima`) by the Angular webpack plugin
|
||||
var esprima = require("../../js-libs/esprima").esprima;
|
||||
|
||||
var Path = require("./path-parser").Path;
|
||||
|
||||
(function (global) {
|
||||
'use strict';
|
||||
|
||||
// TODO(rafaelw): Implement simple LRU.
|
||||
var expressionParseCache = Object.create(null);
|
||||
|
||||
function getExpression(expressionText) {
|
||||
var expression = expressionParseCache[expressionText];
|
||||
if (!expression) {
|
||||
var delegate = new ASTDelegate();
|
||||
esprima.parse(expressionText, delegate);
|
||||
expression = new Expression(delegate);
|
||||
expressionParseCache[expressionText] = expression;
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
function Literal(value) {
|
||||
this.value = value;
|
||||
this.valueFn_ = undefined;
|
||||
}
|
||||
|
||||
Literal.prototype = {
|
||||
valueFn: function () {
|
||||
if (!this.valueFn_) {
|
||||
var value = this.value;
|
||||
this.valueFn_ = function () {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return this.valueFn_;
|
||||
}
|
||||
}
|
||||
|
||||
function IdentPath(name) {
|
||||
this.name = name;
|
||||
this.path = Path.get(name);
|
||||
}
|
||||
|
||||
IdentPath.prototype = {
|
||||
valueFn: function () {
|
||||
if (!this.valueFn_) {
|
||||
var name = this.name;
|
||||
var path = this.path;
|
||||
this.valueFn_ = function (model, observer, changedModel) {
|
||||
if (observer)
|
||||
observer.addPath(model, path);
|
||||
|
||||
if (changedModel) {
|
||||
var result = path.getValueFrom(changedModel);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return path.getValueFrom(model);
|
||||
}
|
||||
}
|
||||
|
||||
return this.valueFn_;
|
||||
},
|
||||
|
||||
setValue: function (model, newValue) {
|
||||
if (this.path.length == 1) {
|
||||
model = findScope(model, this.path[0]);
|
||||
}
|
||||
|
||||
return this.path.setValueFrom(model, newValue);
|
||||
}
|
||||
};
|
||||
|
||||
function MemberExpression(object, property, accessor) {
|
||||
this.computed = accessor == '[';
|
||||
|
||||
this.dynamicDeps = typeof object == 'function' ||
|
||||
object.dynamicDeps ||
|
||||
(this.computed && !(property instanceof Literal));
|
||||
|
||||
this.simplePath =
|
||||
!this.dynamicDeps &&
|
||||
(property instanceof IdentPath || property instanceof Literal) &&
|
||||
(object instanceof MemberExpression || object instanceof IdentPath);
|
||||
|
||||
this.object = this.simplePath ? object : getFn(object);
|
||||
this.property = !this.computed || this.simplePath ?
|
||||
property : getFn(property);
|
||||
}
|
||||
|
||||
MemberExpression.prototype = {
|
||||
get fullPath() {
|
||||
if (!this.fullPath_) {
|
||||
|
||||
var parts = this.object instanceof MemberExpression ?
|
||||
this.object.fullPath.slice() : [this.object.name];
|
||||
parts.push(this.property instanceof IdentPath ?
|
||||
this.property.name : this.property.value);
|
||||
this.fullPath_ = Path.get(parts);
|
||||
}
|
||||
|
||||
return this.fullPath_;
|
||||
},
|
||||
|
||||
valueFn: function () {
|
||||
if (!this.valueFn_) {
|
||||
var object = this.object;
|
||||
|
||||
if (this.simplePath) {
|
||||
var path = this.fullPath;
|
||||
|
||||
this.valueFn_ = function (model, observer) {
|
||||
if (observer)
|
||||
observer.addPath(model, path);
|
||||
|
||||
return path.getValueFrom(model);
|
||||
};
|
||||
} else if (!this.computed) {
|
||||
var path = Path.get(this.property.name);
|
||||
|
||||
this.valueFn_ = function (model, observer, filterRegistry) {
|
||||
var context = object(model, observer, filterRegistry);
|
||||
|
||||
if (observer)
|
||||
observer.addPath(context, path);
|
||||
|
||||
return path.getValueFrom(context);
|
||||
}
|
||||
} else {
|
||||
// Computed property.
|
||||
var property = this.property;
|
||||
|
||||
this.valueFn_ = function (model, observer, filterRegistry) {
|
||||
var context = object(model, observer, filterRegistry);
|
||||
var propName = property(model, observer, filterRegistry);
|
||||
if (observer)
|
||||
observer.addPath(context, [propName]);
|
||||
|
||||
return context ? context[propName] : undefined;
|
||||
};
|
||||
}
|
||||
}
|
||||
return this.valueFn_;
|
||||
},
|
||||
|
||||
setValue: function (model, newValue) {
|
||||
if (this.simplePath) {
|
||||
this.fullPath.setValueFrom(model, newValue);
|
||||
return newValue;
|
||||
}
|
||||
|
||||
var object = this.object(model);
|
||||
var propName = this.property instanceof IdentPath ? this.property.name :
|
||||
this.property(model);
|
||||
return object[propName] = newValue;
|
||||
}
|
||||
};
|
||||
|
||||
function Filter(name, args) {
|
||||
this.name = name;
|
||||
this.args = [];
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
this.args[i] = getFn(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Filter.prototype = {
|
||||
transform: function (model, observer, filterRegistry, toModelDirection,
|
||||
initialArgs) {
|
||||
var fn = filterRegistry[this.name];
|
||||
var context = model;
|
||||
if (fn) {
|
||||
context = undefined;
|
||||
} else {
|
||||
fn = context[this.name];
|
||||
if (!fn) {
|
||||
console.error('Cannot find function or filter: ' + this.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If toModelDirection is falsey, then the "normal" (dom-bound) direction
|
||||
// is used. Otherwise, it looks for a 'toModel' property function on the
|
||||
// object.
|
||||
if (toModelDirection) {
|
||||
fn = fn.toModel;
|
||||
} else if (typeof fn.toView == 'function') {
|
||||
fn = fn.toView;
|
||||
}
|
||||
|
||||
if (typeof fn != 'function') {
|
||||
console.error('Cannot find function or filter: ' + this.name);
|
||||
return;
|
||||
}
|
||||
|
||||
var args = initialArgs || [];
|
||||
for (var i = 0; i < this.args.length; i++) {
|
||||
args.push(getFn(this.args[i])(model, observer, filterRegistry));
|
||||
}
|
||||
|
||||
return fn.apply(context, args);
|
||||
}
|
||||
};
|
||||
|
||||
function notImplemented() { throw Error('Not Implemented'); }
|
||||
|
||||
var unaryOperators = {
|
||||
'+': function (v) { return +v; },
|
||||
'-': function (v) { return -v; },
|
||||
'!': function (v) { return !v; }
|
||||
};
|
||||
|
||||
var binaryOperators = {
|
||||
'+': function (l, r) { return l + r; },
|
||||
'-': function (l, r) { return l - r; },
|
||||
'*': function (l, r) { return l * r; },
|
||||
'/': function (l, r) { return l / r; },
|
||||
'%': function (l, r) { return l % r; },
|
||||
'<': function (l, r) { return l < r; },
|
||||
'>': function (l, r) { return l > r; },
|
||||
'<=': function (l, r) { return l <= r; },
|
||||
'>=': function (l, r) { return l >= r; },
|
||||
'==': function (l, r) { return l == r; },
|
||||
'!=': function (l, r) { return l != r; },
|
||||
'===': function (l, r) { return l === r; },
|
||||
'!==': function (l, r) { return l !== r; },
|
||||
'&&': function (l, r) { return l && r; },
|
||||
'||': function (l, r) { return l || r; },
|
||||
};
|
||||
|
||||
function getFn(arg) {
|
||||
return typeof arg == 'function' ? arg : arg.valueFn();
|
||||
}
|
||||
|
||||
function ASTDelegate() {
|
||||
this.expression = null;
|
||||
this.filters = [];
|
||||
this.deps = {};
|
||||
this.currentPath = undefined;
|
||||
this.scopeIdent = undefined;
|
||||
this.indexIdent = undefined;
|
||||
this.dynamicDeps = false;
|
||||
}
|
||||
|
||||
ASTDelegate.prototype = {
|
||||
createUnaryExpression: function (op, argument) {
|
||||
if (!unaryOperators[op])
|
||||
throw Error('Disallowed operator: ' + op);
|
||||
|
||||
argument = getFn(argument);
|
||||
|
||||
return function (model, observer, filterRegistry) {
|
||||
return unaryOperators[op](argument(model, observer, filterRegistry));
|
||||
};
|
||||
},
|
||||
|
||||
createBinaryExpression: function (op, left, right) {
|
||||
if (!binaryOperators[op])
|
||||
throw Error('Disallowed operator: ' + op);
|
||||
|
||||
left = getFn(left);
|
||||
right = getFn(right);
|
||||
|
||||
switch (op) {
|
||||
case '||':
|
||||
this.dynamicDeps = true;
|
||||
return function (model, observer, filterRegistry) {
|
||||
return left(model, observer, filterRegistry) ||
|
||||
right(model, observer, filterRegistry);
|
||||
};
|
||||
case '&&':
|
||||
this.dynamicDeps = true;
|
||||
return function (model, observer, filterRegistry) {
|
||||
return left(model, observer, filterRegistry) &&
|
||||
right(model, observer, filterRegistry);
|
||||
};
|
||||
}
|
||||
|
||||
return function (model, observer, filterRegistry) {
|
||||
return binaryOperators[op](left(model, observer, filterRegistry),
|
||||
right(model, observer, filterRegistry));
|
||||
};
|
||||
},
|
||||
|
||||
createConditionalExpression: function (test, consequent, alternate) {
|
||||
test = getFn(test);
|
||||
consequent = getFn(consequent);
|
||||
alternate = getFn(alternate);
|
||||
|
||||
this.dynamicDeps = true;
|
||||
|
||||
return function (model, observer, filterRegistry) {
|
||||
return test(model, observer, filterRegistry) ?
|
||||
consequent(model, observer, filterRegistry) :
|
||||
alternate(model, observer, filterRegistry);
|
||||
}
|
||||
},
|
||||
|
||||
createIdentifier: function (name) {
|
||||
var ident = new IdentPath(name);
|
||||
ident.type = 'Identifier';
|
||||
return ident;
|
||||
},
|
||||
|
||||
createMemberExpression: function (accessor, object, property) {
|
||||
var ex = new MemberExpression(object, property, accessor);
|
||||
if (ex.dynamicDeps)
|
||||
this.dynamicDeps = true;
|
||||
return ex;
|
||||
},
|
||||
|
||||
createCallExpression: function (expression, args) {
|
||||
if (!(expression instanceof IdentPath))
|
||||
throw Error('Only identifier function invocations are allowed');
|
||||
|
||||
var filter = new Filter(expression.name, args);
|
||||
|
||||
return function (model, observer, filterRegistry) {
|
||||
return filter.transform(model, observer, filterRegistry, false);
|
||||
};
|
||||
},
|
||||
|
||||
createLiteral: function (token) {
|
||||
return new Literal(token.value);
|
||||
},
|
||||
|
||||
createArrayExpression: function (elements) {
|
||||
for (var i = 0; i < elements.length; i++)
|
||||
elements[i] = getFn(elements[i]);
|
||||
|
||||
return function (model, observer, filterRegistry) {
|
||||
var arr = []
|
||||
for (var i = 0; i < elements.length; i++)
|
||||
arr.push(elements[i](model, observer, filterRegistry));
|
||||
return arr;
|
||||
}
|
||||
},
|
||||
|
||||
createProperty: function (kind, key, value) {
|
||||
return {
|
||||
key: key instanceof IdentPath ? key.name : key.value,
|
||||
value: value
|
||||
};
|
||||
},
|
||||
|
||||
createObjectExpression: function (properties) {
|
||||
for (var i = 0; i < properties.length; i++)
|
||||
properties[i].value = getFn(properties[i].value);
|
||||
|
||||
return function (model, observer, filterRegistry) {
|
||||
var obj = {};
|
||||
for (var i = 0; i < properties.length; i++)
|
||||
obj[properties[i].key] =
|
||||
properties[i].value(model, observer, filterRegistry);
|
||||
return obj;
|
||||
}
|
||||
},
|
||||
|
||||
createFilter: function (name, args) {
|
||||
this.filters.push(new Filter(name, args));
|
||||
},
|
||||
|
||||
createAsExpression: function (expression, scopeIdent) {
|
||||
this.expression = expression;
|
||||
this.scopeIdent = scopeIdent;
|
||||
},
|
||||
|
||||
createInExpression: function (scopeIdent, indexIdent, expression) {
|
||||
this.expression = expression;
|
||||
this.scopeIdent = scopeIdent;
|
||||
this.indexIdent = indexIdent;
|
||||
},
|
||||
|
||||
createTopLevel: function (expression) {
|
||||
this.expression = expression;
|
||||
},
|
||||
|
||||
createThisExpression: notImplemented
|
||||
}
|
||||
|
||||
function Expression(delegate) {
|
||||
this.scopeIdent = delegate.scopeIdent;
|
||||
this.indexIdent = delegate.indexIdent;
|
||||
|
||||
if (!delegate.expression)
|
||||
throw Error('No expression found.');
|
||||
|
||||
this.expression = delegate.expression;
|
||||
getFn(this.expression); // forces enumeration of path dependencies
|
||||
|
||||
this.filters = delegate.filters;
|
||||
this.dynamicDeps = delegate.dynamicDeps;
|
||||
}
|
||||
|
||||
Expression.prototype = {
|
||||
getValue: function (model, isBackConvert, changedModel, observer) {
|
||||
var value = getFn(this.expression)(model.context, observer, changedModel);
|
||||
for (var i = 0; i < this.filters.length; i++) {
|
||||
value = this.filters[i].transform(model.context, observer, model.context, isBackConvert, [value]);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
setValue: function (model, newValue, filterRegistry) {
|
||||
var count = this.filters ? this.filters.length : 0;
|
||||
while (count-- > 0) {
|
||||
newValue = this.filters[count].transform(model, undefined,
|
||||
filterRegistry, true, [newValue]);
|
||||
}
|
||||
|
||||
if (this.expression.setValue)
|
||||
return this.expression.setValue(model, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a style property name to a css property name. For example:
|
||||
* "WebkitUserSelect" to "-webkit-user-select"
|
||||
*/
|
||||
function convertStylePropertyName(name) {
|
||||
return String(name).replace(/[A-Z]/g, function (c) {
|
||||
return '-' + c.toLowerCase();
|
||||
});
|
||||
}
|
||||
|
||||
var parentScopeName = '@' + Math.random().toString(36).slice(2);
|
||||
|
||||
// Single ident paths must bind directly to the appropriate scope object.
|
||||
// I.e. Pushed values in two-bindings need to be assigned to the actual model
|
||||
// object.
|
||||
function findScope(model, prop) {
|
||||
while (model[parentScopeName] &&
|
||||
!Object.prototype.hasOwnProperty.call(model, prop)) {
|
||||
model = model[parentScopeName];
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
function isLiteralExpression(pathString) {
|
||||
switch (pathString) {
|
||||
case '':
|
||||
return false;
|
||||
|
||||
case 'false':
|
||||
case 'null':
|
||||
case 'true':
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isNaN(Number(pathString)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
function PolymerExpressions() { }
|
||||
|
||||
PolymerExpressions.prototype = {
|
||||
// "built-in" filters
|
||||
styleObject: function (value) {
|
||||
var parts = [];
|
||||
for (var key in value) {
|
||||
parts.push(convertStylePropertyName(key) + ': ' + value[key]);
|
||||
}
|
||||
return parts.join('; ');
|
||||
},
|
||||
|
||||
tokenList: function (value) {
|
||||
var tokens = [];
|
||||
for (var key in value) {
|
||||
if (value[key])
|
||||
tokens.push(key);
|
||||
}
|
||||
return tokens.join(' ');
|
||||
},
|
||||
|
||||
// binding delegate API
|
||||
prepareInstancePositionChanged: function (template) {
|
||||
var indexIdent = template.polymerExpressionIndexIdent_;
|
||||
if (!indexIdent)
|
||||
return;
|
||||
|
||||
return function (templateInstance, index) {
|
||||
templateInstance.model[indexIdent] = index;
|
||||
};
|
||||
},
|
||||
|
||||
prepareInstanceModel: function (template) {
|
||||
var scopeName = template.polymerExpressionScopeIdent_;
|
||||
if (!scopeName)
|
||||
return;
|
||||
|
||||
var parentScope = template.templateInstance ?
|
||||
template.templateInstance.model :
|
||||
template.model;
|
||||
|
||||
var indexName = template.polymerExpressionIndexIdent_;
|
||||
|
||||
return function (model) {
|
||||
return createScopeObject(parentScope, model, scopeName, indexName);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var createScopeObject = ('__proto__' in {}) ?
|
||||
function (parentScope, model, scopeName, indexName) {
|
||||
var scope = {};
|
||||
scope[scopeName] = model;
|
||||
scope[indexName] = undefined;
|
||||
scope[parentScopeName] = parentScope;
|
||||
scope.__proto__ = parentScope;
|
||||
return scope;
|
||||
} :
|
||||
function (parentScope, model, scopeName, indexName) {
|
||||
var scope = Object.create(parentScope);
|
||||
Object.defineProperty(scope, scopeName,
|
||||
{ value: model, configurable: true, writable: true });
|
||||
Object.defineProperty(scope, indexName,
|
||||
{ value: undefined, configurable: true, writable: true });
|
||||
Object.defineProperty(scope, parentScopeName,
|
||||
{ value: parentScope, configurable: true, writable: true });
|
||||
return scope;
|
||||
};
|
||||
|
||||
global.PolymerExpressions = PolymerExpressions;
|
||||
PolymerExpressions.getExpression = getExpression;
|
||||
})(module.exports);
|
@ -6,8 +6,8 @@
|
||||
"version": "8.2.0-alpha.0",
|
||||
"homepage": "https://nativescript.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/NativeScript/NativeScript"
|
||||
"type": "git",
|
||||
"url": "https://github.com/NativeScript/NativeScript"
|
||||
},
|
||||
"sideEffects": [
|
||||
"bundle-entry-points.js",
|
||||
@ -15,36 +15,37 @@
|
||||
"./globals"
|
||||
],
|
||||
"files": [
|
||||
"**/*.d.ts",
|
||||
"**/*.js",
|
||||
"**/*.map",
|
||||
"**/platforms/ios/**",
|
||||
"**/platforms/android/**",
|
||||
"**/package.json"
|
||||
"**/*.d.ts",
|
||||
"**/*.js",
|
||||
"**/*.map",
|
||||
"**/platforms/ios/**",
|
||||
"**/platforms/android/**",
|
||||
"**/package.json"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"postinstall": "node cli-hooks/postinstall.js",
|
||||
"preuninstall": "node cli-hooks/preuninstall.js"
|
||||
"postinstall": "node cli-hooks/postinstall.js",
|
||||
"preuninstall": "node cli-hooks/preuninstall.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"css-tree": "^1.1.2",
|
||||
"@nativescript/hook": "~2.0.0",
|
||||
"acorn": "^8.7.0",
|
||||
"css-tree": "^1.1.2",
|
||||
"reduce-css-calc": "^2.1.7",
|
||||
"tslib": "~2.0.0"
|
||||
},
|
||||
"nativescript": {
|
||||
"platforms": {
|
||||
"ios": "6.0.0",
|
||||
"android": "6.0.0"
|
||||
},
|
||||
"hooks": [
|
||||
{
|
||||
"name": "nativescript-core",
|
||||
"type": "before-checkForChanges",
|
||||
"script": "cli-hooks/before-checkForChanges.js",
|
||||
"inject": true
|
||||
}
|
||||
]
|
||||
"platforms": {
|
||||
"ios": "6.0.0",
|
||||
"android": "6.0.0"
|
||||
},
|
||||
"hooks": [
|
||||
{
|
||||
"name": "nativescript-core",
|
||||
"type": "before-checkForChanges",
|
||||
"script": "cli-hooks/before-checkForChanges.js",
|
||||
"inject": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
208
packages/core/ui/core/bindable/bindable-expressions.ts
Normal file
208
packages/core/ui/core/bindable/bindable-expressions.ts
Normal file
@ -0,0 +1,208 @@
|
||||
import { parse } from 'acorn';
|
||||
import { isFunction, isNullOrUndefined, isObject } from '../../../utils/types';
|
||||
|
||||
interface ASTExpression {
|
||||
readonly type: string;
|
||||
readonly [prop: string]: any;
|
||||
}
|
||||
|
||||
const expressionsCache = {};
|
||||
|
||||
// prettier-ignore
|
||||
const unaryOperators = {
|
||||
'+': (v) => +v,
|
||||
'-': (v) => -v,
|
||||
'!': (v) => !v,
|
||||
'void': (v) => void v,
|
||||
'typeof': (v) => typeof v
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
const binaryOperators = {
|
||||
'+': (l, r) => l + r,
|
||||
'-': (l, r) => l - r,
|
||||
'*': (l, r) => l * r,
|
||||
'/': (l, r) => l / r,
|
||||
'%': (l, r) => l % r,
|
||||
'<': (l, r) => l < r,
|
||||
'>': (l, r) => l > r,
|
||||
'<=': (l, r) => l <= r,
|
||||
'>=': (l, r) => l >= r,
|
||||
'==': (l, r) => l == r,
|
||||
'!=': (l, r) => l != r,
|
||||
'===': (l, r) => l === r,
|
||||
'!==': (l, r) => l !== r,
|
||||
'|': (l, r) => l | r,
|
||||
'in': (l, r) => l in r,
|
||||
'instanceof': (l, r) => l instanceof r
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
const logicalOperators = {
|
||||
'&&': (l, r) => l && r(),
|
||||
'||': (l, r) => l || r(),
|
||||
'??': (l, r) => l ?? r()
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
const expressionParsers = {
|
||||
'ArrayExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
const parsed = [];
|
||||
for (let element of expression.elements) {
|
||||
let value = convertExpressionToValue(element, model, isBackConvert, changedModel);
|
||||
element.type == 'SpreadElement' ? parsed.push(...value) : parsed.push(value);
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
'BinaryExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
if (binaryOperators[expression.operator] == null) {
|
||||
throw new Error('Disallowed binary operator: ' + expression.operator);
|
||||
}
|
||||
|
||||
const left = convertExpressionToValue(expression.left, model, isBackConvert, changedModel);
|
||||
const right = convertExpressionToValue(expression.right, model, isBackConvert, changedModel);
|
||||
|
||||
if (expression.operator == '|') {
|
||||
if (right != null && isFunction(right.callback) && right.context != null && right.args != null) {
|
||||
right.args.unshift(left);
|
||||
return right.callback.apply(right.context, right.args);
|
||||
}
|
||||
throw new Error('Invalid converter after ' + expression.operator + ' operator');
|
||||
}
|
||||
|
||||
return binaryOperators[expression.operator](left, right);
|
||||
},
|
||||
'CallExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
let object;
|
||||
let property;
|
||||
if (expression.callee.type == 'MemberExpression') {
|
||||
object = convertExpressionToValue(expression.callee.object, model, isBackConvert, changedModel);
|
||||
property = expression.callee.computed ? convertExpressionToValue(expression.callee.property, model, isBackConvert, changedModel) : expression.callee.property?.name;
|
||||
} else {
|
||||
object = getContext(expression.callee.name, model, changedModel);
|
||||
property = expression.callee?.name;
|
||||
}
|
||||
const callback = expression.callee.optional ? object?.[property] : object[property];
|
||||
const isConverter = isObject(callback) && (isFunction(callback.toModel) || isFunction(callback.toView));
|
||||
|
||||
const parsedArgs = [];
|
||||
for (let argument of expression.arguments) {
|
||||
let value = convertExpressionToValue(argument, model, isBackConvert, changedModel);
|
||||
argument.type == 'SpreadElement' ? parsedArgs.push(...value) : parsedArgs.push(value);
|
||||
}
|
||||
|
||||
if (isNullOrUndefined(callback) || (!isFunction(callback) && !isConverter)) {
|
||||
throw new Error('Cannot perform a call using a non-function property');
|
||||
}
|
||||
|
||||
return isConverter ? getConverter(callback, parsedArgs, isBackConvert) : callback.apply(object, parsedArgs);
|
||||
},
|
||||
'ChainExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
return convertExpressionToValue(expression.expression, model, isBackConvert, changedModel);
|
||||
},
|
||||
'ConditionalExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
const test = convertExpressionToValue(expression.test, model, isBackConvert, changedModel);
|
||||
return convertExpressionToValue(expression[test ? 'consequent' : 'alternate'], model, isBackConvert, changedModel);
|
||||
},
|
||||
'Identifier': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
const context = getContext(expression.name, model, changedModel);
|
||||
return context[expression.name];
|
||||
},
|
||||
'Literal': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
return expression.regex != null ? new RegExp(expression.regex.pattern, expression.regex.flags) : expression.value;
|
||||
},
|
||||
'LogicalExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
if (logicalOperators[expression.operator] == null) {
|
||||
throw new Error('Disallowed logical operator: ' + expression.operator);
|
||||
}
|
||||
const left = convertExpressionToValue(expression.left, model, isBackConvert, changedModel);
|
||||
return logicalOperators[expression.operator](left, () => convertExpressionToValue(expression.right, model, isBackConvert, changedModel));
|
||||
},
|
||||
'MemberExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
const object = convertExpressionToValue(expression.object, model, isBackConvert, changedModel);
|
||||
const property = expression.computed ? convertExpressionToValue(expression.property, model, isBackConvert, changedModel) : expression.property?.name;
|
||||
return expression.optional ? object?.[property] : object[property];
|
||||
},
|
||||
'NewExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
const callback = convertExpressionToValue(expression.callee, model, isBackConvert, changedModel);
|
||||
const parsedArgs = [];
|
||||
for (let argument of expression.arguments) {
|
||||
let value = convertExpressionToValue(argument, model, isBackConvert, changedModel);
|
||||
argument.type == 'SpreadElement' ? parsedArgs.push(...value) : parsedArgs.push(value);
|
||||
}
|
||||
return new callback(...parsedArgs);
|
||||
},
|
||||
'ObjectExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
const parsedObject = {};
|
||||
for (let property of expression.properties) {
|
||||
const value = convertExpressionToValue(property, model, isBackConvert, changedModel);
|
||||
Object.assign(parsedObject, value);
|
||||
}
|
||||
return parsedObject;
|
||||
},
|
||||
'Property': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
const key = expression.computed ? convertExpressionToValue(expression.key, model, isBackConvert, changedModel) : expression.key?.name;
|
||||
const value = convertExpressionToValue(expression.value, model, isBackConvert, changedModel);
|
||||
return {[key]: value};
|
||||
},
|
||||
'SpreadElement': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel);
|
||||
return argument;
|
||||
},
|
||||
'TemplateElement': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
return expression.value.cooked;
|
||||
},
|
||||
'TemplateLiteral': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
let parsedText = '';
|
||||
for (let q of expression.quasis) {
|
||||
parsedText += convertExpressionToValue(q, model, isBackConvert, changedModel);
|
||||
}
|
||||
|
||||
for (let ex of expression.expressions) {
|
||||
parsedText += convertExpressionToValue(ex, model, isBackConvert, changedModel);
|
||||
}
|
||||
return parsedText;
|
||||
},
|
||||
'UnaryExpression': (expression: ASTExpression, model, isBackConvert: boolean, changedModel) => {
|
||||
if (unaryOperators[expression.operator] == null) {
|
||||
throw Error('Disallowed unary operator: ' + expression.operator);
|
||||
}
|
||||
|
||||
const argument = convertExpressionToValue(expression.argument, model, isBackConvert, changedModel);
|
||||
return unaryOperators[expression.operator](argument);
|
||||
}
|
||||
};
|
||||
|
||||
function getContext(key, model, changedModel) {
|
||||
return key in changedModel ? changedModel : model;
|
||||
}
|
||||
|
||||
function getConverter(context, args, isBackConvert: boolean) {
|
||||
const converter = { callback: null, context, args };
|
||||
let callback = isBackConvert ? context.toModel : context.toView;
|
||||
if (callback == null) {
|
||||
callback = Function.prototype;
|
||||
}
|
||||
converter.callback = callback;
|
||||
return converter;
|
||||
}
|
||||
|
||||
export function parseExpression(expressionText: string): ASTExpression {
|
||||
let expression = expressionsCache[expressionText];
|
||||
if (expression == null) {
|
||||
const program: any = parse(expressionText, { ecmaVersion: 2020 });
|
||||
const statements = program.body;
|
||||
for (let statement of statements) {
|
||||
if (statement.type == 'ExpressionStatement') {
|
||||
expression = statement.expression;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expressionsCache[expressionText] = expression;
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
export function convertExpressionToValue(expression: ASTExpression, model, isBackConvert: boolean, changedModel) {
|
||||
return expressionParsers[expression.type](expression, model, isBackConvert, changedModel);
|
||||
}
|
@ -8,10 +8,9 @@ import { addWeakEventListener, removeWeakEventListener } from '../weak-event-lis
|
||||
import { bindingConstants, parentsRegex } from '../../builder/binding-builder';
|
||||
import { escapeRegexSymbols } from '../../../utils';
|
||||
import { Trace } from '../../../trace';
|
||||
import { parseExpression, convertExpressionToValue } from './bindable-expressions';
|
||||
import * as types from '../../../utils/types';
|
||||
import * as bindableResources from './bindable-resources';
|
||||
const polymerExpressions = require('../../../js-libs/polymer-expressions');
|
||||
import { PolymerExpressions } from '../../../js-libs/polymer-expressions';
|
||||
|
||||
const contextKey = 'context';
|
||||
// this regex is used to get parameters inside [] for example:
|
||||
@ -360,7 +359,7 @@ export class Binding {
|
||||
}
|
||||
|
||||
const updateExpression = this.prepareExpressionForUpdate();
|
||||
this.prepareContextForExpression(changedModel, updateExpression, undefined);
|
||||
this.prepareContextForExpression(changedModel, updateExpression);
|
||||
|
||||
const expressionValue = this._getExpressionValue(updateExpression, true, changedModel);
|
||||
if (expressionValue instanceof Error) {
|
||||
@ -374,13 +373,21 @@ export class Binding {
|
||||
}
|
||||
|
||||
private _getExpressionValue(expression: string, isBackConvert: boolean, changedModel: any): any {
|
||||
let result: any = '';
|
||||
|
||||
if (!__UI_USE_EXTERNAL_RENDERER__) {
|
||||
let context;
|
||||
const addedProps = [];
|
||||
try {
|
||||
const exp = PolymerExpressions.getExpression(expression);
|
||||
let exp;
|
||||
try {
|
||||
exp = parseExpression(expression);
|
||||
} catch (e) {
|
||||
result = e;
|
||||
}
|
||||
|
||||
if (exp) {
|
||||
const context = (this.source && this.source.get && this.source.get()) || global;
|
||||
const model = {};
|
||||
const addedProps = [];
|
||||
context = (this.source && this.source.get && this.source.get()) || global;
|
||||
const resources = bindableResources.get();
|
||||
for (const prop in resources) {
|
||||
if (resources.hasOwnProperty(prop) && !context.hasOwnProperty(prop)) {
|
||||
@ -389,26 +396,27 @@ export class Binding {
|
||||
}
|
||||
}
|
||||
|
||||
this.prepareContextForExpression(context, expression, addedProps);
|
||||
model[contextKey] = context;
|
||||
const result = exp.getValue(model, isBackConvert, changedModel ? changedModel : model);
|
||||
// clear added props
|
||||
const addedPropsLength = addedProps.length;
|
||||
for (let i = 0; i < addedPropsLength; i++) {
|
||||
delete context[addedProps[i]];
|
||||
// For expressions, there are also cases when binding must be updated after component is loaded (e.g. ListView)
|
||||
if (this.prepareContextForExpression(context, expression, addedProps)) {
|
||||
result = convertExpressionToValue(exp, context, isBackConvert, changedModel ? changedModel : context);
|
||||
} else {
|
||||
const targetInstance = this.target.get();
|
||||
targetInstance.off('loaded', this.loadedHandlerVisualTreeBinding, this);
|
||||
targetInstance.on('loaded', this.loadedHandlerVisualTreeBinding, this);
|
||||
}
|
||||
addedProps.length = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return new Error(expression + ' is not a valid expression.');
|
||||
} catch (e) {
|
||||
const errorMessage = 'Run-time error occured in file: ' + e.sourceURL + ' at line: ' + e.line + ' and column: ' + e.column;
|
||||
|
||||
return new Error(errorMessage);
|
||||
result = new Error(errorMessage);
|
||||
}
|
||||
|
||||
// Clear added props
|
||||
for (let prop of addedProps) {
|
||||
delete context[prop];
|
||||
}
|
||||
addedProps.length = 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public onSourcePropertyChanged(data: PropertyChangeData) {
|
||||
@ -472,24 +480,24 @@ export class Binding {
|
||||
}
|
||||
}
|
||||
|
||||
private prepareContextForExpression(model: Object, expression: string, newProps: Array<string>) {
|
||||
private prepareContextForExpression(model: Object, expression: string, addedProps = []) {
|
||||
const targetInstance = this.target.get();
|
||||
|
||||
let success = true;
|
||||
let parentViewAndIndex: { view: ViewBase; index: number };
|
||||
let parentView;
|
||||
const addedProps = newProps || [];
|
||||
let expressionCP = expression;
|
||||
if (expressionCP.indexOf(bc.bindingValueKey) > -1) {
|
||||
model[bc.bindingValueKey] = model;
|
||||
addedProps.push(bc.bindingValueKey);
|
||||
}
|
||||
|
||||
let success = true;
|
||||
|
||||
const parentsArray = expressionCP.match(parentsRegex);
|
||||
if (parentsArray) {
|
||||
for (let i = 0; i < parentsArray.length; i++) {
|
||||
// This prevents later checks to mistake $parents[] for $parent
|
||||
expressionCP = expressionCP.replace(parentsArray[i], '');
|
||||
parentViewAndIndex = this.getParentView(this.target.get(), parentsArray[i]);
|
||||
parentViewAndIndex = this.getParentView(targetInstance, parentsArray[i]);
|
||||
if (parentViewAndIndex.view) {
|
||||
model[bc.parentsValueKey] = model[bc.parentsValueKey] || {};
|
||||
model[bc.parentsValueKey][parentViewAndIndex.index] = parentViewAndIndex.view.bindingContext;
|
||||
@ -501,7 +509,7 @@ export class Binding {
|
||||
}
|
||||
|
||||
if (expressionCP.indexOf(bc.parentValueKey) > -1) {
|
||||
parentView = this.getParentView(this.target.get(), bc.parentValueKey).view;
|
||||
parentView = this.getParentView(targetInstance, bc.parentValueKey).view;
|
||||
if (parentView) {
|
||||
model[bc.parentValueKey] = parentView.bindingContext;
|
||||
addedProps.push(bc.parentValueKey);
|
||||
@ -509,13 +517,7 @@ export class Binding {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
// For expressions, there are also cases when binding must be updated after component is loaded (e.g. ListView)
|
||||
if (!success) {
|
||||
const targetInstance = this.target.get();
|
||||
targetInstance.off('loaded', this.loadedHandlerVisualTreeBinding, this);
|
||||
targetInstance.on('loaded', this.loadedHandlerVisualTreeBinding, this);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private getSourcePropertyValue() {
|
||||
|
Reference in New Issue
Block a user