From b9266f77bd3689a4da65f19482ee95e14b972e7f Mon Sep 17 00:00:00 2001 From: Rafael Weinstein Date: Wed, 18 Dec 2013 12:46:35 -0800 Subject: [PATCH] NodeBind: oneTime. This patch changes the call signature of bind(), which now takes: name, value, oneTime. oneTime is expected to be true if the caller wishes the bound value updated but doesn't require it to track an observable. if oneTime is true, it is expected that value will NOT be an observable, otherwise it will be. R=arv BUG= Review URL: https://codereview.appspot.com/43700043 --- src/NodeBind.js | 85 +++++++++++++++++++++++++++++-------------------- tests/tests.js | 58 +++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 34 deletions(-) diff --git a/src/NodeBind.js b/src/NodeBind.js index 2122fcf..cb1d3c9 100644 --- a/src/NodeBind.js +++ b/src/NodeBind.js @@ -103,13 +103,16 @@ }; } - Text.prototype.bind = function(name, observable) { + Text.prototype.bind = function(name, value, oneTime) { if (name !== 'textContent') - return Node.prototype.bind.call(this, name, observable); + return Node.prototype.bind.call(this, name, value, oneTime); + + if (oneTime) + return updateText(this, value); unbind(this, 'textContent'); - updateText(this, observable.open(textBinding(this))); - return this.bindings.textContent = observable; + updateText(this, value.open(textBinding(this))); + return this.bindings.textContent = value; } function updateAttribute(el, name, conditional, value) { @@ -130,19 +133,21 @@ }; } - Element.prototype.bind = function(name, observable) { + Element.prototype.bind = function(name, value, oneTime) { var conditional = name[name.length - 1] == '?'; if (conditional) { this.removeAttribute(name); name = name.slice(0, -1); } - unbind(this, name); + if (oneTime) + return updateAttribute(this, name, conditional, value); + unbind(this, name); updateAttribute(this, name, conditional, - observable.open(attributeBinding(this, name, conditional))); + value.open(attributeBinding(this, name, conditional))); - return this.bindings[name] = observable; + return this.bindings[name] = value; }; var checkboxEventType; @@ -269,35 +274,42 @@ } } - HTMLInputElement.prototype.bind = function(name, observable) { + HTMLInputElement.prototype.bind = function(name, value, oneTime) { if (name !== 'value' && name !== 'checked') - return HTMLElement.prototype.bind.call(this, name, observable); + return HTMLElement.prototype.bind.call(this, name, value, oneTime); - unbind(this, name); - this.removeAttribute(name); + this.removeAttribute(name); var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue; var postEventFn = name == 'checked' ? checkedPostEvent : noop; - bindInputEvent(this, name, observable, postEventFn); + + if (oneTime) + return updateInput(this, name, value, sanitizeFn); + + unbind(this, name); + bindInputEvent(this, name, value, postEventFn); updateInput(this, name, - observable.open(inputBinding(this, name, sanitizeFn)), + value.open(inputBinding(this, name, sanitizeFn)), sanitizeFn); - return this.bindings[name] = observable; + return this.bindings[name] = value; } - HTMLTextAreaElement.prototype.bind = function(name, observable) { + HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) { if (name !== 'value') - return HTMLElement.prototype.bind.call(this, name, observable); + return HTMLElement.prototype.bind.call(this, name, value, oneTime); - unbind(this, 'value'); this.removeAttribute('value'); - bindInputEvent(this, 'value', observable); + if (oneTime) + return updateInput(this, 'value', value); + + unbind(this, 'value'); + bindInputEvent(this, 'value', value); updateInput(this, 'value', - observable.open(inputBinding(this, 'value', sanitizeValue))); + value.open(inputBinding(this, 'value', sanitizeValue))); - return this.bindings.value = observable; + return this.bindings.value = value; } function updateOption(option, value) { @@ -326,16 +338,19 @@ } } - HTMLOptionElement.prototype.bind = function(name, observable) { + HTMLOptionElement.prototype.bind = function(name, value, oneTime) { if (name !== 'value') - return HTMLElement.prototype.bind.call(this, name, observable); + return HTMLElement.prototype.bind.call(this, name, value, oneTime); - unbind(this, 'value'); this.removeAttribute('value'); - bindInputEvent(this, 'value', observable); - updateOption(this, observable.open(optionBinding(this))); - return this.bindings.value = observable; + if (oneTime) + return updateOption(this, value); + + unbind(this, 'value'); + bindInputEvent(this, 'value', value); + updateOption(this, value.open(optionBinding(this))); + return this.bindings.value = value; } function updateSelect(select, property, value, retries) { @@ -356,20 +371,22 @@ } } - HTMLSelectElement.prototype.bind = function(name, observable) { + HTMLSelectElement.prototype.bind = function(name, value, oneTime) { if (name === 'selectedindex') name = 'selectedIndex'; if (name !== 'selectedIndex' && name !== 'value') - return HTMLElement.prototype.bind.call(this, name, observable); + return HTMLElement.prototype.bind.call(this, name, value, oneTime); - - unbind(this, name); this.removeAttribute(name); - bindInputEvent(this, name, observable); - updateSelect(this, name, observable.open(selectBinding(this, name)), 2); - return this.bindings[name] = observable; + if (oneTime) + return updateSelect(this, name, value); + + unbind(this, name); + bindInputEvent(this, name, value); + updateSelect(this, name, value.open(selectBinding(this, name)), 2); + return this.bindings[name] = value; } // TODO(rafaelw): We should polyfill a Microtask Promise and define it if it isn't. diff --git a/tests/tests.js b/tests/tests.js index 209714c..ff4e948 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -65,6 +65,12 @@ suite('Text bindings', function() { // TODO(rafaelw): Throw on binding to unavailable property? }); + test('oneTime', function() { + var text = document.createTextNode('hi'); + text.bind('textContent', 1, true); + assert.strictEqual('1', text.data); + }); + test('No Path', function() { var text = testDiv.appendChild(document.createTextNode('hi')); var model = 1; @@ -127,6 +133,13 @@ suite('Element attribute bindings', function() { assert.strictEqual('', el.getAttribute('foo')); }); + test('oneTime', function() { + var el = testDiv.appendChild(document.createElement('div')); + var model = {a: '1'}; + el.bind('foo', 1, true); + assert.strictEqual('1', el.getAttribute('foo')); + }); + test('No path', function() { var el = testDiv.appendChild(document.createElement('div')); var model = 1; @@ -235,6 +248,12 @@ suite('Form Element Bindings', function() { inputTextAreaValueTest('input'); }); + test('Input.value - oneTime', function() { + var el = testDiv.appendChild(document.createElement('input')); + el.bind('value', 42, true); + assert.strictEqual('42', el.value); + }); + test('Input.value - no path', function() { inputTextAreaNoPath('input'); }); @@ -247,6 +266,12 @@ suite('Form Element Bindings', function() { inputTextAreaValueTest('textarea'); }); + test('TextArea.value - oneTime', function() { + var el = testDiv.appendChild(document.createElement('textarea')); + el.bind('value', 42, true); + assert.strictEqual('42', el.value); + }); + test('TextArea.value - no path', function() { inputTextAreaNoPath('textarea'); }); @@ -325,6 +350,14 @@ suite('Form Element Bindings', function() { assert.isFalse(model.x); }); + test('(Checkbox)Input.checked - oneTime', function() { + var input = testDiv.appendChild(document.createElement('input')); + testDiv.appendChild(input); + input.type = 'checkbox'; + input.bind('checked', true, true); + assert.isTrue(input.checked); + }); + test('(Checkbox)Input.checked - path unreachable', function() { var input = testDiv.appendChild(document.createElement('input')); testDiv.appendChild(input); @@ -430,6 +463,13 @@ suite('Form Element Bindings', function() { assert.isTrue(model.x); }); + test('(Radio)Input.checked - oneTime', function() { + var input = testDiv.appendChild(document.createElement('input')); + input.type = 'radio'; + input.bind('checked', true, true); + assert.isTrue(input.checked); + }); + test('(Radio)Input.checked - path unreachable', function() { var input = testDiv.appendChild(document.createElement('input')); input.type = 'radio'; @@ -594,6 +634,18 @@ test('(Radio)Input.checked - multiple forms - ShadowRoot', function() { assert.strictEqual(1, model.val); }); + test('Select.selectedIndex - oneTime', function() { + var select = testDiv.appendChild(document.createElement('select')); + testDiv.appendChild(select); + var option0 = select.appendChild(document.createElement('option')); + var option1 = select.appendChild(document.createElement('option')); + var option2 = select.appendChild(document.createElement('option')); + + select.bind('selectedIndex', 2, true); + Platform.performMicrotaskCheckpoint(); + assert.strictEqual(2, select.selectedIndex); + }); + test('Select.selectedIndex - path NaN', function() { var select = testDiv.appendChild(document.createElement('select')); testDiv.appendChild(select); @@ -623,6 +675,12 @@ test('(Radio)Input.checked - multiple forms - ShadowRoot', function() { assert.strictEqual('Hi', option.value); }); + test('Option.value - oneTime', function() { + var option = testDiv.appendChild(document.createElement('option')); + option.bind('value', 42, true); + assert.strictEqual('42', option.value); + }); + test('Select.selectedIndex - path unreachable', function() { var select = testDiv.appendChild(document.createElement('select')); testDiv.appendChild(select);