PageRenderTime 67ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

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