Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2182 allow es2015 style params and bindings #2185

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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