Skip to content

Commit

Permalink
#2182 allow es2015 style params and bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
meltuhamy committed Jan 17, 2017
1 parent 87e8a59 commit 07dc75e
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 23 deletions.
2 changes: 1 addition & 1 deletion spec/bindingPreprocessingBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('Binding preprocessing', function() {
return value || "false";
}
};
var rewritten = ko.expressionRewriting.preProcessBindings("a: 1, b");
var rewritten = ko.expressionRewriting.preProcessBindings("a: 1, 'b'");
var parsedRewritten = eval("({" + rewritten + "})");
expect(parsedRewritten.a).toEqual(1);
expect(parsedRewritten.b).toEqual(false);
Expand Down
34 changes: 34 additions & 0 deletions spec/components/customElementBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,40 @@ describe('Components: Custom elements', function() {
expect(suppliedParams).toEqual([{ nothing: null, num: 123, bool: true, obj: { abc: 123 }, str: 'mystr' }]);
});

it('Is possible to pass es2015 object shorthand properties', function() {
var suppliedParams = [];
ko.components.register('test-component', {
template: 'Ignored',
viewModel: function(params) {
suppliedParams.push(params);

// The raw value for each param is a computed giving the literal value
ko.utils.objectForEach(params, function(key, value) {
if (key !== '$raw') {
expect(ko.isComputed(params.$raw[key])).toBe(true);
expect(params.$raw[key]()).toBe(value);
}
});
}
});

testNode.innerHTML = '<test-component params="myModelValue, anotherModelValue, oldWay: oldWay, literal: \'hello\'"></test-component>';
ko.applyBindings({
myModelValue: 'actual myModelValue',
anotherModelValue: 'actual anotherModelValue',
oldWay: 'actual oldWay'
}, testNode);
jasmine.Clock.tick(1);

delete suppliedParams[0].$raw; // Don't include '$raw' in the following assertion, as we only want to compare supplied values
expect(suppliedParams).toEqual([{
myModelValue: 'actual myModelValue',
anotherModelValue: 'actual anotherModelValue',
oldWay: 'actual oldWay',
literal: 'hello'
}]);
});

