Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('widget-position-constrain', function (Y, NAME) {
2
 
3
/**
4
 * Provides constrained xy positioning support for Widgets, through an extension.
5
 *
6
 * It builds on top of the widget-position module, to provide constrained positioning support.
7
 *
8
 * @module widget-position-constrain
9
 */
10
var CONSTRAIN = "constrain",
11
    CONSTRAIN_XYCHANGE = "constrain|xyChange",
12
    CONSTRAIN_CHANGE = "constrainChange",
13
 
14
    PREVENT_OVERLAP = "preventOverlap",
15
    ALIGN = "align",
16
 
17
    EMPTY_STR = "",
18
 
19
    BINDUI = "bindUI",
20
 
21
    XY = "xy",
22
    X_COORD = "x",
23
    Y_COORD = "y",
24
 
25
    Node = Y.Node,
26
 
27
    VIEWPORT_REGION = "viewportRegion",
28
    REGION = "region",
29
 
30
    PREVENT_OVERLAP_MAP;
31
 
32
/**
33
 * A widget extension, which can be used to add constrained xy positioning support to the base Widget class,
34
 * through the <a href="Base.html#method_build">Base.build</a> method. This extension requires that
35
 * the WidgetPosition extension be added to the Widget (before WidgetPositionConstrain, if part of the same
36
 * extension list passed to Base.build).
37
 *
38
 * @class WidgetPositionConstrain
39
 * @param {Object} User configuration object
40
 */
41
function PositionConstrain(config) {}
42
 
43
/**
44
 * Static property used to define the default attribute
45
 * configuration introduced by WidgetPositionConstrain.
46
 *
47
 * @property ATTRS
48
 * @type Object
49
 * @static
50
 */
51
PositionConstrain.ATTRS = {
52
 
53
    /**
54
     * @attribute constrain
55
     * @type boolean | Node
56
     * @default null
57
     * @description The node to constrain the widget's bounding box to, when setting xy. Can also be
58
     * set to true, to constrain to the viewport.
59
     */
60
    constrain : {
61
        value: null,
62
        setter: "_setConstrain"
63
    },
64
 
65
    /**
66
     * @attribute preventOverlap
67
     * @type boolean
68
     * @description If set to true, and WidgetPositionAlign is also added to the Widget,
69
     * constrained positioning will attempt to prevent the widget's bounding box from overlapping
70
     * the element to which it has been aligned, by flipping the orientation of the alignment
71
     * for corner based alignments
72
     */
73
    preventOverlap : {
74
        value:false
75
    }
76
};
77
 
78
/**
79
 * @property _PREVENT_OVERLAP
80
 * @static
81
 * @protected
82
 * @type Object
83
 * @description The set of positions for which to prevent
84
 * overlap.
85
 */
86
PREVENT_OVERLAP_MAP = PositionConstrain._PREVENT_OVERLAP = {
87
    x: {
88
        "tltr": 1,
89
        "blbr": 1,
90
        "brbl": 1,
91
        "trtl": 1
92
    },
93
    y : {
94
        "trbr": 1,
95
        "tlbl": 1,
96
        "bltl": 1,
97
        "brtr": 1
98
    }
99
};
100
 
101
PositionConstrain.prototype = {
102
 
103
    initializer : function() {
104
        if (!this._posNode) {
105
            Y.error("WidgetPosition needs to be added to the Widget, before WidgetPositionConstrain is added");
106
        }
107
        Y.after(this._bindUIPosConstrained, this, BINDUI);
108
    },
109
 
110
    /**
111
     * Calculates the constrained positions for the XY positions provided, using
112
     * the provided node argument is passed in. If no node value is passed in, the value of
113
     * the "constrain" attribute is used.
114
     *
115
     * @method getConstrainedXY
116
     * @param {Array} xy The xy values to constrain
117
     * @param {Node | boolean} node Optional. The node to constrain to, or true for the viewport
118
     * @return {Array} The constrained xy values
119
     */
120
    getConstrainedXY : function(xy, node) {
121
        node = node || this.get(CONSTRAIN);
122
 
123
        var constrainingRegion = this._getRegion((node === true) ? null : node),
124
            nodeRegion = this._posNode.get(REGION);
125
 
126
        return [
127
            this._constrain(xy[0], X_COORD, nodeRegion, constrainingRegion),
128
            this._constrain(xy[1], Y_COORD, nodeRegion, constrainingRegion)
129
        ];
130
    },
131
 
132
    /**
133
     * Constrains the widget's bounding box to a node (or the viewport). If xy or node are not
134
     * passed in, the current position and the value of "constrain" will be used respectively.
135
     *
136
     * The widget's position will be changed to the constrained position.
137
     *
138
     * @method constrain
139
     * @param {Array} xy Optional. The xy values to constrain
140
     * @param {Node | boolean} node Optional. The node to constrain to, or true for the viewport
141
     */
142
    constrain : function(xy, node) {
143
        var currentXY,
144
            constrainedXY,
145
            constraint = node || this.get(CONSTRAIN);
146
 
147
        if (constraint) {
148
            currentXY = xy || this.get(XY);
149
            constrainedXY = this.getConstrainedXY(currentXY, constraint);
150
 
151
            if (constrainedXY[0] !== currentXY[0] || constrainedXY[1] !== currentXY[1]) {
152
                this.set(XY, constrainedXY, { constrained:true });
153
            }
154
        }
155
    },
156
 
157
    /**
158
     * The setter implementation for the "constrain" attribute.
159
     *
160
     * @method _setConstrain
161
     * @protected
162
     * @param {Node | boolean} val The attribute value
163
     */
164
    _setConstrain : function(val) {
165
        return (val === true) ? val : Node.one(val);
166
    },
167
 
168
    /**
169
     * The method which performs the actual constrain calculations for a given axis ("x" or "y") based
170
     * on the regions provided.
171
     *
172
     * @method _constrain
173
     * @protected
174
     *
175
     * @param {Number} val The value to constrain
176
     * @param {String} axis The axis to use for constrainment
177
     * @param {Region} nodeRegion The region of the node to constrain
178
     * @param {Region} constrainingRegion The region of the node (or viewport) to constrain to
179
     *
180
     * @return {Number} The constrained value
181
     */
182
    _constrain: function(val, axis, nodeRegion, constrainingRegion) {
183
        if (constrainingRegion) {
184
 
185
            if (this.get(PREVENT_OVERLAP)) {
186
                val = this._preventOverlap(val, axis, nodeRegion, constrainingRegion);
187
            }
188
 
189
            var x = (axis == X_COORD),
190
 
191
                regionSize    = (x) ? constrainingRegion.width : constrainingRegion.height,
192
                nodeSize      = (x) ? nodeRegion.width : nodeRegion.height,
193
                minConstraint = (x) ? constrainingRegion.left : constrainingRegion.top,
194
                maxConstraint = (x) ? constrainingRegion.right - nodeSize : constrainingRegion.bottom - nodeSize;
195
 
196
            if (val < minConstraint || val > maxConstraint) {
197
                if (nodeSize < regionSize) {
198
                    if (val < minConstraint) {
199
                        val = minConstraint;
200
                    } else if (val > maxConstraint) {
201
                        val = maxConstraint;
202
                    }
203
                } else {
204
                    val = minConstraint;
205
                }
206
            }
207
        }
208
 
209
        return val;
210
    },
211
 
212
    /**
213
     * The method which performs the preventOverlap calculations for a given axis ("x" or "y") based
214
     * on the value and regions provided.
215
     *
216
     * @method _preventOverlap
217
     * @protected
218
     *
219
     * @param {Number} val The value being constrain
220
     * @param {String} axis The axis to being constrained
221
     * @param {Region} nodeRegion The region of the node being constrained
222
     * @param {Region} constrainingRegion The region of the node (or viewport) we need to constrain to
223
     *
224
     * @return {Number} The constrained value
225
     */
226
    _preventOverlap : function(val, axis, nodeRegion, constrainingRegion) {
227
 
228
        var align = this.get(ALIGN),
229
            x = (axis === X_COORD),
230
            nodeSize,
231
            alignRegion,
232
            nearEdge,
233
            farEdge,
234
            spaceOnNearSide,
235
            spaceOnFarSide;
236
 
237
        if (align && align.points && PREVENT_OVERLAP_MAP[axis][align.points.join(EMPTY_STR)]) {
238
 
239
            alignRegion = this._getRegion(align.node);
240
 
241
            if (alignRegion) {
242
                nodeSize        = (x) ? nodeRegion.width : nodeRegion.height;
243
                nearEdge        = (x) ? alignRegion.left : alignRegion.top;
244
                farEdge         = (x) ? alignRegion.right : alignRegion.bottom;
245
                spaceOnNearSide = (x) ? alignRegion.left - constrainingRegion.left : alignRegion.top - constrainingRegion.top;
246
                spaceOnFarSide  = (x) ? constrainingRegion.right - alignRegion.right : constrainingRegion.bottom - alignRegion.bottom;
247
            }
248
 
249
            if (val > nearEdge) {
250
                if (spaceOnFarSide < nodeSize && spaceOnNearSide > nodeSize) {
251
                    val = nearEdge - nodeSize;
252
                }
253
            } else {
254
                if (spaceOnNearSide < nodeSize && spaceOnFarSide > nodeSize) {
255
                    val = farEdge;
256
                }
257
            }
258
        }
259
 
260
        return val;
261
    },
262
 
263
    /**
264
     * Binds event listeners responsible for updating the UI state in response to
265
     * Widget constrained positioning related state changes.
266
     * <p>
267
     * This method is invoked after bindUI is invoked for the Widget class
268
     * using YUI's aop infrastructure.
269
     * </p>
270
     *
271
     * @method _bindUIPosConstrained
272
     * @protected
273
     */
274
    _bindUIPosConstrained : function() {
275
        this.after(CONSTRAIN_CHANGE, this._afterConstrainChange);
276
        this._enableConstraints(this.get(CONSTRAIN));
277
    },
278
 
279
    /**
280
     * After change listener for the "constrain" attribute, responsible
281
     * for updating the UI, in response to attribute changes.
282
     *
283
     * @method _afterConstrainChange
284
     * @protected
285
     * @param {EventFacade} e The event facade
286
     */
287
    _afterConstrainChange : function(e) {
288
        this._enableConstraints(e.newVal);
289
    },
290
 
291
    /**
292
     * Updates the UI if enabling constraints, and sets up the xyChange event listeners
293
     * to constrain whenever the widget is moved. Disabling constraints removes the listeners.
294
     *
295
     * @method _enableConstraints
296
     * @private
297
     * @param {boolean} enable Enable or disable constraints
298
     */
299
    _enableConstraints : function(enable) {
300
        if (enable) {
301
            this.constrain();
302
            this._cxyHandle = this._cxyHandle || this.on(CONSTRAIN_XYCHANGE, this._constrainOnXYChange);
303
        } else if (this._cxyHandle) {
304
            this._cxyHandle.detach();
305
            this._cxyHandle = null;
306
        }
307
    },
308
 
309
    /**
310
     * The on change listener for the "xy" attribute. Modifies the event facade's
311
     * newVal property with the constrained XY value.
312
     *
313
     * @method _constrainOnXYChange
314
     * @protected
315
     * @param {EventFacade} e The event facade for the attribute change
316
     */
317
    _constrainOnXYChange : function(e) {
318
        if (!e.constrained) {
319
            e.newVal = this.getConstrainedXY(e.newVal);
320
        }
321
    },
322
 
323
    /**
324
     * Utility method to normalize region retrieval from a node instance,
325
     * or the viewport, if no node is provided.
326
     *
327
     * @method _getRegion
328
     * @private
329
     * @param {Node} node Optional.
330
     */
331
    _getRegion : function(node) {
332
        var region;
333
        if (!node) {
334
            region = this._posNode.get(VIEWPORT_REGION);
335
        } else {
336
            node = Node.one(node);
337
            if (node) {
338
                region = node.get(REGION);
339
            }
340
        }
341
        return region;
342
    }
343
};
344
 
345
Y.WidgetPositionConstrain = PositionConstrain;
346
 
347
 
348
}, '3.18.1', {"requires": ["widget-position"]});