Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## next release
* Enable export of geometries as geojson by using the attribute "allowGeometryExport" ([PR#137](https://github.com/mapbender/mapbender-digitizer/pull/137))


## 2.0.3
* updateMultiple now returns a Promise; TableRenderer includes addOrRefreshRow for automatic feature handling post-AJAX call; unique namespace ID for unsaved features enables updates post-AJAX; oldGeometry set after successful updateMultiple call. ([PR#133](https://github.com/mapbender/mapbender-digitizer/pull/133))
* prevent Draw Donut On Non-Digitizer Features ([PR#120](https://github.com/mapbender/mapbender-digitizer/pull/120))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ $buttonActiveTextColor: #fff !default;
.ui-dialog-titlebar-close {
&:before {
content: "\f00d"; // =.fa-times
font-family: "FontAwesome", "Font Awesome 5 Free", "Font Awesome 5 Pro";
font-family: "FontAwesome", "Font Awesome 6 Free", "Font Awesome 6 Pro", "Font Awesome 5 Free", "Font Awesome 5 Pro";
}

.ui-button-icon {
Expand Down Expand Up @@ -334,3 +334,37 @@ $buttonActiveTextColor: #fff !default;
margin-right: 0.5em;
}
}


.data-manager-dialog, .mb-element-data-manager {
.sorting, .sorting_asc, .sorting_desc {
cursor: pointer;
}

.sorting:before, .sorting_asc:before, .sorting_desc:after {
display: inline-block !important;
min-width: 1.4em; // Prevent width change on sorting state change
font-family: var(--fa-style-family, "Font Awesome 6 Free");
font-weight: var(--fa-style, 900);
line-height: 1;
transform: translateY(1px);
right: 1em !important;
}

.sorting:before {
content: "\f0dc" !important; // = .fa-sort / .fa-unsorted
opacity: 1 !important;
}

.sorting_asc:before {
content: "\f160" !important; // = .fa-sort-amount-asc
}

.sorting_desc:after {
content: "\f161" !important; // = .fa-sort-amount-desc
}

.sorting:after, .sorting_asc:after, .sorting_desc:before {
content: "" !important;
}
}
105 changes: 66 additions & 39 deletions src/Mapbender/DigitizerBundle/Resources/public/TableRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,105 +2,139 @@
"use strict";

Mapbender.Digitizer = Mapbender.Digitizer || {};

Mapbender.Digitizer.TableRenderer = function() {
// Call parent constructor
Mapbender.DataManager.TableRenderer.apply(this, arguments);

// Initialize an array to store checked row IDs
this.selectedFeatures = [];
};
Mapbender.Digitizer.TableRenderer.prototype = Object.create(Mapbender.DataManager.TableRenderer.prototype);
Object.assign(Mapbender.Digitizer.TableRenderer.prototype, {
constructor: Mapbender.Digitizer.TableRenderer
});

// Inherit from DataManager.TableRenderer
Mapbender.Digitizer.TableRenderer.prototype =
Object.create(Mapbender.DataManager.TableRenderer.prototype);

Object.assign(Mapbender.Digitizer.TableRenderer.prototype, {
constructor: Mapbender.Digitizer.TableRenderer,

render: function(schema) {
var table = Mapbender.DataManager.TableRenderer.prototype.render.call(this, schema);
this.registerEvents(schema, $(table));
return table;
},

registerEvents: function(schema, $table) {
var widget = this.owner;

// Hover highlight
$table.on('mouseenter mouseleave', 'tbody > tr', function(event) {
var hover = event.handleObj.origType === 'mouseenter';
var feature = $(this).data().item;
if (feature) {
feature.set('hover', hover);
}
});

// Row click => zoom to feature
$table.on('click', 'tbody > tr', function (e) {
// Do nothing if click hit an interaction button; return true to allow other handlers
var $target = $(e.target);
var $parentsAndSelf = $target.parentsUntil(this).add($target);
if ($parentsAndSelf.filter('.button,.btn').length) {
if ($target.is('.row-checkbox')) {
return true;
}
var feature = $(this).data().item;
if (feature) {
widget.zoomToFeature(schema, feature);
}
});


// Existing buttons
this.registerButtonEvents(schema, $table);
},

registerButtonEvents: function(schema, $table) {
var self = this;
$table.on('click', 'tbody > tr .-fn-save', function(event) {
// Avoid calling row click handlers (may zoom to feature or open the edit dialog, depending on schema config)

$table.on('click', '.-fn-check-for-export', function(e) {
e.stopPropagation();
var $row = $(this).closest('tr');
var feature = $row.data('item');
if (!feature) { return; }
// Toggle selected state, maintain selectedFeatures array
var idx = self.selectedFeatures.indexOf(feature);
var selected = (idx >= 0);
if (selected) {
self.selectedFeatures.splice(idx, 1);
$('i', this).removeClass('fa-check-square').addClass('fa-square');
} else {
self.selectedFeatures.push(feature);
$('i', this).removeClass('fa-square').addClass('fa-check-square');
}
});

// Save
$table.on('click', '.-fn-save', function(event) {
event.stopPropagation();
var data = $(this).closest('tr').data();
if (data.schema && data.item) {
self.owner._saveItem(data.schema, data.item).fail(function(){
self.owner._afterFailedSave(data.schema,data.item);
});
self.owner._saveItem(data.schema, data.item)
.fail(function(){
self.owner._afterFailedSave(data.schema, data.item);
});
}
});
$table.on('click', 'tbody > tr .-fn-toggle-visibility', function(event) {
// Avoid calling row click handlers (may zoom to feature or open the edit dialog, depending on schema config)

// Toggle visibility
$table.on('click', '.-fn-toggle-visibility', function(event) {
event.stopPropagation();
var $tr = $(this).closest('tr');
var feature = $tr.data().item;
feature.set('hidden', !feature.get('hidden'));
self.updateButtonStates_($tr.get(0), feature);
});
$table.on('click', 'tbody > tr .-fn-edit-style', function(event) {

// Edit style
$table.on('click', '.-fn-edit-style', function(event) {
event.stopPropagation();
var data = $(this).closest('tr').data();
if (data.schema && data.item) {
self.owner.openStyleEditor(data.schema, data.item);
}
});
$table.on('click', 'tbody > tr .-fn-copy', function(event) {
// Avoid calling row click handlers (may already try to zoom to feature, or open the edit dialog, depending on schema config)

// Copy
$table.on('click', '.-fn-copy', function(event) {
event.stopPropagation();
var data = $(this).closest('tr').data();
if (data.schema && data.item) {
self.owner.cloneFeature(data.schema, data.item);
}
});
},

onRowCreation: function(tableSchema, tr, feature) {
Mapbender.DataManager.TableRenderer.prototype.onRowCreation.apply(this, arguments);
// Place table row into feature data for quick access (synchronized highlighting etc)
// Link table row with feature
feature.set('table-row', tr);
// Inline save buttons start out disabled
$('.-fn-save', tr).prop('disabled', !feature.get('dirty'));
this.registerFeatureEvents(feature);
},

registerFeatureEvents: function(feature) {
// Avoid registering same event handlers on the same feature multiple times
if (feature.get('table-events')) {
return;
}
// @todo: track across multiple tables

var self = this;
// Update interaction buttons when "hidden" and "dirty" values change
feature.on(ol.ObjectEventType.PROPERTYCHANGE, function(event) {
var feature = event.target;
var tr = feature && feature.get('table-row');
if (tr) {
switch (event.key) {
default:
// do nothing
break;
case 'dirty':
// Page to feature on initial modification
if (feature.get('dirty')) {
self.showRow(tr);
}
Expand All @@ -113,7 +147,6 @@
break;
}


if (event.key === 'dirty' && feature.get('dirty')) {
self.showRow(tr);
}
Expand All @@ -122,34 +155,28 @@
});
feature.set('table-events', true);
},
/**
* @param {Object} feature
* @param {Boolean} show to automatically update pagination
*/

refreshRow: function(feature, show) {
Mapbender.DataManager.TableRenderer.prototype.refreshRow.apply(this, arguments);

var tr = feature && feature.get('table-row');
if (tr) {
this.updateButtonStates_(tr, feature);
}
},

updateButtonStates_: function(tr, feature) {
var hidden = !!feature.get('hidden');
var tooltip;
if (hidden) {
tooltip = Mapbender.trans('mb.digitizer.feature.visibility.toggleon')
} else {
tooltip = Mapbender.trans('mb.digitizer.feature.visibility.toggleoff')
}
var tooltip = hidden
? Mapbender.trans('mb.digitizer.feature.visibility.toggleon')
: Mapbender.trans('mb.digitizer.feature.visibility.toggleoff');
var $visibilityButton = $('.-fn-toggle-visibility', tr);
// Support both icon class ON button (legacy misuse) and icon markup INSIDE button transparently
// Handle icon class toggling
var $visibilityIcon = $visibilityButton.children().add($visibilityButton).filter('.fa');
$visibilityIcon
.toggleClass('fa-eye-slash', hidden)
.toggleClass('fa-eye', !hidden)
.attr('title', tooltip)
;
.attr('title', tooltip);

$('.-fn-save', tr).prop('disabled', !feature.get('dirty'));

if (!!feature.get('dirty')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,11 @@
}
var $geometryToolGroup = $('.-js-drawing-tools', $toolset);
$geometryToolGroup.empty().append(this.toolsetRenderer.renderGeometryToolButtons(schema));

if (!(schema.allowGeometryExport || subSchemas.some(subSchema => subSchema.allowGeometryExport))) {
console.warn("No export button for schema", schema);
$('.-js-export', $toolset).hide();
}
},
_openEditDialog: function(schema, feature) {
// Make feature visible in table when editing was started
Expand Down Expand Up @@ -422,7 +427,8 @@
schema.allowDigitize && schema.allowEdit && '-fn-save',
schema.copy && schema.copy.enable && '-fn-copy',
schema.allowCustomStyle && '-fn-edit-style',
schema.allowChangeVisibility && '-fn-toggle-visibility'
schema.allowChangeVisibility && '-fn-toggle-visibility',
schema.allowGeometryExport && '-fn-check-for-export'
]);
return codes.filter(function(x) { return !!x; });
},
Expand Down Expand Up @@ -478,6 +484,7 @@
return this._super(schema).then(function(features) {
self.queuedRefresh_[schema.schemaName] = false;
self.renderer.replaceFeatures(schema, features);
self.tableRenderer.selectedFeatures = [];
return features;
});
},
Expand Down Expand Up @@ -796,6 +803,23 @@
adjustStyle: function(schema, feature) {

},
createExportData: function(geojson) {

var geojsonstring = JSON.stringify(geojson);
this._downloadBlob(geojsonstring, 'application/json', 'export.geojson');
},

_downloadBlob: function(data, mimeType, filename) {
let blob = (data instanceof Blob) ? data : new Blob([data], { type: mimeType });
let url = URL.createObjectURL(blob);
let tempLink = document.createElement('a');
tempLink.href = url;
tempLink.download = filename;
document.body.appendChild(tempLink);
tempLink.click();
document.body.removeChild(tempLink);
},

__formatting_dummy: null
});

Expand Down
45 changes: 42 additions & 3 deletions src/Mapbender/DigitizerBundle/Resources/public/toolset.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
return (!seen[toolSpec.type])
&& self.checkToolAccess_(subSchemas[s], toolSpec.type)
&& -1 !== validNames.indexOf(toolSpec.type)
;
;
});
for (var t = 0; t < subSchemaTools.length; ++t) {
var toolSpec = subSchemaTools[t];
Expand Down Expand Up @@ -102,10 +102,10 @@
.append($icon)
.data({
schema: btnSchema
})
;
});
buttons.push($button);
}

return buttons;
},
checkToolAccess_: function(schema, toolName) {
Expand All @@ -126,6 +126,45 @@
}
});
});

// Add an event listener for exporting the currently selected features
widget.element.on('click', '.-fn-export-selected', function() {
// Get the selected features
var exportFeatures = widget.tableRenderer.selectedFeatures || [];

// Check if there are any features to export
if (exportFeatures.length === 0) {
$.notify(Mapbender.trans('mb.digitizer.feature.export.notFound'));
return;
}

// Create a new array of features that include geometry and properties
var strippedFeatures = exportFeatures.map(function(originalFeature) {
var geometryClone = originalFeature.getGeometry().clone();
var properties = originalFeature.get("data");
delete properties.geometry; // Remove the geometry property to avoid duplication
return new ol.Feature({
geometry: geometryClone,
...properties // Add the properties to the new feature
});
});

var format = new ol.format.GeoJSON();
var dataProjection = 'EPSG:4326';

// Write features as GeoJSON, specifying only geometry
var geojson = format.writeFeaturesObject(strippedFeatures, {
dataProjection: dataProjection,
featureProjection: widget.mbMap.getModel().olMap.getView().getProjection()
});


widget.createExportData(geojson);



});

}
};
})(jQuery);
Loading