diff --git a/src/script/loader.js b/src/script/loader.js index 93b3a370..58ba99a8 100644 --- a/src/script/loader.js +++ b/src/script/loader.js @@ -19,6 +19,7 @@ "widgets/QueryPanel.js", "widgets/StylePropertiesDialog.js", "widgets/WMSLayerPanel.js", + "widgets/StylesDialog.js", "widgets/WMSStylesDialog.js", "widgets/VectorStylesDialog.js", "widgets/NewSourceDialog.js", diff --git a/src/script/plugins/VectorStyleWriter.js b/src/script/plugins/VectorStyleWriter.js index 05418acc..8ce8a303 100644 --- a/src/script/plugins/VectorStyleWriter.js +++ b/src/script/plugins/VectorStyleWriter.js @@ -69,42 +69,29 @@ gxp.plugins.VectorStyleWriter = Ext.extend(gxp.plugins.StyleWriter, { * * scope - ``Object`` A scope to call the ``success`` function with. */ write: function (options) { + var layerRecord = this.target.layerRecord; + var layer = layerRecord.getLayer(); + if (layer.customStyling && layer.features && layer.styleMap && layer.styleMap.styles['default']) { + var layerStyle = layer.styleMap.styles['default']; + + var features = layer.features, feature, featureStyle; + for (var f = 0; f < features.length; f++) { + feature = features[f]; + if (feature.style) { + continue; + } + featureStyle = layerStyle; + if (feature.renderIntent && feature.renderIntent != 'default') { + featureStyle = layer.styleMap.styles[feature.renderIntent ]; + } + // Some features still may not yet have local style object + // assign now from Layer Style before Style permanently changes + feature.style = featureStyle.createSymbolizer(feature); + } + } + this.target.stylesStore.commitChanges(); this.target.fireEvent("saved", this.target, this.target.selectedStyle.get("name")); - return; - -// delete this._failed; -// options = options || {}; -// var dispatchQueue = []; -// var store = this.target.stylesStore; -// store.each(function(rec) { -// (rec.phantom || store.modified.indexOf(rec) !== -1) && -// this.writeStyle(rec, dispatchQueue); -// }, this); -// var success = function() { -// var target = this.target; -// if (this._failed !== true) { -// // we don't need any callbacks for deleting styles. -// this.deleteStyles(); -// var modified = this.target.stylesStore.getModifiedRecords(); -// for (var i=modified.length-1; i>=0; --i) { -// // mark saved -// modified[i].phantom = false; -// } -// target.stylesStore.commitChanges(); -// options.success && options.success.call(options.scope); -// target.fireEvent("saved", target, target.selectedStyle.get("name")); -// } else { -// target.fireEvent("savefailed", target, target.selectedStyle.get("name")); -// } -// }; -// if(dispatchQueue.length > 0) { -// gxp.util.dispatch(dispatchQueue, function() { -// this.assignStyles(options.defaultStyle, success); -// }, this); -// } else { -// this.assignStyles(options.defaultStyle, success); -// } }, /** private: method[writeStyle] @@ -118,45 +105,10 @@ gxp.plugins.VectorStyleWriter = Ext.extend(gxp.plugins.StyleWriter, { */ writeStyle: function (styleRec, dispatchQueue) { var styleName = styleRec.get("userStyle").name; -// dispatchQueue.push(function(callback, storage) { -// Ext.Ajax.request({ -// method: styleRec.phantom === true ? "POST" : "PUT", -// url: this.baseUrl + "/styles" + (styleRec.phantom === true ? -// "" : "/" + styleName + ".xml"), -// headers: { -// "Content-Type": "application/vnd.ogc.sld+xml; charset=UTF-8" -// }, -// xmlData: this.target.createSLD({ -// userStyles: [styleName] -// }), -// failure: function() { -// this._failed = true; -// callback.call(this); -// }, -// success: styleRec.phantom === true ? function(){ -// Ext.Ajax.request({ -// method: "POST", -// url: this.baseUrl + "/layers/" + -// this.target.layerRecord.get("name") + "/styles.json", -// jsonData: { -// "style": { -// "name": styleName -// } -// }, -// failure: function() { -// this._failed = true; -// callback.call(this); -// }, -// success: callback, -// scope: this -// }); -// } : callback, -// scope: this -// }); -// }); }, /** private: method[assignStyles] + * Assigns Style's symbology from (Vector) Layer to Features in Layer. * :arg defaultStyle: ``String`` The default style. Optional. * :arg callback: ``Function`` The function to call when all operations * succeeded. Will be called in the scope of this instance. Optional. @@ -170,7 +122,7 @@ gxp.plugins.VectorStyleWriter = Ext.extend(gxp.plugins.StyleWriter, { var layerStyles = layer.styleMap; var styleRec = this.target.selectedStyle; if (styleRec) { - var oldStyleName = styleRec.get("userStyle").name; + // var oldStyleName = styleRec.get("userStyle").name; var oldStyle = styleRec.get("userStyle"); var newStyle = oldStyle.clone(); newStyle.defaultsPerSymbolizer = false; @@ -181,7 +133,7 @@ gxp.plugins.VectorStyleWriter = Ext.extend(gxp.plugins.StyleWriter, { var symbolizer; if (newStyle.rules) { for (var i = 0, len = newStyle.rules.length; i < len; i++) { - var rule = newStyle.rules[i].clone(); + var rule = newStyle.rules[i]; rule.symbolizer = {}; for (var j = 0; j < rule.symbolizers.length; j++) { @@ -193,12 +145,11 @@ gxp.plugins.VectorStyleWriter = Ext.extend(gxp.plugins.StyleWriter, { textStyle.fontSize = symbolizer.fontSize; textStyle.fontWeight = symbolizer.fontWeight; textStyle.fontStyle = symbolizer.fontStyle; - textStyle.fontColor= symbolizer.fontColor; + textStyle.fontColor = symbolizer.fontColor; } - rule.symbolizer[symbolType] = rule.symbolizers[j]; + rule.symbolizer[symbolType] = rule.symbolizers[j].clone(); // newStyle.label = rule.symbolizer[symbolType].label; } - newStyle.rules[i] = rule; rule.symbolizers = undefined; } } @@ -208,51 +159,29 @@ gxp.plugins.VectorStyleWriter = Ext.extend(gxp.plugins.StyleWriter, { layerStyles.styles[styleName] = newStyle; // newStyle.defaultStyle = undefined; var feature; - layer.eraseFeatures(layer.features); - for (var f = 0; f < layer.features.length; f++) { - feature = layer.features[f]; - // Some features still may have local style object - if (feature.style) { - delete feature.style; +// if (layer.customStyling === true && (!layer.selectedFeatures || layer.selectedFeatures.length == 0)) { +// return; +// } + // Assign Style to all or, if features selected, to individual Layer features + var features = (layer.selectedFeatures && layer.selectedFeatures.length > 0) ? layer.selectedFeatures : layer.features; + layer.eraseFeatures(features); + for (var f = 0; f < features.length; f++) { + feature = features[f]; + + var changeFeatureStyle = !layer.customStyling || (layer.customStyling && ((layer.selectedFeatures && layer.selectedFeatures.length > 0) || !feature.style) && !(feature.renderIntent && feature.renderIntent != 'default')); + if (changeFeatureStyle) { + // Some features still may have local style object + if (feature.style) { + delete feature.style; + } + feature.style = newStyle.createSymbolizer(feature); } - feature.style = newStyle.createSymbolizer(feature); layer.drawFeature(feature); } // layerRecord.store.fireEvent("update", layerRecord.store, layerRecord, Ext.data.Record.EDIT) } - // this.target.stylesStore.each(function(rec) { -// if (!defaultStyle && rec.get("userStyle").isDefault === true) { -// defaultStyle = rec.get("name"); -// } -// if (rec.get("name") !== defaultStyle && -// this.deletedStyles.indexOf(rec.id) === -1) { -// styles.push({"name": rec.get("name")}); -// } -// }, this); -// Ext.Ajax.request({ -// method: "PUT", -// url: this.baseUrl + "/layers/" + -// this.target.layerRecord.get("name") + ".json", -// jsonData: { -// "layer": { -// "defaultStyle": { -// "name": defaultStyle -// }, -// "styles": styles.length > 0 ? { -// "style": styles -// } : {}, -// "enabled": true -// } -// }, -// success: callback, -// failure: function() { -// this._failed = true; -// callback.call(this); -// }, -// scope: this -// }); }, /** private: method[deleteStyles] diff --git a/src/script/widgets/StylesDialog.js b/src/script/widgets/StylesDialog.js new file mode 100644 index 00000000..60e1e07e --- /dev/null +++ b/src/script/widgets/StylesDialog.js @@ -0,0 +1,1218 @@ +/** + * Copyright (c) 2008-2011 The Open Planning Project + * + * Published under the GPL license. + * See https://github.com/opengeo/gxp/raw/master/license.txt for the full text + * of the license. + */ + +/** + * @require util.js + * @require widgets/RulePanel.js + * @require widgets/StylePropertiesDialog.js + * @requires OpenLayers/Renderer/SVG.js + * @requires OpenLayers/Renderer/VML.js + * @requires OpenLayers/Renderer/Canvas.js + * @require OpenLayers/Style2.js + * @require OpenLayers/Format/SLD/v1_0_0_GeoServer.js + * @require GeoExt/data/AttributeStore.js + * @require GeoExt/widgets/WMSLegend.js + * @require GeoExt/widgets/VectorLegend.js + */ + +/** api: (define) + * module = gxp + * class = StylesDialog + * base_link = `Ext.Container `_ + */ +Ext.namespace("gxp"); + +/** api: constructor + * .. class:: StylesDialog(config) + * + * Abstract base class for specific Styling (Vector, WMS) dialog containers. + */ +gxp.StylesDialog = Ext.extend(Ext.Container, { + + /** api: config[addStyleText] (i18n) */ + addStyleText: "Add", + /** api: config[addStyleTip] (i18n) */ + addStyleTip: "Add a new style", + /** api: config[chooseStyleText] (i18n) */ + chooseStyleText: "Choose style", + /** api: config[addStyleText] (i18n) */ + deleteStyleText: "Remove", + /** api: config[addStyleTip] (i18n) */ + deleteStyleTip: "Delete the selected style", + /** api: config[addStyleText] (i18n) */ + editStyleText: "Edit", + /** api: config[addStyleTip] (i18n) */ + editStyleTip: "Edit the selected style", + /** api: config[addStyleText] (i18n) */ + duplicateStyleText: "Duplicate", + /** api: config[addStyleTip] (i18n) */ + duplicateStyleTip: "Duplicate the selected style", + /** api: config[addStyleText] (i18n) */ + addRuleText: "Add", + /** api: config[addStyleTip] (i18n) */ + addRuleTip: "Add a new rule", + /** api: config[newRuleText] (i18n) */ + newRuleText: "New Rule", + /** api: config[addStyleText] (i18n) */ + deleteRuleText: "Remove", + /** api: config[addStyleTip] (i18n) */ + deleteRuleTip: "Delete the selected rule", + /** api: config[addStyleText] (i18n) */ + editRuleText: "Edit", + /** api: config[addStyleTip] (i18n) */ + editRuleTip: "Edit the selected rule", + /** api: config[addStyleText] (i18n) */ + duplicateRuleText: "Duplicate", + /** api: config[addStyleTip] (i18n) */ + duplicateRuleTip: "Duplicate the selected rule", + /** api: config[cancelText] (i18n) */ + cancelText: "Cancel", + /** api: config[saveText] (i18n) */ + saveText: "Save", + /** api: config[stylePropertiesWindowTitle] (i18n) */ + styleWindowTitle: "User Style: {0}", + /** api: config[ruleWindowTitle] (i18n) */ + ruleWindowTitle: "Style Rule: {0}", + /** api: config[stylesFieldsetTitle] (i18n) */ + stylesFieldsetTitle: "Styles", + /** api: config[rulesFieldsetTitle] (i18n) */ + rulesFieldsetTitle: "Rules", + /** api: config[errorTitle] (i18n) */ + errorTitle: "Error saving style", + /** api: config[errorMsg] (i18n) */ + errorMsg: "There was an error saving the style back to the server.", + + //TODO create a StylesStore which can read styles using GetStyles. Create + // subclasses for that store with writing capabilities, e.g. + // for GeoServer's RESTconfig API. This should replace the current + // StyleWriter plugins. + + /** api: config[layerRecord] + * ``GeoExt.data.LayerRecord`` The layer to edit/select styles for. + */ + + /** private: property[layerRecord] + * ``GeoExt.data.LayerRecord`` The layer to edit/select styles for. + */ + layerRecord: null, + + /** api: config[styleName] + * ``String`` A style's name to select in the styles combo box. Optional. + * If not provided, the layer's current style will be selected. + */ + + /** api: config[stylesComboOptions] + * ``Object`` configuration options to pass to the styles combo of this + * dialog. Optional. + */ + + /** api: config[layerDescription] + * ``Object`` Array entry of a DescribeLayer response as read by + * ``OpenLayers.Format.WMSDescribeLayer``. Optional. If not provided, + * a DescribeLayer request will be issued to the WMS. + */ + + /** private: property[layerDescription] + * ``Object`` Array entry of a DescribeLayer response as read by + * ``OpenLayers.Format.WMSDescribeLayer``. + */ + layerDescription: null, + + /** private: property[symbolType] + * ``Point`` or ``Line`` or ``Polygon`` - the primary symbol type for the + * layer. This is the symbolizer type of the first symbolizer of the + * first rule of the current layer style. Only available if the WMS + * supports GetStyles. + */ + symbolType: null, + + /** api: property[stylesStore] + * ``Ext.data.Store`` A store representing the styles returned from + * GetCapabilities and GetStyles. It has "name", "title", "abstract", + * "legend" and "userStyle" fields. If the WMS supports GetStyles, the + * "legend" field will not be available. If it does not, the "userStyle" + * field will not be available. + */ + stylesStore: null, + + /** api: property[selectedStyle] + * ``Ext.data.Record`` The currently selected style from the + * ``stylesStore``. + */ + selectedStyle: null, + + /** private: property[selectedRule] + * ``OpenLayers.Rule`` The currently selected rule, or null if none + * selected. + */ + selectedRule: null, + + /** api: config[editable] + * ``Boolean`` Set to false if styles should not be editable. Default is + * true. + */ + + /** api: property[editable] + * ``Boolean`` Read-only once the dialog is rendered. True if this + * component could gather enough information to allow styles being edited, + * false otherwise. This is not supposed to be read before the + * ``ready`` event is fired. + */ + editable: true, + + /** private: property[modified] + * ``Boolean`` Will be true if styles were modified. Initial state is + * false. + */ + modified: false, + + /** private: config[dialogCls] + * ``Ext.Component`` The dialogue class to use. Default is ``Ext.Window``. + * If using e.g. ``Ext.Container``, override the ``showDlg`` method to + * add the dialogue to a container. + */ + dialogCls: Ext.Window, + + /** private: method[initComponent] + */ + initComponent: function() { + this.addEvents( + /** api: event[ready] + * Fires when this component is ready for user interaction. + */ + "ready", + + /** api: event[modified] + * Fires on every style modification. + * + * Listener arguments: + * + * * :class:`gxp.StylesDialog` this component + * * ``String`` the name of the modified style + */ + "modified", + + /** api: event[styleselected] + * Fires whenever an existing style is selected from this dialog's + * Style combo box. + * + * Listener arguments: + * + * * :class:`gxp.StylesDialog` this component + * * ``String`` the name of the selected style + */ + "styleselected", + + /** api: event[beforesaved] + * Fires before the styles are saved (using a + * :class:`gxp.plugins.StyleWriter` plugin) + * + * Listener arguments: + * + * * :class:`gxp.StylesDialog` this component + * * ``Object`` options for the ``write`` method of the + * :class:`gxp.plugins.StyleWriter` + */ + "beforesaved", + + /** api: event[saved] + * Fires when a style was successfully saved. Applications should + * listen for this event and redraw layers with the currently + * selected style. + * + * Listener arguments: + * + * * :class:`gxp.StylesDialog` this component + * * ``String`` the name of the currently selected style + */ + "saved" + ); + + var defConfig = { + layout: "form", + disabled: true, + items: [{ + xtype: "fieldset", + title: this.stylesFieldsetTitle, + labelWidth: 85, + style: "margin-bottom: 0;" + }, { + xtype: "toolbar", + style: "border-width: 0 1px 1px 1px; margin-bottom: 10px;", + items: [ + { + xtype: "button", + iconCls: "add", + text: this.addStyleText, + tooltip: this.addStyleTip, + handler: this.addStyle, + scope: this + }, { + xtype: "button", + iconCls: "delete", + text: this.deleteStyleText, + tooltip: this.deleteStyleTip, + handler: function() { + this.stylesStore.remove(this.selectedStyle); + }, + scope: this + }, { + xtype: "button", + iconCls: "edit", + text: this.editStyleText, + tooltip: this.editStyleTip, + handler: function() { + this.editStyle(); + }, + scope: this + }, { + xtype: "button", + iconCls: "duplicate", + text: this.duplicateStyleText, + tooltip: this.duplicateStyleTip, + handler: function() { + var prevStyle = this.selectedStyle; + var newStyle = prevStyle.get( + "userStyle").clone(); + newStyle.isDefault = false; + newStyle.name = this.newStyleName(); + var store = this.stylesStore; + store.add(new store.recordType({ + "name": newStyle.name, + "title": newStyle.title, + "abstract": newStyle.description, + "userStyle": newStyle + })); + this.editStyle(prevStyle); + }, + scope: this + } + ] + }] + }; + Ext.applyIf(this, defConfig); + + this.createStylesStore(); + + this.on({ + "beforesaved": function() { this._saving = true; }, + "saved": function() { delete this._saving; }, + "savefailed": function() { + Ext.Msg.show({ + title: this.errorTitle, + msg: this.errorMsg, + icon: Ext.MessageBox.ERROR, + buttons: {ok: true} + }); + delete this._saving; + }, + "render": function() { + gxp.util.dispatch([this.getStyles], function() { + this.enable(); + }, this); + }, + scope: this + }); + + gxp.StylesDialog.superclass.initComponent.apply(this, arguments); + }, + + /** api: method[addStyle] + * Creates a new style and selects it in the styles combo. + */ + addStyle: function() { + if(!this._ready) { + this.on("ready", this.addStyle, this); + return; + } + var prevStyle = this.selectedStyle; + var store = this.stylesStore; + var newStyle = new OpenLayers.Style(null, { + name: this.newStyleName(), + rules: [this.createRule()] + }); + store.add(new store.recordType({ + "name": newStyle.name, + "userStyle": newStyle + })); + this.editStyle(prevStyle); + }, + + /** api: method[editStyle] + * :arg prevStyle: ``Ext.data.Record`` + * + * Edit the currently selected style. + */ + editStyle: function(prevStyle) { + var userStyle = this.selectedStyle.get("userStyle"); + var buttonCfg = { + bbar: ["->", { + text: this.cancelText, + iconCls: "cancel", + handler: function() { + styleProperties.propertiesDialog.userStyle = userStyle; + styleProperties.destroy(); + if (prevStyle) { + this._cancelling = true; + this.stylesStore.remove(this.selectedStyle); + this.changeStyle(prevStyle, { + updateCombo: true, + markModified: true + }); + delete this._cancelling; + } + }, + scope: this + }, { + text: this.saveText, + iconCls: "save", + handler: function() { + styleProperties.destroy(); + } + }] + }; + var styleProperties = new this.dialogCls(Ext.apply(buttonCfg, { + title: String.format(this.styleWindowTitle, + userStyle.title || userStyle.name), + shortTitle: userStyle.title || userStyle.name, + bodyBorder: false, + autoHeight: true, + width: 300, + modal: true, + items: { + border: false, + items: { + xtype: "gxp_stylepropertiesdialog", + ref: "../propertiesDialog", + userStyle: userStyle.clone(), + nameEditable: false, + style: "padding: 10px;" + } + }, + listeners: { + "beforedestroy": function() { + this.selectedStyle.set( + "userStyle", + styleProperties.propertiesDialog.userStyle); + }, + scope: this + } + })); + this.showDlg(styleProperties); + }, + + /** api: method[createSLD] + * :arg options: ``Object`` + * :return: ``String`` The current SLD for the NamedLayer. + * + * Supported ``options``: + * + * * userStyles - ``Array(String)`` list of userStyles (by name) that are + * to be included in the SLD. By default, all will be included. + */ + createSLD: function(options) { + options = options || {}; + var sld = { + version: "1.0.0", + namedLayers: {} + }; + var layerName = this.layerRecord.get("name"); + sld.namedLayers[layerName] = { + name: layerName, + userStyles: [] + }; + this.stylesStore.each(function(r) { + if(!options.userStyles || + options.userStyles.indexOf(r.get("name")) !== -1) { + sld.namedLayers[layerName].userStyles.push(r.get("userStyle")); + } + }); + return new OpenLayers.Format.SLD({ + multipleSymbolizers: true, + profile: "GeoServer" + }).write(sld); + }, + + /** api: method[saveStyles] + * :arg options: ``Object`` Options to pass to the + * :class:`gxp.plugins.StyleWriter` plugin + * + * Saves the styles. Without a :class:`gxp.plugins.StyleWriter` plugin + * configured for this instance, nothing will happen. + */ + saveStyles: function(options) { + this.modified === true && this.fireEvent("beforesaved", this, options); + }, + + /** private: method[updateStyleRemoveButton] + * Enable/disable the "Remove" button to make sure that we don't delete + * the last style. + */ + updateStyleRemoveButton: function() { + var userStyle = this.selectedStyle && + this.selectedStyle.get("userStyle"); + this.items.get(1).items.get(1).setDisabled(!userStyle || + this.stylesStore.getCount() <= 1 || userStyle.isDefault === true); + }, + + /** private: method[updateRuleRemoveButton] + * Enable/disable the "Remove" button to make sure that we don't delete + * the last rule. + */ + updateRuleRemoveButton: function() { + this.items.get(3).items.get(1).setDisabled( + !this.selectedRule || this.items.get(2).items.get(0).rules.length < 2 + ); + }, + + /** private: method[createRule] + */ + createRule: function() { + return new OpenLayers.Rule({ + symbolizers: [new OpenLayers.Symbolizer[this.symbolType]] + }); + }, + + /** private: method[addRulesFieldSet] + * :return: ``Ext.form.FieldSet`` + * + * Creates the rules fieldSet and adds it to this container. + */ + addRulesFieldSet: function() { + var rulesFieldSet = new Ext.form.FieldSet({ + itemId: "rulesfieldset", + title: this.rulesFieldsetTitle, + autoScroll: true, + style: "margin-bottom: 0;", + hideMode: "offsets", + hidden: true + }); + var rulesToolbar = new Ext.Toolbar({ + style: "border-width: 0 1px 1px 1px;", + hidden: true, + items: [ + { + xtype: "button", + iconCls: "add", + text: this.addRuleText, + tooltip: this.addRuleTip, + handler: this.addRule, + scope: this + }, { + xtype: "button", + iconCls: "delete", + text: this.deleteRuleText, + tooltip: this.deleteRuleTip, + handler: this.removeRule, + scope: this, + disabled: true + }, { + xtype: "button", + iconCls: "edit", + text: this.editRuleText, + toolitp: this.editRuleTip, + handler: function() { + this.layerDescription ? + this.editRule() : + this.describeLayer(this.editRule); + }, + scope: this, + disabled: true + }, { + xtype: "button", + iconCls: "duplicate", + text: this.duplicateRuleText, + tip: this.duplicateRuleTip, + handler: this.duplicateRule, + scope: this, + disabled: true + } + ] + }); + this.add(rulesFieldSet, rulesToolbar); + this.doLayout(); + return rulesFieldSet; + }, + + /** private: method[addRule] + */ + addRule: function() { + var legend = this.items.get(2).items.get(0); + this.selectedStyle.get("userStyle").rules.push( + this.createRule() + ); + legend.update(); + // mark the style as modified + this.selectedStyle.store.afterEdit(this.selectedStyle); + this.updateRuleRemoveButton(); + }, + + /** private: method[removeRule] + */ + removeRule: function() { + var selectedRule = this.selectedRule; + this.items.get(2).items.get(0).unselect(); + this.selectedStyle.get("userStyle").rules.remove(selectedRule); + // mark the style as modified + this.afterRuleChange(); + }, + + /** private: method[duplicateRule] + */ + duplicateRule: function() { + var legend = this.items.get(2).items.get(0); + var newRule = this.selectedRule.clone(); + this.selectedStyle.get("userStyle").rules.push( + newRule + ); + legend.update(); + // mark the style as modified + this.selectedStyle.store.afterEdit(this.selectedStyle); + this.updateRuleRemoveButton(); + }, + + /** private: method[editRule] + */ + editRule: function() { + var rule = this.selectedRule; + var origRule = rule.clone(); + + var ruleDlg = new this.dialogCls({ + title: String.format(this.ruleWindowTitle, + rule.title || rule.name || this.newRuleText), + shortTitle: rule.title || rule.name || this.newRuleText, + layout: "fit", + width: 320, + height: 450, + modal: true, + items: [{ + xtype: "gxp_rulepanel", + ref: "rulePanel", + symbolType: this.symbolType, + rule: rule, + attributes: new GeoExt.data.AttributeStore({ + url: this.layerDescription.owsURL, + baseParams: { + "SERVICE": this.layerDescription.owsType, + "REQUEST": "DescribeFeatureType", + "TYPENAME": this.layerDescription.typeName + }, + method: "GET", + disableCaching: false + }), + autoScroll: true, + border: false, + defaults: { + autoHeight: true, + hideMode: "offsets" + }, + listeners: { + "change": this.saveRule, + "tabchange": function() { + if (ruleDlg instanceof Ext.Window) { + ruleDlg.syncShadow(); + } + }, + scope: this + } + }], + bbar: ["->", { + text: this.cancelText, + iconCls: "cancel", + handler: function() { + this.saveRule(ruleDlg.rulePanel, origRule); + ruleDlg.destroy(); + }, + scope: this + }, { + text: this.saveText, + iconCls: "save", + handler: function() { ruleDlg.destroy(); } + }] + }); + this.showDlg(ruleDlg); + }, + + /** private: method[saveRule] + * :arg cmp: + * :arg rule: the rule to save back to the userStyle + */ + saveRule: function(cmp, rule) { + var style = this.selectedStyle; + var legend = this.items.get(2).items.get(0); + var userStyle = style.get("userStyle"); + var i = userStyle.rules.indexOf(this.selectedRule); + userStyle.rules[i] = rule; + this.afterRuleChange(rule); + }, + + /** private: method[afterRuleChange] + * :arg rule: the rule to set as selectedRule, can be null + * + * Performs actions that are required to update the selectedRule and + * selectedStyle after a rule was changed. + */ + afterRuleChange: function(rule) { + var legend = this.items.get(2).items.get(0); + this.selectedRule = rule; + // mark the style as modified + this.selectedStyle.store.afterEdit(this.selectedStyle); + }, + + /** private: method[setRulesFieldSetVisible] + * :arg visible: ``Boolean`` + * + * Sets the visibility of the rules fieldset + */ + setRulesFieldSetVisible: function(visible) { + // the toolbar + this.items.get(3).setVisible(visible && this.editable); + // and the fieldset itself + this.items.get(2).setVisible(visible); + this.doLayout(); + }, + + /** private: method[parseSLD] + * :arg response: ``Object`` + * :arg options: ``Object`` + * + * Success handler for the GetStyles response. Includes a fallback + * to GetLegendGraphic if no valid SLD is returned. + */ + parseSLD: function(response, options) { + var data = response.responseXML; + if (!data || !data.documentElement) { + data = new OpenLayers.Format.XML().read(response.responseText); + } + var layerParams = this.layerRecord.getLayer().params; + + var initialStyle = this.initialConfig.styleName || layerParams.STYLES; + if (initialStyle) { + this.selectedStyle = this.stylesStore.getAt( + this.stylesStore.findExact("name", initialStyle)); + } + + var format = new OpenLayers.Format.SLD({profile: "GeoServer", multipleSymbolizers: true}); + + try { + var sld = format.read(data); + + // add userStyle objects to the stylesStore + //TODO this only works if the LAYERS param contains one layer + var userStyles = sld.namedLayers[layerParams.LAYERS].userStyles; + + // add styles from the layer's SLD_BODY *after* the userStyles + var inlineStyles; + if (layerParams.SLD_BODY) { + var sldBody = format.read(layerParams.SLD_BODY); + inlineStyles = sldBody.namedLayers[layerParams.LAYERS].userStyles; + Array.prototype.push.apply(userStyles, inlineStyles); + } + + // our stylesStore comes from the layerRecord's styles - clear it + // and repopulate from GetStyles + this.stylesStore.removeAll(); + this.selectedStyle = null; + + var userStyle, record, index, defaultStyle; + for (var i=0, len=userStyles.length; i=0; --i) { + rec = records[i]; + store.suspendEvents(); + rec.get("title") || rec.set("title", rec.get("name")); + store.resumeEvents(); + } + } + } + }); + }, + + /** private: method[getStyles] + * :arg callback: ``Function`` function that will be called when the + * request result was returned. + */ + getStyles: function(callback) { + var layer = this.layerRecord.getLayer(); + if(this.editable === true) { + var version = layer.params["VERSION"]; + if (parseFloat(version) > 1.1) { + //TODO don't force 1.1.1, fall back instead + version = "1.1.1"; + } + Ext.Ajax.request({ + url: layer.url, + params: { + "SERVICE": "WMS", + "VERSION": version, + "REQUEST": "GetStyles", + "LAYERS": [layer.params["LAYERS"]].join(",") + }, + method: "GET", + disableCaching: false, + success: this.parseSLD, + failure: this.setupNonEditable, + callback: callback, + scope: this + }); + } else { + this.setupNonEditable(); + } + }, + + /** private: method[describeLayer] + * :arg callback: ``Function`` function that will be called when the + * request result was returned. + */ + describeLayer: function(callback) { + if (this.layerDescription) { + // always return before calling callback + window.setTimeout(function() { + callback.call(this); + }, 0); + } else { + var layer = this.layerRecord.getLayer(); + var version = layer.params["VERSION"]; + if (parseFloat(version) > 1.1) { + //TODO don't force 1.1.1, fall back instead + version = "1.1.1"; + } + Ext.Ajax.request({ + url: layer.url, + params: { + "SERVICE": "WMS", + "VERSION": version, + "REQUEST": "DescribeLayer", + "LAYERS": [layer.params["LAYERS"]].join(",") + }, + method: "GET", + disableCaching: false, + success: function(response) { + var result = new OpenLayers.Format.WMSDescribeLayer().read( + response.responseXML && response.responseXML.documentElement ? + response.responseXML : response.responseText); + this.layerDescription = result[0]; + }, + callback: callback, + scope: this + }); + } + }, + + /** private: method[addStylesCombo] + * + * Adds a combo box with the available style names found for the layer + * in the capabilities document to this component's stylesFieldset. + */ + addStylesCombo: function() { + var store = this.stylesStore; + var combo = new Ext.form.ComboBox(Ext.apply({ + fieldLabel: this.chooseStyleText, + store: store, + editable: false, + displayField: "title", + valueField: "name", + value: this.selectedStyle ? + this.selectedStyle.get("title") : + this.layerRecord.getLayer().params.STYLES || "default", + disabled: !store.getCount(), + mode: "local", + typeAhead: true, + triggerAction: "all", + forceSelection: true, + anchor: "100%", + listeners: { + "select": function(combo, record) { + this.changeStyle(record); + if (!record.phantom && !this._removing) { + this.fireEvent("styleselected", this, record.get("name")); + } + }, + scope: this + } + }, this.initialConfig.stylesComboOptions)); + // add combo to the styles fieldset + this.items.get(0).add(combo); + this.doLayout(); + }, + + /** private: method[createLegendImage] + * :return: ``GeoExt.LegendImage`` or undefined if none available. + * + * Creates a legend image for the first style of the current layer. This + * is used when GetStyles is not available from the layer's WMS. + */ + createLegendImage: function() { + var legend = new GeoExt.WMSLegend({ + showTitle: false, + layerRecord: this.layerRecord, + autoScroll: true, + defaults: { + listeners: { + "render": function(cmp) { + cmp.getEl().on({ + load: function(evt, img) { + if (img.getAttribute("src") != cmp.defaultImgSrc) { + this.setRulesFieldSetVisible(true); + if (cmp.getEl().getHeight() > 250) { + legend.setHeight(250); + } + } + }, + "error": function() { + this.setRulesFieldSetVisible(false); + }, + scope: this + }); + }, + scope: this + } + } + }); + return legend; + }, + + /** api: method[changeStyle] + * :arg value: ``Ext.data.Record`` + * :arg options: ``Object`` Additional options for this method. + * + * Available options: + * * updateCombo - ``Boolean`` set to true to update the combo box + * * markModified - ``Boolean`` set to true to mark the dialog modified + * + * Handler for the stylesCombo's ``select`` and the store's ``update`` + * event. Updates the layer and the rules fieldset. + */ + changeStyle: function(record, options) { + options = options || {}; + var legend = this.items.get(2).items.get(0); + this.selectedStyle = record; + this.updateStyleRemoveButton(); + var styleName = record.get("name"); + + if (this.editable === true) { + var userStyle = record.get("userStyle"); + if (userStyle.isDefault === true) { + styleName = ""; + } + var ruleIdx = legend.rules.indexOf(this.selectedRule); + // replace the legend + legend.ownerCt.remove(legend); + this.createLegend(userStyle.rules, {selectedRuleIndex: ruleIdx}); + } + if (options.updateCombo === true) { + // update the combo's value with the new name + this.items.get(0).items.get(0).setValue(userStyle.name); + options.markModified === true && this.markModified(); + } + }, + + /** private: method[addVectorLegend] + * :arg rules: ``Array`` + * :arg options: ``Object`` + * :return: ``GeoExt.VectorLegend`` the legend that was created + * + * Creates the vector legend for the provided rules and adds it to the + * rules fieldset. + */ + addVectorLegend: function(rules, options) { + options = Ext.applyIf(options || {}, {enableDD: true}); + + this.symbolType = options.symbolType; + if (!this.symbolType) { + var typeHierarchy = ["Point", "Line", "Polygon"]; + // use the highest symbolizer type of the 1st rule + var highest = 0; + var symbolizers = rules[0].symbolizers, symbolType; + for (var i=symbolizers.length-1; i>=0; i--) { + symbolType = symbolizers[i].CLASS_NAME.split(".").pop(); + highest = Math.max(highest, typeHierarchy.indexOf(symbolType)); + } + this.symbolType = typeHierarchy[highest]; + } + var legend = this.items.get(2).add({ + xtype: "gx_vectorlegend", + showTitle: false, + height: rules.length > 10 ? 250 : undefined, + autoScroll: rules.length > 10, + rules: rules, + symbolType: this.symbolType, + selectOnClick: true, + enableDD: options.enableDD, + listeners: { + "ruleselected": function(cmp, rule) { + this.selectedRule = rule; + // enable the Remove, Edit and Duplicate buttons + var tbItems = this.items.get(3).items; + this.updateRuleRemoveButton(); + tbItems.get(2).enable(); + tbItems.get(3).enable(); + // cmp.items.get(0).focus(); + }, + "ruleunselected": function(cmp, rule) { + this.selectedRule = null; + // disable the Remove, Edit and Duplicate buttons + var tbItems = this.items.get(3).items; + tbItems.get(1).disable(); + tbItems.get(2).disable(); + tbItems.get(3).disable(); + }, + "rulemoved": function() { + this.markModified(); + }, + "afterlayout": function() { + // restore selection + //TODO QA: avoid accessing private properties/methods + if (this.selectedRule !== null && + legend.selectedRule === null && + legend.rules.indexOf(this.selectedRule) !== -1) { + legend.selectRuleEntry(this.selectedRule); + } + }, + scope: this + } + }); + this.setRulesFieldSetVisible(true); + return legend; + }, + + newStyleName: function() { + var layerName = this.layerRecord.get("name"); + return layerName.split(":").pop() + "_" + + gxp.util.md5(layerName + new Date() + Math.random()).substr(0, 8); + }, + + /** private: method[showDlg] + * :arg dlg: + * + * Shows a subdialog + */ + showDlg: function(dlg) { + dlg.show(); + } + +}); + +/** api: function[createGeoServerStylerConfig] + * :arg layerRecord: ``GeoExt.data.LayerRecord`` Layer record to configure the + * dialog for. + * :arg url: ``String`` Optional. Custaom URL for the GeoServer REST endpoint + * for writing styles. + * + * Creates a configuration object for a :class:`gxp.StylesDialog` with a + * :class:`gxp.plugins.GeoServerStyleWriter` plugin and listeners for the + * "styleselected", "modified" and "saved" events that take care of saving + * styles and keeping the layer view updated. + */ +gxp.StylesDialog.createGeoServerStylerConfig = function(layerRecord, url) { + var layer = layerRecord.getLayer(); + if (!url) { + url = layerRecord.get("restUrl"); + } + if (!url) { + url = layer.url.split("?").shift().replace(/\/(wms|ows)\/?$/, "/rest"); + } + return { + xtype: "gxp_stylesdialog", + layerRecord: layerRecord, + plugins: [{ + ptype: "gxp_geoserverstylewriter", + baseUrl: url + }], + listeners: { + "styleselected": function(cmp, style) { + layer.mergeNewParams({ + styles: style + }); + }, + "modified": function(cmp, style) { + cmp.saveStyles(); + }, + "saved": function(cmp, style) { + layer.mergeNewParams({ + _olSalt: Math.random(), + styles: style + }); + }, + scope: this + } + }; +}; + +// set SLD defaults for symbolizer +OpenLayers.Renderer.defaultSymbolizerGXP = { + fillColor: "#808080", + fillOpacity: 1, + strokeColor: "#000000", + strokeOpacity: 1, + strokeWidth: 1, + strokeDashstyle: "solid", + pointRadius: 3, + graphicName: "square", + fontColor: "#000000", + fontSize: 10, + haloColor: "#FFFFFF", + haloOpacity: 1, + haloRadius: 1, + labelAlign: 'cm' +}; + +/** api: xtype = gxp_stylesdialog */ +Ext.reg('gxp_stylesdialog', gxp.StylesDialog); diff --git a/src/script/widgets/VectorStylesDialog.js b/src/script/widgets/VectorStylesDialog.js index 7e330495..bb5131f8 100644 --- a/src/script/widgets/VectorStylesDialog.js +++ b/src/script/widgets/VectorStylesDialog.js @@ -34,7 +34,7 @@ Ext.namespace("gxp"); * Extend the GXP WMSStylesDialog to work with Vector Layers * that originate from a WFS or local OpenLayers Features from upload or drawing. */ -gxp.VectorStylesDialog = Ext.extend(gxp.WMSStylesDialog, { +gxp.VectorStylesDialog = Ext.extend(gxp.StylesDialog, { /** private: method[initComponent] */ @@ -44,6 +44,7 @@ gxp.VectorStylesDialog = Ext.extend(gxp.WMSStylesDialog, { // We cannot create/delete new styles for Vector Layers (StyleMap restriction) // this.items.removeAt(1); this.initialConfig.styleName = 'default'; +// this.items.get(0).setDisabled(true); this.items.get(1).setDisabled(true); this.on({ @@ -108,8 +109,10 @@ gxp.VectorStylesDialog = Ext.extend(gxp.WMSStylesDialog, { // explicitly create reader // id for each record will be the first element} reader: new Ext.data.ArrayReader( - {idIndex: 0}, - Ext.data.Record.create([{name: 'name'}]) + {idIndex: 0}, + Ext.data.Record.create([ + {name: 'name'} + ]) ) }); @@ -124,12 +127,13 @@ gxp.VectorStylesDialog = Ext.extend(gxp.WMSStylesDialog, { } attributeStore.loadData(myData); // Silence the proxy (must be better way...) - attributeStore.proxy = {request: function () {}}; + attributeStore.proxy = {request: function () { + }}; } var ruleDlg = new this.dialogCls({ title: String.format(this.ruleWindowTitle, - rule.title || rule.name || this.newRuleText), + rule.title || rule.name || this.newRuleText), shortTitle: rule.title || rule.name || this.newRuleText, layout: "fit", width: 320, @@ -138,7 +142,7 @@ gxp.VectorStylesDialog = Ext.extend(gxp.WMSStylesDialog, { pageY: 100, modal: true, listeners: { - hide: function() { + hide: function () { if (gxp.ColorManager.pickerWin) { gxp.ColorManager.pickerWin.hide(); } @@ -149,7 +153,7 @@ gxp.VectorStylesDialog = Ext.extend(gxp.WMSStylesDialog, { { xtype: "gxp_rulepanel", ref: "rulePanel", - symbolType: this.symbolType, + symbolType: rule.symbolType ? rule.symbolType : this.symbolType, rule: rule, attributes: attributeStore, autoScroll: true, @@ -188,53 +192,81 @@ gxp.VectorStylesDialog = Ext.extend(gxp.WMSStylesDialog, { this.showDlg(ruleDlg); }, + /** private: method[createSymbolizer] + * Create OpenLayers Symbolizer object. + * :arg symbol: ``String`` symbolizer type: 'Point', 'Line' or 'Polygon'. + * :arg styleHash: ``Object`` object with OpenLayers Style properties. + */ + createSymbolizer: function (symbol, styleHash) { + var Type = eval('OpenLayers.Symbolizer.' + symbol); + return new Type(styleHash); + }, /** private: method[prepareStyle] * :arg style: ``Style`` object to be cloned and prepared for GXP editing. */ - prepareStyle: function (layer, style, name) { - style = style.clone(); - style.isDefault = name === 'default'; + prepareStyle: function (layer, styl, name) { + // Makes deep copy + var style = styl.clone(); + style.isDefault = (name === 'default'); style.name = name; style.title = name + ' style'; style.description = name + ' style for this layer'; style.layerName = layer.name; + var symbolizers = [], symbolizer, symbol, rule; if (style.rules && style.rules.length > 0) { - for (var i = 0, len = style.rules.length; i < len; i++) { - var rule = style.rules[i]; - rule.symbolizers = []; - - for (var symbol in rule.symbolizer) { - var symbolizer = rule.symbolizer[symbol]; - if (symbolizer.CLASS_NAME && symbolizer.CLASS_NAME.indexOf('OpenLayers.Symbolizer.') > 0) { - ; - } else if (symbolizer instanceof Object) { + for (var i = 0; i < style.rules.length; i++) { + rule = style.rules[i]; + symbolizers = []; + + // GXP Style Editing needs symbolizers array in Rule object + // while Vector/Style drawing needs symbolizer hash, so convert for GXP here. + for (symbol in rule.symbolizer) { + symbolizer = rule.symbolizer[symbol]; + if (!symbolizer.CLASS_NAME) { // In some cases the symbolizer may be a hash: create corresponding class object - symbolizer = Heron.Utils.createOLObject(['OpenLayers.Symbolizer.' + symbol, symbolizer]); + symbolizer = this.createSymbolizer(symbol, symbolizer); + } else { + symbolizer = symbolizer.clone(); } - rule.symbolizers.push(symbolizer); + symbolizers.push(symbolizer); } + rule.symbolizers = symbolizers; rule.symbolizer = undefined; } - - } else { + // style.defaultsPerSymbolizer = true; + + } else if (layer.customStyling) { + // One rule per symbol for custom styling, also for Layers with more geom-types + var symbols = ['Point', 'Line', 'Polygon']; + style.rules = []; + var symbolizerStyle = style.defaultStyle; + for (var s = 0; s < symbols.length; s++) { + symbol = symbols[s]; + rule = new OpenLayers.Rule({title: symbol, symbolType: symbol, symbolizers: [this.createSymbolizer(symbol, symbolizerStyle)]}); + style.rules.push(rule); + } + style.defaultsPerSymbolizer = false; + } + else { // GXP Style Editing needs symbolizers array in Rule object // while Vector/Style drawing needs symbolizer hash... - var symbolizer = new OpenLayers.Symbolizer.Polygon(style.defaultStyle); + symbol = 'Polygon'; if (layer && layer.features && layer.features.length > 0) { - var geom = layer.features[0].geometry; - if (geom) { - if (geom.CLASS_NAME.indexOf('Point') > 0) { - symbolizer = new OpenLayers.Symbolizer.Point(style.defaultStyle); - } else if (geom.CLASS_NAME.indexOf('Line') > 0) { - symbolizer = new OpenLayers.Symbolizer.Line(style.defaultStyle); - } - } - } - var symbolizers = [symbolizer]; + var geom = layer.features[0].geometry; + if (geom) { + if (geom.CLASS_NAME.indexOf('Point') > 0) { + symbol = 'Point'; + } else if (geom.CLASS_NAME.indexOf('Line') > 0) { + symbol = 'Line'; + } + } + } + symbolizer = this.createSymbolizer(symbol, style.defaultStyle); + symbolizers = [symbolizer]; style.rules = [new OpenLayers.Rule({title: style.name, symbolizers: symbolizers})]; - style.defaultsPerSymbolizer = true; + // style.defaultsPerSymbolizer = true; } return style; }, @@ -292,7 +324,7 @@ gxp.VectorStylesDialog = Ext.extend(gxp.WMSStylesDialog, { this.stylesStore.add(record); // set the default style if no STYLES param is set on the layer if (!this.selectedStyle && (initialStyle === userStyle.name || - (!initialStyle && userStyle.isDefault === true))) { + (!initialStyle && userStyle.isDefault === true))) { this.selectedStyle = record; } } @@ -408,7 +440,7 @@ gxp.VectorStylesDialog.createVectorStylerConfig = function (layerRecord) { layerRecord: layerRecord, listeners: { hide: function () { - alert('hode'); + alert('hide'); } }, plugins: [ @@ -482,13 +514,72 @@ Ext.override(Ext.ColorPalette, { ] }); -(function() { +Ext.override(gxp.form.ColorField, { + + /** private: method[expand3DigitHex] + * :returns: ``String`` A RGB 6-digit hex color string. + * + * Return the 6-digit RGB hex representation for a shorthand 3-digit hex color. + * See http://en.wikipedia.org/wiki/Web_colors + * + */ + expand3DigitHex: function (color) { + if (color && color.length == 4 && color.indexOf('#') == 0) { + // For example #37f becomes #3377ff + color = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3]; + } + return color; + }, + + /** private: method[colorToHex] + * :returns: ``String`` A RGB hex color string or null if none found. + * + * Return the RGB hex representation of a color string. If a CSS supported + * named color is supplied, the hex representation will be returned. + * If a non-CSS supported named color is supplied, null will be + * returned. If a RGB hex string is supplied, the same will be returned. + * Shorthand (3-digit) hexcodes will be expanded to 6-digits. + */ + colorToHex: function (color) { + if (!color) { + return color; + } + color = this.expand3DigitHex(color); + var hex; + if (color.match(/^#[0-9a-f]{6}$/i)) { + hex = color; + } else { + hex = this.cssColors[color.toLowerCase()] || null; + } + return hex; + }, + + /** private: method[hexToColor] + */ + hexToColor: function (hex) { + if (!hex) { + return hex; + } + + // Added by Just: in some cases a 3-digit hexcolor may be used + hex = this.expand3DigitHex(hex); + var color = hex; + for (var c in this.cssColors) { + if (this.cssColors[c] == color.toUpperCase()) { + color = c; + break; + } + } + return color; + }}); + +(function () { // register the color manager with every color field Ext.util.Observable.observeClass(Ext.ColorPalette); Ext.ColorPalette.on({ - render: function() { + render: function () { if (gxp.ColorManager.pickerWin) { - gxp.ColorManager.pickerWin.setPagePosition(200,100); + gxp.ColorManager.pickerWin.setPagePosition(200, 100); } } }); diff --git a/src/script/widgets/WMSStylesDialog.js b/src/script/widgets/WMSStylesDialog.js index abe11934..5179b41a 100644 --- a/src/script/widgets/WMSStylesDialog.js +++ b/src/script/widgets/WMSStylesDialog.js @@ -1,6 +1,6 @@ /** * Copyright (c) 2008-2011 The Open Planning Project - * + * * Published under the GPL license. * See https://github.com/opengeo/gxp/raw/master/license.txt for the full text * of the license. @@ -29,7 +29,7 @@ Ext.namespace("gxp"); /** api: constructor * .. class:: WMSStylesDialog(config) - * + * * Create a dialog for selecting and layer styles. If the WMS supports * GetStyles, styles can also be edited. The dialog does not provide any * means of writing modified styles back to the server. To save styles, @@ -42,12 +42,12 @@ Ext.namespace("gxp"); * to support vendor specific extensions added to SLD by GeoTools. */ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { - + /** api: config[addStyleText] (i18n) */ addStyleText: "Add", /** api: config[addStyleTip] (i18n) */ addStyleTip: "Add a new style", - /** api: config[chooseStyleText] (i18n) */ + /** api: config[chooseStyleText] (i18n) */ chooseStyleText: "Choose style", /** api: config[addStyleText] (i18n) */ deleteStyleText: "Remove", @@ -100,38 +100,38 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { // subclasses for that store with writing capabilities, e.g. // for GeoServer's RESTconfig API. This should replace the current // StyleWriter plugins. - + /** api: config[layerRecord] * ``GeoExt.data.LayerRecord`` The layer to edit/select styles for. */ - + /** private: property[layerRecord] * ``GeoExt.data.LayerRecord`` The layer to edit/select styles for. */ layerRecord: null, - + /** api: config[styleName] * ``String`` A style's name to select in the styles combo box. Optional. * If not provided, the layer's current style will be selected. */ - + /** api: config[stylesComboOptions] * ``Object`` configuration options to pass to the styles combo of this * dialog. Optional. */ - + /** api: config[layerDescription] * ``Object`` Array entry of a DescribeLayer response as read by * ``OpenLayers.Format.WMSDescribeLayer``. Optional. If not provided, * a DescribeLayer request will be issued to the WMS. */ - + /** private: property[layerDescription] * ``Object`` Array entry of a DescribeLayer response as read by * ``OpenLayers.Format.WMSDescribeLayer``. */ layerDescription: null, - + /** private: property[symbolType] * ``Point`` or ``Line`` or ``Polygon`` - the primary symbol type for the * layer. This is the symbolizer type of the first symbolizer of the @@ -139,7 +139,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { * supports GetStyles. */ symbolType: null, - + /** api: property[stylesStore] * ``Ext.data.Store`` A store representing the styles returned from * GetCapabilities and GetStyles. It has "name", "title", "abstract", @@ -148,24 +148,24 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { * field will not be available. */ stylesStore: null, - + /** api: property[selectedStyle] * ``Ext.data.Record`` The currently selected style from the * ``stylesStore``. */ selectedStyle: null, - + /** private: property[selectedRule] * ``OpenLayers.Rule`` The currently selected rule, or null if none * selected. */ selectedRule: null, - + /** api: config[editable] * ``Boolean`` Set to false if styles should not be editable. Default is * true. */ - + /** api: property[editable] * ``Boolean`` Read-only once the dialog is rendered. True if this * component could gather enough information to allow styles being edited, @@ -173,13 +173,13 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { * ``ready`` event is fired. */ editable: true, - + /** private: property[modified] * ``Boolean`` Will be true if styles were modified. Initial state is * false. */ modified: false, - + /** private: config[dialogCls] * ``Ext.Component`` The dialogue class to use. Default is ``Ext.Window``. * If using e.g. ``Ext.Container``, override the ``showDlg`` method to @@ -195,7 +195,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { * Fires when this component is ready for user interaction. */ "ready", - + /** api: event[modified] * Fires on every style modification. * @@ -205,18 +205,18 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { * * ``String`` the name of the modified style */ "modified", - + /** api: event[styleselected] * Fires whenever an existing style is selected from this dialog's * Style combo box. - * + * * Listener arguments: * * * :class:`gxp.WMSStylesDialog` this component * * ``String`` the name of the selected style */ "styleselected", - + /** api: event[beforesaved] * Fires before the styles are saved (using a * :class:`gxp.plugins.StyleWriter` plugin) @@ -228,7 +228,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { * :class:`gxp.plugins.StyleWriter` */ "beforesaved", - + /** api: event[saved] * Fires when a style was successfully saved. Applications should * listen for this event and redraw layers with the currently @@ -239,7 +239,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { * * :class:`gxp.WMSStylesDialog` this component * * ``String`` the name of the currently selected style */ - "saved" + "saved" ); var defConfig = { @@ -305,20 +305,20 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { }] }; Ext.applyIf(this, defConfig); - + this.createStylesStore(); - + this.on({ "beforesaved": function() { this._saving = true; }, "saved": function() { delete this._saving; }, - "savefailed": function() { + "savefailed": function() { Ext.Msg.show({ title: this.errorTitle, msg: this.errorMsg, icon: Ext.MessageBox.ERROR, buttons: {ok: true} }); - delete this._saving; + delete this._saving; }, "render": function() { gxp.util.dispatch([this.getStyles], function() { @@ -330,7 +330,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { gxp.WMSStylesDialog.superclass.initComponent.apply(this, arguments); }, - + /** api: method[addStyle] * Creates a new style and selects it in the styles combo. */ @@ -351,7 +351,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { })); this.editStyle(prevStyle); }, - + /** api: method[editStyle] * :arg prevStyle: ``Ext.data.Record`` * @@ -414,11 +414,11 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { })); this.showDlg(styleProperties); }, - + /** api: method[createSLD] * :arg options: ``Object`` * :return: ``String`` The current SLD for the NamedLayer. - * + * * Supported ``options``: * * * userStyles - ``Array(String)`` list of userStyles (by name) that are @@ -446,7 +446,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { profile: "GeoServer" }).write(sld); }, - + /** api: method[saveStyles] * :arg options: ``Object`` Options to pass to the * :class:`gxp.plugins.StyleWriter` plugin @@ -457,7 +457,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { saveStyles: function(options) { this.modified === true && this.fireEvent("beforesaved", this, options); }, - + /** private: method[updateStyleRemoveButton] * Enable/disable the "Remove" button to make sure that we don't delete * the last style. @@ -468,7 +468,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { this.items.get(1).items.get(1).setDisabled(!userStyle || this.stylesStore.getCount() <= 1 || userStyle.isDefault === true); }, - + /** private: method[updateRuleRemoveButton] * Enable/disable the "Remove" button to make sure that we don't delete * the last rule. @@ -478,7 +478,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { !this.selectedRule || this.items.get(2).items.get(0).rules.length < 2 ); }, - + /** private: method[createRule] */ createRule: function() { @@ -486,7 +486,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { symbolizers: [new OpenLayers.Symbolizer[this.symbolType]] }); }, - + /** private: method[addRulesFieldSet] * :return: ``Ext.form.FieldSet`` * @@ -547,7 +547,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { this.doLayout(); return rulesFieldSet; }, - + /** private: method[addRule] */ addRule: function() { @@ -560,7 +560,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { this.selectedStyle.store.afterEdit(this.selectedStyle); this.updateRuleRemoveButton(); }, - + /** private: method[removeRule] */ removeRule: function() { @@ -570,7 +570,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { // mark the style as modified this.afterRuleChange(); }, - + /** private: method[duplicateRule] */ duplicateRule: function() { @@ -584,7 +584,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { this.selectedStyle.store.afterEdit(this.selectedStyle); this.updateRuleRemoveButton(); }, - + /** private: method[editRule] */ editRule: function() { @@ -646,7 +646,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { }); this.showDlg(ruleDlg); }, - + /** private: method[saveRule] * :arg cmp: * :arg rule: the rule to save back to the userStyle @@ -659,10 +659,10 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { userStyle.rules[i] = rule; this.afterRuleChange(rule); }, - + /** private: method[afterRuleChange] * :arg rule: the rule to set as selectedRule, can be null - * + * * Performs actions that are required to update the selectedRule and * selectedStyle after a rule was changed. */ @@ -672,7 +672,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { // mark the style as modified this.selectedStyle.store.afterEdit(this.selectedStyle); }, - + /** private: method[setRulesFieldSetVisible] * :arg visible: ``Boolean`` * @@ -689,7 +689,7 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { /** private: method[parseSLD] * :arg response: ``Object`` * :arg options: ``Object`` - * + * * Success handler for the GetStyles response. Includes a fallback * to GetLegendGraphic if no valid SLD is returned. */ @@ -705,9 +705,9 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { this.selectedStyle = this.stylesStore.getAt( this.stylesStore.findExact("name", initialStyle)); } - + var format = new OpenLayers.Format.SLD({profile: "GeoServer", multipleSymbolizers: true}); - + try { var sld = format.read(data); @@ -721,13 +721,13 @@ gxp.WMSStylesDialog = Ext.extend(Ext.Container, { var sldBody = format.read(layerParams.SLD_BODY); inlineStyles = sldBody.namedLayers[layerParams.LAYERS].userStyles; Array.prototype.push.apply(userStyles, inlineStyles); - } - + } + // our stylesStore comes from the layerRecord's styles - clear it // and repopulate from GetStyles this.stylesStore.removeAll(); this.selectedStyle = null; - + var userStyle, record, index, defaultStyle; for (var i=0, len=userStyles.length; i