PageRenderTime 456ms CodeModel.GetById 109ms app.highlight 135ms RepoModel.GetById 89ms app.codeStats 0ms

/ajax/scripts/window-manager.js

http://showslow.googlecode.com/
JavaScript | 414 lines | 340 code | 63 blank | 11 comment | 89 complexity | fd674cb778ce57e647f746979dd8bcf3 MD5 | raw file
  1/**
  2 * @fileOverview UI layers and window-wide dragging
  3 * @name SimileAjax.WindowManager
  4 */
  5
  6/**
  7 *  This is a singleton that keeps track of UI layers (modal and 
  8 *  modeless) and enables/disables UI elements based on which layers
  9 *  they belong to. It also provides window-wide dragging 
 10 *  implementation.
 11 */ 
 12SimileAjax.WindowManager = {
 13    _initialized:       false,
 14    _listeners:         [],
 15    
 16    _draggedElement:                null,
 17    _draggedElementCallback:        null,
 18    _dropTargetHighlightElement:    null,
 19    _lastCoords:                    null,
 20    _ghostCoords:                   null,
 21    _draggingMode:                  "",
 22    _dragging:                      false,
 23    
 24    _layers:            []
 25};
 26
 27SimileAjax.WindowManager.initialize = function() {
 28    if (SimileAjax.WindowManager._initialized) {
 29        return;
 30    }
 31    
 32    SimileAjax.DOM.registerEvent(document.body, "mousedown", SimileAjax.WindowManager._onBodyMouseDown);
 33    SimileAjax.DOM.registerEvent(document.body, "mousemove", SimileAjax.WindowManager._onBodyMouseMove);
 34    SimileAjax.DOM.registerEvent(document.body, "mouseup",   SimileAjax.WindowManager._onBodyMouseUp);
 35    SimileAjax.DOM.registerEvent(document, "keydown",       SimileAjax.WindowManager._onBodyKeyDown);
 36    SimileAjax.DOM.registerEvent(document, "keyup",         SimileAjax.WindowManager._onBodyKeyUp);
 37    
 38    SimileAjax.WindowManager._layers.push({index: 0});
 39    
 40    SimileAjax.WindowManager._historyListener = {
 41        onBeforeUndoSeveral:    function() {},
 42        onAfterUndoSeveral:     function() {},
 43        onBeforeUndo:           function() {},
 44        onAfterUndo:            function() {},
 45        
 46        onBeforeRedoSeveral:    function() {},
 47        onAfterRedoSeveral:     function() {},
 48        onBeforeRedo:           function() {},
 49        onAfterRedo:            function() {}
 50    };
 51    SimileAjax.History.addListener(SimileAjax.WindowManager._historyListener);
 52    
 53    SimileAjax.WindowManager._initialized = true;
 54};
 55
 56SimileAjax.WindowManager.getBaseLayer = function() {
 57    SimileAjax.WindowManager.initialize();
 58    return SimileAjax.WindowManager._layers[0];
 59};
 60
 61SimileAjax.WindowManager.getHighestLayer = function() {
 62    SimileAjax.WindowManager.initialize();
 63    return SimileAjax.WindowManager._layers[SimileAjax.WindowManager._layers.length - 1];
 64};
 65
 66SimileAjax.WindowManager.registerEventWithObject = function(elmt, eventName, obj, handlerName, layer) {
 67    SimileAjax.WindowManager.registerEvent(
 68        elmt, 
 69        eventName, 
 70        function(elmt2, evt, target) {
 71            return obj[handlerName].call(obj, elmt2, evt, target);
 72        },
 73        layer
 74    );
 75};
 76
 77SimileAjax.WindowManager.registerEvent = function(elmt, eventName, handler, layer) {
 78    if (layer == null) {
 79        layer = SimileAjax.WindowManager.getHighestLayer();
 80    }
 81    
 82    var handler2 = function(elmt, evt, target) {
 83        if (SimileAjax.WindowManager._canProcessEventAtLayer(layer)) {
 84            SimileAjax.WindowManager._popToLayer(layer.index);
 85            try {
 86                handler(elmt, evt, target);
 87            } catch (e) {
 88                SimileAjax.Debug.exception(e);
 89            }
 90        }
 91        SimileAjax.DOM.cancelEvent(evt);
 92        return false;
 93    }
 94    
 95    SimileAjax.DOM.registerEvent(elmt, eventName, handler2);
 96};
 97
 98SimileAjax.WindowManager.pushLayer = function(f, ephemeral, elmt) {
 99    var layer = { onPop: f, index: SimileAjax.WindowManager._layers.length, ephemeral: (ephemeral), elmt: elmt };
100    SimileAjax.WindowManager._layers.push(layer);
101    
102    return layer;
103};
104
105SimileAjax.WindowManager.popLayer = function(layer) {
106    for (var i = 1; i < SimileAjax.WindowManager._layers.length; i++) {
107        if (SimileAjax.WindowManager._layers[i] == layer) {
108            SimileAjax.WindowManager._popToLayer(i - 1);
109            break;
110        }
111    }
112};
113
114SimileAjax.WindowManager.popAllLayers = function() {
115    SimileAjax.WindowManager._popToLayer(0);
116};
117
118SimileAjax.WindowManager.registerForDragging = function(elmt, callback, layer) {
119    SimileAjax.WindowManager.registerEvent(
120        elmt, 
121        "mousedown", 
122        function(elmt, evt, target) {
123            SimileAjax.WindowManager._handleMouseDown(elmt, evt, callback);
124        }, 
125        layer
126    );
127};
128
129SimileAjax.WindowManager._popToLayer = function(level) {
130    while (level+1 < SimileAjax.WindowManager._layers.length) {
131        try {
132            var layer = SimileAjax.WindowManager._layers.pop();
133            if (layer.onPop != null) {
134                layer.onPop();
135            }
136        } catch (e) {
137        }
138    }
139};
140
141SimileAjax.WindowManager._canProcessEventAtLayer = function(layer) {
142    if (layer.index == (SimileAjax.WindowManager._layers.length - 1)) {
143        return true;
144    }
145    for (var i = layer.index + 1; i < SimileAjax.WindowManager._layers.length; i++) {
146        if (!SimileAjax.WindowManager._layers[i].ephemeral) {
147            return false;
148        }
149    }
150    return true;
151};
152
153SimileAjax.WindowManager.cancelPopups = function(evt) {
154    var evtCoords = (evt) ? SimileAjax.DOM.getEventPageCoordinates(evt) : { x: -1, y: -1 };
155    
156    var i = SimileAjax.WindowManager._layers.length - 1;
157    while (i > 0 && SimileAjax.WindowManager._layers[i].ephemeral) {
158        var layer = SimileAjax.WindowManager._layers[i];
159        if (layer.elmt != null) { // if event falls within main element of layer then don't cancel
160            var elmt = layer.elmt;
161            var elmtCoords = SimileAjax.DOM.getPageCoordinates(elmt);
162            if (evtCoords.x >= elmtCoords.left && evtCoords.x < (elmtCoords.left + elmt.offsetWidth) &&
163                evtCoords.y >= elmtCoords.top && evtCoords.y < (elmtCoords.top + elmt.offsetHeight)) {
164                break;
165            }
166        }
167        i--;
168    }
169    SimileAjax.WindowManager._popToLayer(i);
170};
171
172SimileAjax.WindowManager._onBodyMouseDown = function(elmt, evt, target) {
173    if (!("eventPhase" in evt) || evt.eventPhase == evt.BUBBLING_PHASE) {
174        SimileAjax.WindowManager.cancelPopups(evt);
175    }
176};
177
178SimileAjax.WindowManager._handleMouseDown = function(elmt, evt, callback) {
179    SimileAjax.WindowManager._draggedElement = elmt;
180    SimileAjax.WindowManager._draggedElementCallback = callback;
181    SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY };
182        
183    SimileAjax.DOM.cancelEvent(evt);
184    return false;
185};
186
187SimileAjax.WindowManager._onBodyKeyDown = function(elmt, evt, target) {
188    if (SimileAjax.WindowManager._dragging) {
189        if (evt.keyCode == 27) { // esc
190            SimileAjax.WindowManager._cancelDragging();
191        } else if ((evt.keyCode == 17 || evt.keyCode == 16) && SimileAjax.WindowManager._draggingMode != "copy") {
192            SimileAjax.WindowManager._draggingMode = "copy";
193            
194            var img = SimileAjax.Graphics.createTranslucentImage(SimileAjax.urlPrefix + "images/copy.png");
195            img.style.position = "absolute";
196            img.style.left = (SimileAjax.WindowManager._ghostCoords.left - 16) + "px";
197            img.style.top = (SimileAjax.WindowManager._ghostCoords.top) + "px";
198            document.body.appendChild(img);
199            
200            SimileAjax.WindowManager._draggingModeIndicatorElmt = img;
201        }
202    }
203};
204
205SimileAjax.WindowManager._onBodyKeyUp = function(elmt, evt, target) {
206    if (SimileAjax.WindowManager._dragging) {
207        if (evt.keyCode == 17 || evt.keyCode == 16) {
208            SimileAjax.WindowManager._draggingMode = "";
209            if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) {
210                document.body.removeChild(SimileAjax.WindowManager._draggingModeIndicatorElmt);
211                SimileAjax.WindowManager._draggingModeIndicatorElmt = null;
212            }
213        }
214    }
215};
216
217SimileAjax.WindowManager._onBodyMouseMove = function(elmt, evt, target) {
218    if (SimileAjax.WindowManager._draggedElement != null) {
219        var callback = SimileAjax.WindowManager._draggedElementCallback;
220        
221        var lastCoords = SimileAjax.WindowManager._lastCoords;
222        var diffX = evt.clientX - lastCoords.x;
223        var diffY = evt.clientY - lastCoords.y;
224        
225        if (!SimileAjax.WindowManager._dragging) {
226            if (Math.abs(diffX) > 5 || Math.abs(diffY) > 5) {
227                try {
228                    if ("onDragStart" in callback) {
229                        callback.onDragStart();
230                    }
231                    
232                    if ("ghost" in callback && callback.ghost) {
233                        var draggedElmt = SimileAjax.WindowManager._draggedElement;
234                        
235                        SimileAjax.WindowManager._ghostCoords = SimileAjax.DOM.getPageCoordinates(draggedElmt);
236                        SimileAjax.WindowManager._ghostCoords.left += diffX;
237                        SimileAjax.WindowManager._ghostCoords.top += diffY;
238                        
239                        var ghostElmt = draggedElmt.cloneNode(true);
240                        ghostElmt.style.position = "absolute";
241                        ghostElmt.style.left = SimileAjax.WindowManager._ghostCoords.left + "px";
242                        ghostElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px";
243                        ghostElmt.style.zIndex = 1000;
244                        SimileAjax.Graphics.setOpacity(ghostElmt, 50);
245                        
246                        document.body.appendChild(ghostElmt);
247                        callback._ghostElmt = ghostElmt;
248                    }
249                    
250                    SimileAjax.WindowManager._dragging = true;
251                    SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY };
252                    
253                    document.body.focus();
254                } catch (e) {
255                    SimileAjax.Debug.exception("WindowManager: Error handling mouse down", e);
256                    SimileAjax.WindowManager._cancelDragging();
257                }
258            }
259        } else {
260            try {
261                SimileAjax.WindowManager._lastCoords = { x: evt.clientX, y: evt.clientY };
262                
263                if ("onDragBy" in callback) {
264                    callback.onDragBy(diffX, diffY);
265                }
266                
267                if ("_ghostElmt" in callback) {
268                    var ghostElmt = callback._ghostElmt;
269                    
270                    SimileAjax.WindowManager._ghostCoords.left += diffX;
271                    SimileAjax.WindowManager._ghostCoords.top += diffY;
272                    
273                    ghostElmt.style.left = SimileAjax.WindowManager._ghostCoords.left + "px";
274                    ghostElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px";
275                    if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) {
276                        var indicatorElmt = SimileAjax.WindowManager._draggingModeIndicatorElmt;
277                        
278                        indicatorElmt.style.left = (SimileAjax.WindowManager._ghostCoords.left - 16) + "px";
279                        indicatorElmt.style.top = SimileAjax.WindowManager._ghostCoords.top + "px";
280                    }
281                    
282                    if ("droppable" in callback && callback.droppable) {
283                        var coords = SimileAjax.DOM.getEventPageCoordinates(evt);
284                        var target = SimileAjax.DOM.hittest(
285                            coords.x, coords.y, 
286                            [   SimileAjax.WindowManager._ghostElmt, 
287                                SimileAjax.WindowManager._dropTargetHighlightElement 
288                            ]
289                        );
290                        target = SimileAjax.WindowManager._findDropTarget(target);
291                        
292                        if (target != SimileAjax.WindowManager._potentialDropTarget) {
293                            if (SimileAjax.WindowManager._dropTargetHighlightElement != null) {
294                                document.body.removeChild(SimileAjax.WindowManager._dropTargetHighlightElement);
295                                
296                                SimileAjax.WindowManager._dropTargetHighlightElement = null;
297                                SimileAjax.WindowManager._potentialDropTarget = null;
298                            }
299
300                            var droppable = false;
301                            if (target != null) {
302                                if ((!("canDropOn" in callback) || callback.canDropOn(target)) &&
303                                    (!("canDrop" in target) || target.canDrop(SimileAjax.WindowManager._draggedElement))) {
304                                    
305                                    droppable = true;
306                                }
307                            }
308                            
309                            if (droppable) {
310                                var border = 4;
311                                var targetCoords = SimileAjax.DOM.getPageCoordinates(target);
312                                var highlight = document.createElement("div");
313                                highlight.style.border = border + "px solid yellow";
314                                highlight.style.backgroundColor = "yellow";
315                                highlight.style.position = "absolute";
316                                highlight.style.left = targetCoords.left + "px";
317                                highlight.style.top = targetCoords.top + "px";
318                                highlight.style.width = (target.offsetWidth - border * 2) + "px";
319                                highlight.style.height = (target.offsetHeight - border * 2) + "px";
320                                SimileAjax.Graphics.setOpacity(highlight, 30);
321                                document.body.appendChild(highlight);
322                                
323                                SimileAjax.WindowManager._potentialDropTarget = target;
324                                SimileAjax.WindowManager._dropTargetHighlightElement = highlight;
325                            }
326                        }
327                    }
328                }
329            } catch (e) {
330                SimileAjax.Debug.exception("WindowManager: Error handling mouse move", e);
331                SimileAjax.WindowManager._cancelDragging();
332            }
333        }
334        
335        SimileAjax.DOM.cancelEvent(evt);
336        return false;
337    }
338};
339
340SimileAjax.WindowManager._onBodyMouseUp = function(elmt, evt, target) {
341    if (SimileAjax.WindowManager._draggedElement != null) {
342        try {
343            if (SimileAjax.WindowManager._dragging) {
344                var callback = SimileAjax.WindowManager._draggedElementCallback;
345                if ("onDragEnd" in callback) {
346                    callback.onDragEnd();
347                }
348                if ("droppable" in callback && callback.droppable) {
349                    var dropped = false;
350                    
351                    var target = SimileAjax.WindowManager._potentialDropTarget;
352                    if (target != null) {
353                        if ((!("canDropOn" in callback) || callback.canDropOn(target)) &&
354                            (!("canDrop" in target) || target.canDrop(SimileAjax.WindowManager._draggedElement))) {
355                            
356                            if ("onDropOn" in callback) {
357                                callback.onDropOn(target);
358                            }
359                            target.ondrop(SimileAjax.WindowManager._draggedElement, SimileAjax.WindowManager._draggingMode);
360                            
361                            dropped = true;
362                        }
363                    }
364                    
365                    if (!dropped) {
366                        // TODO: do holywood explosion here
367                    }
368                }
369            }
370        } finally {
371            SimileAjax.WindowManager._cancelDragging();
372        }
373        
374        SimileAjax.DOM.cancelEvent(evt);
375        return false;
376    }
377};
378
379SimileAjax.WindowManager._cancelDragging = function() {
380    var callback = SimileAjax.WindowManager._draggedElementCallback;
381    if ("_ghostElmt" in callback) {
382        var ghostElmt = callback._ghostElmt;
383        document.body.removeChild(ghostElmt);
384        
385        delete callback._ghostElmt;
386    }
387    if (SimileAjax.WindowManager._dropTargetHighlightElement != null) {
388        document.body.removeChild(SimileAjax.WindowManager._dropTargetHighlightElement);
389        SimileAjax.WindowManager._dropTargetHighlightElement = null;
390    }
391    if (SimileAjax.WindowManager._draggingModeIndicatorElmt != null) {
392        document.body.removeChild(SimileAjax.WindowManager._draggingModeIndicatorElmt);
393        SimileAjax.WindowManager._draggingModeIndicatorElmt = null;
394    }
395    
396    SimileAjax.WindowManager._draggedElement = null;
397    SimileAjax.WindowManager._draggedElementCallback = null;
398    SimileAjax.WindowManager._potentialDropTarget = null;
399    SimileAjax.WindowManager._dropTargetHighlightElement = null;
400    SimileAjax.WindowManager._lastCoords = null;
401    SimileAjax.WindowManager._ghostCoords = null;
402    SimileAjax.WindowManager._draggingMode = "";
403    SimileAjax.WindowManager._dragging = false;
404};
405
406SimileAjax.WindowManager._findDropTarget = function(elmt) {
407    while (elmt != null) {
408        if ("ondrop" in elmt && (typeof elmt.ondrop) == "function") {
409            break;
410        }
411        elmt = elmt.parentNode;
412    }
413    return elmt;
414};