PageRenderTime 54ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/app/static/script/app/GeoExplorer.js

https://github.com/nrb/geonode-client
JavaScript | 1003 lines | 788 code | 79 blank | 136 comment | 56 complexity | 8ad4fce718e18bd85dd628fd20ea7d96 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. /**
  2. * Copyright (c) 2009 The Open Planning Project
  3. */
  4. /**
  5. * Constructor: GeoExplorer
  6. * Create a new GeoExplorer application.
  7. *
  8. * Parameters:
  9. * config - {Object} Optional application configuration properties.
  10. *
  11. * Valid config properties:
  12. * map - {Object} Map configuration object.
  13. * ows - {String} OWS URL
  14. *
  15. * Valid map config properties:
  16. * layers - {Array} A list of layer configuration objects.
  17. * center - {Array} A two item array with center coordinates.
  18. * zoom - {Number} An initial zoom level.
  19. *
  20. * Valid layer config properties:
  21. * name - {String} Required WMS layer name.
  22. * title - {String} Optional title to display for layer.
  23. */
  24. var GeoExplorer = Ext.extend(gxp.Viewer, {
  25. /**
  26. * api: config[localGeoServerBaseUrl]
  27. * ``String`` url of the local GeoServer instance
  28. */
  29. localGeoServerBaseUrl: "",
  30. /**
  31. * api: config[fromLayer]
  32. * ``Boolean`` true if map view was loaded with layer parameters
  33. */
  34. fromLayer: false,
  35. /**
  36. * private: property[mapPanel]
  37. * the :class:`GeoExt.MapPanel` instance for the main viewport
  38. */
  39. mapPanel: null,
  40. /**
  41. * Property: legendPanel
  42. * {GeoExt.LegendPanel} the legend for the main viewport's map
  43. */
  44. legendPanel: null,
  45. /**
  46. * Property: toolbar
  47. * {Ext.Toolbar} the toolbar for the main viewport
  48. */
  49. toolbar: null,
  50. /**
  51. * Property: capGrid
  52. * {<Ext.Window>} A window which includes a CapabilitiesGrid panel.
  53. */
  54. capGrid: null,
  55. /**
  56. * Property: modified
  57. * ``Number``
  58. */
  59. modified: 0,
  60. /**
  61. * Property: popupCache
  62. * {Object} An object containing references to visible popups so that
  63. * we can insert responses from multiple requests.
  64. */
  65. popupCache: null,
  66. /** private: property[busyMask]
  67. * ``Ext.LoadMask``
  68. */
  69. busyMask: null,
  70. /** private: property[urlPortRegEx]
  71. * ``RegExp``
  72. */
  73. urlPortRegEx: /^(http[s]?:\/\/[^:]*)(:80|:443)?\//,
  74. //public variables for string literals needed for localization
  75. backgroundContainerText: "UT:Background",
  76. connErrorTitleText: "UT:Connection Error",
  77. connErrorText: "UT:The server returned an error",
  78. connErrorDetailsText: "UT:Details...",
  79. heightLabel: 'UT: Height',
  80. largeSizeLabel: 'UT:Large',
  81. layerContainerText: "UT:Map Layers",
  82. layerSelectionLabel: "UT:View available data from:",
  83. layersContainerText: "UT:Data",
  84. layersPanelText: "UT:Layers",
  85. legendPanelText: "UT:Legend",
  86. loadingMapMessage: "UT:Loading Map...",
  87. mapSizeLabel: 'UT: Map Size',
  88. metadataFormCancelText : "UT:Cancel",
  89. metadataFormSaveAsCopyText : "UT:Save as Copy",
  90. metadataFormSaveText : "UT:Save",
  91. metaDataHeader: 'UT:About this Map',
  92. metaDataMapAbstract: 'UT:Abstract',
  93. metaDataMapTitle: 'UT:Title',
  94. miniSizeLabel: 'UT: Mini',
  95. premiumSizeLabel: 'UT: Premium',
  96. printTipText: "UT:Print Map",
  97. printWindowTitleText: "UT:Print Preview",
  98. propertiesText: "UT:Properties",
  99. publishActionText: 'UT:Publish Map',
  100. saveFailMessage: "UT: Sorry, your map could not be saved.",
  101. saveFailTitle: "UT: Error While Saving",
  102. saveMapText: "UT: Save Map",
  103. saveMapAsText: "UT: Save Map As",
  104. saveNotAuthorizedMessage: "UT: You Must be logged in to save this map.",
  105. smallSizeLabel: 'UT: Small',
  106. sourceLoadFailureMessage: 'UT: Error contacting server.\n Please check the url and try again.',
  107. switchTo3DActionText: "UT:Switch to Google Earth 3D Viewer",
  108. unknownMapMessage: 'UT: The map that you are trying to load does not exist. Creating a new map instead.',
  109. unknownMapTitle: 'UT: Unknown Map',
  110. unsupportedLayersTitleText: 'UT:Unsupported Layers',
  111. unsupportedLayersText: 'UT:The following layers cannot be printed:',
  112. widthLabel: 'UT: Width',
  113. zoomSelectorText: 'UT:Zoom level',
  114. zoomSliderTipText: "UT: Zoom Level",
  115. zoomToLayerExtentText: "UT:Zoom to Layer Extent",
  116. constructor: function(config) {
  117. this.popupCache = {};
  118. // add any custom application events
  119. this.addEvents(
  120. /**
  121. * api: event[saved]
  122. * Fires when the map has been saved.
  123. * Listener arguments:
  124. * * ``String`` the map id
  125. */
  126. "saved",
  127. /**
  128. * api: event[beforeunload]
  129. * Fires before the page unloads. Return false to stop the page
  130. * from unloading.
  131. */
  132. "beforeunload"
  133. );
  134. // add old ptypes
  135. Ext.preg("gx_wmssource", gxp.plugins.WMSSource);
  136. Ext.preg("gx_olsource", gxp.plugins.OLSource);
  137. Ext.preg("gx_googlesource", gxp.plugins.GoogleSource);
  138. // global request proxy and error handling
  139. Ext.util.Observable.observeClass(Ext.data.Connection);
  140. Ext.data.Connection.on({
  141. "beforerequest": function(conn, options) {
  142. // use django's /geoserver endpoint when talking to the local
  143. // GeoServer's RESTconfig API
  144. var url = options.url.replace(this.urlPortRegEx, "$1/");
  145. if (this.localGeoServerBaseUrl) {
  146. if (url.indexOf(this.localGeoServerBaseUrl) == 0) {
  147. // replace local GeoServer url with /geoserver/
  148. options.url = url.replace(
  149. new RegExp("^" + this.localGeoServerBaseUrl),
  150. "/geoserver/"
  151. );
  152. return;
  153. }
  154. var localUrl = this.localGeoServerBaseUrl.replace(
  155. this.urlPortRegEx, "$1/");
  156. if(url.indexOf(localUrl + "rest/") === 0) {
  157. options.url = url.replace(new RegExp("^" +
  158. localUrl), "/geoserver/");
  159. return;
  160. };
  161. }
  162. // use the proxy for all non-local requests
  163. if(this.proxy && options.url.indexOf(this.proxy) !== 0 &&
  164. options.url.indexOf(window.location.protocol) === 0) {
  165. var parts = options.url.replace(/&$/, "").split("?");
  166. var params = Ext.apply(parts[1] && Ext.urlDecode(
  167. parts[1]) || {}, options.params);
  168. url = Ext.urlAppend(parts[0], Ext.urlEncode(params));
  169. delete options.params;
  170. options.url = this.proxy + encodeURIComponent(url);
  171. }
  172. },
  173. "requestexception": function(conn, response, options) {
  174. if(options.failure) {
  175. // exceptions are handled elsewhere
  176. } else {
  177. this.busyMask && this.busyMask.hide();
  178. var url = options.url;
  179. if (response.status == 401 && url.indexOf("http" != 0) &&
  180. url.indexOf(this.proxy) === -1) {
  181. var submit = function() {
  182. form.getForm().submit({
  183. waitMsg: "Logging in...",
  184. success: function(form, action) {
  185. win.close();
  186. document.cookie = action.response.getResponseHeader("Set-Cookie");
  187. // resend the original request
  188. Ext.Ajax.request(options);
  189. },
  190. failure: function(form, action) {
  191. var username = form.items.get(0);
  192. var password = form.items.get(1);
  193. username.markInvalid();
  194. password.markInvalid();
  195. username.focus(true);
  196. },
  197. scope: this
  198. });
  199. }.bind(this);
  200. var win = new Ext.Window({
  201. title: "GeoNode Login",
  202. modal: true,
  203. width: 230,
  204. autoHeight: true,
  205. layout: "fit",
  206. items: [{
  207. xtype: "form",
  208. autoHeight: true,
  209. labelWidth: 55,
  210. border: false,
  211. bodyStyle: "padding: 10px;",
  212. url: "/accounts/ajax_login",
  213. waitMsgTarget: true,
  214. errorReader: {
  215. // teach ExtJS a bit of RESTfulness
  216. read: function(response) {
  217. return {
  218. success: response.status == 200,
  219. records: []
  220. };
  221. }
  222. },
  223. defaults: {
  224. anchor: "100%"
  225. },
  226. items: [{
  227. xtype: "textfield",
  228. name: "username",
  229. fieldLabel: "Username"
  230. }, {
  231. xtype: "textfield",
  232. name: "password",
  233. fieldLabel: "Password",
  234. inputType: "password"
  235. }, {
  236. xtype: "hidden",
  237. name: "csrfmiddlewaretoken",
  238. value: this.csrfToken
  239. }, {
  240. xtype: "button",
  241. text: "Login",
  242. inputType: "submit",
  243. handler: submit
  244. }]
  245. }],
  246. keys: {
  247. "key": Ext.EventObject.ENTER,
  248. "fn": submit
  249. }
  250. });
  251. win.show();
  252. var form = win.items.get(0);
  253. form.items.get(0).focus(false, 100);
  254. } else if (response.status != 405 && url != "/geoserver/rest/styles") {
  255. // 405 from /rest/styles is ok because we use it to
  256. // test whether we're authenticated or not
  257. this.displayXHRTrouble(response);
  258. }
  259. }
  260. },
  261. scope: this
  262. });
  263. // register the color manager with every color field, for Styler
  264. Ext.util.Observable.observeClass(gxp.form.ColorField);
  265. gxp.form.ColorField.on({
  266. render: function(field) {
  267. var manager = new Styler.ColorManager();
  268. manager.register(field);
  269. }
  270. });
  271. // global beforeunload handler
  272. window.onbeforeunload = (function() {
  273. if (this.fireEvent("beforeunload") === false) {
  274. return "If you leave this page, unsaved changes will be lost.";
  275. }
  276. }).bind(this);
  277. // limit combo boxes to the window they belong to - fixes issues with
  278. // list shadow covering list items
  279. Ext.form.ComboBox.prototype.getListParent = function() {
  280. return this.el.up(".x-window") || document.body;
  281. };
  282. // don't draw window shadows - allows us to use autoHeight: true
  283. // without using syncShadow on the window
  284. Ext.Window.prototype.shadow = false;
  285. if (!config.map) {
  286. config.map = {};
  287. }
  288. config.map.numZoomLevels = config.map.numZoomLevels || 22;
  289. GeoExplorer.superclass.constructor.apply(this, arguments);
  290. this.mapID = this.initialConfig.id;
  291. },
  292. displayXHRTrouble: function(response) {
  293. response.status && Ext.Msg.show({
  294. title: this.connErrorTitleText,
  295. msg: this.connErrorText +
  296. ": " + response.status + " " + response.statusText,
  297. icon: Ext.MessageBox.ERROR,
  298. buttons: {ok: this.connErrorDetailsText, cancel: true},
  299. fn: function(result) {
  300. if(result == "ok") {
  301. var details = new Ext.Window({
  302. title: response.status + " " + response.statusText,
  303. width: 400,
  304. height: 300,
  305. items: {
  306. xtype: "container",
  307. cls: "error-details",
  308. html: response.responseText
  309. },
  310. autoScroll: true,
  311. buttons: [{
  312. text: "OK",
  313. handler: function() { details.close(); }
  314. }]
  315. });
  316. details.show();
  317. }
  318. }
  319. });
  320. },
  321. loadConfig: function(config) {
  322. config.tools = (config.tools || []).concat({
  323. ptype: "gxp_zoom",
  324. actionTarget: {target: "paneltbar", index: 4}
  325. }, {
  326. ptype: "gxp_navigationhistory",
  327. actionTarget: {target: "paneltbar", index: 6}
  328. }, {
  329. ptype: "gxp_zoomtoextent",
  330. actionTarget: {target: "paneltbar", index: 8}
  331. }, {
  332. ptype: "gxp_layertree",
  333. outputConfig: {id: "treecontent"},
  334. outputTarget: "layertree"
  335. }, {
  336. ptype: "gxp_zoomtolayerextent",
  337. actionTarget: "treecontent.contextMenu"
  338. }, {
  339. ptype: "gxp_addlayers",
  340. actionTarget: "treetbar",
  341. createExpander: function() {
  342. return new GeoExplorer.CapabilitiesRowExpander({
  343. ows: config.localGeoServerBaseUrl + "ows"
  344. });
  345. }
  346. }, {
  347. ptype: "gxp_removelayer",
  348. actionTarget: ["treetbar", "treecontent.contextMenu"]
  349. }, {
  350. ptype: "gxp_layerproperties",
  351. layerPanelConfig: {
  352. "gxp_wmslayerpanel": {rasterStyling: true}
  353. },
  354. actionTarget: ["treetbar", "treecontent.contextMenu"]
  355. }, {
  356. ptype: "gxp_styler",
  357. rasterStyling: true,
  358. actionTarget: ["treetbar", "treecontent.contextMenu"]
  359. });
  360. GeoExplorer.superclass.loadConfig.apply(this, arguments);
  361. },
  362. initMapPanel: function() {
  363. this.mapItems = [{
  364. xtype: "gx_zoomslider",
  365. vertical: true,
  366. height: 100,
  367. plugins: new GeoExt.ZoomSliderTip({
  368. template: "<div>"+this.zoomSliderTipText+": {zoom}<div>"
  369. })
  370. }];
  371. GeoExplorer.superclass.initMapPanel.apply(this, arguments);
  372. var layerCount = 0;
  373. this.mapPanel.map.events.register("preaddlayer", this, function(e) {
  374. var layer = e.layer;
  375. if (layer instanceof OpenLayers.Layer.WMS) {
  376. layer.events.on({
  377. "loadstart": function() {
  378. layerCount++;
  379. if (!this.busyMask) {
  380. this.busyMask = new Ext.LoadMask(
  381. this.mapPanel.map.div, {
  382. msg: this.loadingMapMessage
  383. }
  384. );
  385. this.busyMask.show();
  386. }
  387. layer.events.unregister("loadstart", this, arguments.callee);
  388. },
  389. "loadend": function() {
  390. layerCount--;
  391. if(layerCount === 0) {
  392. this.busyMask.hide();
  393. }
  394. layer.events.unregister("loadend", this, arguments.callee);
  395. },
  396. scope: this
  397. });
  398. }
  399. });
  400. },
  401. /**
  402. * Method: initPortal
  403. * Create the various parts that compose the layout.
  404. */
  405. initPortal: function() {
  406. this.on("beforeunload", function() {
  407. if (this.modified) {
  408. this.showMetadataForm();
  409. return false;
  410. }
  411. }, this);
  412. // TODO: make a proper component out of this
  413. var mapOverlay = this.createMapOverlay();
  414. this.mapPanel.add(mapOverlay);
  415. this.on("ready", function() {
  416. this.mapPanel.layers.on({
  417. "update": function() {this.modified |= 1;},
  418. "add": function() {this.modified |= 1;},
  419. "remove": function(store, rec) {
  420. this.modified |= 1;
  421. },
  422. scope: this
  423. });
  424. });
  425. var layersContainer = new Ext.Panel({
  426. id: "layertree",
  427. autoScroll: true,
  428. border: false,
  429. title: this.layersContainerText,
  430. tbar: {
  431. id: 'treetbar'
  432. }
  433. });
  434. this.legendPanel = new GeoExt.LegendPanel({
  435. title: this.legendPanelText,
  436. border: false,
  437. hideMode: "offsets",
  438. split: true,
  439. autoScroll: true,
  440. ascending: false,
  441. map: this.mapPanel.map,
  442. defaults: {cls: 'legend-item'}
  443. });
  444. var layerTree;
  445. this.on("ready", function(){
  446. var startSourceId = null;
  447. for (var id in this.layerSources) {
  448. source = this.layerSources[id];
  449. if (source.store && source instanceof gxp.plugins.WMSSource &&
  450. source.url.indexOf("/geoserver/wms" === 0)) {
  451. startSourceId = id;
  452. }
  453. }
  454. // find the add layers plugin
  455. var addLayers = null;
  456. for (var key in this.tools) {
  457. var tool = this.tools[key];
  458. if (tool.ptype === "gxp_addlayers") {
  459. addLayers = tool;
  460. addLayers.startSourceId = startSourceId;
  461. }
  462. }
  463. if (!this.fromLayer && !this.mapID) {
  464. if (addLayers !== null) {
  465. addLayers.showCapabilitiesGrid();
  466. }
  467. }
  468. // add custom tree contextmenu items
  469. layerTree = Ext.getCmp("treecontent");
  470. }, this);
  471. var layersTabPanel = new Ext.TabPanel({
  472. border: false,
  473. deferredRender: false,
  474. items: [layersContainer, this.legendPanel],
  475. activeTab: 0
  476. });
  477. //needed for Safari
  478. var westPanel = new Ext.Panel({
  479. layout: "fit",
  480. collapseMode: "mini",
  481. header: false,
  482. split: true,
  483. items: [layersTabPanel],
  484. region: "west",
  485. width: 250
  486. });
  487. this.toolbar = new Ext.Toolbar({
  488. disabled: true,
  489. id: 'paneltbar',
  490. items: this.createTools()
  491. });
  492. this.on("ready", function() {
  493. // enable only those items that were not specifically disabled
  494. var disabled = this.toolbar.items.filterBy(function(item) {
  495. return item.initialConfig && item.initialConfig.disabled;
  496. });
  497. this.toolbar.enable();
  498. disabled.each(function(item) {
  499. item.disable();
  500. });
  501. }, this);
  502. var showContextMenu;
  503. this.googleEarthPanel = new gxp.GoogleEarthPanel({
  504. mapPanel: this.mapPanel,
  505. listeners: {
  506. "beforeadd": function(record) {
  507. return record.get("group") !== "background";
  508. },
  509. "show": function() {
  510. // disable layers toolbar, selection and context menu
  511. layerTree.contextMenu.on("beforeshow", OpenLayers.Function.False);
  512. this.on(
  513. "beforelayerselectionchange", OpenLayers.Function.False
  514. );
  515. Ext.getCmp("treetbar").disable();
  516. },
  517. "hide": function() {
  518. var layerTree = Ext.getCmp("treecontent");
  519. if (layerTree) {
  520. // enable layers toolbar, selection and context menu
  521. layerTree.contextMenu.un("beforeshow", OpenLayers.Function.False);
  522. this.un(
  523. "beforelayerselectionchange", OpenLayers.Function.False
  524. );
  525. Ext.getCmp("treetbar").enable();
  526. }
  527. },
  528. scope: this
  529. }
  530. });
  531. this.mapPanelContainer = new Ext.Panel({
  532. layout: "card",
  533. region: "center",
  534. defaults: {
  535. // applied to each contained panel
  536. border:false
  537. },
  538. items: [
  539. this.mapPanel,
  540. this.googleEarthPanel
  541. ],
  542. activeItem: 0
  543. });
  544. var header = new Ext.Panel({
  545. region: "north",
  546. autoHeight: true,
  547. contentEl: 'header-wrapper'
  548. });
  549. Lang.registerLinks();
  550. this.portalItems = [
  551. header, {
  552. region: "center",
  553. xtype: "container",
  554. layout: "fit",
  555. border: false,
  556. hideBorders: true,
  557. items: {
  558. layout: "border",
  559. deferredRender: false,
  560. tbar: this.toolbar,
  561. items: [
  562. this.mapPanelContainer,
  563. westPanel
  564. ],
  565. ref: "../../main"
  566. }
  567. }
  568. ];
  569. GeoExplorer.superclass.initPortal.apply(this, arguments);
  570. },
  571. /** private: method[createMapOverlay]
  572. * Builds the :class:`Ext.Panel` containing components to be overlaid on the
  573. * map, setting up the special configuration for its layout and
  574. * map-friendliness.
  575. */
  576. createMapOverlay: function() {
  577. var scaleLinePanel = new Ext.BoxComponent({
  578. autoEl: {
  579. tag: "div",
  580. cls: "olControlScaleLine overlay-element overlay-scaleline"
  581. }
  582. });
  583. scaleLinePanel.on('render', function(){
  584. var scaleLine = new OpenLayers.Control.ScaleLine({
  585. div: scaleLinePanel.getEl().dom,
  586. geodesic: true
  587. });
  588. this.mapPanel.map.addControl(scaleLine);
  589. scaleLine.activate();
  590. }, this);
  591. var zoomSelectorWrapper = new Ext.Panel({
  592. cls: 'overlay-element overlay-scalechooser',
  593. border: false
  594. });
  595. this.on("ready", function() {
  596. var zoomStore = new GeoExt.data.ScaleStore({
  597. map: this.mapPanel.map
  598. });
  599. var zoomSelector = new Ext.form.ComboBox({
  600. emptyText: this.zoomSelectorText,
  601. tpl: '<tpl for="."><div class="x-combo-list-item">1 : {[parseInt(values.scale)]}</div></tpl>',
  602. editable: false,
  603. triggerAction: 'all',
  604. mode: 'local',
  605. store: zoomStore,
  606. width: 110
  607. });
  608. zoomSelector.on({
  609. click: function(evt) {
  610. evt.stopEvent();
  611. },
  612. mousedown: function(evt) {
  613. evt.stopEvent();
  614. },
  615. select: function(combo, record, index) {
  616. this.mapPanel.map.zoomTo(record.data.level);
  617. },
  618. scope: this
  619. });
  620. function setScale() {
  621. var scale = zoomStore.queryBy(function(record) {
  622. return this.mapPanel.map.getZoom() == record.data.level;
  623. }, this);
  624. if (scale.length > 0) {
  625. scale = scale.items[0];
  626. zoomSelector.setValue("1 : " + parseInt(scale.data.scale, 10));
  627. } else {
  628. if (!zoomSelector.rendered) {
  629. return;
  630. }
  631. zoomSelector.clearValue();
  632. }
  633. }
  634. setScale.call(this);
  635. this.mapPanel.map.events.register('zoomend', this, setScale);
  636. zoomSelectorWrapper.add(zoomSelector);
  637. zoomSelectorWrapper.doLayout();
  638. }, this);
  639. var mapOverlay = new Ext.Panel({
  640. // title: "Overlay",
  641. cls: 'map-overlay',
  642. items: [
  643. scaleLinePanel,
  644. zoomSelectorWrapper
  645. ]
  646. });
  647. mapOverlay.on("afterlayout", function(){
  648. scaleLinePanel.getEl().dom.style.position = 'relative';
  649. scaleLinePanel.getEl().dom.style.display = 'inline';
  650. mapOverlay.getEl().on("click", function(x){x.stopEvent();});
  651. mapOverlay.getEl().on("mousedown", function(x){x.stopEvent();});
  652. }, this);
  653. return mapOverlay;
  654. },
  655. createTools: function() {
  656. var toolGroup = "toolGroup";
  657. var printButton = new Ext.Button({
  658. tooltip: this.printTipText,
  659. iconCls: "icon-print",
  660. handler: function() {
  661. var unsupportedLayers = [];
  662. var printWindow = new Ext.Window({
  663. title: this.printWindowTitleText,
  664. modal: true,
  665. border: false,
  666. autoHeight: true,
  667. resizable: false,
  668. items: [{
  669. xtype: "gxux_printpreview",
  670. mapTitle: this.about["title"],
  671. comment: this.about["abstract"],
  672. minWidth: 336,
  673. printMapPanel: {
  674. height: Math.min(450, Ext.get(document.body).getHeight()-150),
  675. autoWidth: true,
  676. limitScales: true,
  677. map: {
  678. controls: [
  679. new OpenLayers.Control.Navigation({
  680. zoomWheelEnabled: false,
  681. zoomBoxEnabled: false
  682. }),
  683. new OpenLayers.Control.PanPanel(),
  684. new OpenLayers.Control.Attribution()
  685. ],
  686. eventListeners: {
  687. "preaddlayer": function(evt) {
  688. if(evt.layer instanceof OpenLayers.Layer.Google) {
  689. unsupportedLayers.push(evt.layer.name);
  690. return false;
  691. }
  692. },
  693. scope: this
  694. }
  695. }
  696. },
  697. printProvider: {
  698. capabilities: window.printCapabilities,
  699. listeners: {
  700. "beforeprint": function() {
  701. // The print module does not like array params.
  702. //TODO Remove when http://trac.geoext.org/ticket/216 is fixed.
  703. printWindow.items.get(0).printMapPanel.layers.each(function(l){
  704. var params = l.getLayer().params;
  705. for(var p in params) {
  706. if (params[p] instanceof Array) {
  707. params[p] = params[p].join(",");
  708. }
  709. }
  710. });
  711. },
  712. "print": function() {printWindow.close();},
  713. "printException": function(cmp, response) {
  714. this.displayXHRTrouble(response);
  715. },
  716. scope: this
  717. }
  718. },
  719. includeLegend: true,
  720. sourceMap: this.mapPanel,
  721. legend: this.legendPanel
  722. }]
  723. }).show();
  724. printWindow.center();
  725. unsupportedLayers.length &&
  726. Ext.Msg.alert(this.unsupportedLayersTitleText, this.unsupportedLayersText +
  727. "<ul><li>" + unsupportedLayers.join("</li><li>") + "</li></ul>");
  728. },
  729. scope: this
  730. });
  731. var enable3DButton = new Ext.Button({
  732. iconCls:"icon-3D",
  733. tooltip: this.switchTo3DActionText,
  734. enableToggle: true,
  735. toggleHandler: function(button, state) {
  736. if (state === true) {
  737. this.mapPanelContainer.getLayout().setActiveItem(1);
  738. this.toolbar.disable();
  739. button.enable();
  740. } else {
  741. this.mapPanelContainer.getLayout().setActiveItem(0);
  742. this.toolbar.enable();
  743. }
  744. },
  745. scope: this
  746. });
  747. var tools = [
  748. new Ext.Button({
  749. tooltip: this.saveMapText,
  750. handler: this.showMetadataForm,
  751. scope: this,
  752. iconCls: "icon-save"
  753. }),
  754. new Ext.Action({
  755. tooltip: this.publishActionText,
  756. handler: this.makeExportDialog,
  757. scope: this,
  758. iconCls: 'icon-export',
  759. disabled: !this.mapID
  760. }),
  761. window.printCapabilities ? printButton : "",
  762. "-",
  763. enable3DButton
  764. ];
  765. this.on("saved", function() {
  766. // enable the "Publish Map" button
  767. tools[1].enable();
  768. this.modified ^= this.modified & 1;
  769. }, this);
  770. return tools;
  771. },
  772. /** private: method[makeExportDialog]
  773. *
  774. * Create a dialog providing the HTML snippet to use for embedding the
  775. * (persisted) map, etc.
  776. */
  777. makeExportDialog: function() {
  778. new Ext.Window({
  779. title: this.publishActionText,
  780. layout: "fit",
  781. width: 380,
  782. autoHeight: true,
  783. items: [{
  784. xtype: "gxp_embedmapdialog",
  785. url: this.rest + this.mapID + "/embed"
  786. }]
  787. }).show();
  788. },
  789. /** private: method[initMetadataForm]
  790. *
  791. * Initialize metadata entry form.
  792. */
  793. initMetadataForm: function(){
  794. var titleField = new Ext.form.TextField({
  795. width: '95%',
  796. fieldLabel: this.metaDataMapTitle,
  797. value: this.about.title,
  798. allowBlank: false,
  799. enableKeyEvents: true,
  800. listeners: {
  801. "valid": function() {
  802. saveAsButton.enable();
  803. saveButton.enable();
  804. },
  805. "invalid": function() {
  806. saveAsButton.disable();
  807. saveButton.disable();
  808. }
  809. }
  810. });
  811. var abstractField = new Ext.form.TextArea({
  812. width: '95%',
  813. height: 200,
  814. fieldLabel: this.metaDataMapAbstract,
  815. value: this.about["abstract"]
  816. });
  817. var metaDataPanel = new Ext.FormPanel({
  818. bodyStyle: {padding: "5px"},
  819. labelAlign: "top",
  820. items: [
  821. titleField,
  822. abstractField
  823. ]
  824. });
  825. metaDataPanel.enable();
  826. var saveAsButton = new Ext.Button({
  827. text: this.metadataFormSaveAsCopyText,
  828. disabled: !this.about.title,
  829. handler: function(e){
  830. this.about.title = titleField.getValue();
  831. this.about["abstract"] = abstractField.getValue();
  832. this.metadataForm.hide();
  833. this.save(true);
  834. },
  835. scope: this
  836. });
  837. var saveButton = new Ext.Button({
  838. text: this.metadataFormSaveText,
  839. disabled: !this.about.title,
  840. handler: function(e){
  841. this.about.title = titleField.getValue();
  842. this.about["abstract"] = abstractField.getValue();
  843. this.metadataForm.hide();
  844. this.save();
  845. },
  846. scope: this
  847. });
  848. this.metadataForm = new Ext.Window({
  849. title: this.metaDataHeader,
  850. closeAction: 'hide',
  851. items: metaDataPanel,
  852. modal: true,
  853. width: 400,
  854. autoHeight: true,
  855. bbar: [
  856. "->",
  857. saveAsButton,
  858. saveButton,
  859. new Ext.Button({
  860. text: this.metadataFormCancelText,
  861. handler: function() {
  862. titleField.setValue(this.about.title);
  863. abstractField.setValue(this.about["abstract"]);
  864. this.metadataForm.hide();
  865. },
  866. scope: this
  867. })
  868. ]
  869. });
  870. },
  871. /** private: method[showMetadataForm]
  872. * Shows the window with a metadata form
  873. */
  874. showMetadataForm: function() {
  875. if(!this.metadataForm) {
  876. this.initMetadataForm();
  877. }
  878. this.metadataForm.show();
  879. },
  880. updateURL: function() {
  881. /* PUT to this url to update an existing map */
  882. return this.rest + this.mapID + '/data';
  883. },
  884. /** api: method[save]
  885. * :arg as: ''Boolean'' True if map should be "Saved as..."
  886. *
  887. * Subclasses that load config asynchronously can override this to load
  888. * any configuration before applyConfig is called.
  889. */
  890. save: function(as){
  891. var config = this.getState();
  892. if (!this.mapID || as) {
  893. /* create a new map */
  894. Ext.Ajax.request({
  895. url: this.rest,
  896. method: 'POST',
  897. jsonData: config,
  898. success: function(response, options) {
  899. var id = response.getResponseHeader("Location");
  900. // trim whitespace to avoid Safari issue where the trailing newline is included
  901. id = id.replace(/^\s*/,'');
  902. id = id.replace(/\s*$/,'');
  903. id = id.match(/[\d]*$/)[0];
  904. this.mapID = id; //id is url, not mapID
  905. this.fireEvent("saved", id);
  906. },
  907. scope: this
  908. });
  909. }
  910. else {
  911. /* save an existing map */
  912. Ext.Ajax.request({
  913. url: this.updateURL(),
  914. method: 'PUT',
  915. jsonData: config,
  916. success: function(response, options) {
  917. /* nothing for now */
  918. this.fireEvent("saved", this.mapID);
  919. },
  920. scope: this
  921. });
  922. }
  923. }
  924. });