InfoBox.js 6.3 KB

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