Popup.js 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063
  1. /* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the Clear BSD license.
  3. * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/BaseTypes/Class.js
  7. */
  8. /**
  9. * Class: OpenLayers.Popup
  10. * A popup is a small div that can opened and closed on the map.
  11. * Typically opened in response to clicking on a marker.
  12. * See <OpenLayers.Marker>. Popup's don't require their own
  13. * layer and are added the the map using the <OpenLayers.Map.addPopup>
  14. * method.
  15. *
  16. * Example:
  17. * (code)
  18. * popup = new OpenLayers.Popup("chicken",
  19. * new OpenLayers.LonLat(5,40),
  20. * new OpenLayers.Size(200,200),
  21. * "example popup",
  22. * true);
  23. *
  24. * map.addPopup(popup);
  25. * (end)
  26. */
  27. OpenLayers.Popup = OpenLayers.Class({
  28. /**
  29. * Property: events
  30. * {<OpenLayers.Events>} custom event manager
  31. */
  32. events: null,
  33. /** Property: id
  34. * {String} the unique identifier assigned to this popup.
  35. */
  36. id: "",
  37. /**
  38. * Property: lonlat
  39. * {<OpenLayers.LonLat>} the position of this popup on the map
  40. */
  41. lonlat: null,
  42. /**
  43. * Property: div
  44. * {DOMElement} the div that contains this popup.
  45. */
  46. div: null,
  47. /**
  48. * Property: contentSize
  49. * {<OpenLayers.Size>} the width and height of the content.
  50. */
  51. contentSize: null,
  52. /**
  53. * Property: size
  54. * {<OpenLayers.Size>} the width and height of the popup.
  55. */
  56. size: null,
  57. /**
  58. * Property: contentHTML
  59. * {String} An HTML string for this popup to display.
  60. */
  61. contentHTML: null,
  62. /**
  63. * Property: backgroundColor
  64. * {String} the background color used by the popup.
  65. */
  66. backgroundColor: "",
  67. /**
  68. * Property: opacity
  69. * {float} the opacity of this popup (between 0.0 and 1.0)
  70. */
  71. opacity: "",
  72. /**
  73. * Property: border
  74. * {String} the border size of the popup. (eg 2px)
  75. */
  76. border: "",
  77. /**
  78. * Property: contentDiv
  79. * {DOMElement} a reference to the element that holds the content of
  80. * the div.
  81. */
  82. contentDiv: null,
  83. /**
  84. * Property: groupDiv
  85. * {DOMElement} First and only child of 'div'. The group Div contains the
  86. * 'contentDiv' and the 'closeDiv'.
  87. */
  88. groupDiv: null,
  89. /**
  90. * Property: closeDiv
  91. * {DOMElement} the optional closer image
  92. */
  93. closeDiv: null,
  94. /**
  95. * APIProperty: autoSize
  96. * {Boolean} Resize the popup to auto-fit the contents.
  97. * Default is false.
  98. */
  99. autoSize: false,
  100. /**
  101. * APIProperty: minSize
  102. * {<OpenLayers.Size>} Minimum size allowed for the popup's contents.
  103. */
  104. minSize: null,
  105. /**
  106. * APIProperty: maxSize
  107. * {<OpenLayers.Size>} Maximum size allowed for the popup's contents.
  108. */
  109. maxSize: null,
  110. /**
  111. * Property: displayClass
  112. * {String} The CSS class of the popup.
  113. */
  114. displayClass: "olPopup",
  115. /**
  116. * Property: contentDisplayClass
  117. * {String} The CSS class of the popup content div.
  118. */
  119. contentDisplayClass: "olPopupContent",
  120. /**
  121. * Property: padding
  122. * {int or <OpenLayers.Bounds>} An extra opportunity to specify internal
  123. * padding of the content div inside the popup. This was originally
  124. * confused with the css padding as specified in style.css's
  125. * 'olPopupContent' class. We would like to get rid of this altogether,
  126. * except that it does come in handy for the framed and anchoredbubble
  127. * popups, who need to maintain yet another barrier between their
  128. * content and the outer border of the popup itself.
  129. *
  130. * Note that in order to not break API, we must continue to support
  131. * this property being set as an integer. Really, though, we'd like to
  132. * have this specified as a Bounds object so that user can specify
  133. * distinct left, top, right, bottom paddings. With the 3.0 release
  134. * we can make this only a bounds.
  135. */
  136. padding: 0,
  137. /**
  138. * Property: disableFirefoxOverflowHack
  139. * {Boolean} The hack for overflow in Firefox causes all elements
  140. * to be re-drawn, which causes Flash elements to be
  141. * re-initialized, which is troublesome.
  142. * With this property the hack can be disabled.
  143. */
  144. disableFirefoxOverflowHack: false,
  145. /**
  146. * Method: fixPadding
  147. * To be removed in 3.0, this function merely helps us to deal with the
  148. * case where the user may have set an integer value for padding,
  149. * instead of an <OpenLayers.Bounds> object.
  150. */
  151. fixPadding: function() {
  152. if (typeof this.padding == "number") {
  153. this.padding = new OpenLayers.Bounds(
  154. this.padding, this.padding, this.padding, this.padding
  155. );
  156. }
  157. },
  158. /**
  159. * APIProperty: panMapIfOutOfView
  160. * {Boolean} When drawn, pan map such that the entire popup is visible in
  161. * the current viewport (if necessary).
  162. * Default is false.
  163. */
  164. panMapIfOutOfView: false,
  165. /**
  166. * APIProperty: keepInMap
  167. * {Boolean} If panMapIfOutOfView is false, and this property is true,
  168. * contrain the popup such that it always fits in the available map
  169. * space. By default, this is not set on the base class. If you are
  170. * creating popups that are near map edges and not allowing pannning,
  171. * and especially if you have a popup which has a
  172. * fixedRelativePosition, setting this to false may be a smart thing to
  173. * do. Subclasses may want to override this setting.
  174. *
  175. * Default is false.
  176. */
  177. keepInMap: false,
  178. /**
  179. * APIProperty: closeOnMove
  180. * {Boolean} When map pans, close the popup.
  181. * Default is false.
  182. */
  183. closeOnMove: false,
  184. /**
  185. * Property: map
  186. * {<OpenLayers.Map>} this gets set in Map.js when the popup is added to the map
  187. */
  188. map: null,
  189. /**
  190. * Constructor: OpenLayers.Popup
  191. * Create a popup.
  192. *
  193. * Parameters:
  194. * id - {String} a unqiue identifier for this popup. If null is passed
  195. * an identifier will be automatically generated.
  196. * lonlat - {<OpenLayers.LonLat>} The position on the map the popup will
  197. * be shown.
  198. * contentSize - {<OpenLayers.Size>} The size of the content.
  199. * contentHTML - {String} An HTML string to display inside the
  200. * popup.
  201. * closeBox - {Boolean} Whether to display a close box inside
  202. * the popup.
  203. * closeBoxCallback - {Function} Function to be called on closeBox click.
  204. */
  205. initialize:function(id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback) {
  206. if (id == null) {
  207. id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
  208. }
  209. this.id = id;
  210. this.lonlat = lonlat;
  211. this.contentSize = (contentSize != null) ? contentSize
  212. : new OpenLayers.Size(
  213. OpenLayers.Popup.WIDTH,
  214. OpenLayers.Popup.HEIGHT);
  215. if (contentHTML != null) {
  216. this.contentHTML = contentHTML;
  217. }
  218. this.backgroundColor = OpenLayers.Popup.COLOR;
  219. this.opacity = OpenLayers.Popup.OPACITY;
  220. this.border = OpenLayers.Popup.BORDER;
  221. this.div = OpenLayers.Util.createDiv(this.id, null, null,
  222. null, null, null, "hidden");
  223. this.div.className = this.displayClass;
  224. var groupDivId = this.id + "_GroupDiv";
  225. this.groupDiv = OpenLayers.Util.createDiv(groupDivId, null, null,
  226. null, "relative", null,
  227. "hidden");
  228. var id = this.div.id + "_contentDiv";
  229. this.contentDiv = OpenLayers.Util.createDiv(id, null, this.contentSize.clone(),
  230. null, "relative");
  231. this.contentDiv.className = this.contentDisplayClass;
  232. this.groupDiv.appendChild(this.contentDiv);
  233. this.div.appendChild(this.groupDiv);
  234. if (closeBox) {
  235. this.addCloseBox(closeBoxCallback);
  236. }
  237. this.registerEvents();
  238. },
  239. /**
  240. * Method: destroy
  241. * nullify references to prevent circular references and memory leaks
  242. */
  243. destroy: function() {
  244. this.id = null;
  245. this.lonlat = null;
  246. this.size = null;
  247. this.contentHTML = null;
  248. this.backgroundColor = null;
  249. this.opacity = null;
  250. this.border = null;
  251. if (this.closeOnMove && this.map) {
  252. this.map.events.unregister("movestart", this, this.hide);
  253. }
  254. this.events.destroy();
  255. this.events = null;
  256. if (this.closeDiv) {
  257. OpenLayers.Event.stopObservingElement(this.closeDiv);
  258. this.groupDiv.removeChild(this.closeDiv);
  259. }
  260. this.closeDiv = null;
  261. this.div.removeChild(this.groupDiv);
  262. this.groupDiv = null;
  263. if (this.map != null) {
  264. this.map.removePopup(this);
  265. }
  266. this.map = null;
  267. this.div = null;
  268. this.autoSize = null;
  269. this.minSize = null;
  270. this.maxSize = null;
  271. this.padding = null;
  272. this.panMapIfOutOfView = null;
  273. },
  274. /**
  275. * Method: draw
  276. * Constructs the elements that make up the popup.
  277. *
  278. * Parameters:
  279. * px - {<OpenLayers.Pixel>} the position the popup in pixels.
  280. *
  281. * Returns:
  282. * {DOMElement} Reference to a div that contains the drawn popup
  283. */
  284. draw: function(px) {
  285. if (px == null) {
  286. if ((this.lonlat != null) && (this.map != null)) {
  287. px = this.map.getLayerPxFromLonLat(this.lonlat);
  288. }
  289. }
  290. // this assumes that this.map already exists, which is okay because
  291. // this.draw is only called once the popup has been added to the map.
  292. if (this.closeOnMove) {
  293. this.map.events.register("movestart", this, this.hide);
  294. }
  295. //listen to movestart, moveend to disable overflow (FF bug)
  296. if (!this.disableFirefoxOverflowHack && OpenLayers.BROWSER_NAME == 'firefox') {
  297. this.map.events.register("movestart", this, function() {
  298. var style = document.defaultView.getComputedStyle(
  299. this.contentDiv, null
  300. );
  301. var currentOverflow = style.getPropertyValue("overflow");
  302. if (currentOverflow != "hidden") {
  303. this.contentDiv._oldOverflow = currentOverflow;
  304. this.contentDiv.style.overflow = "hidden";
  305. }
  306. });
  307. this.map.events.register("moveend", this, function() {
  308. var oldOverflow = this.contentDiv._oldOverflow;
  309. if (oldOverflow) {
  310. this.contentDiv.style.overflow = oldOverflow;
  311. this.contentDiv._oldOverflow = null;
  312. }
  313. });
  314. }
  315. this.moveTo(px);
  316. if (!this.autoSize && !this.size) {
  317. this.setSize(this.contentSize);
  318. }
  319. this.setBackgroundColor();
  320. this.setOpacity();
  321. this.setBorder();
  322. this.setContentHTML();
  323. if (this.panMapIfOutOfView) {
  324. this.panIntoView();
  325. }
  326. return this.div;
  327. },
  328. /**
  329. * Method: updatePosition
  330. * if the popup has a lonlat and its map members set,
  331. * then have it move itself to its proper position
  332. */
  333. updatePosition: function() {
  334. if ((this.lonlat) && (this.map)) {
  335. var px = this.map.getLayerPxFromLonLat(this.lonlat);
  336. if (px) {
  337. this.moveTo(px);
  338. }
  339. }
  340. },
  341. /**
  342. * Method: moveTo
  343. *
  344. * Parameters:
  345. * px - {<OpenLayers.Pixel>} the top and left position of the popup div.
  346. */
  347. moveTo: function(px) {
  348. if ((px != null) && (this.div != null)) {
  349. this.div.style.left = px.x + "px";
  350. this.div.style.top = px.y + "px";
  351. }
  352. },
  353. /**
  354. * Method: visible
  355. *
  356. * Returns:
  357. * {Boolean} Boolean indicating whether or not the popup is visible
  358. */
  359. visible: function() {
  360. return OpenLayers.Element.visible(this.div);
  361. },
  362. /**
  363. * Method: toggle
  364. * Toggles visibility of the popup.
  365. */
  366. toggle: function() {
  367. if (this.visible()) {
  368. this.hide();
  369. } else {
  370. this.show();
  371. }
  372. },
  373. /**
  374. * Method: show
  375. * Makes the popup visible.
  376. */
  377. show: function() {
  378. this.div.style.display = '';
  379. if (this.panMapIfOutOfView) {
  380. this.panIntoView();
  381. }
  382. },
  383. /**
  384. * Method: hide
  385. * Makes the popup invisible.
  386. */
  387. hide: function() {
  388. this.div.style.display = 'none';
  389. },
  390. /**
  391. * Method: setSize
  392. * Used to adjust the size of the popup.
  393. *
  394. * Parameters:
  395. * contentSize - {<OpenLayers.Size>} the new size for the popup's
  396. * contents div (in pixels).
  397. */
  398. setSize:function(contentSize) {
  399. this.size = contentSize.clone();
  400. // if our contentDiv has a css 'padding' set on it by a stylesheet, we
  401. // must add that to the desired "size".
  402. var contentDivPadding = this.getContentDivPadding();
  403. var wPadding = contentDivPadding.left + contentDivPadding.right;
  404. var hPadding = contentDivPadding.top + contentDivPadding.bottom;
  405. // take into account the popup's 'padding' property
  406. this.fixPadding();
  407. wPadding += this.padding.left + this.padding.right;
  408. hPadding += this.padding.top + this.padding.bottom;
  409. // make extra space for the close div
  410. if (this.closeDiv) {
  411. var closeDivWidth = parseInt(this.closeDiv.style.width);
  412. wPadding += closeDivWidth + contentDivPadding.right;
  413. }
  414. //increase size of the main popup div to take into account the
  415. // users's desired padding and close div.
  416. this.size.w += wPadding;
  417. this.size.h += hPadding;
  418. //now if our browser is IE, we need to actually make the contents
  419. // div itself bigger to take its own padding into effect. this makes
  420. // me want to shoot someone, but so it goes.
  421. if (OpenLayers.BROWSER_NAME == "msie") {
  422. this.contentSize.w +=
  423. contentDivPadding.left + contentDivPadding.right;
  424. this.contentSize.h +=
  425. contentDivPadding.bottom + contentDivPadding.top;
  426. }
  427. if (this.div != null) {
  428. this.div.style.width = this.size.w + "px";
  429. this.div.style.height = this.size.h + "px";
  430. }
  431. if (this.contentDiv != null){
  432. this.contentDiv.style.width = contentSize.w + "px";
  433. this.contentDiv.style.height = contentSize.h + "px";
  434. }
  435. },
  436. /**
  437. * APIMethod: updateSize
  438. * Auto size the popup so that it precisely fits its contents (as
  439. * determined by this.contentDiv.innerHTML). Popup size will, of
  440. * course, be limited by the available space on the current map
  441. */
  442. updateSize: function() {
  443. // determine actual render dimensions of the contents by putting its
  444. // contents into a fake contentDiv (for the CSS) and then measuring it
  445. var preparedHTML = "<div class='" + this.contentDisplayClass+ "'>" +
  446. this.contentDiv.innerHTML +
  447. "</div>";
  448. var containerElement = (this.map) ? this.map.layerContainerDiv
  449. : document.body;
  450. var realSize = OpenLayers.Util.getRenderedDimensions(
  451. preparedHTML, null, {
  452. displayClass: this.displayClass,
  453. containerElement: containerElement
  454. }
  455. );
  456. // is the "real" size of the div is safe to display in our map?
  457. var safeSize = this.getSafeContentSize(realSize);
  458. var newSize = null;
  459. if (safeSize.equals(realSize)) {
  460. //real size of content is small enough to fit on the map,
  461. // so we use real size.
  462. newSize = realSize;
  463. } else {
  464. //make a new OL.Size object with the clipped dimensions
  465. // set or null if not clipped.
  466. var fixedSize = new OpenLayers.Size();
  467. fixedSize.w = (safeSize.w < realSize.w) ? safeSize.w : null;
  468. fixedSize.h = (safeSize.h < realSize.h) ? safeSize.h : null;
  469. if (fixedSize.w && fixedSize.h) {
  470. //content is too big in both directions, so we will use
  471. // max popup size (safeSize), knowing well that it will
  472. // overflow both ways.
  473. newSize = safeSize;
  474. } else {
  475. //content is clipped in only one direction, so we need to
  476. // run getRenderedDimensions() again with a fixed dimension
  477. var clippedSize = OpenLayers.Util.getRenderedDimensions(
  478. preparedHTML, fixedSize, {
  479. displayClass: this.contentDisplayClass,
  480. containerElement: containerElement
  481. }
  482. );
  483. //if the clipped size is still the same as the safeSize,
  484. // that means that our content must be fixed in the
  485. // offending direction. If overflow is 'auto', this means
  486. // we are going to have a scrollbar for sure, so we must
  487. // adjust for that.
  488. //
  489. var currentOverflow = OpenLayers.Element.getStyle(
  490. this.contentDiv, "overflow"
  491. );
  492. if ( (currentOverflow != "hidden") &&
  493. (clippedSize.equals(safeSize)) ) {
  494. var scrollBar = OpenLayers.Util.getScrollbarWidth();
  495. if (fixedSize.w) {
  496. clippedSize.h += scrollBar;
  497. } else {
  498. clippedSize.w += scrollBar;
  499. }
  500. }
  501. newSize = this.getSafeContentSize(clippedSize);
  502. }
  503. }
  504. this.setSize(newSize);
  505. },
  506. /**
  507. * Method: setBackgroundColor
  508. * Sets the background color of the popup.
  509. *
  510. * Parameters:
  511. * color - {String} the background color. eg "#FFBBBB"
  512. */
  513. setBackgroundColor:function(color) {
  514. if (color != undefined) {
  515. this.backgroundColor = color;
  516. }
  517. if (this.div != null) {
  518. this.div.style.backgroundColor = this.backgroundColor;
  519. }
  520. },
  521. /**
  522. * Method: setOpacity
  523. * Sets the opacity of the popup.
  524. *
  525. * Parameters:
  526. * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).
  527. */
  528. setOpacity:function(opacity) {
  529. if (opacity != undefined) {
  530. this.opacity = opacity;
  531. }
  532. if (this.div != null) {
  533. // for Mozilla and Safari
  534. this.div.style.opacity = this.opacity;
  535. // for IE
  536. this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
  537. }
  538. },
  539. /**
  540. * Method: setBorder
  541. * Sets the border style of the popup.
  542. *
  543. * Parameters:
  544. * border - {String} The border style value. eg 2px
  545. */
  546. setBorder:function(border) {
  547. if (border != undefined) {
  548. this.border = border;
  549. }
  550. if (this.div != null) {
  551. this.div.style.border = this.border;
  552. }
  553. },
  554. /**
  555. * Method: setContentHTML
  556. * Allows the user to set the HTML content of the popup.
  557. *
  558. * Parameters:
  559. * contentHTML - {String} HTML for the div.
  560. */
  561. setContentHTML:function(contentHTML) {
  562. if (contentHTML != null) {
  563. this.contentHTML = contentHTML;
  564. }
  565. if ((this.contentDiv != null) &&
  566. (this.contentHTML != null) &&
  567. (this.contentHTML != this.contentDiv.innerHTML)) {
  568. this.contentDiv.innerHTML = this.contentHTML;
  569. if (this.autoSize) {
  570. //if popup has images, listen for when they finish
  571. // loading and resize accordingly
  572. this.registerImageListeners();
  573. //auto size the popup to its current contents
  574. this.updateSize();
  575. }
  576. }
  577. },
  578. /**
  579. * Method: registerImageListeners
  580. * Called when an image contained by the popup loaded. this function
  581. * updates the popup size, then unregisters the image load listener.
  582. */
  583. registerImageListeners: function() {
  584. // As the images load, this function will call updateSize() to
  585. // resize the popup to fit the content div (which presumably is now
  586. // bigger than when the image was not loaded).
  587. //
  588. // If the 'panMapIfOutOfView' property is set, we will pan the newly
  589. // resized popup back into view.
  590. //
  591. // Note that this function, when called, will have 'popup' and
  592. // 'img' properties in the context.
  593. //
  594. var onImgLoad = function() {
  595. this.popup.updateSize();
  596. if ( this.popup.visible() && this.popup.panMapIfOutOfView ) {
  597. this.popup.panIntoView();
  598. }
  599. OpenLayers.Event.stopObserving(
  600. this.img, "load", this.img._onImageLoad
  601. );
  602. };
  603. //cycle through the images and if their size is 0x0, that means that
  604. // they haven't been loaded yet, so we attach the listener, which
  605. // will fire when the images finish loading and will resize the
  606. // popup accordingly to its new size.
  607. var images = this.contentDiv.getElementsByTagName("img");
  608. for (var i = 0, len = images.length; i < len; i++) {
  609. var img = images[i];
  610. if (img.width == 0 || img.height == 0) {
  611. var context = {
  612. 'popup': this,
  613. 'img': img
  614. };
  615. //expando this function to the image itself before registering
  616. // it. This way we can easily and properly unregister it.
  617. img._onImgLoad = OpenLayers.Function.bind(onImgLoad, context);
  618. OpenLayers.Event.observe(img, 'load', img._onImgLoad);
  619. }
  620. }
  621. },
  622. /**
  623. * APIMethod: getSafeContentSize
  624. *
  625. * Parameters:
  626. * size - {<OpenLayers.Size>} Desired size to make the popup.
  627. *
  628. * Returns:
  629. * {<OpenLayers.Size>} A size to make the popup which is neither smaller
  630. * than the specified minimum size, nor bigger than the maximum
  631. * size (which is calculated relative to the size of the viewport).
  632. */
  633. getSafeContentSize: function(size) {
  634. var safeContentSize = size.clone();
  635. // if our contentDiv has a css 'padding' set on it by a stylesheet, we
  636. // must add that to the desired "size".
  637. var contentDivPadding = this.getContentDivPadding();
  638. var wPadding = contentDivPadding.left + contentDivPadding.right;
  639. var hPadding = contentDivPadding.top + contentDivPadding.bottom;
  640. // take into account the popup's 'padding' property
  641. this.fixPadding();
  642. wPadding += this.padding.left + this.padding.right;
  643. hPadding += this.padding.top + this.padding.bottom;
  644. if (this.closeDiv) {
  645. var closeDivWidth = parseInt(this.closeDiv.style.width);
  646. wPadding += closeDivWidth + contentDivPadding.right;
  647. }
  648. // prevent the popup from being smaller than a specified minimal size
  649. if (this.minSize) {
  650. safeContentSize.w = Math.max(safeContentSize.w,
  651. (this.minSize.w - wPadding));
  652. safeContentSize.h = Math.max(safeContentSize.h,
  653. (this.minSize.h - hPadding));
  654. }
  655. // prevent the popup from being bigger than a specified maximum size
  656. if (this.maxSize) {
  657. safeContentSize.w = Math.min(safeContentSize.w,
  658. (this.maxSize.w - wPadding));
  659. safeContentSize.h = Math.min(safeContentSize.h,
  660. (this.maxSize.h - hPadding));
  661. }
  662. //make sure the desired size to set doesn't result in a popup that
  663. // is bigger than the map's viewport.
  664. //
  665. if (this.map && this.map.size) {
  666. var extraX = 0, extraY = 0;
  667. if (this.keepInMap && !this.panMapIfOutOfView) {
  668. var px = this.map.getPixelFromLonLat(this.lonlat);
  669. switch (this.relativePosition) {
  670. case "tr":
  671. extraX = px.x;
  672. extraY = this.map.size.h - px.y;
  673. break;
  674. case "tl":
  675. extraX = this.map.size.w - px.x;
  676. extraY = this.map.size.h - px.y;
  677. break;
  678. case "bl":
  679. extraX = this.map.size.w - px.x;
  680. extraY = px.y;
  681. break;
  682. case "br":
  683. extraX = px.x;
  684. extraY = px.y;
  685. break;
  686. default:
  687. extraX = px.x;
  688. extraY = this.map.size.h - px.y;
  689. break;
  690. }
  691. }
  692. var maxY = this.map.size.h -
  693. this.map.paddingForPopups.top -
  694. this.map.paddingForPopups.bottom -
  695. hPadding - extraY;
  696. var maxX = this.map.size.w -
  697. this.map.paddingForPopups.left -
  698. this.map.paddingForPopups.right -
  699. wPadding - extraX;
  700. safeContentSize.w = Math.min(safeContentSize.w, maxX);
  701. safeContentSize.h = Math.min(safeContentSize.h, maxY);
  702. }
  703. return safeContentSize;
  704. },
  705. /**
  706. * Method: getContentDivPadding
  707. * Glorious, oh glorious hack in order to determine the css 'padding' of
  708. * the contentDiv. IE/Opera return null here unless we actually add the
  709. * popup's main 'div' element (which contains contentDiv) to the DOM.
  710. * So we make it invisible and then add it to the document temporarily.
  711. *
  712. * Once we've taken the padding readings we need, we then remove it
  713. * from the DOM (it will actually get added to the DOM in
  714. * Map.js's addPopup)
  715. *
  716. * Returns:
  717. * {<OpenLayers.Bounds>}
  718. */
  719. getContentDivPadding: function() {
  720. //use cached value if we have it
  721. var contentDivPadding = this._contentDivPadding;
  722. if (!contentDivPadding) {
  723. if (this.div.parentNode == null) {
  724. //make the div invisible and add it to the page
  725. this.div.style.display = "none";
  726. document.body.appendChild(this.div);
  727. }
  728. //read the padding settings from css, put them in an OL.Bounds
  729. contentDivPadding = new OpenLayers.Bounds(
  730. OpenLayers.Element.getStyle(this.contentDiv, "padding-left"),
  731. OpenLayers.Element.getStyle(this.contentDiv, "padding-bottom"),
  732. OpenLayers.Element.getStyle(this.contentDiv, "padding-right"),
  733. OpenLayers.Element.getStyle(this.contentDiv, "padding-top")
  734. );
  735. //cache the value
  736. this._contentDivPadding = contentDivPadding;
  737. if (this.div.parentNode == document.body) {
  738. //remove the div from the page and make it visible again
  739. document.body.removeChild(this.div);
  740. this.div.style.display = "";
  741. }
  742. }
  743. return contentDivPadding;
  744. },
  745. /**
  746. * Method: addCloseBox
  747. *
  748. * Parameters:
  749. * callback - {Function} The callback to be called when the close button
  750. * is clicked.
  751. */
  752. addCloseBox: function(callback) {
  753. this.closeDiv = OpenLayers.Util.createDiv(
  754. this.id + "_close", null, new OpenLayers.Size(17, 17)
  755. );
  756. this.closeDiv.className = "olPopupCloseBox";
  757. // use the content div's css padding to determine if we should
  758. // padd the close div
  759. var contentDivPadding = this.getContentDivPadding();
  760. this.closeDiv.style.right = contentDivPadding.right + "px";
  761. this.closeDiv.style.top = contentDivPadding.top + "px";
  762. this.groupDiv.appendChild(this.closeDiv);
  763. var closePopup = callback || function(e) {
  764. this.hide();
  765. OpenLayers.Event.stop(e);
  766. };
  767. OpenLayers.Event.observe(this.closeDiv, "touchend",
  768. OpenLayers.Function.bindAsEventListener(closePopup, this));
  769. OpenLayers.Event.observe(this.closeDiv, "click",
  770. OpenLayers.Function.bindAsEventListener(closePopup, this));
  771. },
  772. /**
  773. * Method: panIntoView
  774. * Pans the map such that the popup is totaly viewable (if necessary)
  775. */
  776. panIntoView: function() {
  777. var mapSize = this.map.getSize();
  778. //start with the top left corner of the popup, in px,
  779. // relative to the viewport
  780. var origTL = this.map.getViewPortPxFromLayerPx( new OpenLayers.Pixel(
  781. parseInt(this.div.style.left),
  782. parseInt(this.div.style.top)
  783. ));
  784. var newTL = origTL.clone();
  785. //new left (compare to margins, using this.size to calculate right)
  786. if (origTL.x < this.map.paddingForPopups.left) {
  787. newTL.x = this.map.paddingForPopups.left;
  788. } else
  789. if ( (origTL.x + this.size.w) > (mapSize.w - this.map.paddingForPopups.right)) {
  790. newTL.x = mapSize.w - this.map.paddingForPopups.right - this.size.w;
  791. }
  792. //new top (compare to margins, using this.size to calculate bottom)
  793. if (origTL.y < this.map.paddingForPopups.top) {
  794. newTL.y = this.map.paddingForPopups.top;
  795. } else
  796. if ( (origTL.y + this.size.h) > (mapSize.h - this.map.paddingForPopups.bottom)) {
  797. newTL.y = mapSize.h - this.map.paddingForPopups.bottom - this.size.h;
  798. }
  799. var dx = origTL.x - newTL.x;
  800. var dy = origTL.y - newTL.y;
  801. this.map.pan(dx, dy);
  802. },
  803. /**
  804. * Method: registerEvents
  805. * Registers events on the popup.
  806. *
  807. * Do this in a separate function so that subclasses can
  808. * choose to override it if they wish to deal differently
  809. * with mouse events
  810. *
  811. * Note in the following handler functions that some special
  812. * care is needed to deal correctly with mousing and popups.
  813. *
  814. * Because the user might select the zoom-rectangle option and
  815. * then drag it over a popup, we need a safe way to allow the
  816. * mousemove and mouseup events to pass through the popup when
  817. * they are initiated from outside. The same procedure is needed for
  818. * touchmove and touchend events.
  819. *
  820. * Otherwise, we want to essentially kill the event propagation
  821. * for all other events, though we have to do so carefully,
  822. * without disabling basic html functionality, like clicking on
  823. * hyperlinks or drag-selecting text.
  824. */
  825. registerEvents:function() {
  826. this.events = new OpenLayers.Events(this, this.div, null, true);
  827. function onTouchstart(evt) {
  828. OpenLayers.Event.stop(evt, true);
  829. }
  830. this.events.on({
  831. "mousedown": this.onmousedown,
  832. "mousemove": this.onmousemove,
  833. "mouseup": this.onmouseup,
  834. "click": this.onclick,
  835. "mouseout": this.onmouseout,
  836. "dblclick": this.ondblclick,
  837. "touchstart": onTouchstart,
  838. scope: this
  839. });
  840. },
  841. /**
  842. * Method: onmousedown
  843. * When mouse goes down within the popup, make a note of
  844. * it locally, and then do not propagate the mousedown
  845. * (but do so safely so that user can select text inside)
  846. *
  847. * Parameters:
  848. * evt - {Event}
  849. */
  850. onmousedown: function (evt) {
  851. this.mousedown = true;
  852. OpenLayers.Event.stop(evt, true);
  853. },
  854. /**
  855. * Method: onmousemove
  856. * If the drag was started within the popup, then
  857. * do not propagate the mousemove (but do so safely
  858. * so that user can select text inside)
  859. *
  860. * Parameters:
  861. * evt - {Event}
  862. */
  863. onmousemove: function (evt) {
  864. if (this.mousedown) {
  865. OpenLayers.Event.stop(evt, true);
  866. }
  867. },
  868. /**
  869. * Method: onmouseup
  870. * When mouse comes up within the popup, after going down
  871. * in it, reset the flag, and then (once again) do not
  872. * propagate the event, but do so safely so that user can
  873. * select text inside
  874. *
  875. * Parameters:
  876. * evt - {Event}
  877. */
  878. onmouseup: function (evt) {
  879. if (this.mousedown) {
  880. this.mousedown = false;
  881. OpenLayers.Event.stop(evt, true);
  882. }
  883. },
  884. /**
  885. * Method: onclick
  886. * Ignore clicks, but allowing default browser handling
  887. *
  888. * Parameters:
  889. * evt - {Event}
  890. */
  891. onclick: function (evt) {
  892. OpenLayers.Event.stop(evt, true);
  893. },
  894. /**
  895. * Method: onmouseout
  896. * When mouse goes out of the popup set the flag to false so that
  897. * if they let go and then drag back in, we won't be confused.
  898. *
  899. * Parameters:
  900. * evt - {Event}
  901. */
  902. onmouseout: function (evt) {
  903. this.mousedown = false;
  904. },
  905. /**
  906. * Method: ondblclick
  907. * Ignore double-clicks, but allowing default browser handling
  908. *
  909. * Parameters:
  910. * evt - {Event}
  911. */
  912. ondblclick: function (evt) {
  913. OpenLayers.Event.stop(evt, true);
  914. },
  915. CLASS_NAME: "OpenLayers.Popup"
  916. });
  917. OpenLayers.Popup.WIDTH = 200;
  918. OpenLayers.Popup.HEIGHT = 200;
  919. OpenLayers.Popup.COLOR = "white";
  920. OpenLayers.Popup.OPACITY = 1;
  921. OpenLayers.Popup.BORDER = "0px";