InfoBox.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import buildModuleUrl from "../../Core/buildModuleUrl.js";
  2. import Check from "../../Core/Check.js";
  3. import Color from "../../Core/Color.js";
  4. import defined from "../../Core/defined.js";
  5. import destroyObject from "../../Core/destroyObject.js";
  6. import knockout from "../../ThirdParty/knockout.js";
  7. import getElement from "../getElement.js";
  8. import subscribeAndEvaluate from "../subscribeAndEvaluate.js";
  9. import InfoBoxViewModel from "./InfoBoxViewModel.js";
  10. /**
  11. * A widget for displaying information or a description.
  12. *
  13. * @alias InfoBox
  14. * @constructor
  15. *
  16. * @param {Element|String} container The DOM element or ID that will contain the widget.
  17. *
  18. * @exception {DeveloperError} Element with id "container" does not exist in the document.
  19. */
  20. function InfoBox(container) {
  21. //>>includeStart('debug', pragmas.debug);
  22. Check.defined("container", container);
  23. //>>includeEnd('debug')
  24. container = getElement(container);
  25. const infoElement = document.createElement("div");
  26. infoElement.className = "cesium-infoBox";
  27. infoElement.setAttribute(
  28. "data-bind",
  29. '\
  30. css: { "cesium-infoBox-visible" : showInfo, "cesium-infoBox-bodyless" : _bodyless }'
  31. );
  32. container.appendChild(infoElement);
  33. const titleElement = document.createElement("div");
  34. titleElement.className = "cesium-infoBox-title";
  35. titleElement.setAttribute("data-bind", "text: titleText");
  36. infoElement.appendChild(titleElement);
  37. const cameraElement = document.createElement("button");
  38. cameraElement.type = "button";
  39. cameraElement.className = "cesium-button cesium-infoBox-camera";
  40. cameraElement.setAttribute(
  41. "data-bind",
  42. '\
  43. attr: { title: "Focus camera on object" },\
  44. click: function () { cameraClicked.raiseEvent(this); },\
  45. enable: enableCamera,\
  46. cesiumSvgPath: { path: cameraIconPath, width: 32, height: 32 }'
  47. );
  48. infoElement.appendChild(cameraElement);
  49. const closeElement = document.createElement("button");
  50. closeElement.type = "button";
  51. closeElement.className = "cesium-infoBox-close";
  52. closeElement.setAttribute(
  53. "data-bind",
  54. "\
  55. click: function () { closeClicked.raiseEvent(this); }"
  56. );
  57. closeElement.innerHTML = "×";
  58. infoElement.appendChild(closeElement);
  59. const frame = document.createElement("iframe");
  60. frame.className = "cesium-infoBox-iframe";
  61. frame.setAttribute("sandbox", "allow-same-origin allow-popups allow-forms"); //allow-pointer-lock allow-scripts allow-top-navigation
  62. frame.setAttribute(
  63. "data-bind",
  64. "style : { maxHeight : maxHeightOffset(40) }"
  65. );
  66. frame.setAttribute("allowfullscreen", true);
  67. infoElement.appendChild(frame);
  68. const viewModel = new InfoBoxViewModel();
  69. knockout.applyBindings(viewModel, infoElement);
  70. this._container = container;
  71. this._element = infoElement;
  72. this._frame = frame;
  73. this._viewModel = viewModel;
  74. this._descriptionSubscription = undefined;
  75. const that = this;
  76. //We can't actually add anything into the frame until the load event is fired
  77. frame.addEventListener("load", function () {
  78. const frameDocument = frame.contentDocument;
  79. //We inject default css into the content iframe,
  80. //end users can remove it or add their own via the exposed frame property.
  81. const cssLink = frameDocument.createElement("link");
  82. cssLink.href = buildModuleUrl("Widgets/InfoBox/InfoBoxDescription.css");
  83. cssLink.rel = "stylesheet";
  84. cssLink.type = "text/css";
  85. //div to use for description content.
  86. const frameContent = frameDocument.createElement("div");
  87. frameContent.className = "cesium-infoBox-description";
  88. frameDocument.head.appendChild(cssLink);
  89. frameDocument.body.appendChild(frameContent);
  90. //We manually subscribe to the description event rather than through a binding for two reasons.
  91. //1. It's an easy way to ensure order of operation so that we can adjust the height.
  92. //2. Knockout does not bind to elements inside of an iFrame, so we would have to apply a second binding
  93. // model anyway.
  94. that._descriptionSubscription = subscribeAndEvaluate(
  95. viewModel,
  96. "description",
  97. function (value) {
  98. // Set the frame to small height, force vertical scroll bar to appear, and text to wrap accordingly.
  99. frame.style.height = "5px";
  100. frameContent.innerHTML = value;
  101. //If the snippet is a single element, then use its background
  102. //color for the body of the InfoBox. This makes the padding match
  103. //the content and produces much nicer results.
  104. let background = null;
  105. const firstElementChild = frameContent.firstElementChild;
  106. if (
  107. firstElementChild !== null &&
  108. frameContent.childNodes.length === 1
  109. ) {
  110. const style = window.getComputedStyle(firstElementChild);
  111. if (style !== null) {
  112. const backgroundColor = style["background-color"];
  113. const color = Color.fromCssColorString(backgroundColor);
  114. if (defined(color) && color.alpha !== 0) {
  115. background = style["background-color"];
  116. }
  117. }
  118. }
  119. infoElement.style["background-color"] = background;
  120. // Measure and set the new custom height, based on text wrapped above.
  121. const height = frameContent.getBoundingClientRect().height;
  122. frame.style.height = `${height}px`;
  123. }
  124. );
  125. });
  126. //Chrome does not send the load event unless we explicitly set a src
  127. frame.setAttribute("src", "about:blank");
  128. }
  129. Object.defineProperties(InfoBox.prototype, {
  130. /**
  131. * Gets the parent container.
  132. * @memberof InfoBox.prototype
  133. *
  134. * @type {Element}
  135. */
  136. container: {
  137. get: function () {
  138. return this._container;
  139. },
  140. },
  141. /**
  142. * Gets the view model.
  143. * @memberof InfoBox.prototype
  144. *
  145. * @type {InfoBoxViewModel}
  146. */
  147. viewModel: {
  148. get: function () {
  149. return this._viewModel;
  150. },
  151. },
  152. /**
  153. * Gets the iframe used to display the description.
  154. * @memberof InfoBox.prototype
  155. *
  156. * @type {HTMLIFrameElement}
  157. */
  158. frame: {
  159. get: function () {
  160. return this._frame;
  161. },
  162. },
  163. });
  164. /**
  165. * @returns {Boolean} true if the object has been destroyed, false otherwise.
  166. */
  167. InfoBox.prototype.isDestroyed = function () {
  168. return false;
  169. };
  170. /**
  171. * Destroys the widget. Should be called if permanently
  172. * removing the widget from layout.
  173. */
  174. InfoBox.prototype.destroy = function () {
  175. const container = this._container;
  176. knockout.cleanNode(this._element);
  177. container.removeChild(this._element);
  178. if (defined(this._descriptionSubscription)) {
  179. this._descriptionSubscription.dispose();
  180. }
  181. return destroyObject(this);
  182. };
  183. export default InfoBox;