-
Notifications
You must be signed in to change notification settings - Fork 297
/
UiComponent.js
248 lines (245 loc) · 7.35 KB
/
UiComponent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/**
_enyo.UiComponent_ implements a container strategy suitable for presentation layers.
UiComponent itself is abstract. Concrete subkinds include <a href="#enyo.Control">enyo.Control</a> (for HTML/DOM) and _enyo.CanvasControl_ for Canvas contexts.
*/
enyo.kind({
name: "enyo.UiComponent",
kind: enyo.Component,
published: {
container: null,
parent: null,
controlParentName: "client",
layoutKind: ""
},
handlers: {
onresize: "resizeHandler"
},
create: function() {
this.controls = [];
this.children = [];
this.inherited(arguments);
this.containerChanged();
this.layoutKindChanged();
},
destroy: function() {
// destroys all non-chrome controls (regardless of owner)
this.destroyClientControls();
// remove us from our container
this.setContainer(null);
// destroys chrome controls owned by this
this.inherited(arguments);
},
importProps: function(inProps) {
this.inherited(arguments);
if (!this.owner) {
//this.log("registering ownerless control [" + this.kindName + "] with enyo.master");
this.owner = enyo.master;
}
},
// As implemented, controlParentName only works to identify an owned control created via createComponents
// (i.e. usually in our components block).
// To attach a controlParent via other means, one needs to call discoverControlParent
// or set controlParent directly.
// We could discoverControlParent in addComponent, but it would cause a lot of useless checking.
createComponents: function() {
this.inherited(arguments);
this.discoverControlParent();
},
discoverControlParent: function() {
this.controlParent = this.$[this.controlParentName] || this.controlParent;
},
// components we create have us as a container by default
adjustComponentProps: function(inProps) {
this.inherited(arguments);
inProps.container = inProps.container || this;
},
// containment
containerChanged: function(inOldContainer) {
if (inOldContainer) {
inOldContainer.removeControl(this);
}
if (this.container) {
this.container.addControl(this);
}
},
// parentage
parentChanged: function(inOldParent) {
if (inOldParent && inOldParent != this.parent) {
inOldParent.removeChild(this);
}
},
//* @public
// Note: oddly, a Control is considered a descendant of itself
isDescendantOf: function(inAncestor) {
var p = this;
while (p && p!=inAncestor) {
p = p.parent;
}
return inAncestor && (p == inAncestor);
},
/**
Returns all controls.
*/
getControls: function() {
return this.controls;
},
/**
Returns all non-chrome controls.
*/
getClientControls: function() {
var results = [];
for (var i=0, cs=this.controls, c; c=cs[i]; i++) {
if (!c.isChrome) {
results.push(c);
}
}
return results;
},
/**
Destroys 'client controls', the same set of controls returned by getClientControls.
*/
destroyClientControls: function() {
var c$ = this.getClientControls();
for (var i=0, c; c=c$[i]; i++) {
c.destroy();
}
},
//* @protected
addControl: function(inControl) {
this.controls.push(inControl);
// when we add a Control, we also establish a parent
this.addChild(inControl);
},
removeControl: function(inControl) {
// when we remove a Control, we also remove it from it's parent
inControl.setParent(null);
return enyo.remove(inControl, this.controls);
},
indexOfControl: function(inControl) {
return enyo.indexOf(inControl, this.controls);
},
indexOfClientControl: function(inControl) {
return enyo.indexOf(inControl, this.getClientControls());
},
indexInContainer: function() {
return this.container.indexOfControl(this);
},
clientIndexInContainer: function() {
return this.container.indexOfClientControl(this);
},
// children
addChild: function(inChild) {
// allow delegating the child to a different container
if (this.controlParent /*&& !inChild.isChrome*/) {
// this.controlParent might have a controlParent, and so on; seek the ultimate parent
this.controlParent.addChild(inChild);
} else {
// NOTE: addChild drives setParent.
// It's the opposite for setContainer, where containerChanged (in Containable)
// drives addControl.
// Because of the way 'parent' is derived from 'container', this difference is
// helpful for implementing controlParent.
// By the same token, since 'parent' is derived from 'container', setParent is
// not intended to be called by client code. Therefore, the lack of parallelism
// should be private to this implementation.
// Set the child's parent property to this
inChild.setParent(this);
// track in children array
this.children[this.prepend ? "unshift" : "push"](inChild);
/*
// FIXME: hacky, allows us to reparent a rendered control; we need better API for dynamic reparenting
if (inChild.hasNode()) {
inChild[this.prepend ? "_prepend" : "_append"]();
}
*/
}
},
removeChild: function(inChild) {
return enyo.remove(inChild, this.children);
},
indexOfChild: function(inChild) {
return enyo.indexOf(inChild, this.children);
},
layoutKindChanged: function() {
if (this.layout) {
this.layout.destroy();
}
this.layout = enyo.createFromKind(this.layoutKind, this);
if (this.generated) {
this.render();
}
},
flow: function() {
if (this.layout) {
this.layout.flow();
}
},
// CAVEAT: currently we use the entry point for both
// post-render layout work *and* post-resize layout work.
reflow: function() {
if (this.layout) {
this.layout.reflow();
}
},
/**
Call after this control has been resized to allow it to process the size change.
To respond to a resize, override "resizeHandler" instead.
*/
// syntactic sugar for 'broadcastMessage("resize")'
resized: function() {
this.waterfall("onresize");
},
//* @protected
resizeHandler: function() {
// FIXME: once we are the business of reflowing layouts on resize, then we have a
// inside/outside problem: some scenarios will need to reflow before child
// controls reflow, and some will need to reflow after. Even more complex scenarios
// have circular dependencies, and can require multiple passes or other resolution.
// When we can rely on CSS to manage reflows we do not have these problems.
this.reflow();
},
/**
Send a message to all my descendents
*/
waterfallDown: function(inMessage, inPayload, inSender) {
for (var i=0, cs=this.children, c; c=cs[i]; i++) {
c.waterfall(inMessage, inPayload, inSender);
}
},
getBubbleTarget: function() {
return this.parent;
}
});
enyo.createFromKind = function(inKind, inParam) {
var ctor = inKind && enyo.constructorForKind(inKind);
if (ctor) {
return new ctor(inParam);
}
};
//
// Default owner for ownerless UiComponents to allow notifying such UiComponents of important system events
// like window resize.
//
// NOTE: ownerless UiComponents will not GC unless explicity destroyed as they will be referenced by enyo.master.
//
enyo.master = new enyo.Component({
name: "master",
/*
notInstanceOwner: true,
getId: function() {
return '';
},
*/
bubble: function(inEventName, inEvent, inSender) {
//console.log("master event: " + inEventName);
if (inEventName == "onresize") {
// resize is special, waterfall this message
// this works because master is a Component, so it' waterfalls
// to it's owned Components (i.e. master has no children)
enyo.master.waterfallDown("onresize");
} else {
// all other top level events are sent only to interested Signal receivers
enyo.Signals.send(inEventName, inEvent);
}
}
});