it('Supplies an empty params object (with empty $raw) if a custom element has no params attribute', function() {
var suppliedParams = [];
ko.components.register('test-component', {
Expand Down
6 changes: 6 additions & 0 deletions spec/defaultBindings/valueBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ describe('Binding: Value', function() {
expect(testNode.childNodes[0].value).toEqual("0");
});

it('Should allow es2015 style data bind value', function () {
testNode.innerHTML = "<input data-bind='value' />";
ko.applyBindings({ value: ko.observable(0) }, testNode);
expect(testNode.childNodes[0].value).toEqual("0");
});

it('Should assign an empty string as value if the model value is null', function () {
testNode.innerHTML = "<input data-bind='value:(null)' />";
ko.applyBindings(null, testNode);
Expand Down
71 changes: 51 additions & 20 deletions spec/expressionRewritingBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@ describe('Expression Rewriting', function() {
expect(result[3].value).toEqual("4");
});

it('Should be able to parse ES2015 style shorthand property names', function() {
var result = ko.expressionRewriting.parseObjectLiteral("someKeyValue");
var mixedResult = ko.expressionRewriting.parseObjectLiteral("normal: 1, someKeyValue, secondNormal: 3, thirdNormal: thirdValue");

expect(result.length).toEqual(1);
expect(result[0].key).toEqual("someKeyValue");
expect(result[0].value).toEqual("someKeyValue");

expect(mixedResult.length).toEqual(4);
expect(mixedResult[0].key).toEqual("normal");
expect(mixedResult[0].value).toEqual("1");
expect(mixedResult[1].key).toEqual("someKeyValue");
expect(mixedResult[1].value).toEqual("someKeyValue");
expect(mixedResult[2].key).toEqual("secondNormal");
expect(mixedResult[2].value).toEqual("3");
expect(mixedResult[3].key).toEqual("thirdNormal");
expect(mixedResult[3].value).toEqual("thirdValue");
});

it('Should ignore any outer braces', function() {
var result = ko.expressionRewriting.parseObjectLiteral("{a: 1}");
expect(result.length).toEqual(1);
Expand Down Expand Up @@ -73,35 +92,35 @@ describe('Expression Rewriting', function() {
});

it('Should be able to cope with malformed syntax (things that aren\'t key-value pairs)', function() {
var result = ko.expressionRewriting.parseObjectLiteral("malformed1, 'mal:formed2', good:3, {malformed:4}, good5:5, keyonly:");
expect(result.length).toEqual(6);
expect(result[0].unknown).toEqual("malformed1");
expect(result[1].unknown).toEqual("mal:formed2");
expect(result[2].key).toEqual("good");
expect(result[2].value).toEqual("3");
expect(result[3].unknown).toEqual("{malformed:4}");
expect(result[4].key).toEqual("good5");
expect(result[4].value).toEqual("5");
expect(result[5].unknown).toEqual("keyonly");
var result = ko.expressionRewriting.parseObjectLiteral("'mal:formed2', good:3, {malformed:4}, good5:5, keyonly:");
expect(result.length).toEqual(5);
expect(result[0].unknown).toEqual("mal:formed2");
expect(result[1].key).toEqual("good");
expect(result[1].value).toEqual("3");
expect(result[2].unknown).toEqual("{malformed:4}");
expect(result[3].key).toEqual("good5");
expect(result[3].value).toEqual("5");
expect(result[4].unknown).toEqual("keyonly");
});

it('Should ensure all keys are wrapped in quotes', function() {
var rewritten = ko.expressionRewriting.preProcessBindings("a: 1, 'b': 2, \"c\": 3");
expect(rewritten).toEqual("'a':1,'b':2,'c':3");
var rewritten = ko.expressionRewriting.preProcessBindings("a: 1, 'b': 2, \"c\": 3, es2015KeyValue");
expect(rewritten).toEqual("'a':1,'b':2,'c':3,'es2015KeyValue':es2015KeyValue");
});

it('(Private API) Should convert writable values to property accessors', function () {
// Note that both _twoWayBindings and _ko_property_writers are undocumented private APIs.
// We reserve the right to remove or change either or both of these, especially if we
// create an official public property writers API.
var w = ko.expressionRewriting._twoWayBindings;
w.a = w.b = w.c = w.d = w.e = w.f = w.g = w.h = w.i = w.j = true;
w.a = w.b = w.c = w.d = w.e = w.f = w.g = w.h = w.i = w.j = w.firstName = true;

var rewritten = ko.expressionRewriting.preProcessBindings(
'a : 1, b : firstName, c : function() { return "returnValue"; }, ' +
'd: firstName+lastName, e: boss.firstName, f: boss . lastName, ' +
'g: getAssitant(), h: getAssitant().firstName, i: getAssitant("[dummy]")[ "lastName" ], ' +
'j: boss.firstName + boss.lastName'
'j: boss.firstName + boss.lastName, ' +
'firstName, boss, getAssitant' // es2015 style object literal.
);

// Clear the two-way flag
Expand All @@ -125,10 +144,13 @@ describe('Expression Rewriting', function() {
expect(parsed.g).toEqual(assistant);
expect(parsed.h).toEqual("john");
expect(parsed.i).toEqual("english");
expect(parsed.firstName).toEqual("bob");
expect(parsed.boss).toEqual(model.boss);
expect(parsed.getAssitant()).toEqual(assistant);

// test that only writable expressions are set up for writing
// 'j' matches due to the simple checking for trailing property accessor
expect(parsed._ko_property_writers).toHaveOwnProperties(['b','e','f','h','i','j']);
expect(parsed._ko_property_writers).toHaveOwnProperties(['b','e','f','h','i','j','firstName']);

// make sure writing to them works
parsed._ko_property_writers.b("bob2");
Expand All @@ -141,6 +163,9 @@ describe('Expression Rewriting', function() {
expect(assistant.firstName).toEqual("john2");
parsed._ko_property_writers.i("english2");
expect(assistant.lastName).toEqual("english2");
parsed._ko_property_writers.firstName("bob3");
expect(model.firstName).toEqual("bob3");


// make sure writing to 'j' doesn't error or actually change anything
parsed._ko_property_writers.j("nothing at all");
Expand All @@ -157,18 +182,24 @@ describe('Expression Rewriting', function() {
});

it('Should eval keys without a value as if the value is undefined', function() {
var rewritten = ko.expressionRewriting.preProcessBindings("a: 1, b");
var rewritten = ko.expressionRewriting.preProcessBindings("a: 1, 'b'");
var parsedRewritten = eval("({" + rewritten + "})");
expect(parsedRewritten.a).toEqual(1);
expect('b' in parsedRewritten).toBeTruthy();
expect(parsedRewritten.b).toBeUndefined();
});

it('Should return accessor functions for each value when called with the valueAccessors option', function() {
var rewritten = ko.expressionRewriting.preProcessBindings("a: 1", {valueAccessors:true});
expect(rewritten).toEqual("'a':function(){return 1 }");
var evaluated = eval("({" + rewritten + "})");
expect(evaluated['a']()).toEqual(1);
var rewritten = ko.expressionRewriting.preProcessBindings("a: 1, b", {valueAccessors:true});
expect(rewritten).toEqual("'a':function(){return 1 },'b':function(){return b }");

var model = {b: 'hello'};
with(model){
var evaluated = eval("({" + rewritten + "})");
expect(evaluated['a']()).toEqual(1);
expect(evaluated['b']()).toEqual('hello');
}

});

it('Should be able to parse and evaluate object literals containing division', function() {
Expand Down
9 changes: 7 additions & 2 deletions src/binding/expressionRewriting.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,25 @@ ko.expressionRewriting = (function () {
str += "\n,";

// Split into tokens
var result = [], toks = str.match(bindingToken), key, values = [], depth = 0;
var result = [], toks = str.match(bindingToken), key, values = [], depth = 0, isStringKey = false;

if (toks.length > 1) {
for (var i = 0, tok; tok = toks[i]; ++i) {
var c = tok.charCodeAt(0);
// A comma signals the end of a key/value pair if depth is zero
if (c === 44) { // ","
if (depth <= 0) {
if (!key && values.length === 1 && !isStringKey) { // es6 style object literal
key = values [ 0 ];
}
result.push((key && values.length) ? {key: key, value: values.join('')} : {'unknown': key || values.join('')});
key = depth = 0;
values = [];
isStringKey = false;
continue;
}
// Simply skip the colon that separates the name and value
} else if (c === 58) { // ":"
} else if (c === 58 ) { // ":"
if (!depth && !key && values.length === 1) {
key = values.pop();
continue;
Expand All @@ -96,6 +100,7 @@ ko.expressionRewriting = (function () {
--depth;
// The key will be the first token; if it's a string, trim the quotes
} else if (!key && !values.length && (c === 34 || c === 39)) { // '"', "'"
isStringKey = true;
tok = tok.slice(1, -1);
}
values.push(tok);
Expand Down

0 comments on commit 07dc75e

Please sign in to comment.