Skip to content

Commit 11786e5

Browse files
committed
add (dualbind) observe components property
1 parent c06ea6b commit 11786e5

File tree

3 files changed

+146
-67
lines changed

3 files changed

+146
-67
lines changed

projects/mask-binding/src/BindingProvider.ts

+97-56
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import { Component } from '@compo/exports';
1717

1818
export const CustomProviders = {};
1919

20+
const A_dom_slot = 'dom-slot';
21+
const A_property = 'property';
22+
const A_change_event = 'change-event';
23+
2024
export class BindingProvider {
2125
validations = null
2226
ctx = null
@@ -33,31 +37,37 @@ export class BindingProvider {
3337
mapToObj: string
3438
mapToDom: string
3539
changeEvent: string
36-
typeof: string
40+
typeOf: string
3741

3842
slots: any
3943
pipes: any
4044

4145
parent: any
4246

47+
private dismiss: number = 0
48+
bindingType: 'dual' | 'single'
4349

44-
dismiss: number = 0
45-
bindingType: string
46-
log = false
47-
logExpression: string
48-
signal_domChanged: string
49-
signal_objectChanged: string
50+
private log = false
51+
private logExpression: string
52+
private signal_domChanged: string
53+
private signal_objectChanged: string
5054

51-
pipe_domChanged: { pipe: string, signal: string}
52-
pipe_objectChanged: { pipe: string, signal: string}
53-
locked = false
55+
private pipe_domChanged: { pipe: string, signal: string}
56+
private pipe_objectChanged: { pipe: string, signal: string}
57+
private locked = false
5458

5559
domWay: IDomWay = DomObjectTransport.domWay
5660
objectWay: IObjectWay = DomObjectTransport.objectWay
57-
61+
62+
// -
5863
binder: Function
64+
domObserveBinder: Function
65+
66+
domListenerType: 'event' | 'signal' | 'pipe' | 'observe'
5967

60-
constructor (public model, public element: HTMLElement, public ctr, bindingType?: string) {
68+
owner;
69+
70+
constructor (public model, public element: HTMLElement, public ctr, bindingType?: 'dual' | 'single') {
6171
if (bindingType == null) {
6272
bindingType = 'dual';
6373

@@ -68,40 +78,66 @@ export class BindingProvider {
6878
}
6979
let attr = ctr.attr;
7080

81+
this.bindingType = bindingType;
7182
this.value = attr.value;
72-
this.property = attr.property;
83+
this.property = attr[A_property];
7384
this.domSetter = attr['dom-setter'] || attr.setter;
7485
this.domGetter = attr['dom-getter'] || attr.getter;
7586
this.objSetter = attr['obj-setter'];
7687
this.objGetter = attr['obj-getter'];
7788
this.mapToObj = attr['map-to-obj'];
7889
this.mapToDom = attr['map-to-dom'];
79-
this.changeEvent = attr['change-event'] || 'change';
90+
this.owner = ctr.parent;
91+
92+
this.changeEvent = attr[A_change_event] || 'change';
8093

8194
/* Convert to an instance, e.g. Number, on domchange event */
82-
this['typeof'] = attr['typeof'] || null;
83-
84-
this.bindingType = bindingType;
95+
this.typeOf = attr['typeof'] || null;
96+
97+
let isCompoBinder = ctr.node.parent.tagName === this.owner.compoName;
98+
switch (true) {
99+
case (A_dom_slot in attr):
100+
this.domListenerType = 'signal';
101+
break;
102+
case (A_change_event in attr):
103+
this.domListenerType = 'event';
104+
break;
105+
case (isCompoBinder && (A_property in attr)):
106+
this.domListenerType = 'observe';
107+
break;
108+
}
85109

86-
let isCompoBinder = ctr.node.parent.tagName === ctr.parent.compoName;
87-
if (
88-
isCompoBinder &&
89-
(element.nodeType !== 1 || element.tagName !== 'INPUT')
90-
) {
91-
if (this.domSetter == null) this.domSetter = 'setValue';
92-
if (this.domGetter == null) this.domGetter = 'getValue';
93-
if (attr['dom-slot'] == null) attr['dom-slot'] = 'input';
110+
if (isCompoBinder) {
111+
if (this.domListenerType === 'observe') {
112+
this.domWay = DomObjectTransport.domModelWay;
113+
} else {
114+
let isInput = element.nodeType === 1 && (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA');
115+
if (isInput === false) {
116+
if (this.domSetter == null) this.domSetter = 'setValue';
117+
if (this.domGetter == null) this.domGetter = 'getValue';
118+
if (attr[A_dom_slot] == null) attr[A_dom_slot] = 'input';
119+
}
120+
}
121+
}
122+
if (this.domListenerType == null) {
123+
this.domListenerType = 'event';
94124
}
95125

96126
if (this.property == null && this.domGetter == null) {
97127
switch (element.tagName) {
98128
case 'INPUT':
99129
// Do not use .type accessor, as some browsers do not support e.g. date
100-
var type = element.getAttribute('type');
130+
let type = element.getAttribute('type');
101131
if ('checkbox' === type) {
102132
this.property = 'element.checked';
103133
break;
104-
} else if (
134+
}
135+
if ('radio' === type) {
136+
this.domWay = DomObjectTransport.RADIO.domWay;
137+
break;
138+
}
139+
140+
if (
105141
'date' === type ||
106142
'time' === type ||
107143
'month' === type
@@ -110,13 +146,9 @@ export class BindingProvider {
110146
this.domWay = x.domWay;
111147
this.objectWay = x.objectWay;
112148
} else if ('number' === type) {
113-
this['typeof'] = 'Number';
114-
} else if ('radio' === type) {
115-
var x = DomObjectTransport.RADIO;
116-
this.domWay = x.domWay;
117-
break;
118-
}
119-
this.changeEvent = attr['change-event'] || 'change,input';
149+
this['typeOf'] = 'Number';
150+
}
151+
this.changeEvent = attr[A_change_event] || 'change,input';
120152
this.property = 'element.value';
121153
break;
122154
case 'TEXTAREA':
@@ -170,7 +202,7 @@ export class BindingProvider {
170202
}
171203
}
172204

173-
var domSlot = attr['dom-slot'];
205+
var domSlot = attr[A_dom_slot];
174206
if (domSlot != null) {
175207
this.slots = {};
176208
// @hack - place dualb. provider on the way of a signal
@@ -221,7 +253,12 @@ export class BindingProvider {
221253
this.expression = this.value;
222254
}
223255
dispose () {
224-
expression_unbind(this.expression, this.model, this.ctr, this.binder);
256+
if (this.binder != null) {
257+
expression_unbind(this.expression, this.model, this.ctr, this.binder);
258+
}
259+
if (this.domObserveBinder != null) {
260+
expression_unbind(this.property, this.ctr, this.ctr, this.domObserveBinder);
261+
}
225262
}
226263
objectChanged (val?) {
227264
if (this.dismiss-- > 0) {
@@ -272,7 +309,7 @@ export class BindingProvider {
272309
if (val == null) {
273310
val = this.domWay.get(this);
274311
}
275-
let typeof_ = this['typeof'];
312+
let typeof_ = this['typeOf'];
276313
if (typeof_ != null) {
277314
let Converter = window[typeof_];
278315
val = Converter(val);
@@ -375,11 +412,9 @@ export class BindingProvider {
375412

376413

377414
function apply_bind(provider: BindingProvider) {
378-
var expr = provider.expression,
415+
let expr = provider.expression,
379416
model = provider.model,
380-
onObjChanged = (provider.objectChanged = provider.objectChanged.bind(
381-
provider
382-
));
417+
onObjChanged = provider.objectChanged = provider.objectChanged.bind(provider);
383418

384419
provider.binder = expression_createBinder(
385420
expr,
@@ -392,25 +427,31 @@ function apply_bind(provider: BindingProvider) {
392427
expression_bind(expr, model, provider.ctx, provider.ctr, provider.binder);
393428

394429
if (provider.bindingType === 'dual') {
395-
var attr = provider.ctr.attr;
396-
397-
if (!attr['dom-slot'] && !attr['change-pipe-event']) {
398-
var element = provider.element,
399-
eventType = provider.changeEvent,
400-
onDomChange = provider.domChanged.bind(provider),
401-
doListen = Component.Dom.addEventListener;
402-
403-
if (eventType.indexOf(',') !== -1) {
404-
let arr = eventType.split(',');
405-
for (let i = 0; i < arr.length; i++) {
406-
doListen(element, arr[i].trim(), onDomChange);
430+
431+
let onDomChange = provider.domChanged.bind(provider);
432+
switch (provider.domListenerType) {
433+
case 'event': {
434+
let el = provider.element,
435+
event = provider.changeEvent,
436+
attachListener = Component.Dom.addEventListener;
437+
438+
if (event.indexOf(',') !== -1) {
439+
let arr = event.split(',');
440+
for (let i = 0; i < arr.length; i++) {
441+
attachListener(el, arr[i].trim(), onDomChange);
442+
}
407443
}
444+
attachListener(el, event, onDomChange);
445+
break;
446+
}
447+
case 'observe': {
448+
provider.domObserveBinder = onDomChange;
449+
expression_bind(provider.property, provider.owner, provider.ctx, null, onDomChange);
450+
break;
408451
}
409-
doListen(element, eventType, onDomChange);
410452
}
411-
412453
if (provider.objectWay.get(provider, provider.expression) == null) {
413-
// object has no value, so check the dom
454+
// object has no value, so check the dom
414455
setTimeout(function() {
415456
if (provider.domWay.get(provider))
416457
// and apply when exists

projects/mask-binding/src/DomObjectTransport.ts

+17-8
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ var objectWay = <IObjectWay> {
5656
}
5757
};
5858
var domWay = <IDomWay> {
59-
get: function(provider) {
59+
get (provider: BindingProvider) {
6060
var getter = provider.domGetter;
6161
if (getter == null) {
6262
return obj_getProperty(provider, provider.property);
@@ -67,7 +67,7 @@ var domWay = <IDomWay> {
6767
}
6868
return ctr[getter](provider.element);
6969
},
70-
set: function(provider, value) {
70+
set (provider: BindingProvider, value) {
7171
var setter = provider.domSetter;
7272
if (setter == null) {
7373
obj_setProperty(provider, provider.property, value);
@@ -115,9 +115,18 @@ export const DomObjectTransport = {
115115
objectWay: objectWay,
116116
domWay: domWay,
117117

118+
domModelWay: {
119+
get (provider: BindingProvider) {
120+
return obj_getProperty(provider.owner, provider.property);
121+
},
122+
set (provider: BindingProvider, val) {
123+
obj_setProperty(provider.owner, provider.property, val);
124+
}
125+
},
126+
118127
SELECT: {
119-
get: function(provider) {
120-
var el = provider.element,
128+
get (provider: BindingProvider) {
129+
let el = provider.element as HTMLSelectElement,
121130
i = el.selectedIndex;
122131
if (i === -1)
123132
return '';
@@ -129,7 +138,7 @@ export const DomObjectTransport = {
129138
: val
130139
;
131140
},
132-
set: function(provider, val) {
141+
set (provider, val) {
133142
var el = provider.element,
134143
options = el.options,
135144
imax = options.length,
@@ -151,12 +160,12 @@ export const DomObjectTransport = {
151160
}
152161
},
153162
SELECT_MULT: {
154-
get: function(provider) {
163+
get (provider) {
155164
return coll_map(provider.element.selectedOptions, function(x){
156165
return x.value;
157166
});
158167
},
159-
set: function(provider, mix) {
168+
set (provider, mix) {
160169
coll_each(provider.element.options, function(el){
161170
el.selected = false;
162171
});
@@ -182,7 +191,7 @@ export const DomObjectTransport = {
182191
DATE: {
183192
domWay: {
184193
get: domWay.get,
185-
set: function(prov, val){
194+
set (prov, val){
186195
var date = date_ensure(val);
187196
prov.element.value = date == null ? '' : formatDate(date);
188197
}

projects/mask-binding/test/dualbind.spec.ts

+32-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Mask as mask } from '../../../src/mask'
22
const Compo = mask.Compo;
33

4-
54
UTest({
65
'input text' () {
76
var model = <any> { foo: 'Foo' };
@@ -245,7 +244,32 @@ UTest({
245244

246245
app.remove();
247246
eq_(foo.scope.__observers.letter.length, 0);
248-
},
247+
},
248+
async 'should observe components property' () {
249+
var template = `
250+
define Foo {
251+
var myLetter = 'a';
252+
span > '~[bind: this.myLetter]'
253+
@placeholder;
254+
};
255+
256+
Foo {
257+
dualbind value='letter' property='myLetter';
258+
}
259+
`;
260+
261+
let model = { letter: null };
262+
let parent = Compo.initialize(template, model);
263+
let foo = Compo.find(parent, 'Foo');
264+
265+
await pause(20)
266+
267+
eq_(foo.myLetter, 'a');
268+
eq_(model.letter, 'a', 'Must got from component as initial was null');
269+
270+
foo.myLetter = 'b';
271+
eq_(model.letter, 'b');
272+
},
249273
'mappings': {
250274
'should map to dom and to object' () {
251275
var template = `
@@ -318,4 +342,9 @@ UTest({
318342
eq_(foo.model.letter, 'q/');
319343
}
320344
}
321-
})
345+
})
346+
347+
348+
function pause (ms) {
349+
return new Promise(resolve => setTimeout(resolve, ms));
350+
}

0 commit comments

Comments
 (0)