2
* Copyright (c) 2008-2010 The Open Source Geospatial Foundation
4
* Published under the BSD license.
5
* See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text
8
Ext.namespace("GeoExt.data");
11
* module = GeoExt.data
12
* class = PrintProvider
13
* base_link = `Ext.util.Observable <http://extjs.com/deploy/dev/docs/?class=Ext.util.Observable>`_
17
* Minimal code to print as much of the current map extent as possible as
18
* soon as the print service capabilities are loaded, using the first layout
19
* reported by the print service:
21
* .. code-block:: javascript
23
* var mapPanel = new GeoExt.MapPanel({
24
* renderTo: "mappanel",
25
* layers: [new OpenLayers.Layer.WMS("wms", "/geoserver/wms",
26
* {layers: "topp:tasmania_state_boundaries"})],
27
* center: [146.56, -41.56],
30
* var printProvider = new GeoExt.data.PrintProvider({
31
* url: "/geoserver/pdf",
33
* "loadcapabilities": function() {
34
* var printPage = new GeoExt.data.PrintPage({
35
* printProvider: printProvider
37
* printPage.fit(mapPanel, true);
38
* printProvider.print(mapPanel, printPage);
45
* .. class:: PrintProvider
47
* Provides an interface to a Mapfish or GeoServer print module. For printing,
48
* one or more instances of :class:`GeoExt.data.PrintPage` are also required
49
* to tell the PrintProvider about the scale and extent (and optionally
50
* rotation) of the page(s) we want to print.
52
GeoExt.data.PrintProvider = Ext.extend(Ext.util.Observable, {
55
* ``String`` Base url of the print service. Only required if
56
* ``capabilities`` is not provided. This
57
* is usually something like http://path/to/mapfish/print for Mapfish,
58
* and http://path/to/geoserver/pdf for GeoServer with the printing
59
* extension installed. This property requires that the print service is
60
* at the same origin as the application (or accessible via proxy).
63
/** private: property[url]
64
* ``String`` Base url of the print service. Will always have a trailing
69
/** api: config[capabilities]
70
* ``Object`` Capabilities of the print service. Only required if ``url``
71
* is not provided. This is the object returned by the ``info.json``
72
* endpoint of the print service, and is usually obtained by including a
73
* script tag pointing to
74
* http://path/to/printservice/info.json?var=myvar in the head of the
75
* html document, making the capabilities accessible as ``window.myvar``.
76
* This property should be used when no local print service or proxy is
77
* available, or when you do not listen for the ``loadcapabilities``
78
* events before creating components that require the PrintProvider's
79
* capabilities to be available.
82
/** private: property[capabilities]
83
* ``Object`` Capabilities as returned from the print service.
87
/** api: config[method]
88
* ``String`` Either ``POST`` or ``GET`` (case-sensitive). Method to use
89
* when sending print requests to the servlet. If the print service is at
90
* the same origin as the application (or accessible via proxy), then
91
* ``POST`` is recommended. Use ``GET`` when accessing a remote print
92
* service with no proxy available, but expect issues with character
93
* encoding and URLs exceeding the maximum length. Default is ``POST``.
96
/** private: property[method]
97
* ``String`` Either ``POST`` or ``GET`` (case-sensitive). Method to use
98
* when sending print requests to the servlet.
102
/** api: config[customParams]
103
* ``Object`` Key-value pairs of custom data to be sent to the print
104
* service. Optional. This is e.g. useful for complex layout definitions
105
* on the server side that require additional parameters.
108
/** api: property[customParams]
109
* ``Object`` Key-value pairs of custom data to be sent to the print
110
* service. Optional. This is e.g. useful for complex layout definitions
111
* on the server side that require additional parameters.
115
/** api: property[scales]
116
* ``Ext.data.JsonStore`` read-only. A store representing the scales
119
* Fields of records in this store:
121
* * name - ``String`` the name of the scale
122
* * value - ``Float`` the scale denominator
126
/** api: property[dpis]
127
* ``Ext.data.JsonStore`` read-only. A store representing the dpis
130
* Fields of records in this store:
132
* * name - ``String`` the name of the dpi
133
* * value - ``Float`` the dots per inch
137
/** api: property[layouts]
138
* ``Ext.data.JsonStore`` read-only. A store representing the layouts
141
* Fields of records in this store:
143
* * name - ``String`` the name of the layout
144
* * size - ``Object`` width and height of the map in points
145
* * rotation - ``Boolean`` indicates if rotation is supported
149
/** api: property[dpi]
150
* ``Ext.data.Record`` the record for the currently used resolution.
151
* Read-only, use ``setDpi`` to set the value.
155
/** api: property[layout]
156
* ``Ext.data.Record`` the record of the currently used layout. Read-only,
157
* use ``setLayout`` to set the value.
161
/** private: method[constructor]
162
* Private constructor override.
164
constructor: function(config) {
165
this.initialConfig = config;
166
Ext.apply(this, config);
168
if(!this.customParams) {
169
this.customParams = {};
173
/** api: events[loadcapabilities]
174
* Triggered when the capabilities have finished loading. This
175
* event will only fire when ``capabilities`` is not configured.
177
* Listener arguments:
178
* * printProvider - :class:`GeoExt.data.PrintProvider` this
180
* * capabilities - ``Object`` the capabilities
184
/** api: events[layoutchange]
185
* Triggered when the layout is changed.
187
* Listener arguments:
188
* * printProvider - :class:`GeoExt.data.PrintProvider` this
190
* * layout - ``Ext.data.Record`` the new layout
194
/** api: events[dpichange]
195
* Triggered when the dpi value is changed.
197
* Listener arguments:
198
* * printProvider - :class:`GeoExt.data.PrintProvider` this
200
* * dpi - ``Ext.data.Record`` the new dpi record
204
/** api: events[beforeprint]
205
* Triggered when the print method is called.
207
* Listener arguments:
208
* * printProvider - :class:`GeoExt.data.PrintProvider` this
210
* * map - ``OpenLayers.Map`` the map being printed
211
* * pages - Array of :class:`GeoExt.data.PrintPage` the print
212
* pages being printed
213
* * options - ``Object`` the options to the print command
217
/** api: events[print]
218
* Triggered when the print document is opened.
220
* Listener arguments:
221
* * printProvider - :class:`GeoExt.data.PrintProvider` this
223
* * url - ``String`` the url of the print document
227
/** api: events[printexception]
228
* Triggered when using the ``POST`` method, when the print
229
* backend returns an exception.
231
* Listener arguments:
232
* * printProvider - :class:`GeoExt.data.PrintProvider` this
234
* * response - ``Object`` the response object of the XHR
238
/** api: events[beforeencodelayer]
239
* Triggered before a layer is encoded. This can be used to
240
* exclude layers from the printing, by having the listener
243
* Listener arguments:
244
* * printProvider - :class:`GeoExt.data.PrintProvider` this
246
* * layer - ``OpenLayers.Layer`` the layer which is about to be
251
/** api: events[encodelayer]
252
* Triggered when a layer is encoded. This can be used to modify
253
* the encoded low-level layer object that will be sent to the
256
* Listener arguments:
257
* * printProvider - :class:`GeoExt.data.PrintProvider` this
259
* * layer - ``OpenLayers.Layer`` the layer which is about to be
261
* * encodedLayer - ``Object`` the encoded layer that will be
262
* sent to the print service.
268
GeoExt.data.PrintProvider.superclass.constructor.apply(this, arguments);
270
this.scales = new Ext.data.JsonStore({
272
sortInfo: {field: "value", direction: "DESC"},
273
fields: ["name", {name: "value", type: "float"}]
276
this.dpis = new Ext.data.JsonStore({
278
fields: ["name", {name: "value", type: "float"}]
281
this.layouts = new Ext.data.JsonStore({
285
{name: "size", mapping: "map"},
286
{name: "rotation", type: "boolean"}
290
if(config.capabilities) {
293
if(this.url.split("/").pop()) {
296
this.loadCapabilities();
300
/** api: method[setLayout]
301
* :param layout: ``Ext.data.Record`` the record of the layout.
303
* Sets the layout for this printProvider.
305
setLayout: function(layout) {
306
this.layout = layout;
307
this.fireEvent("layoutchange", this, layout);
310
/** api: method[setDpi]
311
* :param dpi: ``Ext.data.Record`` the dpi record.
313
* Sets the dpi for this printProvider.
315
setDpi: function(dpi) {
317
this.fireEvent("dpichange", this, dpi);
320
/** api: method[print]
321
* :param map: ``GeoExt.MapPanel`` or ``OpenLayers.Map`` The map to print.
322
* :param pages: ``Array`` of :class:`GeoExt.data.PrintPage` or
323
* :class:`GeoExt.data.PrintPage` page(s) to print.
324
* :param options: ``Object`` of additional options, see below.
326
* Sends the print command to the print service and opens a new window
327
* with the resulting PDF.
329
* Valid properties for the ``options`` argument:
331
* * ``legend`` - :class:`GeoExt.LegendPanel` If provided, the legend
332
* will be added to the print document. For the printed result to
333
* look like the LegendPanel, the following settings in the
334
* ``!legends`` block of the print module are recommended:
336
* .. code-block:: none
340
* classIndentation: 0
342
* * ``overview`` - :class:`OpenLayers.Control.OverviewMap` If provided,
343
* the layers for the overview map in the printout will be taken from
344
* the OverviewMap control. If not provided, the print service will
345
* use the main map's layers for the overview map. Applies only for
346
* layouts configured to print an overview map.
348
print: function(map, pages, options) {
349
if(map instanceof GeoExt.MapPanel) {
352
pages = pages instanceof Array ? pages : [pages];
353
options = options || {};
354
if(this.fireEvent("beforeprint", this, map, pages, options) === false) {
358
var jsonData = Ext.apply({
359
units: map.getUnits(),
360
srs: map.baseLayer.projection.getCode(),
361
layout: this.layout.get("name"),
362
dpi: this.dpi.get("value")
363
}, this.customParams);
365
var pagesLayer = pages[0].feature.layer;
366
var encodedLayers = [];
367
Ext.each(map.layers, function(layer){
368
if(layer !== pagesLayer && layer.getVisibility() === true) {
369
var enc = this.encodeLayer(layer);
370
enc && encodedLayers.push(enc);
373
jsonData.layers = encodedLayers;
375
var encodedPages = [];
376
Ext.each(pages, function(page) {
377
encodedPages.push(Ext.apply({
378
center: [page.center.lon, page.center.lat],
379
scale: page.scale.get("value"),
380
rotation: page.rotation
381
}, page.customParams));
383
jsonData.pages = encodedPages;
385
if (options.overview) {
386
var encodedOverviewLayers = [];
387
Ext.each(options.overview.layers, function(layer) {
388
var enc = this.encodeLayer(layer);
389
enc && encodedOverviewLayers.push(enc);
391
jsonData.overviewLayers = encodedOverviewLayers;
395
var encodedLegends = [];
396
options.legend.items.each(function(cmp) {
398
var encFn = this.encoders.legends[cmp.getXType()];
399
encodedLegends = encodedLegends.concat(
400
encFn.call(this, cmp));
403
jsonData.legends = encodedLegends;
406
if(this.method === "GET") {
407
var url = this.capabilities.printURL + "?spec=" +
408
encodeURIComponent(Ext.encode(jsonData));
410
this.fireEvent("print", this, url);
413
url: this.capabilities.createURL,
415
success: function(response) {
416
// In IE, using a Content-disposition: attachment header
417
// may make it hard or impossible to download the pdf due
418
// to security settings. So we'll display the pdf inline.
419
var url = Ext.decode(response.responseText).getURL +
420
(Ext.isIE ? "?inline=true" : "");
421
if(Ext.isOpera || Ext.isIE) {
422
// Make sure that Opera and IE don't replace the
423
// content tab with the pdf
426
// This avoids popup blockers for all other browers
427
window.location.href = url;
429
this.fireEvent("print", this, url);
431
failure: function(response) {
432
this.fireEvent("printexception", this, response);
439
/** private: method[loadCapabilities]
441
loadCapabilities: function() {
442
var url = this.url + "info.json";
445
disableCaching: false,
446
success: function(response) {
447
this.capabilities = Ext.decode(response.responseText);
454
/** private: method[loadStores]
456
loadStores: function() {
457
this.scales.loadData(this.capabilities);
458
this.dpis.loadData(this.capabilities);
459
this.layouts.loadData(this.capabilities);
461
this.setLayout(this.layouts.getAt(0));
462
this.setDpi(this.dpis.getAt(0));
463
this.fireEvent("loadcapabilities", this, this.capabilities);
466
/** private: method[encodeLayer]
467
* :param layer: ``OpenLayers.Layer``
468
* :return: ``Object``
470
* Encodes a layer for the print service.
472
encodeLayer: function(layer) {
474
for(var c in this.encoders.layers) {
475
if(OpenLayers.Layer[c] && layer instanceof OpenLayers.Layer[c]) {
476
if(this.fireEvent("beforeencodelayer", this, layer) === false) {
479
encLayer = this.encoders.layers[c].call(this, layer);
480
this.fireEvent("encodelayer", this, layer, encLayer);
484
// only return the encLayer object when we have a type. Prevents a
485
// fallback on base encoders like HTTPRequest.
486
return (encLayer && encLayer.type) ? encLayer : null;
489
/** private: method[getAbsoluteUrl]
490
* :param url: ``String``
491
* :return: ``String``
493
* Converts the provided url to an absolute url.
495
getAbsoluteUrl: function(url) {
498
a = document.createElement("<a href='" + url + "'/>");
499
a.style.display = "none";
500
document.body.appendChild(a);
502
document.body.removeChild(a);
504
a = document.createElement("a");
510
/** private: property[encoders]
511
* ``Object`` Encoders for all print content
515
"WMS": function(layer) {
516
var enc = this.encoders.layers.HTTPRequest.call(this, layer);
519
layers: [layer.params.LAYERS].join(",").split(","),
520
format: layer.params.FORMAT,
521
styles: [layer.params.STYLES].join(",").split(",")
524
for(var p in layer.params) {
525
param = p.toLowerCase();
526
if(!layer.DEFAULT_PARAMS[param] &&
527
"layers,styles,width,height,srs".indexOf(param) == -1) {
528
if(!enc.customParams) {
529
enc.customParams = {};
531
enc.customParams[p] = layer.params[p];
536
"OSM": function(layer) {
537
var enc = this.encoders.layers.TileCache.call(this, layer);
538
return Ext.apply(enc, {
540
baseURL: enc.baseURL.substr(0, enc.baseURL.indexOf("$")),
544
"TMS": function(layer) {
545
var enc = this.encoders.layers.TileCache.call(this, layer);
546
return Ext.apply(enc, {
551
"TileCache": function(layer) {
552
var enc = this.encoders.layers.HTTPRequest.call(this, layer);
553
return Ext.apply(enc, {
555
layer: layer.layername,
556
maxExtent: layer.maxExtent.toArray(),
557
tileSize: [layer.tileSize.w, layer.tileSize.h],
558
extension: layer.extension,
559
resolutions: layer.serverResolutions || layer.resolutions
562
"HTTPRequest": function(layer) {
564
baseURL: this.getAbsoluteUrl(layer.url instanceof Array ?
565
layer.url[0] : layer.url),
566
opacity: (layer.opacity != null) ? layer.opacity : 1.0,
567
singleTile: layer.singleTile
570
"Image": function(layer) {
573
baseURL: this.getAbsoluteUrl(layer.getURL(layer.extent)),
574
opacity: (layer.opacity != null) ? layer.opacity : 1.0,
575
extent: layer.extent.toArray(),
576
pixelSize: [layer.size.w, layer.size.h],
580
"Vector": function(layer) {
581
if(!layer.features.length) {
585
var encFeatures = [];
587
var features = layer.features;
588
var featureFormat = new OpenLayers.Format.GeoJSON();
589
var styleFormat = new OpenLayers.Format.JSON();
592
var feature, style, dictKey, dictItem, styleName;
593
for(var i=0, len=features.length; i<len; ++i) {
594
feature = features[i];
595
style = feature.style || layer.style ||
596
layer.styleMap.createSymbolizer(feature,
597
feature.renderIntent);
598
dictKey = styleFormat.write(style);
599
dictItem = styleDict[dictKey];
601
//this style is already known
602
styleName = dictItem;
605
styleDict[dictKey] = styleName = nextId++;
606
if(style.externalGraphic) {
607
encStyles[styleName] = Ext.applyIf({
608
externalGraphic: this.getAbsoluteUrl(
609
style.externalGraphic)}, style);
611
encStyles[styleName] = style;
614
var featureGeoJson = featureFormat.extract.feature.call(
615
featureFormat, feature);
617
featureGeoJson.properties = OpenLayers.Util.extend({
619
}, featureGeoJson.properties);
621
encFeatures.push(featureGeoJson);
627
styleProperty: '_gx_style',
629
type: "FeatureCollection",
630
features: encFeatures
633
opacity: (layer.opacity != null) ? layer.opacity : 1.0
638
"gx_wmslegend": function(legend) {
639
var enc = this.encoders.legends.base.call(this, legend);
641
for(var i=1, len=legend.items.getCount(); i<len; ++i) {
642
icons.push(this.getAbsoluteUrl(legend.items.get(i).url));
644
enc[0].classes[0] = {
650
"gx_urllegend": function(legend) {
651
var enc = this.encoders.legends.base.call(this, legend);
652
enc[0].classes.push({
654
icon: this.getAbsoluteUrl(legend.items.get(1).url)
658
"base": function(legend){
660
name: legend.items.get(0).text,