InfoBox.js 7.4 KB

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