Skip to content
This repository has been archived by the owner on Mar 13, 2018. It is now read-only.

Commit

Permalink
Add support for two way filters.
Browse files Browse the repository at this point in the history
If the exression is an IdentPath and there are filters we now create a
two way filter. This calls toDOM and toModel respectively .

BUG=
[email protected]

Review URL: https://codereview.appspot.com/12806046
  • Loading branch information
arv committed Aug 21, 2013
1 parent 436bd0b commit ea5da4d
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 3 deletions.
78 changes: 75 additions & 3 deletions src/polymer-expressions.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@
})();
}

// JScript does not have __proto__. We wrap all object literals with
// createObject which uses Object.create, Object.defineProperty and
// Object.getOwnPropertyDescriptor to create a new object that does the exact
// same thing. The main downside to this solution is that we have to extract
// all those property descriptors for IE.
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 ident = identStart + '+' + identPart + '*';
Expand Down Expand Up @@ -121,7 +140,7 @@
// is that ECMAScript indentifiers are more limited than CSS classnames.
var resolveFn = delegate.labeledStatements.length ?
newLabeledResolve(delegate.labeledStatements) :
delegate.expression;
getFn(delegate.expression);

delegate.filters.forEach(function(filter) {
resolveFn = filter.toDOM(resolveFn);
Expand All @@ -135,6 +154,14 @@
if (!paths.length)
return { value: resolveFn({}) }; // only literals in expression.

if (paths.length === 1 && delegate.filters.length &&
delegate.expression instanceof IdentPath) {
var binding = new TwoWayFilterBinding(resolveFn, delegate.filters);
var path = delegate.expression.getPath();
binding.bind(path, model, path);
return binding;
}

var binding = new CompoundBinding(resolveFn);
for (var i = 0; i < paths.length; i++) {
binding.bind(paths[i], model, paths[i]);
Expand All @@ -158,6 +185,44 @@
}
}

function TwoWayFilterBinding(combinator, filters) {
CompoundBinding.call(this, combinator);
this.model = null;
this.path = null;
this.filters = filters;
this.selfObserver = null;
}

TwoWayFilterBinding.prototype = createObject({
__proto__: CompoundBinding.prototype,

domValueChanged: function(value, oldValue) {
var modelValue = this.toModel(value);
PathObserver.setValueAtPath(this.model, this.path, modelValue);
},

bind: function(name, model, path) {
CompoundBinding.prototype.bind.call(this, name, model, path);
this.model = model;
this.path = path;
this.selfObserver = new PathObserver(this, 'value', this.domValueChanged,
this, name);
},

unbind: function(name) {
CompoundBinding.prototype.unbind.call(this, name);
if (this.selfObserver)
this.selfObserver.close();
},

toModel: function(value) {
for (var i = this.filters.length - 1; i >= 0; i--) {
value = this.filters[i].toModel(value);
}
return value;
}
});

function IdentPath(deps, name, last) {
this.deps = deps;
this.name = name;
Expand Down Expand Up @@ -205,7 +270,14 @@
return function(values) {
var value = fn(values);
return object.toDOM(value);
}
};
},

toModel: function(value) {
var object = this.object;
if (object.toModel)
return object.toModel(value);
return value;
}
};

Expand Down Expand Up @@ -347,7 +419,7 @@
},

createTopLevel: function(expression) {
this.expression = getFn(expression);
this.expression = expression;
},

createThisExpression: notImplemented
Expand Down
107 changes: 107 additions & 0 deletions tests/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,18 @@ suite('PolymerExpressions', function() {
assert.strictEqual(4, Observer._allObserversCount);

delete PolymerExpressions.filters.hex;
delete PolymerExpressions.filters.plusN;
delete PolymerExpressions.filters.toFixed;
delete PolymerExpressions.filters.upperCase;
});

function dispatchEvent(type, target) {
var event = document.createEvent('Event');
event.initEvent(type, true, false);
target.dispatchEvent(event);
Platform.performMicrotaskCheckpoint();
}

function hasClass(node, className) {
return node.className.split(' ').some(function(name) {
return name === className;
Expand Down Expand Up @@ -106,6 +114,17 @@ suite('PolymerExpressions', function() {
};
}

function plusN(n) {
return {
toDOM: function(value) {
return Number(value) + n;
},
toModel: function(value) {
return Number(value) - n;
}
};
}

test('ClassName Singular', function() {
var div = createTestHtml(
'<template bind><div class="{{ foo: bar }}">' +
Expand Down Expand Up @@ -626,6 +645,94 @@ suite('PolymerExpressions', function() {
assert.equal('F', div.childNodes[1].textContent);
});

test('two-way filter', function() {
PolymerExpressions.filters.hex = hex;

var div = createTestHtml(
'<template bind="{{ }}">' +
'<input value="{{ bar | hex }}">' +
'</template>');

var model = {
bar: 32
};

recursivelySetTemplateModel(div, model);
Platform.performMicrotaskCheckpoint();
assert.equal('20', div.childNodes[1].value);

div.childNodes[1].value = 'ff';
dispatchEvent('input', div.childNodes[1]);

Platform.performMicrotaskCheckpoint();
assert.equal(255, model.bar);

model.bar = 15;
Platform.performMicrotaskCheckpoint();
assert.equal('f', div.childNodes[1].value);
});

test('two-way filter too many paths', function() {
PolymerExpressions.filters.hex = hex;

var div = createTestHtml(
'<template bind="{{ }}">' +
'<input value="{{ bar + num | hex }}">' +
'</template>');

var model = {
bar: 32,
num: 10
};

recursivelySetTemplateModel(div, model);
Platform.performMicrotaskCheckpoint();
assert.equal('2a', div.childNodes[1].value);

div.childNodes[1].value = 'ff';
dispatchEvent('input', div.childNodes[1]);

Platform.performMicrotaskCheckpoint();
assert.equal(32, model.bar);
assert.equal(10, model.num);

model.bar = 15;
Platform.performMicrotaskCheckpoint();
assert.equal('19', div.childNodes[1].value);

model.num = 5;
Platform.performMicrotaskCheckpoint();
assert.equal('14', div.childNodes[1].value);
});

test('two-way filter chained', function() {
PolymerExpressions.filters.hex = hex;
PolymerExpressions.filters.plusN = plusN;

var div = createTestHtml(
'<template bind="{{ }}">' +
'<input value="{{ bar | plusN(10) | hex }}">' +
'</template>');

var model = {
bar: 22
};

recursivelySetTemplateModel(div, model);
Platform.performMicrotaskCheckpoint();
assert.equal('20', div.childNodes[1].value);

div.childNodes[1].value = 'ff';
dispatchEvent('input', div.childNodes[1]);

Platform.performMicrotaskCheckpoint();
assert.equal(245, model.bar);

model.bar = 5;
Platform.performMicrotaskCheckpoint();
assert.equal('f', div.childNodes[1].value);
});

test('filter unexpected EOF', function() {
var div = createTestHtml(
'<template bind="{{ }}">' +
Expand Down

0 comments on commit ea5da4d

Please sign in to comment.