forked from ibm-js/delite
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Container.js
186 lines (168 loc) · 5.94 KB
/
Container.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
/** @module delite/Container */
define([
"dcl/dcl",
"./Widget"
], function (dcl, Widget) {
/**
* Dispatched after an Element has been added as child of this widget.
* @example
* element.addEventListener("delite-add-child", function (evt) {
* console.log("container: " + evt.target.id + " has new child " + evt.child.id);
* });
* @event module:delite/Container#delite-add-child
*/
/**
* Dispatched after an child Element has been removed from this widget.
* @example
* element.addEventListener("delite-remove-child", function (evt) {
* console.log("container: " + evt.target.id + " removed child " + evt.child.id);
* });
* @event module:delite/Container#delite-remove-child
*/
/**
* Base class for widgets that contain content.
* Useful both for widgets that contain free-form markup (ex: ContentPane),
* and widgets that contain an ordered list of children (ex: Toolbar).
*
* Note that Container is not meant to be used for widgets that just internally create child
* widgets (ex: a StarRating widget creates stars), but rather for when the widget has children from
* the application's perspective (i.e. from the perspective of the widget *user* rather
* than the widget *author*).
*
* @mixin module:delite/Container
* @augments module:delite/Widget
*/
return dcl(Widget, /** @lends module:delite/Container# */{
declaredClass: "delite/Container",
/**
* Designates where children of the source DOM node will be placed,
* and also the target for nodes inserted via `.appendChild()`, `.insertBefore()`, etc.
* "Children" in this case refers to both DOM nodes and widgets.
*
* @member {Element}
* @default Widget root node itself.
* @protected
*/
containerNode: undefined,
render: dcl.advise({
before: function () {
// Save original markup to put into this.containerNode.
var srcDom = this._srcDom = this.ownerDocument.createDocumentFragment();
var oldContainer = this.containerNode || this;
while (oldContainer.firstChild) {
srcDom.appendChild(oldContainer.firstChild);
}
},
after: function () {
if (!this.containerNode) {
// All widgets with descendants must set containerNode.
this.containerNode = this;
}
// Put original markup into this.containerNode. Note that appendChild() on a DocumentFragment will
// loop through all the Elements in the document fragment, adding each one.
this.containerNode.appendChild(this._srcDom);
}
}),
appendChild: dcl.superCall(function (sup) {
return function (child) {
if (this.rendered) {
var res = sup.call(this.containerNode, child);
this.onAddChild(child);
return res;
} else {
return sup.call(this, child);
}
};
}),
insertBefore: dcl.superCall(function (sup) {
return function (newChild, refChild) {
if (this.rendered) {
var res = sup.call(this.containerNode, newChild, refChild);
this.onAddChild(newChild);
return res;
} else {
return sup.call(this, newChild, refChild);
}
};
}),
/**
* Callback whenever a child element is added to this widget via `appendChild()`, `insertBefore()`,
* or a method like `addChild()` that internally calls `appendChild()` and/or `insertBefore()`.
* @param {Element} node
*/
onAddChild: function (node) {
// If I've been started but the child widget hasn't been started,
// start it now. Make sure to do this after widget has been
// inserted into the DOM tree, so it can see that it's being controlled by me,
// so it doesn't try to size itself.
if (this.attached && node.connectedCallback) {
node.connectedCallback();
}
this.emit("delite-add-child", {
bubbles: false,
cancelable: false,
child: node
});
},
/**
* Inserts the specified Element at the specified index.
* For example, `.addChild(node, 3)` sets this widget's fourth child to node.
* @param {Element} node - Element to add as a child.
* @param {number} [insertIndex] - Position the child as at the specified position relative to other children.
*/
addChild: function (node, insertIndex) {
// Note: insertBefore(node, null) equivalent to appendChild(). Null arg is needed (only) on IE.
var cn = this.containerNode || this, nextSibling = cn.children[insertIndex];
cn.insertBefore(node, nextSibling || null);
},
/**
* Detaches the specified node instance from this widget but does
* not destroy it. You can also pass in an integer indicating
* the index within the container to remove (ie, removeChild(5) removes the sixth node).
* @param {Element|number} node
*/
removeChild: function (node) {
if (typeof node === "number") {
node = this.getChildren()[node];
}
if (node && node.parentNode) {
HTMLElement.prototype.removeChild.call(node.parentNode, node); // detach but don't destroy
}
this.emit("delite-remove-child", {
bubbles: false,
cancelable: false,
child: node
});
},
/**
* Returns all direct children of this widget, i.e. all widgets or DOM nodes underneath
* `this.containerNode`. Note that it does not return all
* descendants, but rather just direct children.
*
* The result intentionally excludes element outside off `this.containerNode`. So, it is different than
* accessing the `children` or `childNode` properties.
*
* @returns {Element[]}
*/
getChildren: function () {
// use Array.prototype.slice to transform the live HTMLCollection into an Array
var cn = this.containerNode || this;
return Array.prototype.slice.call(cn.children);
},
/**
* Returns true if widget has child nodes, i.e. if `this.containerNode` contains widgets.
* @returns {boolean}
*/
hasChildren: function () {
return this.getChildren().length > 0;
},
/**
* Returns the index of the child in this container or -1 if not found.
* @param {Element} child
* @returns {number}
*/
getIndexOfChild: function (child) {
return this.getChildren().indexOf(child);
}
});
});