diff --git a/packages/bind/src/bindingContext.ts b/packages/bind/src/bindingContext.ts index d2bd2d4f..c34c1455 100644 --- a/packages/bind/src/bindingContext.ts +++ b/packages/bind/src/bindingContext.ts @@ -1,4 +1,4 @@ -import { extend, options, domData } from '@tko/utils' +import { extend, options, domData, isObjectLike } from '@tko/utils' import { pureComputed @@ -118,7 +118,7 @@ Object.assign(bindingContext.prototype, { } const $data = this.$data // instanceof Object covers 1. {}, 2. [], 3. function() {}, 4. new *; it excludes undefined, null, primitives. - if ($data instanceof Object && token in $data) { return $data[token] } + if (isObjectLike($data) && token in $data) { return $data[token] } if (token in this) { return this[token] } if (token in globals) { return globals[token] } diff --git a/packages/utils.parser/spec/identifierBehaviors.ts b/packages/utils.parser/spec/identifierBehaviors.ts index 43b80776..81c4ea55 100644 --- a/packages/utils.parser/spec/identifierBehaviors.ts +++ b/packages/utils.parser/spec/identifierBehaviors.ts @@ -32,7 +32,12 @@ import { describe('Identifier', function () { function testLookup (identifier, $data) { const ctx = new bindingContext($data) - return new Identifier(null, identifier).get_value(undefined, ctx) + return new Identifier(null, identifier).get_value(undefined, ctx, {}) + } + + function testWrite (identifier, $data, newValue) { + const ctx = new bindingContext($data) + return new Identifier(null, identifier).set_value(newValue, ctx, {}) } var c = 'Z', @@ -46,6 +51,12 @@ describe('Identifier', function () { assert.equal(testLookup('f', context), f) }) + it('looks up values on no-prototype $data', function () { + const $data = Object.create(null) + $data.c = c + assert.equal(testLookup('c', $data), 'Z') + }) + it('returns null as expected', function () { assert.equal(testLookup('$data', null), null) }) @@ -54,6 +65,27 @@ describe('Identifier', function () { assert.equal(testLookup('$data', undefined), undefined) }) + it('sets plain values on $data', () => { + const $data = { c: c } + assert.equal($data.c, 'Z') + testWrite('c', $data, 'X') + assert.equal($data.c, 'X') + }) + + it('sets observable values on $data', () => { + const $data = { c: observable(c) } + assert.equal($data.c(), 'Z') + testWrite('c', $data, 'X') + assert.equal($data.c(), 'X') + }) + + it('sets plain values on no-prototype $data', () => { + const $data = Object.create(null) + $data.c = c + testWrite('c', $data, 'X') + assert.equal($data.c, 'X') + }) + it('dereferences values on the parser', function () { var context = new bindingContext({ f: f }) var fake_args = new Arguments(null, []) diff --git a/packages/utils.parser/src/Identifier.ts b/packages/utils.parser/src/Identifier.ts index d63a7091..5ec01fd3 100644 --- a/packages/utils.parser/src/Identifier.ts +++ b/packages/utils.parser/src/Identifier.ts @@ -2,7 +2,7 @@ import Node from './Node' import Arguments from './Arguments' -import { hasOwnProperty } from '@tko/utils' +import { hasOwnProperty, isObjectLike } from '@tko/utils' import { isWriteableObservable, isObservable @@ -106,11 +106,11 @@ export default class Identifier { let leaf = this.token let i, n, root - if (hasOwnProperty($data, leaf)) { + if (isObjectLike($data) && leaf in $data) { root = $data - } else if (hasOwnProperty($context, leaf)) { + } else if (leaf in $context) { root = $context - } else if (hasOwnProperty(globals, leaf)) { + } else if (leaf in globals) { root = globals } else { throw new Error('Identifier::set_value -- ' + diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index e229589a..eea43d47 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -6,6 +6,10 @@ export function hasOwnProperty(obj, propName) { return Object.prototype.hasOwnProperty.call(obj, propName) } +export function isObjectLike(obj) { + return !!obj && (typeof obj === 'object' || typeof obj === 'function') +} + export function extend (target, source) { if (source) { for (var prop in source) {