windowing.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  3. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. define(["jquery", "util", "peers", "session"], function ($, util, peers, session) {
  5. var assert = util.assert;
  6. var windowing = util.Module("windowing");
  7. var $window = $(window);
  8. // This is also in togetherjs.less, under .togetherjs-animated
  9. var ANIMATION_DURATION = 1000;
  10. /* Displays one window. A window must already exist. This hides other windows, and
  11. positions the window according to its data-bound-to attributes */
  12. windowing.show = function (element, options) {
  13. element = $(element);
  14. options = options || {};
  15. options.bind = options.bind || element.attr("data-bind-to");
  16. var notification = element.hasClass("togetherjs-notification");
  17. var modal = element.hasClass("togetherjs-modal");
  18. if (options.bind) {
  19. options.bind = $(options.bind);
  20. }
  21. windowing.hide();
  22. element.stop();
  23. element.show();
  24. // In addition to being hidden, the window can be faded out, which we want to undo:
  25. element.css({opacity: "1"});
  26. if (options.bind) {
  27. assert(! modal, "Binding does not currently work with modals");
  28. bind(element, options.bind);
  29. }
  30. if (notification) {
  31. element.slideIn();
  32. } else if (! modal) {
  33. element.popinWindow();
  34. }
  35. if (modal) {
  36. getModalBackground().show();
  37. modalEscape.bind();
  38. }
  39. onClose = options.onClose || null;
  40. session.emit("display-window", element.attr("id"), element);
  41. };
  42. var onClose = null;
  43. /* Moves a window to be attached to data-bind-to, e.g., the button
  44. that opened the window. Or you can provide an element that it should bind to. */
  45. function bind(win, bound) {
  46. if ($.browser.mobile) {
  47. return;
  48. }
  49. win = $(win);
  50. assert(bound.length, "Cannot find binding:", bound.selector, "from:", win.selector);
  51. // FIXME: hardcoding
  52. var ifacePos = "right";
  53. //var ifacePos = panelPosition();
  54. var boundPos = bound.offset();
  55. boundPos.height = bound.height();
  56. boundPos.width = bound.width();
  57. var windowHeight = $window.height();
  58. boundPos.top -= $window.scrollTop();
  59. boundPos.left -= $window.scrollLeft();
  60. // FIXME: I appear to have to add the padding to the width to get a "true"
  61. // width. But it's still not entirely consistent.
  62. var height = win.height() + 5;
  63. var width = win.width() + 20;
  64. var left, top;
  65. if (ifacePos == "right") {
  66. left = boundPos.left - 11 - width;
  67. top = boundPos.top + (boundPos.height / 2) - (height / 2);
  68. } else if (ifacePos == "left") {
  69. left = boundPos.left + boundPos.width + 15;
  70. top = boundPos.top + (boundPos.height / 2) - (height / 2);
  71. } else if (ifacePos == "bottom") {
  72. left = (boundPos.left + boundPos.width / 2) - (width / 2);
  73. top = boundPos.top - 10 - height;
  74. }
  75. top = Math.min(windowHeight - 10 - height, Math.max(10, top));
  76. win.css({
  77. top: top + "px",
  78. left: left + "px"
  79. });
  80. if (win.hasClass("togetherjs-window")) {
  81. $("#togetherjs-window-pointer-right, #togetherjs-window-pointer-left").hide();
  82. var pointer = $("#togetherjs-window-pointer-" + ifacePos);
  83. pointer.show();
  84. if (ifacePos == "right") {
  85. pointer.css({
  86. top: boundPos.top + Math.floor(boundPos.height / 2) + "px",
  87. left: left + win.width() + 9 + "px"
  88. });
  89. } else if (ifacePos == "left") {
  90. pointer.css({
  91. top: boundPos.top + Math.floor(boundPos.height / 2) + "px",
  92. left: (left - 5) + "px"
  93. });
  94. } else {
  95. console.warn("don't know how to deal with position:", ifacePos);
  96. }
  97. }
  98. win.data("boundTo", bound.selector || "#" + bound.attr("id"));
  99. bound.addClass("togetherjs-active");
  100. }
  101. session.on("resize", function () {
  102. var win = $(".togetherjs-modal:visible, .togetherjs-window:visible");
  103. if (! win.length) {
  104. return;
  105. }
  106. var boundTo = win.data("boundTo");
  107. if (! boundTo) {
  108. return;
  109. }
  110. boundTo = $(boundTo);
  111. bind(win, boundTo);
  112. });
  113. windowing.hide = function (els) {
  114. // FIXME: also hide modals?
  115. els = els || ".togetherjs-window, .togetherjs-modal, .togetherjs-notification";
  116. els = $(els);
  117. els = els.filter(":visible");
  118. els.filter(":not(.togetherjs-notification)").hide();
  119. getModalBackground().hide();
  120. var windows = [];
  121. els.each(function (index, element) {
  122. element = $(element);
  123. windows.push(element);
  124. var bound = element.data("boundTo");
  125. if (! bound) {
  126. return;
  127. }
  128. bound = $(bound);
  129. bound.addClass("togetherjs-animated").addClass("togetherjs-color-pulse");
  130. setTimeout(function () {
  131. bound.removeClass("togetherjs-color-pulse").removeClass("togetherjs-animated");
  132. }, ANIMATION_DURATION+10);
  133. element.data("boundTo", null);
  134. bound.removeClass("togetherjs-active");
  135. if (element.hasClass("togetherjs-notification")) {
  136. element.fadeOut().promise().then(function () {
  137. this.hide();
  138. });
  139. }
  140. });
  141. $("#togetherjs-window-pointer-right, #togetherjs-window-pointer-left").hide();
  142. if (onClose) {
  143. onClose();
  144. onClose = null;
  145. }
  146. if (windows.length) {
  147. session.emit("hide-window", windows);
  148. }
  149. };
  150. windowing.showNotification = function (element, options) {
  151. element = $(element);
  152. options = options || {};
  153. assert(false);
  154. };
  155. windowing.toggle = function (el) {
  156. el = $(el);
  157. if (el.is(":visible")) {
  158. windowing.hide(el);
  159. } else {
  160. windowing.show(el);
  161. }
  162. };
  163. function bindEvents(el) {
  164. el.find(".togetherjs-close, .togetherjs-dismiss").click(function (event) {
  165. var w = $(event.target).closest(".togetherjs-window, .togetherjs-modal, .togetherjs-notification");
  166. windowing.hide(w);
  167. event.stopPropagation();
  168. return false;
  169. });
  170. }
  171. function getModalBackground() {
  172. if (getModalBackground.element) {
  173. return getModalBackground.element;
  174. }
  175. var background = $("#togetherjs-modal-background");
  176. assert(background.length);
  177. getModalBackground.element = background;
  178. background.click(function () {
  179. windowing.hide();
  180. });
  181. return background;
  182. }
  183. var modalEscape = {
  184. bind: function () {
  185. $(document).keydown(modalEscape.onKeydown);
  186. },
  187. unbind: function () {
  188. $(document).unbind("keydown", modalEscape.onKeydown);
  189. },
  190. onKeydown: function (event) {
  191. if (event.which == 27) {
  192. windowing.hide();
  193. }
  194. }
  195. };
  196. session.on("close", function () {
  197. modalEscape.unbind();
  198. });
  199. session.on("new-element", function (el) {
  200. bindEvents(el);
  201. });
  202. return windowing;
  203. });