aboutsummaryrefslogtreecommitdiff
path: root/node_modules/montage/ui/slot.reel/slot.js
blob: 41220373b111bd14ad034b4f1131093c1948696c (plain)
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/* <copyright>
 This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
 No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
 (c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
 </copyright> */
/**
	@module "montage/ui/slot.reel"
    @requires montage/core/core
    @requires montage/ui/component
*/
var Montage = require("montage").Montage,
    Component = require("ui/component").Component;
/**
 @class module:"montage/ui/slot.reel".Slot
 @extends module:montage/ui/component.Component
 */
var Slot = exports.Slot = Montage.create(Component, /** @lends module:"montage/ui/slot.reel".Slot# */ {

    hasTemplate: {
        enumerable: false,
        value: false
    },
/**
        Description TODO
        @type {Property}
        @default null
    */
    delegate: {
        enumerable: false,
        value: null,
        serializable: true
    },
/**
        Description TODO
        @type {Property}
        @default null
    */
    transition: {
        enumerable: false,
        value: null
    },
/**
  Description TODO
  @private
*/
    _content: {
        enumerable: false,
        value: null
    },
/**
  Description TODO
  @private
*/
    _contentToRemove: {
        enumerable: false,
        value: null
    },
/**
  Description TODO
  @private
*/
    _contentToAppend: {
        enumerable: false,
        value: null
    },
/**
  Description TODO
  @private
*/
    _contentHasChanged: {
        enumerable: false,
        value: true
    },
/**
        Description TODO
        @type {Function}
        @default null
    */
    content: {
        enumerable: false,
        get: function() {
            return this._content;
        },
        set: function(value) {

            // if no change or busy drawing a switch, ignore this "new" value
            if ((!!value && this._content === value)|| (!!this._contentToAppend && value === this._contentToAppend) || this._isSwitchingContent) {
                return;
            }

            if (this._contentToAppend) {
                // If we already had some content that was going to be appended, don't bother with it
                // the new value we just received will supercede it
                this._contentToAppend.needsDraw = false;
                this._drawList = [];
            }

            // TODO if given a serialized component or something (a template) we should be able to handle that

            this._contentToAppend = value;

            if (this._contentToAppend && typeof this._contentToAppend.needsDraw !== "undefined") {

                // If the incoming content was a component; make sure it has an element before we say it needs to draw
                if (!this._contentToAppend.element) {
                    var nodeToAppend = document.createElement("div");
                    nodeToAppend.id = "appendDiv";

                    if (this.delegate && typeof this.delegate.slotElementForComponent === "function") {
                        nodeToAppend = this.delegate.slotElementForComponent(this, this._contentToAppend, nodeToAppend);
                    }
                    this._contentToAppend.setElementWithParentComponent(nodeToAppend, this);
                } else {
                    this._contentToAppend.setElementWithParentComponent(this._contentToAppend.element, this);
                }

                // The child component will need to draw; this may trigger a draw for the slot itself
                this._contentToAppend.needsDraw = true;
            }

            this.needsDraw = true;
            this._contentToRemove = this._content;
            this._contentHasChanged = true;

            return value;
        }
    },
/**
  Description TODO
  @private
*/
    _isSwitchingContent: {
        enumerable: false,
        value: false
    },

    childComponentWillPrepareForDraw: {
        value: function(child) {
            if (child.element.parentElement == null) {
                // by the time a child component lets us know it's about to prepare to draw for the first time
                // we know we need to append its element to our own element.
                this._element.appendChild(child.element);
                this.needsDraw = true;
            }
        }
    },

    _canAppendContent: {
        enumerable: false,
        value: false
    },

    canDraw: {
        value: function() {

            if (this._contentToAppend) {
                if (typeof this._contentToAppend.needsDraw !== "undefined") {
                    this._canAppendContent = this._contentToAppend.canDraw();
                    if (this._canAppendContent) {
                        this.needsDraw = true;
                    }
                } else {
                    this._canAppendContent = true;
                }
            } else {
                // No content to append, but we can render that situation (empty out the slot)
                this._canAppendContent = true;
            }

            // We'll always offer to draw if asked to allow children to draw, but what the slot does when it draws
            // will depend on the _canAppendContent flag determined at this point
            return true;
        }
    },
/**
    Description TODO
    @function
    */
    draw: {
        value: function() {


            if (!this._canAppendContent) {
                return;
            }

            // Prevent other switching while we're in the middle of rendering this current switch
            this._isSwitchingContent = true;

            var nodeToAppend, nodeToRemove, rangeToRemove;

            // If there's no content currently inside the slot we need to have one for transition support which expects
            // a start and an end node; but we want to make sure the node is included in the rangeToRemove
            if (this._contentToRemove) {
                if (this._contentToRemove.nodeType) {
                    // The content is a node itself; use this node
                    nodeToRemove = this._contentToRemove;
                } else if (this._contentToRemove.element) {
                    // The content has an element property set; use the element
                    nodeToRemove = this._contentToRemove.element;
                }
            } else {
                if (this.transition) {
                    nodeToRemove = document.createElement("div");
                    nodeToRemove.id = "removeDiv";
                    // Since we're trying to remove this node it's expected to be in the slot already; put it there
                    this._element.appendChild(nodeToRemove);
                }
            }

            // If there is new content then whatever is in the slot currently needs to be removed
            if (this._contentHasChanged) {
                rangeToRemove = document.createRange();
                rangeToRemove.selectNodeContents(this._element);
            }

            // Figure out what node this slot is appending given the contentToAppend
            if (this._contentToAppend) {
                if (this._contentToAppend.nodeType) {
                    // The content is a node itself; use this node
                    nodeToAppend = this._contentToAppend;
                } else if (this._contentToAppend.element) {
                    // The content has an element property set; use the element
                    nodeToAppend = this._contentToAppend.element;
                }
            } else {
                if (this.transition) {
                    nodeToAppend = document.createElement("div");
                    nodeToAppend.id = "appendNode";
                }
            }

            // Make sure the nodeToAppend isn't already in the DOM
            if (nodeToAppend && nodeToAppend.parentNode) {
                nodeToAppend.parentNode.removeChild(nodeToAppend);
            }

            if (this.delegate && typeof this.delegate.slotWillSwitchContent === "function") {
                this.delegate.slotWillSwitchContent(this, nodeToAppend, this._contentToAppend, nodeToRemove, this._contentToRemove);
            }

            if (nodeToAppend) {

                this._element.appendChild(nodeToAppend);

                // Introduce to the componentTree if content appended was a component
                if (this._contentToAppend && (typeof this._contentToAppend.element !== "undefined")) {
                    this.childComponents = [this._contentToAppend];
                }
            }

            // Actually draw the change in content now
            if (this.transition) {

                var self = this,
                    cleanupCallback = function() {
                        self._cleanupAfterPopulating(nodeToAppend, nodeToRemove, rangeToRemove);
                    };

                this.transition.start(nodeToRemove, nodeToAppend, cleanupCallback);

            } else {
                this._cleanupAfterPopulating(nodeToAppend, nodeToRemove, rangeToRemove);
            }
        }
    },
/**
  Description TODO
  @private
*/
    _cleanupAfterPopulating: {
        value: function(appendedNode, removedNode, rangeToRemove) {

            if (rangeToRemove) {
                rangeToRemove.deleteContents();
                rangeToRemove.detach();
                this._contentHasChanged = false;
            }

            // If the old content was a component, remove it from the component tree
            if (this._contentToRemove && this._contentToRemove.parentComponent) {
                // TODO may also need to remove this from my drawlist, possibly elsewhere too
                this._contentToRemove.detachFromParentComponent();
            }

            var removedContent = this._contentToRemove;

            this._content = this._contentToAppend;
            this._contentToAppend = null;
            this._contentToRemove = null;

            if (this.delegate && typeof this.delegate.slotDidSwitchContent === "function") {
                this.delegate.slotDidSwitchContent(this, appendedNode, this._content, removedNode, removedContent);
            }

            this._isSwitchingContent = false;
            this._canAppendContent = false;
        }
    }
});