Skip to content

Commit

Permalink
fix(CheckedObserver): synchronize on changes to input value
Browse files Browse the repository at this point in the history
When checkbox/radio's value attribute is data-bound, observe it for changes and synchronize the element's checked status accordingly

fixes #320
  • Loading branch information
jdanyow committed Apr 14, 2016
1 parent 969d3e5 commit f314744
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 1 deletion.
12 changes: 11 additions & 1 deletion src/checked-observer.js
@@ -1,6 +1,7 @@
import {subscriberCollection} from './subscriber-collection';

const checkedArrayContext = 'CheckedObserver:array';
const checkedValueContext = 'CheckedObserver:value';

@subscriberCollection()
export class CheckedObserver {
Expand Down Expand Up @@ -42,8 +43,14 @@ export class CheckedObserver {
}

call(context, splices) {
// called by task queue and array observer.
// called by task queue, array observer, and model/value observer.
this.synchronizeElement();
// if the input's model or value property is data-bound, subscribe to it's
// changes to enable synchronizing the element's checked status when a change occurs.
if (!this.valueObserver
&& (this.valueObserver = this.element.__observers__.model || this.element.__observers__.value)) {
this.valueObserver.subscribe(checkedValueContext, this);
}
}

synchronizeElement() {
Expand Down Expand Up @@ -117,5 +124,8 @@ export class CheckedObserver {
this.arrayObserver.unsubscribe(checkedArrayContext, this);
this.arrayObserver = null;
}
if (this.valueObserver) {
this.valueObserver.unsubscribe(checkedValueContext, this);
}
}
}
1 change: 1 addition & 0 deletions src/observer-locator.js
Expand Up @@ -179,6 +179,7 @@ export class ObserverLocator {
|| propertyName === 'style' || propertyName === 'css'
|| propertyName === 'value' && (obj.tagName.toLowerCase() === 'input' || obj.tagName.toLowerCase() === 'select')
|| propertyName === 'checked' && obj.tagName.toLowerCase() === 'input'
|| propertyName === 'model' && obj.tagName.toLowerCase() === 'input'
|| /^xlink:.+$/.exec(propertyName)) {
return this.getObserver(obj, propertyName);
}
Expand Down
28 changes: 28 additions & 0 deletions test/checked-observer.spec.js
Expand Up @@ -22,6 +22,7 @@ describe('CheckedObserver', () => {
beforeAll(() => {
obj = { selectedItems: [] };
el = createElement('<input type="checkbox" value="A" />');
observerLocator.getObserver(el, 'value');
document.body.appendChild(el);
binding = getBinding(observerLocator, obj, 'selectedItems', el, 'checked', bindingMode.twoWay).binding;
});
Expand All @@ -39,6 +40,19 @@ describe('CheckedObserver', () => {
}, 0);
});

it('responds to element value change', done => {
expect(el.checked).toBe(true);
el.__observers__.value.setValue('ZZZZ');
setTimeout(() => {
expect(el.checked).toBe(false);
el.__observers__.value.setValue('A');
setTimeout(() => {
expect(el.checked).toBe(true);
done();
});
}, 0);
});

it('responds to element change', done => {
el.checked = false;
el.dispatchEvent(DOM.createCustomEvent('change'));
Expand Down Expand Up @@ -77,6 +91,7 @@ describe('CheckedObserver', () => {
obj = { selectedItems: [], itemA: {} };
el = createElement('<input type="checkbox" />');
el.model = obj.itemA;
observerLocator.getObserver(el, 'model');
document.body.appendChild(el);
binding = getBinding(observerLocator, obj, 'selectedItems', el, 'checked', bindingMode.twoWay).binding;
});
Expand All @@ -94,6 +109,19 @@ describe('CheckedObserver', () => {
}, 0);
});

it('responds to element value change', done => {
expect(el.checked).toBe(true);
el.__observers__.model.setValue({});
setTimeout(() => {
expect(el.checked).toBe(false);
el.__observers__.model.setValue(obj.itemA);
setTimeout(() => {
expect(el.checked).toBe(true);
done();
});
}, 0);
});

it('responds to element change', done => {
el.checked = false;
el.dispatchEvent(DOM.createCustomEvent('change'));
Expand Down
4 changes: 4 additions & 0 deletions test/observer-locator.spec.js
Expand Up @@ -165,4 +165,8 @@ describe('ObserverLocator', () => {
it('getAccessor returns ValueAttributeObserver for input.value', () => {
expect(locator.getAccessor(document.createElement('input'), 'value') instanceof ValueAttributeObserver).toBe(true);
});

it('getAccessor returns SetterObserver for input.model', () => {
expect(locator.getAccessor(document.createElement('input'), 'model') instanceof SetterObserver).toBe(true);
});
});

0 comments on commit f314744

Please sign in to comment.