Popup.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065
  1. /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the 2-clause BSD license.
  3. * See license.txt in the OpenLayers distribution or repository 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.div : document.body;
  449. var realSize = OpenLayers.Util.getRenderedDimensions(
  450. preparedHTML, null, {
  451. displayClass: this.displayClass,
  452. containerElement: containerElement
  453. }
  454. );
  455. // is the "real" size of the div is safe to display in our map?
  456. var safeSize = this.getSafeContentSize(realSize);
  457. var newSize = null;
  458. if (safeSize.equals(realSize)) {
  459. //real size of content is small enough to fit on the map,
  460. // so we use real size.
  461. newSize = realSize;
  462. } else {
  463. // make a new 'size' object with the clipped dimensions
  464. // set or null if not clipped.
  465. var fixedSize = {
  466. w: (safeSize.w < realSize.w) ? safeSize.w : null,
  467. h: (safeSize.h < realSize.h) ? safeSize.h : null
  468. };
  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. if (this.popup.id === null) { // this.popup has been destroyed!
  596. return;
  597. }
  598. this.popup.updateSize();
  599. if ( this.popup.visible() && this.popup.panMapIfOutOfView ) {
  600. this.popup.panIntoView();
  601. }
  602. OpenLayers.Event.stopObserving(
  603. this.img, "load", this.img._onImgLoad
  604. );
  605. };
  606. //cycle through the images and if their size is 0x0, that means that
  607. // they haven't been loaded yet, so we attach the listener, which
  608. // will fire when the images finish loading and will resize the
  609. // popup accordingly to its new size.
  610. var images = this.contentDiv.getElementsByTagName("img");
  611. for (var i = 0, len = images.length; i < len; i++) {
  612. var img = images[i];
  613. if (img.width == 0 || img.height == 0) {
  614. var context = {
  615. 'popup': this,
  616. 'img': img
  617. };
  618. //expando this function to the image itself before registering
  619. // it. This way we can easily and properly unregister it.
  620. img._onImgLoad = OpenLayers.Function.bind(onImgLoad, context);
  621. OpenLayers.Event.observe(img, 'load', img._onImgLoad);
  622. }
  623. }
  624. },
  625. /**
  626. * APIMethod: getSafeContentSize
  627. *
  628. * Parameters:
  629. * size - {<OpenLayers.Size>} Desired size to make the popup.
  630. *
  631. * Returns:
  632. * {<OpenLayers.Size>} A size to make the popup which is neither smaller
  633. * than the specified minimum size, nor bigger than the maximum
  634. * size (which is calculated relative to the size of the viewport).
  635. */
  636. getSafeContentSize: function(size) {
  637. var safeContentSize = size.clone();
  638. // if our contentDiv has a css 'padding' set on it by a stylesheet, we
  639. // must add that to the desired "size".
  640. var contentDivPadding = this.getContentDivPadding();
  641. var wPadding = contentDivPadding.left + contentDivPadding.right;
  642. var hPadding = contentDivPadding.top + contentDivPadding.bottom;
  643. // take into account the popup's 'padding' property
  644. this.fixPadding();
  645. wPadding += this.padding.left + this.padding.right;
  646. hPadding += this.padding.top + this.padding.bottom;
  647. if (this.closeDiv) {
  648. var closeDivWidth = parseInt(this.closeDiv.style.width);
  649. wPadding += closeDivWidth + contentDivPadding.right;
  650. }
  651. // prevent the popup from being smaller than a specified minimal size
  652. if (this.minSize) {
  653. safeContentSize.w = Math.max(safeContentSize.w,
  654. (this.minSize.w - wPadding));
  655. safeContentSize.h = Math.max(safeContentSize.h,
  656. (this.minSize.h - hPadding));
  657. }
  658. // prevent the popup from being bigger than a specified maximum size
  659. if (this.maxSize) {
  660. safeContentSize.w = Math.min(safeContentSize.w,
  661. (this.maxSize.w - wPadding));
  662. safeContentSize.h = Math.min(safeContentSize.h,
  663. (this.maxSize.h - hPadding));
  664. }
  665. //make sure the desired size to set doesn't result in a popup that
  666. // is bigger than the map's viewport.
  667. //
  668. if (this.map && this.map.size) {
  669. var extraX = 0, extraY = 0;
  670. if (this.keepInMap && !this.panMapIfOutOfView) {
  671. var px = this.map.getPixelFromLonLat(this.lonlat);
  672. switch (this.relativePosition) {
  673. case "tr":
  674. extraX = px.x;
  675. extraY = this.map.size.h - px.y;
  676. break;
  677. case "tl":
  678. extraX = this.map.size.w - px.x;
  679. extraY = this.map.size.h - px.y;
  680. break;
  681. case "bl":
  682. extraX = this.map.size.w - px.x;
  683. extraY = px.y;
  684. break;
  685. case "br":
  686. extraX = px.x;
  687. extraY = px.y;
  688. break;
  689. default:
  690. extraX = px.x;
  691. extraY = this.map.size.h - px.y;
  692. break;
  693. }
  694. }
  695. var maxY = this.map.size.h -
  696. this.map.paddingForPopups.top -
  697. this.map.paddingForPopups.bottom -
  698. hPadding - extraY;
  699. var maxX = this.map.size.w -
  700. this.map.paddingForPopups.left -
  701. this.map.paddingForPopups.right -
  702. wPadding - extraX;
  703. safeContentSize.w = Math.min(safeContentSize.w, maxX);
  704. safeContentSize.h = Math.min(safeContentSize.h, maxY);
  705. }
  706. return safeContentSize;
  707. },
  708. /**
  709. * Method: getContentDivPadding
  710. * Glorious, oh glorious hack in order to determine the css 'padding' of
  711. * the contentDiv. IE/Opera return null here unless we actually add the
  712. * popup's main 'div' element (which contains contentDiv) to the DOM.
  713. * So we make it invisible and then add it to the document temporarily.
  714. *
  715. * Once we've taken the padding readings we need, we then remove it
  716. * from the DOM (it will actually get added to the DOM in
  717. * Map.js's addPopup)
  718. *
  719. * Returns:
  720. * {<OpenLayers.Bounds>}
  721. */
  722. getContentDivPadding: function() {
  723. //use cached value if we have it
  724. var contentDivPadding = this._contentDivPadding;
  725. if (!contentDivPadding) {
  726. if (this.div.parentNode == null) {
  727. //make the div invisible and add it to the page
  728. this.div.style.display = "none";
  729. document.body.appendChild(this.div);
  730. }
  731. //read the padding settings from css, put them in an OL.Bounds
  732. contentDivPadding = new OpenLayers.Bounds(
  733. OpenLayers.Element.getStyle(this.contentDiv, "padding-left"),
  734. OpenLayers.Element.getStyle(this.contentDiv, "padding-bottom"),
  735. OpenLayers.Element.getStyle(this.contentDiv, "padding-right"),
  736. OpenLayers.Element.getStyle(this.contentDiv, "padding-top")
  737. );
  738. //cache the value
  739. this._contentDivPadding = contentDivPadding;
  740. if (this.div.parentNode == document.body) {
  741. //remove the div from the page and make it visible again
  742. document.body.removeChild(this.div);
  743. this.div.style.display = "";
  744. }
  745. }
  746. return contentDivPadding;
  747. },
  748. /**
  749. * Method: addCloseBox
  750. *
  751. * Parameters:
  752. * callback - {Function} The callback to be called when the close button
  753. * is clicked.
  754. */
  755. addCloseBox: function(callback) {
  756. this.closeDiv = OpenLayers.Util.createDiv(
  757. this.id + "_close", null, {w: 17, h: 17}
  758. );
  759. this.closeDiv.className = "olPopupCloseBox";
  760. // use the content div's css padding to determine if we should
  761. // padd the close div
  762. var contentDivPadding = this.getContentDivPadding();
  763. this.closeDiv.style.right = contentDivPadding.right + "px";
  764. this.closeDiv.style.top = contentDivPadding.top + "px";
  765. this.groupDiv.appendChild(this.closeDiv);
  766. var closePopup = callback || function(e) {
  767. this.hide();
  768. OpenLayers.Event.stop(e);
  769. };
  770. OpenLayers.Event.observe(this.closeDiv, "touchend",
  771. OpenLayers.Function.bindAsEventListener(closePopup, this));
  772. OpenLayers.Event.observe(this.closeDiv, "click",
  773. OpenLayers.Function.bindAsEventListener(closePopup, this));
  774. },
  775. /**
  776. * Method: panIntoView
  777. * Pans the map such that the popup is totaly viewable (if necessary)
  778. */
  779. panIntoView: function() {
  780. var mapSize = this.map.getSize();
  781. //start with the top left corner of the popup, in px,
  782. // relative to the viewport
  783. var origTL = this.map.getViewPortPxFromLayerPx( new OpenLayers.Pixel(
  784. parseInt(this.div.style.left),
  785. parseInt(this.div.style.top)
  786. ));
  787. var newTL = origTL.clone();
  788. //new left (compare to margins, using this.size to calculate right)
  789. if (origTL.x < this.map.paddingForPopups.left) {
  790. newTL.x = this.map.paddingForPopups.left;
  791. } else
  792. if ( (origTL.x + this.size.w) > (mapSize.w - this.map.paddingForPopups.right)) {
  793. newTL.x = mapSize.w - this.map.paddingForPopups.right - this.size.w;
  794. }
  795. //new top (compare to margins, using this.size to calculate bottom)
  796. if (origTL.y < this.map.paddingForPopups.top) {
  797. newTL.y = this.map.paddingForPopups.top;
  798. } else
  799. if ( (origTL.y + this.size.h) > (mapSize.h - this.map.paddingForPopups.bottom)) {
  800. newTL.y = mapSize.h - this.map.paddingForPopups.bottom - this.size.h;
  801. }
  802. var dx = origTL.x - newTL.x;
  803. var dy = origTL.y - newTL.y;
  804. this.map.pan(dx, dy);
  805. },
  806. /**
  807. * Method: registerEvents
  808. * Registers events on the popup.
  809. *
  810. * Do this in a separate function so that subclasses can
  811. * choose to override it if they wish to deal differently
  812. * with mouse events
  813. *
  814. * Note in the following handler functions that some special
  815. * care is needed to deal correctly with mousing and popups.
  816. *
  817. * Because the user might select the zoom-rectangle option and
  818. * then drag it over a popup, we need a safe way to allow the
  819. * mousemove and mouseup events to pass through the popup when
  820. * they are initiated from outside. The same procedure is needed for
  821. * touchmove and touchend events.
  822. *
  823. * Otherwise, we want to essentially kill the event propagation
  824. * for all other events, though we have to do so carefully,
  825. * without disabling basic html functionality, like clicking on
  826. * hyperlinks or drag-selecting text.
  827. */
  828. registerEvents:function() {
  829. this.events = new OpenLayers.Events(this, this.div, null, true);
  830. function onTouchstart(evt) {
  831. OpenLayers.Event.stop(evt, true);
  832. }
  833. this.events.on({
  834. "mousedown": this.onmousedown,
  835. "mousemove": this.onmousemove,
  836. "mouseup": this.onmouseup,
  837. "click": this.onclick,
  838. "mouseout": this.onmouseout,
  839. "dblclick": this.ondblclick,
  840. "touchstart": onTouchstart,
  841. scope: this
  842. });
  843. },
  844. /**
  845. * Method: onmousedown
  846. * When mouse goes down within the popup, make a note of
  847. * it locally, and then do not propagate the mousedown
  848. * (but do so safely so that user can select text inside)
  849. *
  850. * Parameters:
  851. * evt - {Event}
  852. */
  853. onmousedown: function (evt) {
  854. this.mousedown = true;
  855. OpenLayers.Event.stop(evt, true);
  856. },
  857. /**
  858. * Method: onmousemove
  859. * If the drag was started within the popup, then
  860. * do not propagate the mousemove (but do so safely
  861. * so that user can select text inside)
  862. *
  863. * Parameters:
  864. * evt - {Event}
  865. */
  866. onmousemove: function (evt) {
  867. if (this.mousedown) {
  868. OpenLayers.Event.stop(evt, true);
  869. }
  870. },
  871. /**
  872. * Method: onmouseup
  873. * When mouse comes up within the popup, after going down
  874. * in it, reset the flag, and then (once again) do not
  875. * propagate the event, but do so safely so that user can
  876. * select text inside
  877. *
  878. * Parameters:
  879. * evt - {Event}
  880. */
  881. onmouseup: function (evt) {
  882. if (this.mousedown) {
  883. this.mousedown = false;
  884. OpenLayers.Event.stop(evt, true);
  885. }
  886. },
  887. /**
  888. * Method: onclick
  889. * Ignore clicks, but allowing default browser handling
  890. *
  891. * Parameters:
  892. * evt - {Event}
  893. */
  894. onclick: function (evt) {
  895. OpenLayers.Event.stop(evt, true);
  896. },
  897. /**
  898. * Method: onmouseout
  899. * When mouse goes out of the popup set the flag to false so that
  900. * if they let go and then drag back in, we won't be confused.
  901. *
  902. * Parameters:
  903. * evt - {Event}
  904. */
  905. onmouseout: function (evt) {
  906. this.mousedown = false;
  907. },
  908. /**
  909. * Method: ondblclick
  910. * Ignore double-clicks, but allowing default browser handling
  911. *
  912. * Parameters:
  913. * evt - {Event}
  914. */
  915. ondblclick: function (evt) {
  916. OpenLayers.Event.stop(evt, true);
  917. },
  918. CLASS_NAME: "OpenLayers.Popup"
  919. });
  920. OpenLayers.Popup.WIDTH = 200;
  921. OpenLayers.Popup.HEIGHT = 200;
  922. OpenLayers.Popup.COLOR = "white";
  923. OpenLayers.Popup.OPACITY = 1;
  924. OpenLayers.Popup.BORDER = "0px";