1 | /** |
---|
2 | * Copyright (c) 2008-2010 The Open Source Geospatial Foundation |
---|
3 | * |
---|
4 | * Published under the BSD license. |
---|
5 | * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text |
---|
6 | * of the license. |
---|
7 | */ |
---|
8 | |
---|
9 | /** |
---|
10 | * @include GeoExt/widgets/MapPanel.js |
---|
11 | */ |
---|
12 | |
---|
13 | /** api: (define) |
---|
14 | * module = GeoExt |
---|
15 | * class = Popup |
---|
16 | * base_link = `Ext.Window <http://dev.sencha.com/deploy/dev/docs/?class=Ext.Window>`_ |
---|
17 | */ |
---|
18 | Ext.namespace("GeoExt"); |
---|
19 | |
---|
20 | /** api: example |
---|
21 | * Sample code to create a popup anchored to a feature: |
---|
22 | * |
---|
23 | * .. code-block:: javascript |
---|
24 | * |
---|
25 | * var popup = new GeoExt.Popup({ |
---|
26 | * title: "My Popup", |
---|
27 | * location: feature, |
---|
28 | * width: 200, |
---|
29 | * html: "<div>Popup content</div>", |
---|
30 | * collapsible: true |
---|
31 | * }); |
---|
32 | */ |
---|
33 | |
---|
34 | /** api: constructor |
---|
35 | * .. class:: Popup(config) |
---|
36 | * |
---|
37 | * Popups are a specialized Window that supports anchoring |
---|
38 | * to a particular location in a MapPanel. When a popup |
---|
39 | * is anchored to a location, that means that the popup |
---|
40 | * will visibly point to the location on the map, and move |
---|
41 | * accordingly when the map is panned or zoomed. |
---|
42 | */ |
---|
43 | GeoExt.Popup = Ext.extend(Ext.Window, { |
---|
44 | |
---|
45 | /** api: config[anchored] |
---|
46 | * ``Boolean`` The popup begins anchored to its location. Default is |
---|
47 | * ``true``. |
---|
48 | */ |
---|
49 | anchored: true, |
---|
50 | |
---|
51 | /** api: config[map] |
---|
52 | * ``OpenLayers.Map`` or :class:`GeoExt.MapPanel` |
---|
53 | * The map this popup will be anchored to (only required if ``anchored`` |
---|
54 | * is set to true and the map cannot be derived from the ``location``'s |
---|
55 | * layer. |
---|
56 | */ |
---|
57 | map: null, |
---|
58 | |
---|
59 | /** api: config[panIn] |
---|
60 | * ``Boolean`` The popup should pan the map so that the popup is |
---|
61 | * fully in view when it is rendered. Default is ``true``. |
---|
62 | */ |
---|
63 | panIn: true, |
---|
64 | |
---|
65 | /** api: config[unpinnable] |
---|
66 | * ``Boolean`` The popup should have a "unpin" tool that unanchors it from |
---|
67 | * its location. Default is ``true``. |
---|
68 | */ |
---|
69 | unpinnable: true, |
---|
70 | |
---|
71 | /** api: config[location] |
---|
72 | * ``OpenLayers.Feature.Vector`` or ``OpenLayers.LonLat`` or |
---|
73 | * ``OpenLayers.Pixel`` or ``OpenLayers.Geometry`` A location for this |
---|
74 | * popup's anchor. |
---|
75 | */ |
---|
76 | |
---|
77 | /** private: property[location] |
---|
78 | * ``OpenLayers.LonLat`` |
---|
79 | */ |
---|
80 | location: null, |
---|
81 | |
---|
82 | /** |
---|
83 | * Some Ext.Window defaults need to be overriden here |
---|
84 | * because some Ext.Window behavior is not currently supported. |
---|
85 | */ |
---|
86 | |
---|
87 | /** private: config[animCollapse] |
---|
88 | * ``Boolean`` Animate the transition when the panel is collapsed. |
---|
89 | * Default is ``false``. Collapsing animation is not supported yet for |
---|
90 | * popups. |
---|
91 | */ |
---|
92 | animCollapse: false, |
---|
93 | |
---|
94 | /** private: config[draggable] |
---|
95 | * ``Boolean`` Enable dragging of this Panel. Defaults to ``false`` |
---|
96 | * because the popup defaults to being anchored, and anchored popups |
---|
97 | * should not be draggable. |
---|
98 | */ |
---|
99 | draggable: false, |
---|
100 | |
---|
101 | /** private: config[shadow] |
---|
102 | * ``Boolean`` Give the popup window a shadow. Defaults to ``false`` |
---|
103 | * because shadows are not supported yet for popups (the shadow does |
---|
104 | * not look good with the anchor). |
---|
105 | */ |
---|
106 | shadow: false, |
---|
107 | |
---|
108 | /** api: config[popupCls] |
---|
109 | * ``String`` CSS class name for the popup DOM elements. Default is |
---|
110 | * "gx-popup". |
---|
111 | */ |
---|
112 | popupCls: "gx-popup", |
---|
113 | |
---|
114 | /** api: config[ancCls] |
---|
115 | * ``String`` CSS class name for the popup's anchor. |
---|
116 | */ |
---|
117 | ancCls: null, |
---|
118 | |
---|
119 | /** private: method[initComponent] |
---|
120 | * Initializes the popup. |
---|
121 | */ |
---|
122 | initComponent: function() { |
---|
123 | if(this.map instanceof GeoExt.MapPanel) { |
---|
124 | this.map = this.map.map; |
---|
125 | } |
---|
126 | if(!this.map && this.location instanceof OpenLayers.Feature.Vector && |
---|
127 | this.location.layer) { |
---|
128 | this.map = this.location.layer.map; |
---|
129 | } |
---|
130 | if (this.location instanceof OpenLayers.Feature.Vector) { |
---|
131 | this.location = this.location.geometry; |
---|
132 | } |
---|
133 | if (this.location instanceof OpenLayers.Geometry) { |
---|
134 | if (typeof this.location.getCentroid == "function") { |
---|
135 | this.location = this.location.getCentroid(); |
---|
136 | } |
---|
137 | this.location = this.location.getBounds().getCenterLonLat(); |
---|
138 | } else if (this.location instanceof OpenLayers.Pixel) { |
---|
139 | this.location = this.map.getLonLatFromViewPortPx(this.location); |
---|
140 | } |
---|
141 | |
---|
142 | if(this.anchored) { |
---|
143 | this.addAnchorEvents(); |
---|
144 | } |
---|
145 | |
---|
146 | this.baseCls = this.popupCls + " " + this.baseCls; |
---|
147 | |
---|
148 | this.elements += ',anc'; |
---|
149 | |
---|
150 | GeoExt.Popup.superclass.initComponent.call(this); |
---|
151 | }, |
---|
152 | |
---|
153 | /** private: method[onRender] |
---|
154 | * Executes when the popup is rendered. |
---|
155 | */ |
---|
156 | onRender: function(ct, position) { |
---|
157 | GeoExt.Popup.superclass.onRender.call(this, ct, position); |
---|
158 | this.ancCls = this.popupCls + "-anc"; |
---|
159 | |
---|
160 | //create anchor dom element. |
---|
161 | this.createElement("anc", this.el.dom); |
---|
162 | }, |
---|
163 | |
---|
164 | /** private: method[initTools] |
---|
165 | * Initializes the tools on the popup. In particular, |
---|
166 | * it adds the 'unpin' tool if the popup is unpinnable. |
---|
167 | */ |
---|
168 | initTools : function() { |
---|
169 | if(this.unpinnable) { |
---|
170 | this.addTool({ |
---|
171 | id: 'unpin', |
---|
172 | handler: this.unanchorPopup.createDelegate(this, []) |
---|
173 | }); |
---|
174 | } |
---|
175 | |
---|
176 | GeoExt.Popup.superclass.initTools.call(this); |
---|
177 | }, |
---|
178 | |
---|
179 | /** private: method[show] |
---|
180 | * Override. |
---|
181 | */ |
---|
182 | show: function() { |
---|
183 | GeoExt.Popup.superclass.show.apply(this, arguments); |
---|
184 | if(this.anchored) { |
---|
185 | this.position(); |
---|
186 | if(this.panIn && !this._mapMove) { |
---|
187 | this.panIntoView(); |
---|
188 | } |
---|
189 | } |
---|
190 | }, |
---|
191 | |
---|
192 | /** private: method[maximize] |
---|
193 | * Override. |
---|
194 | */ |
---|
195 | maximize: function() { |
---|
196 | if(!this.maximized && this.anc) { |
---|
197 | this.unanchorPopup(); |
---|
198 | } |
---|
199 | GeoExt.Popup.superclass.maximize.apply(this, arguments); |
---|
200 | }, |
---|
201 | |
---|
202 | /** api: method[setSize] |
---|
203 | * :param w: ``Integer`` |
---|
204 | * :param h: ``Integer`` |
---|
205 | * |
---|
206 | * Sets the size of the popup, taking into account the size of the anchor. |
---|
207 | */ |
---|
208 | setSize: function(w, h) { |
---|
209 | if(this.anc) { |
---|
210 | var ancSize = this.anc.getSize(); |
---|
211 | if(typeof w == 'object') { |
---|
212 | h = w.height - ancSize.height; |
---|
213 | w = w.width; |
---|
214 | } else if(!isNaN(h)){ |
---|
215 | h = h - ancSize.height; |
---|
216 | } |
---|
217 | } |
---|
218 | GeoExt.Popup.superclass.setSize.call(this, w, h); |
---|
219 | }, |
---|
220 | |
---|
221 | /** private: method[position] |
---|
222 | * Positions the popup relative to its location |
---|
223 | */ |
---|
224 | position: function() { |
---|
225 | if(this._mapMove === true) { |
---|
226 | var visible = this.map.getExtent().containsLonLat(this.location); |
---|
227 | if(visible !== this.isVisible()) { |
---|
228 | this.setVisible(visible); |
---|
229 | } |
---|
230 | } |
---|
231 | |
---|
232 | if(this.isVisible()) { |
---|
233 | var centerPx = this.map.getViewPortPxFromLonLat(this.location); |
---|
234 | var mapBox = Ext.fly(this.map.div).getBox(); |
---|
235 | |
---|
236 | //This works for positioning with the anchor on the bottom. |
---|
237 | |
---|
238 | var anc = this.anc; |
---|
239 | var dx = anc.getLeft(true) + anc.getWidth() / 2; |
---|
240 | var dy = this.el.getHeight(); |
---|
241 | |
---|
242 | //Assuming for now that the map viewport takes up |
---|
243 | //the entire area of the MapPanel |
---|
244 | this.setPosition(centerPx.x + mapBox.x - dx, centerPx.y + mapBox.y - dy); |
---|
245 | } |
---|
246 | }, |
---|
247 | |
---|
248 | /** private: method[unanchorPopup] |
---|
249 | * Unanchors a popup from its location. This removes the popup from its |
---|
250 | * MapPanel and adds it to the page body. |
---|
251 | */ |
---|
252 | unanchorPopup: function() { |
---|
253 | this.removeAnchorEvents(); |
---|
254 | |
---|
255 | //make the window draggable |
---|
256 | this.draggable = true; |
---|
257 | this.header.addClass("x-window-draggable"); |
---|
258 | this.dd = new Ext.Window.DD(this); |
---|
259 | |
---|
260 | //remove anchor |
---|
261 | this.anc.remove(); |
---|
262 | this.anc = null; |
---|
263 | |
---|
264 | //hide unpin tool |
---|
265 | this.tools.unpin.hide(); |
---|
266 | }, |
---|
267 | |
---|
268 | /** private: method[panIntoView] |
---|
269 | * Pans the MapPanel's map so that an anchored popup can come entirely |
---|
270 | * into view, with padding specified as per normal OpenLayers.Map popup |
---|
271 | * padding. |
---|
272 | */ |
---|
273 | panIntoView: function() { |
---|
274 | var mapBox = Ext.fly(this.map.div).getBox(); |
---|
275 | |
---|
276 | //assumed viewport takes up whole body element of map panel |
---|
277 | var popupPos = this.getPosition(true); |
---|
278 | popupPos[0] -= mapBox.x; |
---|
279 | popupPos[1] -= mapBox.y; |
---|
280 | |
---|
281 | var panelSize = [mapBox.width, mapBox.height]; // [X,Y] |
---|
282 | |
---|
283 | var popupSize = this.getSize(); |
---|
284 | |
---|
285 | var newPos = [popupPos[0], popupPos[1]]; |
---|
286 | |
---|
287 | //For now, using native OpenLayers popup padding. This may not be ideal. |
---|
288 | var padding = this.map.paddingForPopups; |
---|
289 | |
---|
290 | // X |
---|
291 | if(popupPos[0] < padding.left) { |
---|
292 | newPos[0] = padding.left; |
---|
293 | } else if(popupPos[0] + popupSize.width > panelSize[0] - padding.right) { |
---|
294 | newPos[0] = panelSize[0] - padding.right - popupSize.width; |
---|
295 | } |
---|
296 | |
---|
297 | // Y |
---|
298 | if(popupPos[1] < padding.top) { |
---|
299 | newPos[1] = padding.top; |
---|
300 | } else if(popupPos[1] + popupSize.height > panelSize[1] - padding.bottom) { |
---|
301 | newPos[1] = panelSize[1] - padding.bottom - popupSize.height; |
---|
302 | } |
---|
303 | |
---|
304 | var dx = popupPos[0] - newPos[0]; |
---|
305 | var dy = popupPos[1] - newPos[1]; |
---|
306 | |
---|
307 | this.map.pan(dx, dy); |
---|
308 | }, |
---|
309 | |
---|
310 | /** private: method[onMapMove] |
---|
311 | */ |
---|
312 | onMapMove: function() { |
---|
313 | this._mapMove = true; |
---|
314 | this.position(); |
---|
315 | delete this._mapMove; |
---|
316 | }, |
---|
317 | |
---|
318 | /** private: method[addAnchorEvents] |
---|
319 | */ |
---|
320 | addAnchorEvents: function() { |
---|
321 | this.map.events.on({ |
---|
322 | "move" : this.onMapMove, |
---|
323 | scope : this |
---|
324 | }); |
---|
325 | |
---|
326 | this.on({ |
---|
327 | "resize": this.position, |
---|
328 | "collapse": this.position, |
---|
329 | "expand": this.position, |
---|
330 | scope: this |
---|
331 | }); |
---|
332 | }, |
---|
333 | |
---|
334 | /** private: method[removeAnchorEvents] |
---|
335 | */ |
---|
336 | removeAnchorEvents: function() { |
---|
337 | //stop position with location |
---|
338 | this.map.events.un({ |
---|
339 | "move" : this.onMapMove, |
---|
340 | scope : this |
---|
341 | }); |
---|
342 | |
---|
343 | this.un("resize", this.position, this); |
---|
344 | this.un("collapse", this.position, this); |
---|
345 | this.un("expand", this.position, this); |
---|
346 | |
---|
347 | }, |
---|
348 | |
---|
349 | /** private: method[beforeDestroy] |
---|
350 | * Cleanup events before destroying the popup. |
---|
351 | */ |
---|
352 | beforeDestroy: function() { |
---|
353 | if(this.anchored) { |
---|
354 | this.removeAnchorEvents(); |
---|
355 | } |
---|
356 | GeoExt.Popup.superclass.beforeDestroy.call(this); |
---|
357 | } |
---|
358 | }); |
---|
359 | |
---|
360 | /** api: xtype = gx_popup */ |
---|
361 | Ext.reg('gx_popup', GeoExt.Popup); |
---|