diff --git a/components/FormRelation.vue b/components/FormRelation.vue index bcde46df..b929aebf 100644 --- a/components/FormRelation.vue +++ b/components/FormRelation.vue @@ -3,24 +3,16 @@ diff --git a/components/SaveAll.vue b/components/SaveAll.vue deleted file mode 100644 index c8ba25c3..00000000 --- a/components/SaveAll.vue +++ /dev/null @@ -1,125 +0,0 @@ - - - diff --git a/components/Table.vue b/components/Table.vue index cc17adb4..0318e4be 100644 --- a/components/Table.vue +++ b/components/Table.vue @@ -16,7 +16,6 @@

{{ state.title }}

-
this.resize()); - }, - showTool(type) { return undefined !== this.state.capabilities.find(capability => capability === type); }, async resize() { - // skip when element is hidden + // skip when an element is hidden if (this.$el.style.display === 'none') { return; } diff --git a/g3wsdk/workflow/task.js b/g3wsdk/workflow/task.js index a6e03534..204929cc 100644 --- a/g3wsdk/workflow/task.js +++ b/g3wsdk/workflow/task.js @@ -133,7 +133,7 @@ export class EditingTask extends Task { super(options); - this._editingService; + this.selectStyle = options.selectStyle; /* @since 3.8.0 */ this._mapService = GUI.getService('map'); @@ -225,8 +225,7 @@ export class EditingTask extends Task { * @returns {*|EditingService|{}} */ getEditingService() { - this._editingService = this._editingService || require('../../services/editingservice'); - return this._editingService; + return require('../../services/editingservice'); } /** diff --git a/g3wsdk/workflow/workflow.js b/g3wsdk/workflow/workflow.js index baa6f84d..602ce893 100644 --- a/g3wsdk/workflow/workflow.js +++ b/g3wsdk/workflow/workflow.js @@ -389,26 +389,18 @@ proto.start = function(options = {}) { this._flow .start(this) .then(outputs => { - if (showUserMessage) { - setTimeout(() => { this.clearUserMessagesSteps(); d.resolve(outputs); }, 500); - } else { - d.resolve(outputs); - } + if (showUserMessage) { setTimeout(() => { this.clearUserMessagesSteps(); d.resolve(outputs); }, 500); } + else { d.resolve(outputs); } }) - .fail(error => { - if (showUserMessage) { - this.clearUserMessagesSteps(); - } - d.reject(error); + .fail(e => { + if (showUserMessage) { this.clearUserMessagesSteps(); } + d.reject(e); }) .always(() => { - if (this.runOnce) { - this.stop(); - } + if (this.runOnce) { this.stop(); } }); this.emit('start'); - return d.promise(); }; @@ -613,7 +605,7 @@ export class EditingWorkflow extends Workflow { * @FIXME add description */ getLayer() { - return this.getSession().getEditor().getLayer(); + return this.getInputs().layer; } /** diff --git a/services/editingservice.js b/services/editingservice.js index 6454e781..4b4736a6 100644 --- a/services/editingservice.js +++ b/services/editingservice.js @@ -1,4 +1,5 @@ import { EditingWorkflow } from '../g3wsdk/workflow/workflow'; +import { promisify } from '../utils/promisify'; import { OpenFormStep, ConfirmStep @@ -18,7 +19,7 @@ const { ApplicationService, } = g3wsdk.core; const { DataRouterService } = g3wsdk.core.data; -const { base, inherit, XHR, noop } = g3wsdk.core.utils; +const { XHR, noop } = g3wsdk.core.utils; const { Geometry } = g3wsdk.core.geometry; const { getFeaturesFromResponseVectorApi, @@ -44,137 +45,143 @@ const ToolBox = require('../toolboxes/toolbox'); const MAPCONTROL_TOGGLED_EVENT_NAME = 'mapcontrol:toggled'; const OFFLINE_ITEMS = { CHANGES: 'EDITING_CHANGES' }; -function EditingService() { +module.exports = new (class EditingService extends PluginService { - base(this); + constructor() { - /** - * Store all sessions - */ - this._sessions = {}; - - /** - * Constraints - */ - this.constraints = {}; + super(); - /** - * @FIXME add description - */ - this._vectorUrl; + this.EDITING_FIELDS_TYPE = ['unique'] - /** - * @FIXME add description - */ - this._projectType; + /** + * Store all sessions + */ + this._sessions = {}; - /** - * Array of object setter(as key), key to unby (as value) - */ - this._unByKeys = []; + /** + * Constraints + */ + this.constraints = {}; - /** - * Store setter keys event listerner - */ - this.setterKeys = []; + /** + * @FIXME add description + */ + this._vectorUrl; - /** - * Events - */ - this._events = { - layer: { - start_editing: { - before: {}, - after: {} - } - } - }; + /** + * @FIXME add description + */ + this._projectType; - /** - * Store unique fields values for each layer - * - * @type {{ mode: string, messages: undefined, modal: boolean, cb: { error: undefined, done: undefined } }} - */ - this.layersUniqueFieldsValues = {}; + /** + * Array of object setter(as key), key to unby (as value) + */ + this._unByKeys = []; - /** - * Store configuration of how save/commit changes to server - */ - this.saveConfig = { - mode: "default", // default, autosave - modal: false, - messages: undefined, // object to set custom message - cb: { - done: undefined, // function executed after commit change done - error: undefined // function executed after commit changes error - } - }; + /** + * Store setter keys event listerner + */ + this.setterKeys = []; - /** - * Application editing contraints (layer, filter, ..) to get features - */ - this.applicationEditingConstraints = { - toolboxes: {}, - showToolboxesExcluded: true - }; + /** + * Events + */ + this._events = { + layer: { + start_editing: { + before: {}, + after: {} + } + } + }; - /** - * State of editing - */ - this.state = { - open: false, // check if panel is open or not - toolboxes: [], - toolboxselected: null, - toolboxidactivetool: null, - /** @since g3w-client-plugin-editing@v3.6.2 */ - showselectlayers: true, // whether to show or not selected layers on editing panel - message: null, - relations: [], - }; + /** + * Store unique fields values for each layer + * + * @type {{ mode: string, messages: undefined, modal: boolean, cb: { error: undefined, done: undefined } }} + */ + this.layersUniqueFieldsValues = {}; + + /** + * Store configuration of how save/commit changes to server + */ + this.saveConfig = { + mode: "default", // default, autosave + modal: false, + messages: undefined, // object to set custom message + cb: { + done: undefined, // function executed after commit change done + error: undefined // function executed after commit changes error + } + }; - /** - * KEY LAYERID, VALUES ARRAY OF FEATURE FID CHANGES OR ADDED - */ - this.loadLayersFeaturesToResultWhenCloseEditing = {}; + /** + * Application editing contraints (layer, filter, ..) to get features + */ + this.applicationEditingConstraints = { + toolboxes: {}, + showToolboxesExcluded: true + }; - /** - * @FIXME add description - */ - this._layers_in_error = false; + /** + * State of editing + */ + this.state = { + open: false, // check if panel is open or not + toolboxes: [], + toolboxselected: null, + toolboxidactivetool: null, + /** @since g3w-client-plugin-editing@v3.6.2 */ + showselectlayers: true, // whether to show or not selected layers on editing panel + message: null, + relations: [], + }; - /** - * Map Service - */ - this._mapService = GUI.getService('map'); + /** + * KEY LAYERID, VALUES ARRAY OF FEATURE FID CHANGES OR ADDED + */ + this.loadLayersFeaturesToResultWhenCloseEditing = {}; + + /** + * @FIXME add description + */ + this._layers_in_error = false; + + /** + * Map Service + */ + this._mapService = GUI.getService('map'); + + // set map control toggle event + this.mapControlToggleEventHandler = evt => { + if ( + evt.target.isToggled() && + evt.target.isClickMap() && + this.state.toolboxselected && + this.state.toolboxselected.getActiveTool() + ) { + this.state.toolboxselected.stopActiveTool(); + } + }; - // set map control toggle event - this.mapControlToggleEventHandler = evt => { - if ( - evt.target.isToggled() && - evt.target.isClickMap() && - this.state.toolboxselected && - this.state.toolboxselected.getActiveTool() - ) { - this.state.toolboxselected.stopActiveTool(); - } - }; + this._mapService.on(MAPCONTROL_TOGGLED_EVENT_NAME, this.mapControlToggleEventHandler); - this._mapService.on(MAPCONTROL_TOGGLED_EVENT_NAME, this.mapControlToggleEventHandler); + /** + * Plugin components + */ + this._formComponents = {}; - /** - * Plugin components - */ - this._formComponents = {}; + /** + * @FIXME add description + */ + this._subscribers = {}; - /** - * @FIXME add description - */ - this._subscribers = {}; + } /** * @FIXME add description */ - this.init = function(config = {}) { + init(config = {}) { this._vectorUrl = config.vectorurl; this._projectType = config.project_type; this._layersstore = new LayersStore({ id: 'editing', queryable: false }); @@ -210,14 +217,14 @@ function EditingService() { this._ready(); }); - }; + } /** * @FIXME add description * * @fires ready */ - this._ready = function() { + _ready() { // @since g3w-client-plugin-editing@v3.7.0 this.setRelations1_1FieldsEditable(); @@ -266,2833 +273,2812 @@ function EditingService() { this.registerResultEditingAction(); this.emit('ready'); - }; + } -} - -inherit(EditingService, PluginService); - -const proto = EditingService.prototype; - -/** - * [API Method] - */ -proto.getAppState = function() { - return ApplicationState; -}; - -/** - * [API Method] - */ -proto.getFormComponentsById = function(layerId) { - return this._formComponents[layerId] || []; -}; - -/** - * [API Method] - */ -proto.getFormComponents = function() { - return this._formComponents; -}; - -/** - * [API Method] - */ -proto.addFormComponents = function({ - layerId, - components = [], -} = {}) { - if (!this._formComponents[layerId]) { - this._formComponents[layerId] = []; - } - for (let i=0; i < components.length; i++) { - this._formComponents[layerId].push(components[i]) - } -}; - -/** - * [API Method] Get session - * - * @param layerId - * - * @returns {*} - */ -proto.getSession = function({ layerId } = {}) { - return this.getToolBoxById(layerId).getSession(); -}; - -/** - * [API Method] - * - * @param layerId - * - * @returns Feature in editing - */ -proto.getFeature = function({ layerId } = {}) { - return this.getToolBoxById(layerId).getActiveTool().getFeature(); -}; - -/** - * [API Method] Subscribe handler function on event - * - * @param event - * @param { Function } fnc - * - * @returns { Function } function - */ -proto.subscribe = function(event, fnc) { - if (!this._subscribers[event]) this._subscribers[event] = []; - if (!this._subscribers[event].find(subscribe => subscribe === fnc)) this._subscribers[event].push(fnc); - return fnc; -}; - -/** - * [API Method] Unsubscribe handler function on event - * - * @param event - * @param fnc - */ -proto.unsubscribe = function(event, fnc) { - this._subscribers[event] = this._subscribers[event].filter(subscribe => subscribe !== fnc); -}; - -/** - * Check if layer has relation 1:1 (type ONE) and if fields - * - * belong to relation where child layer is editable - * - * @since g3w-client-plugin-editing@v3.7.0 - */ -proto.setRelations1_1FieldsEditable = function() { - this - .getLayers() - .forEach(editingLayer => { - const fatherId = editingLayer.getId(); // father layer - this - .getRelation1_1ByLayerId(fatherId) - .forEach(relation => { // loop `Relations` instances - if (fatherId === relation.getFather()) { // check if father layerId is a father of relation - const isChildEditable = undefined !== this.getLayerById(relation.getChild()); // check if child layerId is editable (in editing) - this - .getRelation1_1EditingLayerFieldsReferredToChildRelation(relation) // loop father layer fields (in editing) - .forEach(field => { field.editable = (field.editable && isChildEditable); }); // current editable boolean value + child editable layer - } + /** + * [API Method] + */ + getAppState() { + return ApplicationState; + } + + /** + * [API Method] + */ + getFormComponentsById(layerId) { + return this._formComponents[layerId] || []; + } + + /** + * [API Method] + */ + getFormComponents() { + return this._formComponents; + } + + /** + * [API Method] + */ + addFormComponents({ + layerId, + components = [], + } = {}) { + if (!this._formComponents[layerId]) { + this._formComponents[layerId] = []; + } + for (let i=0; i < components.length; i++) { + this._formComponents[layerId].push(components[i]) + } + } + + /** + * [API Method] Get session + * + * @param layerId + * + * @returns {*} + */ + getSession({ layerId } = {}) { + return this.getToolBoxById(layerId).getSession(); + } + + /** + * [API Method] + * + * @param layerId + * + * @returns Feature in editing + */ + getFeature({ layerId } = {}) { + return this.getToolBoxById(layerId).getActiveTool().getFeature(); + } + + /** + * [API Method] Subscribe handler function on event + * + * @param event + * @param { Function } fnc + * + * @returns { Function } function + */ + subscribe(event, fnc) { + if (!this._subscribers[event]) this._subscribers[event] = []; + if (!this._subscribers[event].find(subscribe => subscribe === fnc)) this._subscribers[event].push(fnc); + return fnc; + } + + /** + * [API Method] Unsubscribe handler function on event + * + * @param event + * @param fnc + */ + unsubscribe(event, fnc) { + this._subscribers[event] = this._subscribers[event].filter(subscribe => subscribe !== fnc); + } + + /** + * Check if layer has relation 1:1 (type ONE) and if fields + * + * belong to relation where child layer is editable + * + * @since g3w-client-plugin-editing@v3.7.0 + */ + setRelations1_1FieldsEditable() { + this + .getLayers() + .forEach(editingLayer => { + const fatherId = editingLayer.getId(); // father layer + this + .getRelation1_1ByLayerId(fatherId) + .forEach(relation => { // loop `Relations` instances + if (fatherId === relation.getFather()) { // check if father layerId is a father of relation + const isChildEditable = undefined !== this.getLayerById(relation.getChild()); // check if child layerId is editable (in editing) + this + .getRelation1_1EditingLayerFieldsReferredToChildRelation(relation) // loop father layer fields (in editing) + .forEach(field => { field.editable = (field.editable && isChildEditable); }); // current editable boolean value + child editable layer + } + }) + }); + } + + /** + * Get Father layer fields related (in Relation) to Child Layer, + * + * ie. father fields having same `vectorjoin_id` attribute to `relation.id` value + * + * @param { Relation } relation + * + * @returns { Array } fields Array bind to child layer + * + * @since g3w-client-plugin-editing@v3.7.0 + */ + getRelation1_1EditingLayerFieldsReferredToChildRelation(relation) { + return this + .getLayerById(relation.getFather()) + .getEditingFields() + .filter(field => field.vectorjoin_id && field.vectorjoin_id === relation.getId()); + } + + /** + * Get Relation 1:1 from layerId + * + * @param layerId + * + * @returns Array of relations related to layerId that are Join 1:1 (Type ONE) + * + * @since g3w-client-plugin-editing@v3.7.0 + */ + getRelation1_1ByLayerId(layerId) { + return CatalogLayersStoresRegistry + .getLayerById(layerId) + .getRelations() + .getArray() + .filter(relation => 'ONE' === relation.getType()); // 'ONE' == join 1:1 + } + + /** + * Set Boolean value for show select layers to edit + * + * @param bool Default is true + * + * @since g3w-client-plugin-editing@v3.6.2 + */ + setShowSelectLayers(bool=true) { + this.state.showselectlayers = bool; + } + + /** + * Register result editing action + */ + registerResultEditingAction() { + const queryResultsService = GUI.getService('queryresults'); + this.setterKeys.push({ + setter: 'editFeature', + key: queryResultsService.onafter('editFeature', ({layer, feature}) => { + this.editResultLayerFeature({ + layer, + feature }) - }); -}; - -/** - * Get Father layer fields related (in Relation) to Child Layer, - * - * ie. father fields having same `vectorjoin_id` attribute to `relation.id` value - * - * @param { Relation } relation - * - * @returns { Array } fields Array bind to child layer - * - * @since g3w-client-plugin-editing@v3.7.0 - */ -proto.getRelation1_1EditingLayerFieldsReferredToChildRelation = function(relation) { - return this - .getLayerById(relation.getFather()) - .getEditingFields() - .filter(field => field.vectorjoin_id && field.vectorjoin_id === relation.getId()); -} - -/** - * Get Relation 1:1 from layerId - * - * @param layerId - * - * @returns Array of relations related to layerId that are Join 1:1 (Type ONE) - * - * @since g3w-client-plugin-editing@v3.7.0 - */ -proto.getRelation1_1ByLayerId = function(layerId) { - return CatalogLayersStoresRegistry - .getLayerById(layerId) - .getRelations() - .getArray() - .filter(relation => 'ONE' === relation.getType()); // 'ONE' == join 1:1 -}; - -/** - * Set Boolean value for show select layers to edit - * - * @param bool Default is true - * - * @since g3w-client-plugin-editing@v3.6.2 - */ -proto.setShowSelectLayers = function(bool=true) { - this.state.showselectlayers = bool; -}; - -/** - * Register result editing action - */ -proto.registerResultEditingAction = function() { - const queryResultsService = GUI.getService('queryresults'); - this.setterKeys.push({ - setter: 'editFeature', - key: queryResultsService.onafter('editFeature', ({layer, feature}) => { - this.editResultLayerFeature({ - layer, - feature }) - }) - }); -}; - -/** - * Unregister action from query result service setters - */ -proto.unregisterResultEditingAction = function() { - const queryResultsService = GUI.getService('queryresults'); - this.setterKeys.forEach(({ setter, key }) => queryResultsService.un(setter, key)); -}; - -/** - * ORIGINAL SOURCE: g3w-client-plugin/toolboxes/toolboxesfactory.js@v3.7.1 - * - * Start to edit selected feature from results - */ -proto.editResultLayerFeature = function({ - layer, - feature, -} = {}) { - - const fid = feature.attributes[G3W_FID]; - - this.getToolBoxes().forEach(tb => tb.setShow(tb.getId() === layer.id)); - this.getPlugin().showEditingPanel(); - - const toolBox = this.getToolBoxById(layer.id); - const { scale } = toolBox.getEditingConstraints(); // get scale constraint from setting layer - const has_geom = feature.geometry && undefined !== scale; - // start toolbox (filtered by feature id) - toolBox - .start({ filter: { fids: fid } }) - .then(({ features = [] }) => { - const _layer = toolBox.getLayer(); - const source = _layer.getEditingLayer().getSource(); + }); + } + + /** + * Unregister action from query result service setters + */ + unregisterResultEditingAction() { + const queryResultsService = GUI.getService('queryresults'); + this.setterKeys.forEach(({ setter, key }) => queryResultsService.un(setter, key)); + } + + /** + * ORIGINAL SOURCE: g3w-client-plugin/toolboxes/toolboxesfactory.js@v3.7.1 + * + * Start to edit selected feature from results + */ + async editResultLayerFeature({ + layer, + feature, + } = {}) { + + const fid = feature.attributes[G3W_FID]; + + this.getToolBoxes().forEach(tb => tb.setShow(tb.getId() === layer.id)); + this.getPlugin().showEditingPanel(); + + const toolBox = this.getToolBoxById(layer.id); + const { scale } = toolBox.getEditingConstraints(); // get scale constraint from setting layer + const has_geom = feature.geometry && undefined !== scale; + let session; + + // start toolbox (filtered by feature id) + try { + await promisify(toolBox.start({ filter: { fids: fid } })); + + const _layer = toolBox.getLayer(); + const source = _layer.getEditingLayer().getSource(); // get feature from Editing layer source (with styles) - const feature = ( - (_layer.getType() === Layer.LayerTypes.VECTOR) - ? source.getFeatures() - : source.readFeatures() - ).find(f => f.getId() == fid); + const features = (_layer.getType() === Layer.LayerTypes.VECTOR) ? source.getFeatures() : source.readFeatures(); + const feature = features.find(f => f.getId() == fid); // skip when not feature is get from server if (!feature) { return; } - /**If feature has geometry, zoom to geometry */ - if (feature.getGeometry()) { - this._mapService.zoomToGeometry(feature.getGeometry()); - // check map scale after zoom to feature - // if currentScale is more that scale constraint set by layer editing - // need to go to scale setting by layer editing constraint - if (has_geom) { - this._mapService.getMap().once('moveend', () => { - const units = this._mapService.getMapUnits(); - const map = this._mapService.getMap(); - const currentScale = parseInt(getScaleFromResolution(map.getView().getResolution(), units)); - if (currentScale > scale) { - map.getView().setResolution(getResolutionFromScale(scale, units)); - } - //set select only here otherwise is show editing constraint - toolBox.setSelected(true); - }); - } else { + const geom = feature.getGeometry(); + + // feature has geometry → zoom to geometry + if (geom) { + this._mapService.zoomToGeometry(geom); + } + + // check map scale after zoom to feature + // if currentScale is more that scale constraint set by layer editing + // need to go to scale setting by layer editing constraint + if (geom && has_geom) { + this._mapService.getMap().once('moveend', () => { + const units = this._mapService.getMapUnits(); + const map = this._mapService.getMap(); + const currentScale = parseInt(getScaleFromResolution(map.getView().getResolution(), units)); + if (currentScale > scale) { + map.getView().setResolution(getResolutionFromScale(scale, units)); + } + //set select only here otherwise is show editing constraint toolBox.setSelected(true); - } - } else { - //set select toolbox + }); + } + + if (geom && !has_geom) { + toolBox.setSelected(true); + } + + //set select toolbox + if (!geom) { toolBox.setSelected(true); } - const session = toolBox.getSession(); + session = toolBox.getSession(); this.setSelectedToolbox(toolBox); /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/editnopickmapfeatureattributesworkflow.js@v3.7.1 */ // edit feature workFlow const work = new EditingWorkflow({ - type: 'editnopickmapfeatureattributes', - runOnce: true, + type: 'editnopickmapfeatureattributes', helpMessage: 'editing.tools.update_feature', - steps: [ new OpenFormStep() ] + steps: [ new OpenFormStep() ], + runOnce: true, }); - work - .start({ + + await promisify( + work.start({ inputs: { layer: _layer, features: [feature] }, context: { session } }) - .then(() => session - .save() - .then(() => this.saveChange())) - .fail(() => session.rollback()); + ); - }) - .fail(err => console.warn(err)); -}; - -/** - * @FIXME add description - */ -proto.disableMapControlsConflict = function(bool=true) { - this._mapService.disableClickMapControls(bool); -}; - -/** - * Used on commit if no toolbox is passed as parameter - * - * @param toolbox - */ -proto.setSelectedToolbox = function(toolbox) { - this.state.toolboxselected = toolbox; -}; - -/** - * @FIXME add description - */ -proto.getToolboxSelected = function() { - return this.state.toolboxselected; -}; - -/** - * Create a new feature - * - * @param layerId - * @param options.geometry.type - * @param options.geometry.coordinates - * - * @returns { Feature } - */ -proto.addNewFeature = function(layerId, options = {}) { - const feature = new Feature(); - - if (options.geometry) { - feature.setGeometry(new ol.geom[options.geometry.type](options.geometry.coordinates)); - } - - feature.setProperties(options.properties); - feature.setTemporaryId(); - - const toolbox = this.getToolBoxById(layerId); - const editingLayer = toolbox.getLayer().getEditingLayer(); - const session = toolbox.getSession(); - - editingLayer.getSource().addFeature(feature); - session.pushAdd(layerId, feature, false); - - return feature; -}; - -/** - * @returns { boolean } - */ -proto.getLayersInError = function() { - return this._layers_in_error; -}; - -/** - * @returns {*} - */ -proto.getMapService = function() { - return this._mapService; -}; - -/** - * @private - */ -proto._initOffLineItems = function() { - for (const id in OFFLINE_ITEMS) { - !this.getOfflineItem(OFFLINE_ITEMS[id]) && ApplicationService.setOfflineItem(OFFLINE_ITEMS[id]); - } -}; - -/** - * @param data - * - * @returns {*} - * - * @private - */ -proto._handleOfflineChangesBeforeSave = function(data) { - const changes = ApplicationService.getOfflineItem(OFFLINE_ITEMS.CHANGES); - const applyChanges = ({layerId, current, previous})=> { - current[layerId].add = [...previous[layerId].add, ...current[layerId].add]; - current[layerId].delete = [...previous[layerId].delete, ...current[layerId].delete]; - previous[layerId].update.forEach(updateItem => { - const {id} = updateItem; - const find = current[layerId].update.find(updateItem => updateItem.id === id); - if (!find) { - current[layerId].update.unshift(updateItem); + await promisify(session.save()); + + this.saveChange(); + } catch (e) { + console.warn(e); + if (session) { + session.rollback() } - }); - const lockids = previous[layerId].lockids|| []; - lockids - .forEach(lockidItem => { - const {featureid} = lockidItem; - const find = current[layerId].lockids.find(lockidItem => lockidItem.featureid === featureid); - if (!find) { - current[layerId].update.unshift(lockidItem); - } - }) - }; + } + } - for (const layerId in changes) { - // check if previous changes are made in the same layer or in relationlayer of current - const current = data[layerId] ? data : - data[Object.keys(data)[0]].relations[layerId] ? - data[Object.keys(data)[0]].relations : null; - - if (current) { - applyChanges({ - layerId, - current, - previous: changes - }); - } else { - // check if in the last changes - const currentLayerId = Object.keys(data)[0]; - const relationsIds = Object.keys(changes[layerId].relations); - if (relationsIds.length) { - if (relationsIds.indexOf(currentLayerId) !== -1) { - applyChanges({ - layerId: currentLayerId, - current: data, - previous: changes[layerId].relations - }); - changes[layerId].relations[currentLayerId] = data[currentLayerId]; - data = changes; - } - } else data[layerId] = changes[layerId]; + /** + * @FIXME add description + */ + disableMapControlsConflict(bool=true) { + this._mapService.disableClickMapControls(bool); + } + + /** + * Used on commit if no toolbox is passed as parameter + * + * @param toolbox + */ + setSelectedToolbox(toolbox) { + this.state.toolboxselected = toolbox; + } + + /** + * @FIXME add description + */ + getToolboxSelected() { + return this.state.toolboxselected; + } + + /** + * Create a new feature + * + * @param layerId + * @param options.geometry.type + * @param options.geometry.coordinates + * + * @returns { Feature } + */ + addNewFeature(layerId, options = {}) { + const feature = new Feature(); + + if (options.geometry) { + feature.setGeometry(new ol.geom[options.geometry.type](options.geometry.coordinates)); } + + feature.setProperties(options.properties); + feature.setTemporaryId(); + + const toolbox = this.getToolBoxById(layerId); + const editingLayer = toolbox.getLayer().getEditingLayer(); + const session = toolbox.getSession(); + + editingLayer.getSource().addFeature(feature); + session.pushAdd(layerId, feature, false); + + return feature; } - return data; -}; - -/** - * @param { Object } opts - * @param opts.id - * @param opts.data - * - * @returns {*} - */ -proto.saveOfflineItem = function({ - id, - data, -} = {}) { - if (id === OFFLINE_ITEMS.CHANGES) data = this._handleOfflineChangesBeforeSave(data); - return ApplicationService.setOfflineItem(id, data); -}; - -/** - * @param id - * @param data - */ -proto.setOfflineItem = function(id, data) { - ApplicationService.setOfflineItem(id, data); -}; - -/** - * @param id - * @returns {*} - */ -proto.getOfflineItem = function(id) { - return ApplicationService.getOfflineItem(id); -}; - -/** - * Check if alread have off lines changes - * - * @param { Object } opts - * @param { boolean } [opts.modal=true] - * @param { boolean } [opts.unlock=false] - * - * @returns { Promise } - */ -proto.checkOfflineChanges = function({ - modal = true, - unlock = false, -} = {}) { - return new Promise((resolve, reject) => { - const changes = ApplicationService.getOfflineItem(OFFLINE_ITEMS.CHANGES); - // if find changes offline previously - if (changes) { - const promises = []; - const layerIds = []; - //FORCE TO WAIT OTHERWISE STILL OFF LINE - setTimeout(() => { - for (const layerId in changes) { - layerIds.push(layerId); - const toolbox = this.getToolBoxById(layerId); - const commitItems = changes[layerId]; - promises.push(this.commit({ - toolbox, - commitItems, - modal - })) - } - $.when - .apply(this, promises) - .then(() =>resolve()) - .fail(error=>reject(error)) - .always(() =>{ - unlock && layerIds.forEach(layerId => { - this.getLayerById(layerId).unlock() - }); - // always reset items to null - this.setOfflineItem(OFFLINE_ITEMS.CHANGES); - }) - }, 1000) + /** + * @returns { boolean } + */ + getLayersInError() { + return this._layers_in_error; + } + + /** + * @returns {*} + */ + getMapService() { + return this._mapService; + } + /** + * @private + */ + _initOffLineItems() { + for (const id in OFFLINE_ITEMS) { + !this.getOfflineItem(OFFLINE_ITEMS[id]) && ApplicationService.setOfflineItem(OFFLINE_ITEMS[id]); } - }) -}; - -/** - * Called by Editing Panel on creation time - */ -proto.registerOnLineOffLineEvent = function() { - // in case of starting panel editing check if there arae some chenging pending - // if true it have to commit chanhes on server and ulock all layers features temporary locked - if (ApplicationState.online) { - this.checkOfflineChanges({ - unlock: true - }); } - const offlineKey = ApplicationService.onafter('offline', () => {}); - const onlineKey = ApplicationService.onafter('online', () => { - this.checkOfflineChanges({ - modal:false - }) - .then(()=>{}) - .catch(error => GUI.notify.error(error)) - }); - - this._unByKeys.push({ - owner : ApplicationService, - setter: 'offline', - key: offlineKey - }); - - this._unByKeys.push({ - owner : ApplicationService, - setter: 'online', - key: onlineKey - }); - -}; - -/** - * @FIXME add description - */ -proto.unregisterOnLineOffLineEvent = function() { - this.unregisterSettersEvents(['online', 'offline']) -}; - -/** - * @param { Array } setters - */ -proto.unregisterSettersEvents = function(setters=[]) { - this._unByKeys.forEach(registered => { - const {owner, setter, key} = registered; - owner.un(setter, key); - }) -}; - -/** - * @param event - * @param options - * - * @returns { Promise } - */ -proto.fireEvent = function(event, options={}) { - return new Promise(resolve => { - if (this._subscribers[event]) { - this._subscribers[event] - .forEach(fnc => { - const response = fnc(options); - if (response && response.once) { - this.unsubscribe(event, fnc); + + /** + * @param data + * + * @returns {*} + * + * @private + */ + _handleOfflineChangesBeforeSave(data) { + const changes = ApplicationService.getOfflineItem(OFFLINE_ITEMS.CHANGES); + const applyChanges = ({layerId, current, previous})=> { + current[layerId].add = [...previous[layerId].add, ...current[layerId].add]; + current[layerId].delete = [...previous[layerId].delete, ...current[layerId].delete]; + previous[layerId].update.forEach(updateItem => { + const {id} = updateItem; + const find = current[layerId].update.find(updateItem => updateItem.id === id); + if (!find) { + current[layerId].update.unshift(updateItem); + } + }); + const lockids = previous[layerId].lockids|| []; + lockids + .forEach(lockidItem => { + const {featureid} = lockidItem; + const find = current[layerId].lockids.find(lockidItem => lockidItem.featureid === featureid); + if (!find) { + current[layerId].update.unshift(lockidItem); } + }) + }; + + for (const layerId in changes) { + // check if previous changes are made in the same layer or in relationlayer of current + const current = data[layerId] ? data : + data[Object.keys(data)[0]].relations[layerId] ? + data[Object.keys(data)[0]].relations : null; + + if (current) { + applyChanges({ + layerId, + current, + previous: changes }); + } else { + // check if in the last changes + const currentLayerId = Object.keys(data)[0]; + const relationsIds = Object.keys(changes[layerId].relations); + if (relationsIds.length) { + if (relationsIds.indexOf(currentLayerId) !== -1) { + applyChanges({ + layerId: currentLayerId, + current: data, + previous: changes[layerId].relations + }); + changes[layerId].relations[currentLayerId] = data[currentLayerId]; + data = changes; + } + } else data[layerId] = changes[layerId]; } - resolve(); - }); -}; - -/** - * @FIXME add description - */ -proto.activeQueryInfo = function() { - this._mapService.activeMapControl('query'); -}; - -/** - * Set editing layer color style and tyoolbox - */ -proto.setLayersColor = function() { - - const LAYERS_COLOR = [ - "#C43C39", - '#d95f02', - "#91522D", - "#7F9801", - "#0B2637", - "#8D5A99", - "#85B66F", - "#8D2307", - "#2B83BA", - "#7D8B8F", - "#E8718D", - "#1E434C", - "#9B4F07", - '#1b9e77', - "#FF9E17", - '#7570b3', - "#204B24", - "#9795A3", - "#C94F44", - "#7B9F35", - "#373276", - "#882D61", - "#AA9039", - "#F38F3A", - "#712333", - "#3B3A73", - "#9E5165", - "#A51E22", - "#261326", - "#e4572e", - "#29335c", - "#f3a712", - "#669bbc", - "#eb6841", - "#4f372d", - "#cc2a36", - "#00a0b0", - "#00b159", - "#f37735", - "#ffc425", - ]; - - for (const layer of this.getLayers()) { - !layer.getColor() ? layer.setColor(LAYERS_COLOR.splice(0,1)[0]): null; - } -}; - -/** - * @param layer - * - * @returns {*} - * - * @private - */ -proto._layerChildrenRelationInEditing = function(layer) { - return layer.getChildren().filter(relation => this.getLayerById(relation)); -}; - -/** - * Undo method - */ -proto.undo = function() { - const session = this.state.toolboxselected.getSession(); - const layerId = session.getId(); - const sessionItems = session.getLastHistoryState().items; - - this.undoRedoLayerUniqueFieldValues({ - layerId, - sessionItems, - action: 'undo' - }); - - const undoItems = session.undo(); - - this.undoRedoRelationUniqueFieldValues({ - relationSessionItems: undoItems, - action: 'undo' - }); - - this.undoRelations(undoItems); -}; - -/** - * undo relations - */ -proto.undoRelations = function(undoItems) { - Object - .entries(undoItems) - .forEach(([toolboxId, items]) => { this.getToolBoxById(toolboxId).getSession().undo(items); }); -}; - -/** - * rollback relations - */ -proto.rollbackRelations = function(rollbackItems) { - Object - .entries(rollbackItems) - .forEach(([toolboxId, items]) => { this.getToolBoxById(toolboxId).getSession().rollback(items); }); -}; - -/** - * @FIXME add description - */ -proto.redo = function() { - const session = this.state.toolboxselected.getSession(); - const layerId = session.getId(); - const sessionItems = session.getLastHistoryState().items; - this.undoRedoLayerUniqueFieldValues({ - layerId, - sessionItems, - action: 'redo' - }); - const redoItems = session.redo(); - - this.undoRedoRelationUniqueFieldValues({ - relationSessionItems: redoItems, - action: 'redo' - }); - - this.redoRelations(redoItems); -}; - -/** - * redo relations - */ -proto.redoRelations = function(redoItems) { - Object - .entries(redoItems) - .forEach(([toolboxId, items]) => { this.getToolBoxById(toolboxId).getSession().redo(items); }); -}; - -/** - * @param id - * - * @returns {*} - */ -proto.getEditingLayer = function(id) { - return this._editableLayers[id].getEditingLayer(); -}; - -/** - * @param toolbox - */ -proto.addToolBox = function(toolbox) { - this._toolboxes.push(toolbox); - // add session - this._sessions[toolbox.getId()] = toolbox.getSession(); - this.state.toolboxes.push(toolbox.state); -}; - -//** Method to set state in editing -proto.setOpenEditingPanel = function(bool) { - this.state.open = bool; - this._getEditableLayersFromCatalog().forEach(layer => layer.setInEditing(bool)); -}; - -/** - * Add event - * - * @param { Object } event - * @param { string } event.type - * @param event.id - * @param event.fnc - **/ -proto.addEvent = function({ - type, - id, - fnc, -} = {}) { - if (!this._events[type]) this._events[type] = {}; - if (!this._events[type][id]) this._events[type][id] = []; - this._events[type][id].push(fnc); -}; - -/** - * Add events - * - * @param { Object } event - * @param { string[] } event.types - * @param event.id - * @param event.fnc - */ -proto.addEvents = function({ - types = [], - id, - fnc, -} = {}) { - types.forEach(type => this.addEvent({ - type, - id, - fnc - })); -}; - -/** - * @param { Object } handler - * @param handler.type - * @param handler.id - * - * @returns { Promise } - */ -proto.runEventHandler = async function({ - type, - id, -} = {}) { - await (this._events[type] && this._events[type][id] && Promise.allSettled(this._events[type][id].map(fnc => fnc()))); -}; - -/** - * @param { Object } save - * @param save.mode - default or autosave - * @param save.cb - object contain done/error two functions - * @param save.modal - Boolean true or false to show to ask - * @param save.messages - object success or error - */ -proto.setSaveConfig = function({ - mode = 'default', - cb={}, - modal=false, - messages, -} = {}) { - this.saveConfig.mode = mode; - this.saveConfig.modal = modal; - this.saveConfig.messages = messages; - this.saveConfig.cb = { - ...this.saveConfig.cb, - ...cb - } -}; - -/** - * @returns save mode - */ -proto.getSaveConfig = function() { - return this.saveConfig; -}; - -/** - * Reset default values - */ -proto.resetDefault = function() { - this.saveConfig = { - mode: "default", // default, autosave - modal: false, - messages: null, // object to set custom message - cb: { - done: null, // function Called after save - error: null, // function called affte commit error } - }; - this.disableMapControlsConflict(false); -}; - -/** - * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 - * - * Reset default toolbox state modified by other plugin - * - * @since g3w-client-plugin-editing@v3.7.2 - */ -proto.resetAPIDefault = function({ - plugin=true, - toolboxes=true, -} = {}) { - if (toolboxes) { - this.getToolBoxes().forEach(tb => { tb.resetDefault(); }); - } - if (plugin) { - this.resetDefault(); - } -} - -/** - * Get data from api when a field of a layer - * is related to a wgis form widget (ex. relation reference, value map, etc..) - * - * @param layer - * - * @private - */ -proto._attachLayerWidgetsEvent = function(layer) { - const fields = layer.getEditingFields(); - for (let i=0; i < fields.length; i++) { - const field = fields[i]; - if (field.input) { - if (field.input.type === 'select_autocomplete' && !field.input.options.filter_expression) { - const options = field.input.options; - let { - key, - values, - value, - usecompleter, - layer_id, - loading, - relation_id, // @since g3w-client-plugin-editing@v3.7.0 - relation_reference, // @since g3w-client-plugin-editing@v3.7.0 - filter_fields=[], // @since g3w-client-plugin-editing@v3.7.2 - } = options; - const self = this; - if (!usecompleter) { - this.addEvents({ - /** - * @TODO need to avoid to call the same fnc to same event many times to avoid waste server request time - */ - types: ['start-editing', 'show-relation-editing'], - id: layer.getId(), - fnc() { - return new Promise((resolve, reject) => { - // remove all values - loading.state = 'loading'; - field.input.options.values = []; - //check if field has a relation reference widget - // and no filter fields set - if ( - relation_reference - && ( - [undefined, null].indexOf(filter_fields) > -1 - || filter_fields.length === 0 - ) - ) { - //get data with fformatter - layer.getFilterData({ - fformatter: field.name - }) - .then(response => { - //check if response - if (response && response.data) { - //response data is an array ok key value objects - response.data.forEach(([value, key]) => { - field.input.options.values.push({ - key, - value - }) - }) - loading.state = 'ready'; - self.fireEvent('autocomplete', { - field, - data: [response.data] - }); - //resolve - resolve(field.input.options.values); - } - }) - .catch((error) => { - loading.state = 'error'; - reject(error); - }) - } - //check if layer id (field has widget value map) - else if (layer_id) { - const relationLayer = CatalogLayersStoresRegistry.getLayerById(layer_id); - if (relationLayer) { - relationLayer.getDataTable({ - ordering: key - }) + return data; + } + + /** + * @param { Object } opts + * @param opts.id + * @param opts.data + * + * @returns {*} + */ + saveOfflineItem({ + id, + data, + } = {}) { + if (id === OFFLINE_ITEMS.CHANGES) data = this._handleOfflineChangesBeforeSave(data); + return ApplicationService.setOfflineItem(id, data); + } + + /** + * @param id + * @param data + */ + setOfflineItem(id, data) { + ApplicationService.setOfflineItem(id, data); + } + + /** + * @param id + * @returns {*} + */ + getOfflineItem(id) { + return ApplicationService.getOfflineItem(id); + } + + /** + * Check if alread have off lines changes + * + * @param { Object } opts + * @param { boolean } [opts.modal=true] + * @param { boolean } [opts.unlock=false] + * + * @returns { Promise } + */ + checkOfflineChanges({ + modal = true, + unlock = false, + } = {}) { + return new Promise((resolve, reject) => { + const changes = ApplicationService.getOfflineItem(OFFLINE_ITEMS.CHANGES); + // if find changes offline previously + if (changes) { + const promises = []; + const layerIds = []; + //FORCE TO WAIT OTHERWISE STILL OFF LINE + setTimeout(() => { + for (const layerId in changes) { + layerIds.push(layerId); + const toolbox = this.getToolBoxById(layerId); + const commitItems = changes[layerId]; + promises.push(this.commit({ + toolbox, + commitItems, + modal + })) + } + + $.when + .apply(this, promises) + .then(() =>resolve()) + .fail(error=>reject(error)) + .always(() =>{ + unlock && layerIds.forEach(layerId => { + this.getLayerById(layerId).unlock() + }); + // always reset items to null + this.setOfflineItem(OFFLINE_ITEMS.CHANGES); + }) + }, 1000) + + } + }) + } + + /** + * Called by Editing Panel on creation time + */ + registerOnLineOffLineEvent() { + // in case of starting panel editing check if there arae some chenging pending + // if true it have to commit chanhes on server and ulock all layers features temporary locked + if (ApplicationState.online) { + this.checkOfflineChanges({ + unlock: true + }); + } + const offlineKey = ApplicationService.onafter('offline', () => {}); + const onlineKey = ApplicationService.onafter('online', () => { + this.checkOfflineChanges({ + modal:false + }) + .then(()=>{}) + .catch(error => GUI.notify.error(error)) + }); + + this._unByKeys.push({ + owner : ApplicationService, + setter: 'offline', + key: offlineKey + }); + + this._unByKeys.push({ + owner : ApplicationService, + setter: 'online', + key: onlineKey + }); + + } + + /** + * @FIXME add description + */ + unregisterOnLineOffLineEvent() { + this.unregisterSettersEvents(['online', 'offline']) + } + + /** + * @param { Array } setters + */ + unregisterSettersEvents(setters=[]) { + this._unByKeys.forEach(registered => { + const {owner, setter, key} = registered; + owner.un(setter, key); + }) + } + + /** + * @param event + * @param options + * + * @returns { Promise } + */ + fireEvent(event, options={}) { + return new Promise(resolve => { + if (this._subscribers[event]) { + this._subscribers[event] + .forEach(fnc => { + const response = fnc(options); + if (response && response.once) { + this.unsubscribe(event, fnc); + } + }); + } + resolve(); + }); + } + + /** + * @FIXME add description + */ + activeQueryInfo() { + this._mapService.activeMapControl('query'); + } + + /** + * Set editing layer color style and tyoolbox + */ + setLayersColor() { + + const LAYERS_COLOR = [ + "#C43C39", + '#d95f02', + "#91522D", + "#7F9801", + "#0B2637", + "#8D5A99", + "#85B66F", + "#8D2307", + "#2B83BA", + "#7D8B8F", + "#E8718D", + "#1E434C", + "#9B4F07", + '#1b9e77', + "#FF9E17", + '#7570b3', + "#204B24", + "#9795A3", + "#C94F44", + "#7B9F35", + "#373276", + "#882D61", + "#AA9039", + "#F38F3A", + "#712333", + "#3B3A73", + "#9E5165", + "#A51E22", + "#261326", + "#e4572e", + "#29335c", + "#f3a712", + "#669bbc", + "#eb6841", + "#4f372d", + "#cc2a36", + "#00a0b0", + "#00b159", + "#f37735", + "#ffc425", + ]; + + for (const layer of this.getLayers()) { + !layer.getColor() ? layer.setColor(LAYERS_COLOR.splice(0,1)[0]): null; + } + } + + /** + * @param layer + * + * @returns {*} + * + * @private + */ + _layerChildrenRelationInEditing(layer) { + return layer.getChildren().filter(relation => this.getLayerById(relation)); + } + + /** + * Undo method + */ + undo() { + const session = this.state.toolboxselected.getSession(); + const layerId = session.getId(); + const sessionItems = session.getLastHistoryState().items; + + this.undoRedoLayerUniqueFieldValues({ + layerId, + sessionItems, + action: 'undo' + }); + + const undoItems = session.undo(); + + this.undoRedoRelationUniqueFieldValues({ + relationSessionItems: undoItems, + action: 'undo' + }); + + this.undoRelations(undoItems); + } + + /** + * undo relations + */ + undoRelations(undoItems) { + Object + .entries(undoItems) + .forEach(([toolboxId, items]) => { this.getToolBoxById(toolboxId).getSession().undo(items); }); + } + + /** + * rollback relations + */ + rollbackRelations(rollbackItems) { + Object + .entries(rollbackItems) + .forEach(([toolboxId, items]) => { this.getToolBoxById(toolboxId).getSession().rollback(items); }); + } + + /** + * @FIXME add description + */ + redo() { + const session = this.state.toolboxselected.getSession(); + const layerId = session.getId(); + const sessionItems = session.getLastHistoryState().items; + this.undoRedoLayerUniqueFieldValues({ + layerId, + sessionItems, + action: 'redo' + }); + const redoItems = session.redo(); + + this.undoRedoRelationUniqueFieldValues({ + relationSessionItems: redoItems, + action: 'redo' + }); + + this.redoRelations(redoItems); + } + + /** + * redo relations + */ + redoRelations(redoItems) { + Object + .entries(redoItems) + .forEach(([toolboxId, items]) => { this.getToolBoxById(toolboxId).getSession().redo(items); }); + } + + /** + * @param id + * + * @returns {*} + */ + getEditingLayer(id) { + return this._editableLayers[id].getEditingLayer(); + } + + /** + * @param toolbox + */ + addToolBox(toolbox) { + this._toolboxes.push(toolbox); + // add session + this._sessions[toolbox.getId()] = toolbox.getSession(); + this.state.toolboxes.push(toolbox.state); + } + + //** Method to set state in editing + setOpenEditingPanel(bool) { + this.state.open = bool; + this._getEditableLayersFromCatalog().forEach(layer => layer.setInEditing(bool)); + }; + + /** + * Add event + * + * @param { Object } event + * @param { string } event.type + * @param event.id + * @param event.fnc + **/ + addEvent({ + type, + id, + fnc, + } = {}) { + if (!this._events[type]) this._events[type] = {}; + if (!this._events[type][id]) this._events[type][id] = []; + this._events[type][id].push(fnc); + }; + + /** + * Add events + * + * @param { Object } event + * @param { string[] } event.types + * @param event.id + * @param event.fnc + */ + addEvents({ + types = [], + id, + fnc, + } = {}) { + types.forEach(type => this.addEvent({ + type, + id, + fnc + })); + }; + + /** + * @param { Object } handler + * @param handler.type + * @param handler.id + * + * @returns { Promise } + */ + async runEventHandler({ + type, + id, + } = {}) { + await (this._events[type] && this._events[type][id] && Promise.allSettled(this._events[type][id].map(fnc => fnc()))); + } + + /** + * @param { Object } save + * @param save.mode - default or autosave + * @param save.cb - object contain done/error two functions + * @param save.modal - Boolean true or false to show to ask + * @param save.messages - object success or error + */ + setSaveConfig({ + mode = 'default', + cb={}, + modal=false, + messages, + } = {}) { + this.saveConfig.mode = mode; + this.saveConfig.modal = modal; + this.saveConfig.messages = messages; + this.saveConfig.cb = { + ...this.saveConfig.cb, + ...cb + } + } + + /** + * @returns save mode + */ + getSaveConfig() { + return this.saveConfig; + } + + /** + * Reset default values + */ + resetDefault() { + this.saveConfig = { + mode: "default", // default, autosave + modal: false, + messages: null, // object to set custom message + cb: { + done: null, // function Called after save + error: null, // function called affte commit error + } + }; + this.disableMapControlsConflict(false); + } + + /** + * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 + * + * Reset default toolbox state modified by other plugin + * + * @since g3w-client-plugin-editing@v3.7.2 + */ + resetAPIDefault({ + plugin=true, + toolboxes=true, + } = {}) { + if (toolboxes) { + this.getToolBoxes().forEach(tb => { tb.resetDefault(); }); + } + if (plugin) { + this.resetDefault(); + } + } + + /** + * Get data from api when a field of a layer + * is related to a wgis form widget (ex. relation reference, value map, etc..) + * + * @param layer + * + * @private + */ + _attachLayerWidgetsEvent(layer) { + const fields = layer.getEditingFields(); + for (let i=0; i < fields.length; i++) { + const field = fields[i]; + if (field.input) { + if (field.input.type === 'select_autocomplete' && !field.input.options.filter_expression) { + const options = field.input.options; + let { + key, + values, + value, + usecompleter, + layer_id, + loading, + relation_id, // @since g3w-client-plugin-editing@v3.7.0 + relation_reference, // @since g3w-client-plugin-editing@v3.7.0 + filter_fields=[], // @since g3w-client-plugin-editing@v3.7.2 + } = options; + const self = this; + if (!usecompleter) { + this.addEvents({ + /** + * @TODO need to avoid to call the same fnc to same event many times to avoid waste server request time + */ + types: ['start-editing', 'show-relation-editing'], + id: layer.getId(), + fnc() { + return new Promise((resolve, reject) => { + // remove all values + loading.state = 'loading'; + field.input.options.values = []; + //check if field has a relation reference widget + // and no filter fields set + if ( + relation_reference + && ( + [undefined, null].indexOf(filter_fields) > -1 + || filter_fields.length === 0 + ) + ) { + //get data with fformatter + layer.getFilterData({ + fformatter: field.name + }) .then(response => { - if (response && response.features) { - const features = response.features; - for (let i = 0; i < features.length; i++) { + //check if response + if (response && response.data) { + //response data is an array ok key value objects + response.data.forEach(([value, key]) => { field.input.options.values.push({ - key: features[i].properties[key], - value: features[i].properties[value] + key, + value }) - } + }) loading.state = 'ready'; - // Plugin need to know about it self.fireEvent('autocomplete', { field, - features - }) + data: [response.data] + }); + //resolve resolve(field.input.options.values); } }) - .fail(error => { + .catch((error) => { loading.state = 'error'; reject(error); + }) + } + //check if layer id (field has widget value map) + else if (layer_id) { + const relationLayer = CatalogLayersStoresRegistry.getLayerById(layer_id); + if (relationLayer) { + relationLayer.getDataTable({ + ordering: key + }) + .then(response => { + if (response && response.features) { + const features = response.features; + for (let i = 0; i < features.length; i++) { + field.input.options.values.push({ + key: features[i].properties[key], + value: features[i].properties[value] + }) + } + loading.state = 'ready'; + // Plugin need to know about it + self.fireEvent('autocomplete', { + field, + features + }) + resolve(field.input.options.values); + } + }) + .fail(error => { + loading.state = 'error'; + reject(error); + }); + } + } + else { + // @TODO Check if is used otherwise need to deprecate it + const features = []; + loading.state = 'ready'; + // Plugin need to know about it + self.fireEvent('autocomplete', { + field, + features }); + resolve(features); } - } - else { - // @TODO Check if is used otherwise need to deprecate it - const features = []; - loading.state = 'ready'; - // Plugin need to know about it - self.fireEvent('autocomplete', { - field, - features - }); - resolve(features); - } - }) - } - }) + }) + } + }) + } } } } } -}; - -/** - * @private - */ -proto._createToolBoxDependencies = function() { - this._toolboxes.forEach(toolbox => { - const layer = toolbox.getLayer(); - toolbox.setFather(layer.isFather()); - toolbox.state.editing.dependencies = this._getToolBoxEditingDependencies(layer); - if (layer.isFather() && toolbox.hasDependencies() ) { - const layerRelations = layer.getRelations().getRelations(); - for (const relationName in layerRelations) { - const relation = layerRelations[relationName]; - toolbox.addRelation(relation); + + /** + * @private + */ + _createToolBoxDependencies() { + this._toolboxes.forEach(toolbox => { + const layer = toolbox.getLayer(); + toolbox.setFather(layer.isFather()); + toolbox.state.editing.dependencies = this._getToolBoxEditingDependencies(layer); + if (layer.isFather() && toolbox.hasDependencies() ) { + const layerRelations = layer.getRelations().getRelations(); + for (const relationName in layerRelations) { + const relation = layerRelations[relationName]; + toolbox.addRelation(relation); + } } - } - }) -}; - -/** - * Check if field of layer is required - * - * @param layerId - * @param fieldName - * - * @returns {*} - */ -proto.isFieldRequired = function(layerId, fieldName) { - return this.getLayerById(layerId).isFieldRequired(fieldName); -}; - -/** - * @param layer - * - * @returns { Array } - * - * @private - */ -proto._getToolBoxEditingDependencies = function(layer) { - let relationLayers = [...layer.getChildren(), ...layer.getFathers()]; - return relationLayers.filter((layerName) => undefined !== this.getLayerById(layerName)); -}; - -/** - * @param layer - * - * @returns { boolean } - * - * @private - */ -proto._hasEditingDependencies = function(layer) { - let toolboxesIds = this._getToolBoxEditingDependencies(layer); - return toolboxesIds.length > 0; -}; - -/** - * @param toolbox - */ -proto.handleToolboxDependencies = function(toolbox) { - let dependecyToolBox; - if (toolbox.isFather()) { - this.getLayersDependencyFeatures(toolbox.getId()); - } - toolbox.getDependencies() - .forEach(toolboxId => { - dependecyToolBox = this.getToolBoxById(toolboxId); - dependecyToolBox.setEditing(false); }) -}; - -/** - * @returns {*} - * - * @private - */ -proto._getEditableLayersFromCatalog = function() { - return CatalogLayersStoresRegistry.getLayers({ - EDITABLE: true - }); -}; - -/** - * @returns { Array } - */ -proto.getLayers = function() { - return Object.values(this._editableLayers); -}; - -/** - * @returns {*} - */ -proto.getCurrentWorkflow = function() { - return WorkflowsStack.getCurrent(); -}; - -/** - * @returns {{feature: *, session: *, inputs: *, context: *, layer: *}} - */ -proto.getCurrentWorkflowData = function() { - const currentWorkFlow = WorkflowsStack.getCurrent(); - return { - session: currentWorkFlow.getSession(), - inputs: currentWorkFlow.getInputs(), - context: currentWorkFlow.getContext(), - feature: currentWorkFlow.getCurrentFeature(), - layer: currentWorkFlow.getLayer() + } + + /** + * Check if field of layer is required + * + * @param layerId + * @param fieldName + * + * @returns {*} + */ + isFieldRequired(layerId, fieldName) { + return this.getLayerById(layerId).isFieldRequired(fieldName); + } + + /** + * @param layer + * + * @returns { Array } + * + * @private + */ + _getToolBoxEditingDependencies(layer) { + let relationLayers = [...layer.getChildren(), ...layer.getFathers()]; + return relationLayers.filter((layerName) => undefined !== this.getLayerById(layerName)); + } + + /** + * @param layer + * + * @returns { boolean } + * + * @private + */ + _hasEditingDependencies(layer) { + let toolboxesIds = this._getToolBoxEditingDependencies(layer); + return toolboxesIds.length > 0; + } + + /** + * @param toolbox + */ + handleToolboxDependencies(toolbox) { + let dependecyToolBox; + if (toolbox.isFather()) { + this.getLayersDependencyFeatures(toolbox.getId()); + } + toolbox.getDependencies() + .forEach(toolboxId => { + dependecyToolBox = this.getToolBoxById(toolboxId); + dependecyToolBox.setEditing(false); + }) + } + + /** + * @returns {*} + * + * @private + */ + _getEditableLayersFromCatalog() { + return CatalogLayersStoresRegistry.getLayers({ + EDITABLE: true + }); + } + + /** + * @returns { Array } + */ + getLayers() { + return Object.values(this._editableLayers); + } + + /** + * @returns {*} + */ + getCurrentWorkflow() { + return WorkflowsStack.getCurrent(); + } + + /** + * @returns {{feature: *, session: *, inputs: *, context: *, layer: *}} + */ + getCurrentWorkflowData() { + const currentWorkFlow = WorkflowsStack.getCurrent(); + return { + session: currentWorkFlow.getSession(), + inputs: currentWorkFlow.getInputs(), + context: currentWorkFlow.getContext(), + feature: currentWorkFlow.getCurrentFeature(), + layer: currentWorkFlow.getLayer() + }; + } + + /** + * @param { Object } opts + * @param opts.layerId + * @param opts.relation + * @param opts.feature + * + * @returns { BigUint64Array } + */ + getRelationsAttributesByFeature({ + layerId, + relation, + feature, + } = {}) { + const layer = this.getToolBoxById(layerId).getLayer(); + const relations = this.getRelationsByFeature({ layerId, relation, feature }); + return relations + .map(relation => ({ + fields: layer.getFieldsWithValues(relation, { relation: true }), + id: relation.getId() + })) + } + + /** + * @param { Object } opts + * @param opts.layerId + * @param opts.relation + * + * @returns {*} + * + * @private + */ + _getRelationLayerId({ + layerId, + relation, + } = {}) { + + const child = relation.getChild ? + relation.getChild() : + relation.child; + + const father = relation.getFatherField ? + relation.getFatherField() : + relation.fatherField; + + return (child === layerId) ? father: child; + } + + /** + * @param { Object } opts + * @param opts.layerId + * @param opts.relation + * @param opts.feature + */ + getRelationsByFeature({ + layerId, + relation, + feature, + } = {}) { + //get father layer + const fatherLayer = this.getLayerById(relation.getFather ? relation.getFather() : relation.father); + const { ownField, relationField } = this._getRelationFieldsFromRelation({ layerId, relation }); + // get features of relation child layers + // Loop relation fields + const values = relationField.map(field => { + //In case of new feature, need to check if field is pk field + if (feature.isNew() && fatherLayer.isPkField(field)) { + return feature.getId(); + } + return feature.get(field) + }); + return this + ._getFeaturesByLayerId(layerId) + .filter(feature => ownField.every((field, i) => feature.get(field) == values[i])); + } + + /** + * @param { boolean } bool + */ + registerLeavePage(bool) { + ApplicationService.registerLeavePage({ + bool + }); }; -}; - -/** - * @param { Object } opts - * @param opts.layerId - * @param opts.relation - * @param opts.feature - * - * @returns { BigUint64Array } - */ -proto.getRelationsAttributesByFeature = function({ - layerId, - relation, - feature, -} = {}) { - const layer = this.getToolBoxById(layerId).getLayer(); - const relations = this.getRelationsByFeature({ layerId, relation, feature }); - return relations - .map(relation => ({ - fields: layer.getFieldsWithValues(relation, { relation: true }), - id: relation.getId() - })) - -} - -/** - * @param { Object } opts - * @param opts.layerId - * @param opts.relation - * - * @returns {*} - * - * @private - */ -proto._getRelationLayerId = function({ - layerId, - relation, -} = {}) { - - const child = relation.getChild ? - relation.getChild() : - relation.child; - - const father = relation.getFatherField ? - relation.getFatherField() : - relation.fatherField; - - return (child === layerId) ? father: child; -}; - -/** - * @param { Object } opts - * @param opts.layerId - * @param opts.relation - * @param opts.feature - */ -proto.getRelationsByFeature = function({ - layerId, - relation, - feature, -} = {}) { - //get father layer - const fatherLayer = this.getLayerById(relation.getFather ? relation.getFather() : relation.father); - const { ownField, relationField } = this._getRelationFieldsFromRelation({ layerId, relation }); - // get features of relation child layers - // Loop relation fields - const values = relationField.map(field => { - //In case of new feature, need to check if field is pk field - if (feature.isNew() && fatherLayer.isPkField(field)) { - return feature.getId(); + + /** + * @returns { boolean } + */ + loadPlugin() { + return this._load = !!this._getEditableLayersFromCatalog().length; + } + + /** + * @param { string } layerId + * + * @returns {*} + */ + getLayerById(layerId) { + return this._editableLayers[layerId]; + } + + /** + * @param layer + */ + beforeEditingStart({ layer } = {}) { + this._checkLayerWidgets(layer); + } + + /** + * @param layer + */ + afterEditingStart({ layer }= {}) { + //TODO + } + + /** + * @param { string } toolboxId + * + * @returns {*} + */ + getToolBoxById(toolboxId) { + return this._toolboxes.find(tb => tb.getId() === toolboxId); + } + + /** + * Get layer session by id (layer id is the same of session) + * + * @param id + * + * @returns {*} + * + * @since g3w-client-plugin-editing@v3.7.0 + */ + getSessionById(id) { + return this._sessions[id]; + } + + /** + * Method to apply filter editing contsraint to toolbox editing + * Apply filter editing contsraint to toolbox editing + * + * @param constraints + */ + setApplicationEditingConstraints(constraints={showToolboxesExcluded: true, toolboxes:{}}) { + this.applicationEditingConstraints = { + ...this.applicationEditingConstraints, + ...constraints + }; + + const {toolboxes, showToolboxesExcluded} = constraints; + const toolboxIds = Object.keys(toolboxes); + if (false === showToolboxesExcluded) { + this.state.toolboxes.forEach(toolbox => toolbox.show = toolboxIds.indexOf(toolbox.id) !== -1); + } + toolboxIds.forEach(toolboxId => this + .getToolBoxById(toolboxId) + .setEditingConstraints(toolboxes[toolboxId])) + } + + /** + * Get application editing contraints if applied + */ + getApplicationEditingConstraints() { + return this.applicationEditingConstraints; + } + + /** + * @param { string } toolboxId + * + * @returns {*} + */ + getApplicationEditingConstraintById(toolboxId) { + return this.applicationEditingConstraints.toolboxes[toolboxId]; + } + + /** + * @returns { Array } + */ + getToolBoxes() { + return this._toolboxes; + } + + /** + * @returns {*|{}} + */ + getEditableLayers() { + return this._editableLayers; + } + + /** + * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 + * + * @returns { string[] } + * + * @since g3w-client-plugin-editing@v3.7.2 + */ + getEditableLayersId() { + return Object.keys(this.getEditableLayers()); + } + + /** + * @returns {*} + * + * @private + */ + _cancelOrSave() { + return resolve(); + } + + /** + * Stop editing + * + * @returns { Promise } + */ + stop() { + return new Promise((resolve, reject) => { + const commitpromises = []; + this._toolboxes + .forEach(toolbox => { + // check if temp changes are waiting to save on server + if (toolbox.getSession().getHistory().state.commit) { + // ask to commit before exit + commitpromises.push(this.commit({toolbox, modal:true})); + } + }); + $.when.apply(this, commitpromises) + .always(() => { + this._toolboxes + .forEach(toolbox => toolbox.stop()); + this.clearState(); + //this.activeQueryInfo(); + this._mapService.refreshMap(); + resolve(); + }); + }); + } + + /** + * remove Editing LayersStore + */ + clear() { + MapLayersStoreRegistry.removeLayersStore(this._layersstore); + SessionsRegistry.clear(); + //turn off events + this._mapService.off(MAPCONTROL_TOGGLED_EVENT_NAME, this.mapControlToggleEventHandler); + this.unregisterResultEditingAction(); + } + + /** + * @FIXME add description + */ + clearState() { + this.state.toolboxselected = null; + this.state.toolboxidactivetool = null; + this.state.message = null; + } + + /** + * Get Relation in editing + * + * @param { Object } opts + * @param opts.layerId + * @param opts.relations + * @param opts.feature + * + * @returns { Array } + */ + getRelationsInEditing({ + layerId, + relations = [], + feature, + } = {}) { + let relationsinediting = []; + let relationinediting; + relations.forEach(relation => { + const relationLayerId = this._getRelationLayerId({layerId, relation}); + //check if the layer is editable + if (this.getLayerById(relationLayerId)) { + relationinediting = { + relation: relation.getState(), + relations: this.getRelationsAttributesByFeature({ + layerId: relationLayerId, + relation, + feature + }) + }; + relationinediting.validate = { + valid:true + }; + relationsinediting.push(relationinediting); + } + }); + return relationsinediting; + } + + /** + * @param { Object } opts + * @param opts.layerId + * @param opts.relations + * + * @returns { Array } + * + * @private + */ + _filterRelationsInEditing({ + layerId, + relations = [], + }) { + return relations.filter(relation => this.getToolBoxById(this._getRelationId({ layerId, relation }))); + } + + /** + * @param { string } layerId + */ + stopToolboxesChildren(layerId) { + const layer = this.getLayerById(layerId); + const relations = this._filterRelationsInEditing({ + relations: layer.getRelations() ? layer.getRelations().getArray() : [], + layerId + }); + relations + .filter(relation => relation.getFather() === layerId) + .forEach(relation => { + const relationId = this._getRelationId({ layerId, relation }); + if (this.getToolBoxById(relationId).inEditing()) { + this.getToolBoxById(relationId).stop(); + } + }) + } + + /** + * @param { string } layerId + */ + stopSessionChildren(layerId) { + const layer = this.getLayerById(layerId); + const relations = this._filterRelationsInEditing({ + relations: layer.getRelations() ? layer.getRelations().getArray() : [], + layerId + }); + relations + .filter(relation => relation.getFather() === layerId) + .forEach(relation => { + const relationId = this._getRelationId({ layerId, relation }); + // In case of no editing is started (click on pencil of relation layer) need to stop (unlock) features + if (!this.getToolBoxById(relationId).inEditing()) { + this._sessions[relationId].stop(); + } + }) + } + + /** + * Check if father relation is editing and has commit feature + * + * @param { string } layerId + * + * @returns father in editing + */ + fathersInEditing(layerId) { + return this.getLayerById(layerId) + .getFathers() + .filter(id => { + const toolbox = this.getToolBoxById(id); + if (toolbox && toolbox.inEditing() && toolbox.isDirty()) { + //get temporary relations object + const {relations={}} = toolbox.getSession().getCommitItems(); + //check if layerId has some changes + return Object + .keys(relations) + .find(relationLayerId => layerId === relationLayerId); + } + }); + } + + /** + * Based on layerId and relation, + * extract field of relation. + * ownField are array of fields related to relation and belong to layerId + * relationField area array of fields related to relation thar belong to other layer in relation with layerId + * @param { Object } opts + * @param opts.layerId + * @param opts.relation + * + * @returns {{ ownField: [], relationField: [] }} `ownField` and `relationField` are Arrays since g3w-client-plugin-editing@v3.7.0 + * + * @private + */ + _getRelationFieldsFromRelation({ + layerId, + relation, + } = {}) { + /** {String} @type */ + const childId = relation.getChild ? + relation.getChild() : + relation.child; + + /** {Boolean} @type check if is child*/ + const isChild = childId !== layerId; + /** { Array } @type array of fields */ + const _fatherField = relation.getFatherField ? + relation.getFatherField() : + relation.fatherField; + + /** { Array } @type array of fields */ + const _childField = relation.getChildField ? + relation.getChildField() : + relation.childField; + + return { + ownField: isChild ? _fatherField : _childField, + relationField: isChild ? _childField : _fatherField + } + } + + /** + * @param { 'all' | 'bbox' | 'field' | 'fid' | '1:1' } filterType + * @param { Object } options + * @param options.feature + * @param options.relation + * @param options.field + * @param options.layerId + * @param options.operator + */ + createEditingDataOptions(filterType = 'all', options = {}) { + let filter; + + switch (filterType) { + + case 'all': + filter = undefined; + break; + + case 'bbox': + filter = { + bbox: this._mapService.getMapBBOX(), + }; + break; + + case 'field': + filter = { + field: { + field: options.field, + type: 'editing' + } + }; + break; + + case 'fid': + if ('not' !== options.operator) { // get relations of current feature + filter = { + fid: { + fid: options.feature.getId(), + layer: { id: options.layerId }, + type: 'editing', + relation: options.relation.state, + formatter: 0, // 0 = retrieve stored value + } + }; + } + break; + + // relation 1:1 + case '1:1': + filter = { + field: options.relation.getChildField()[0] + '|eq|' + options.feature.get(options.relation.getFatherField()[0]), + type: 'editing', + } + break; + + } + + return { + registerEvents: true, // usefult to get register vent on toolbox example mapmoveend + editing: true, + filter + }; + + } + + /** + * @param { string } layerId + * + * @returns {*} + * + * @private + */ + _getFeaturesByLayerId(layerId) { + return this.getLayerById(layerId).readEditingFeatures(); + } + + /** + * @param { Object } opts + * @param { string } opts.layerId + * @param opts.relation + * @param opts.feature + * @param { string } [opts.operator='eq'] + * + * @returns { Promise } + */ + getLayersDependencyFeaturesFromSource({ + layerId, + relation, + feature, + operator = 'eq', + } = {}) { + return new Promise(resolve => { + // skip when .. + if ('eq' !== operator) { + return resolve(false); + } + + const { ownField, relationField } = this._getRelationFieldsFromRelation({ layerId, relation }); + const features = this._getFeaturesByLayerId(layerId); + const featureValues = relationField.map(field => feature.get(field)); + + resolve(ownField.every((field, i) => features.find(f => f.get(field) == featureValues[i]))) + }) + } + + /** + * Based on layer id and relation, return the layer id + * of the other layer that is in relation with layerId + * @param { Object } opts + * @param opts.layerId + * @param opts.relation + * + * @returns {*|{configurable: boolean}|{configurable}|boolean|(function(): *)} + * + * @private + */ + _getRelationId({ + layerId, + relation, + } = {}) { + const fatherId = relation.getFather ? relation.getFather() : relation.father; + const childId = relation.getChild ? relation.getChild() : relation.child; + + return fatherId === layerId ? childId: fatherId; + } + + /** + * @param { string } layerId + * @param opts + * + * @returns { Promise[]> } + */ + async getLayersDependencyFeatures(layerId, opts = {}) { + const layer = this.getLayerById(layerId); + const relations = opts.relations + || layer.getChildren().length && layer.getRelations() && this._filterRelationsInEditing({ layerId, relations: layer.getRelations().getArray().filter(r => r.getFather() === layerId) }) + || []; + + let response; + + try { + response = await Promise.all(relations.map(relation => { + + if (relation.setLoading) { + relation.setLoading(true); + } else { + relation.loading = true; + } + + const id = this._getRelationId({ layerId, relation }); + + opts.relation = relation; + opts.layerId = layerId; + opts.filterType = 'ONE' === (relation.getType ? relation.getType() : relation.type) ? '1:1' : opts.filterType; // In a case of relation 1:1 + const filterType = opts.filterType || 'fid'; + const options = this.createEditingDataOptions(filterType, opts); + const session = this._sessions[id]; + const online = ApplicationState.online && session; + const toolbox = this.getToolBoxById(id); + + // Promise + return new Promise(async (resolve) => { + + // try to get feature from source without server reques + const find = (!ApplicationState.online || !session || session.isStarted()) && await promisify( + this.getLayersDependencyFeaturesFromSource({ + layerId: id, + relation, + feature: opts.feature, + operator: opts.operator + })); + + toolbox.startLoading(); + + try { + if (online && !session.isStarted()) { + await promisify(session.start(options)); // start session and get features + } else if (online && !find) { + await promisify(session.getFeatures(options)); // request features from server + } + } catch (promise) { + try { await promisify(promise) } catch (e) { console.warn(e, promise); } + } + + toolbox.stopLoading(); + + resolve(id); + }); + })); + } catch (e) { + console.warn(e); + } + + // at the end se loading false + relations.forEach(relation => { + if (relation.setLoading) { + relation.setLoading(false); + } else { + relation.loading = false; + } + }); + + return response; + } + + /** + * @param { string } layerId + * + * @returns { Promise } + */ + commitDirtyToolBoxes(layerId) { + return new Promise((resolve, reject) => { + const toolbox = this.getToolBoxById(layerId); + if (toolbox.isDirty() && toolbox.hasDependencies()) { + this.commit({toolbox}) + .then(() => { + resolve(toolbox); + }) + .fail(() => { + toolbox.revert() + .then(() => { + toolbox.getDependencies() + .forEach((layerId) => { + if (this.getLayerById(layerId).getChildren().indexOf(layerId) !== -1) { + this.getToolBoxById(layerId).revert(); + } + }) + }) + reject(toolbox); + }) + } else + resolve(toolbox); + }); + } + + /** + * @param commitItems + * + * @returns { string } + * + * @private + */ + _createCommitMessage(commitItems) { + function create_changes_list_dom_element(add, update, del) { + const changeIds = {}; + changeIds[`${t('editing.messages.commit.add')}`] = add.length; + changeIds[`${t('editing.messages.commit.update')}`] = `[${update.map((item)=> item.id).join(',')}]`; + changeIds[`${t('editing.messages.commit.delete')}`] = `[${del.join(',')}]`; + let dom = `

${t('editing.messages.commit.header')}

`; + dom+=`
${t('editing.messages.commit.header_add')}
`; + dom+=`
${t('editing.messages.commit.header_update_delete')}
`; + dom+= `
    `; + Object.entries(changeIds).forEach(([action, ids]) => { + dom += `
  • ${action} : ${ids}
  • `; + }); + dom += `
`; + return dom; + } + + let message = ""; + message += create_changes_list_dom_element(commitItems.add, commitItems.update, commitItems.delete); + if (!_.isEmpty(commitItems.relations)) { + message += "
"; + message += "

"+ t('editing.relations') +"

"; + Object.entries(commitItems.relations).forEach(([ relationName, commits]) => { + message += "
" + relationName + "
"; + message += create_changes_list_dom_element(commits.add, commits.update, commits.delete); + }) + } + return message; + } + + /** + * @param { Object } opts + * @param opts.layer + * @param opts.commitItems + * @param opts.close + * @param opts.commitPromise + * + * @returns { Promise } + */ + showCommitModalWindow({ + layer, + commitItems, + close, + commitPromise, + }) { + // messages set to commit + const messages = { + success: { + message: "plugins.editing.messages.saved", + autoclose: true + }, + error: {} + }; + + return new Promise((resolve, reject) =>{ + + /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/commitfeaturesworkflow.js@v3.7.1 */ + const workflow = new EditingWorkflow({ + type: 'commitfeatures', + steps: [ new ConfirmStep({ type: 'commit' }) ] + }) + + workflow.start({ + inputs: { + layer, + message: this._createCommitMessage(commitItems), + close + } + }) + .then(() => { + const dialog = GUI.dialog.dialog({ + message: `

${t('editing.messages.saving')}

`, + closeButton: false + }); + resolve(messages); + commitPromise.always(() => dialog.modal('hide')) // hide saving dialog + }) + .fail(async error => { + const promises = []; + const rollbackRelations = (relations={}) => { + Object + .entries(relations) + .forEach(([ layerId, {add, delete:del, update, relations = {}}]) => { + const layer = this.getLayerById(layerId); + const sourceLayer = layer.getEditingSource(); + // check if the relation layer has some features + if (sourceLayer.readFeatures().length > 0) { + + //add a need to remove + add.forEach(({id}) => { + sourceLayer.removeFeature(sourceLayer.getFeatureById(id)) + }) + + // //need to get original values + update.forEach(({ id }) => { + promises.push( + new Promise((resolve, reject) => { + this.getProjectLayerFeatureById({ layerId, fid: id }) + .then(f => { + const feature = sourceLayer.getFeatureById(id); + feature.setProperties(f.properties); + feature.setGeometry(f.geometry); + resolve(); + }).catch(reject); + }) + ) + }) + } + + //need to add again. + del.forEach(id => { + promises.push( + new Promise((resolve, reject) => { + this.getProjectLayerFeatureById({ layerId, fid: id }) + .then(f => { + const feature = new ol.Feature({ geometry: f.geometry }) + feature.setProperties(f.properties); + feature.setId(id); + // need to add again to source because it is for relation layer is locked + sourceLayer.addFeature(new Feature({ feature })); + resolve(); + }).catch(reject); + }) + ) + }) + rollbackRelations(relations); + }) + } + rollbackRelations(commitItems.relations); + await Promise.allSettled(promises); + reject(error) + }) + .always(() => workflow.stop()) + }) + } + + /** + * Function called very single change saved temporary + */ + async saveChange() { + switch (this.saveConfig.mode) { + case 'autosave': + return this.commit({ + modal: false // set to not show modal ask window + }); + } + } + + /** + * @param { Object } opts + * @param { string } opts.layerId + * @param { Array } opts.fids + */ + addLayersFeaturesToShowOnResult({ + layerId, + fids = [], + }) { + if (undefined === this.loadLayersFeaturesToResultWhenCloseEditing[layerId]) { + this.loadLayersFeaturesToResultWhenCloseEditing[layerId] = new Set(); + } + fids.forEach(fid => this.loadLayersFeaturesToResultWhenCloseEditing[layerId].add(fid)) + } + + /** + * Called on close editingpanel panel + */ + async onCloseEditingPanel() { + await this.showChangesToResult(); + this.getToolBoxes().forEach(toolbox => toolbox.resetDefault()); + } + + /** + * Show feature that are updated or created with editing on result content + * + * @returns { Promise } + */ + async showChangesToResult() { + const layerIdChanges = Object.keys(this.loadLayersFeaturesToResultWhenCloseEditing); + if (layerIdChanges.length) { + const inputs = { + layers: [], + fids: [], + formatter: 1 + }; + layerIdChanges + .forEach(layerId => { + const fids = [...this.loadLayersFeaturesToResultWhenCloseEditing[layerId]]; + if (fids.length) { + const layer = CatalogLayersStoresRegistry.getLayerById(layerId); + inputs.layers.push(layer); + inputs.fids.push(fids); + } + }); + + const promise = inputs.layers.length ? + DataRouterService.getData('search:layersfids', { + inputs, + outputs: { + title: 'plugins.editing.editing_changes', + show: {loading: false} + } + }) : + Promise.resolve(); + try { + await promise; + } catch(err) {} + } + this.loadLayersFeaturesToResultWhenCloseEditing = {}; + } + + /** + * Commit and save changes on server persistently + * + * @param { Object } commit + * @param commit.toolbox + * @param commit.commitItems + * @param commit.messages + * @param commit.done + * @param { boolean } commit.modal + * @param { boolean } commit.close + * + * @returns {*} + */ + commit({ + toolbox, + commitItems, + modal = true, + close = false, + } = {}) { + const d = $.Deferred(); + const commitPromise = d.promise(); + const { + cb = {}, + messages = { + success:{}, + error:{} + }, + } = this.saveConfig; + toolbox = toolbox || this.state.toolboxselected; + let session = toolbox.getSession(); + let layer = toolbox.getLayer(); + const layerType = layer.getType(); + const items = commitItems; + commitItems = commitItems || session.getCommitItems(); + const { + add = [], + delete: cancel = [], + update = [], + relations = {}, + } = commitItems; + + //check if there are some changes to commit + if ( + [ + ...add, + ...cancel, + ...update, + ...Object.keys(relations) + ].length === 0 + ) { + GUI.showUserMessage({ + type: 'info', + message: 'Nothing to save', + autoclose: true, + closable: false + }); + + d.resolve(toolbox); + + return d.promise(); + } + + const promise = modal ? this.showCommitModalWindow({ + layer, + commitItems, + close, + commitPromise // add a commit promise + }) : Promise.resolve(messages); + + promise + .then(messages => { + //check if application is online + if (ApplicationState.online) { + session.commit({items: items || commitItems}) + .then((commitItems, response) => { + //@TODO need to double check why ApplicationState.online is repeated + if (ApplicationState.online) { + //if result is true + if (response.result) { + const {autoclose=true, message="plugins.editing.messages.saved"} = messages.success; + if (messages && messages.success) { + GUI.showUserMessage({ + type: 'success', + message, + duration: 3000, + autoclose + }); + } + + //In case of vector layer need to refresh map commit changes + if (layerType === Layer.LayerTypes.VECTOR) { + this._mapService.refreshMap({force: true}); + } + + if (cb.done && cb.done instanceof Function) { + cb.done(toolbox); + } + + //add items when close editing to results to show changes + this.addLayersFeaturesToShowOnResult({ + layerId: toolbox.getId(), + fids: [ + ...response.response.new.map(({id}) => id), + ...commitItems.update.map(update => update.id) + ] + }); + //@since 3.7.2 + //it is useful when click on save all disk icon in editing forma for relation purpose + this.emit('commit', response.response); + + } else { //result is false. An error occurs + const parser = new serverErrorParser({ + error: response.errors + }); + + const errorMessage = parser.parse({ + type: 'String' + }); + + const {autoclose=false, message} = messages.error; + + GUI.showUserMessage({ + type: 'alert', + message: message || errorMessage, + textMessage: !message, + autoclose + }); + + if (cb.error && cb.error instanceof Function) { + cb.error(toolbox, message || errorMessage); + } + } + + d.resolve(toolbox); + } + }) + .fail((error={}) => { + //parse error server + const parser = new serverErrorParser({ + error: error.errors ? error.errors : error + }); + //set type string + const errorMessage = parser.parse({ + type: 'String' + }); + + const {autoclose = false, message} = messages.error; + + GUI.showUserMessage({ + type: 'alert', + message: message || errorMessage, + textMessage: !message, + autoclose + }); + + d.reject(toolbox); + + if (cb.error && cb.error instanceof Function) { + cb.error(toolbox, message || errorMessage); + } + }); + //case offline + } else { + this.saveOfflineItem({ + data: { + [session.getId()]: commitItems + }, + id: OFFLINE_ITEMS.CHANGES + }) + .then(() => { + GUI.showUserMessage({ + type: 'success', + message: "plugins.editing.messages.saved_local", + autoclose: true + }); + session.clearHistory(); + d.resolve(toolbox); + }) + .catch(error => { + GUI.showUserMessage({ + type: 'alert', + message: error, + textMessage: true, + }); + + d.reject(toolbox); + }) + } + }) + .catch((e) => { + console.warn(e); + d.reject(toolbox) + }); + + return commitPromise; + } + + /** + * Clear all unique values fields related to layer (after closing editing panel). + */ + clearAllLayersUniqueFieldsValues() { + this.layersUniqueFieldsValues = {}; + } + + /** + * Clear single layer unique field values (when stopping toolbox editing). + * + * @param { string } layerId + */ + clearLayerUniqueFieldsValues(layerId) { + this.layersUniqueFieldsValues[layerId] = {}; + } + + /** + * Remove unique values from unique fields of a layer (when deleting a feature) + * + * @param { Object } opts + * @param { string } opts.layerId + * @param opts.feature + */ + removeLayerUniqueFieldValuesFromFeature({ + layerId, + feature, + }) { + const fields = this.layersUniqueFieldsValues[layerId]; + if (fields) { + Object + .keys(feature.getProperties()) + .filter(field => undefined !== fields[field]) + .forEach(field => fields[field].delete(feature.get(field))); + } + }; + + /** + * @param { Object } opts + * @param { string } opts.layerId + * @param { string } opts.relationLayerId + * @param opts.feature + */ + removeRelationLayerUniqueFieldValuesFromFeature({ + layerId, + relationLayerId, + feature, + }) { + + const layer = this.layersUniqueFieldsValues[relationLayerId]; + const fields = this.layersUniqueFieldsValues[layerId]; + + /** @FIXME add description */ + if (undefined === layer || undefined === fields) { + return; + } + + /** @FIXME add description */ + if (undefined === layer.__uniqueFieldsValuesRelations) { + layer.__uniqueFieldsValuesRelations = {}; + } + + Object + .keys(feature.getProperties()) + .forEach(property => { + /** @FIXME add description */ + if (undefined === layer.__uniqueFieldsValuesRelations[layerId]) { + layer.__uniqueFieldsValuesRelations[layerId] = {}; + } + /** @FIXME add description */ + if (undefined !== fields[property]) { + const values = new Set(fields[property]); + values.delete(feature.get(property)); + layer.__uniqueFieldsValuesRelations[layerId][property] = values; + } + }); + } + + /** + * @param { string } layerId + * + * @returns { Promise<*> } + */ + async setLayerUniqueFieldValues(layerId) { + const promises = []; + const layer = CatalogLayersStoresRegistry.getLayerById(layerId); + layer + .getEditingFields() + .forEach(field => { + // skip when .. + if (!(field.validate.unique && undefined === this.getLayerUniqueFieldValues({ layerId, field }))) { + return; + } + promises.push( + layer + .getFilterData({ unique: field.name }) + .then((values = []) => { + if (undefined === this.layersUniqueFieldsValues[layerId]) { + this.layersUniqueFieldsValues[layerId] = {}; + } + this.layersUniqueFieldsValues[layerId][field.name] = new Set(values); + }) + ); + }); + await Promise.allSettled(promises); + + return this.layersUniqueFieldsValues[layerId]; + } + + /** + * Save temporary relation feature changes on father (root) layer feature + * + * @param { string } layerId + */ + saveTemporaryRelationsUniqueFieldsValues(layerId) { + const relations = ( + this.layersUniqueFieldsValues[layerId] && + this.layersUniqueFieldsValues[layerId].__uniqueFieldsValuesRelations + ); + + // skip when no relation unique fields values are stored + if (undefined === relations) { + return; + } + + Object + .keys(relations) + .forEach(relationLayerId => { + Object + .entries(relations[relationLayerId]) + .forEach(([fieldName, uniqueValues]) => { + this.layersUniqueFieldsValues[relationLayerId][fieldName] = uniqueValues; + }) + }); + + this.clearTemporaryRelationsUniqueFieldsValues(layerId); + } + + /** + * @param { string } layerId + */ + clearTemporaryRelationsUniqueFieldsValues(layerId) { + if (this.layersUniqueFieldsValues[layerId]) { + delete this.layersUniqueFieldsValues[layerId].__uniqueFieldsValuesRelations; + } + } + + /** + * Get layer unique field value + * @param { Object } opts + * @param { string } opts.layerId layer id + * @param opts.field filed name + * + * @returns {*} + */ + getLayerUniqueFieldValues({ + layerId, + field, + }) { + + return this.layersUniqueFieldsValues[layerId] ? + this.layersUniqueFieldsValues[layerId][field.name] : + []; + } + + /** + * @param { Object } opts + * @param { string } opts.layerId + * @param { string } opts.relationLayerId + * @param opts.field + * + * @returns {*} + */ + getChildLayerUniqueFieldValues({ + layerId, + relationLayerId, + field, + }) { + const relations = ( + this.layersUniqueFieldsValues[relationLayerId] && + this.layersUniqueFieldsValues[relationLayerId].__uniqueFieldsValuesRelations + ); + const has_values = ( + undefined !== relations && + undefined !== relations[layerId] && + undefined !== relations[layerId][field.name] + ); + return has_values ? relations[layerId][field.name] : this.getLayerUniqueFieldValues({ layerId, field }); + } + + /** + * @param { Object } opts + * @param { string } opts.layerId + * @param opts.field + * @param opts.oldValue + * @param opts.newValue + */ + changeLayerUniqueFieldValues({ + layerId, + field, + oldValue, + newValue, + }) { + if ( + undefined === this.layersUniqueFieldsValues[layerId] || + undefined === this.layersUniqueFieldsValues[layerId][field.name] + ) { + return; } - return feature.get(field) - }); - return this - ._getFeaturesByLayerId(layerId) - .filter(feature => ownField.every((field, i) => feature.get(field) == values[i])); -}; - -/** - * @param { boolean } bool - */ -proto.registerLeavePage = function(bool) { - ApplicationService.registerLeavePage({ - bool - }); -}; - -/** - * @returns { boolean } - */ -proto.loadPlugin = function() { - return this._load = !!this._getEditableLayersFromCatalog().length; -}; - -/** - * @param { string } layerId - * - * @returns {*} - */ -proto.getLayerById = function(layerId) { - return this._editableLayers[layerId]; -}; - -/** - * @param layer - */ -proto.beforeEditingStart = function({ layer } = {}) { - this._checkLayerWidgets(layer); -}; - -/** - * @param layer - */ -proto.afterEditingStart = function({ layer }= {}) { - //TODO -}; - -/** - * @param { string } toolboxId - * - * @returns {*} - */ -proto.getToolBoxById = function(toolboxId) { - return this._toolboxes.find(tb => tb.getId() === toolboxId); -}; - -/** - * Get layer session by id (layer id is the same of session) - * - * @param id - * - * @returns {*} - * - * @since g3w-client-plugin-editing@v3.7.0 - */ -proto.getSessionById = function(id) { - return this._sessions[id]; -}; - -/** - * Method to apply filter editing contsraint to toolbox editing - * Apply filter editing contsraint to toolbox editing - * - * @param constraints - */ -proto.setApplicationEditingConstraints = function(constraints={showToolboxesExcluded: true, toolboxes:{}}) { - this.applicationEditingConstraints = { - ...this.applicationEditingConstraints, - ...constraints - }; - - const {toolboxes, showToolboxesExcluded} = constraints; - const toolboxIds = Object.keys(toolboxes); - if (false === showToolboxesExcluded) { - this.state.toolboxes.forEach(toolbox => toolbox.show = toolboxIds.indexOf(toolbox.id) !== -1); - } - toolboxIds.forEach(toolboxId => this - .getToolBoxById(toolboxId) - .setEditingConstraints(toolboxes[toolboxId])) -} - -/** - * Get application editing contraints if applied - */ -proto.getApplicationEditingConstraints = function() { - return this.applicationEditingConstraints; -}; - -/** - * @param { string } toolboxId - * - * @returns {*} - */ -proto.getApplicationEditingConstraintById = function(toolboxId) { - return this.applicationEditingConstraints.toolboxes[toolboxId]; -}; - -/** - * @returns { Array } - */ -proto.getToolBoxes = function() { - return this._toolboxes; -}; - -/** - * @returns {*|{}} - */ -proto.getEditableLayers = function() { - return this._editableLayers; -}; - -/** - * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 - * - * @returns { string[] } - * - * @since g3w-client-plugin-editing@v3.7.2 - */ -proto.getEditableLayersId = function() { - return Object.keys(this.getEditableLayers()); -}; - -/** - * @returns {*} - * - * @private - */ -proto._cancelOrSave = function() { - return resolve(); -}; - -/** - * Stop editing - * - * @returns { Promise } - */ -proto.stop = function() { - return new Promise((resolve, reject) => { - const commitpromises = []; - this._toolboxes - .forEach(toolbox => { - // check if temp changes are waiting to save on server - if (toolbox.getSession().getHistory().state.commit) { - // ask to commit before exit - commitpromises.push(this.commit({toolbox, modal:true})); - } - }); - $.when.apply(this, commitpromises) - .always(() => { - this._toolboxes - .forEach(toolbox => toolbox.stop()); - this.clearState(); - //this.activeQueryInfo(); - this._mapService.refreshMap(); - resolve(); - }); - }); -}; - -/** - * remove Editing LayersStore - */ -proto.clear = function() { - MapLayersStoreRegistry.removeLayersStore(this._layersstore); - SessionsRegistry.clear(); - //turn off events - this._mapService.off(MAPCONTROL_TOGGLED_EVENT_NAME, this.mapControlToggleEventHandler); - this.unregisterResultEditingAction(); -}; - -/** - * @FIXME add description - */ -proto.clearState = function() { - this.state.toolboxselected = null; - this.state.toolboxidactivetool = null; - this.state.message = null; -}; - -/** - * Get Relation in editing - * - * @param { Object } opts - * @param opts.layerId - * @param opts.relations - * @param opts.feature - * - * @returns { Array } - */ -proto.getRelationsInEditing = function({ - layerId, - relations = [], - feature, -} = {}) { - let relationsinediting = []; - let relationinediting; - relations.forEach(relation => { - const relationLayerId = this._getRelationLayerId({layerId, relation}); - //check if the layer is editable - if (this.getLayerById(relationLayerId)) { - relationinediting = { - relation: relation.getState(), - relations: this.getRelationsAttributesByFeature({ - layerId: relationLayerId, - relation, - feature - }) - }; - relationinediting.validate = { - valid:true - }; - relationsinediting.push(relationinediting); + const values = this.layersUniqueFieldsValues[layerId][field.name]; + values.delete(oldValue); + values.add(newValue); + } + + /** + * @param { Object } opts + * @param { string } opts.layerId + * @param { string } opts.relationLayerId + * @param opts.field + * @param opts.oldValue + * @param opts.newValue + */ + changeRelationLayerUniqueFieldValues({ + layerId, + relationLayerId, + field, + oldValue, + newValue, + }) { + const layer = this.layersUniqueFieldsValues[relationLayerId]; + + if (undefined === layer) { + return; } - }); - return relationsinediting; -}; - -/** - * @param { Object } opts - * @param opts.layerId - * @param opts.relations - * - * @returns { Array } - * - * @private - */ -proto._filterRelationsInEditing = function({ - layerId, - relations = [], -}) { - return relations.filter(relation => this.getToolBoxById(this._getRelationId({ layerId, relation }))); -}; - -/** - * @param { string } layerId - */ -proto.stopToolboxesChildren = function(layerId) { - const layer = this.getLayerById(layerId); - const relations = this._filterRelationsInEditing({ - relations: layer.getRelations() ? layer.getRelations().getArray() : [], - layerId - }); - relations - .filter(relation => relation.getFather() === layerId) - .forEach(relation => { - const relationId = this._getRelationId({ layerId, relation }); - if (this.getToolBoxById(relationId).inEditing()) { - this.getToolBoxById(relationId).stop(); - } - }) -}; - -/** - * @param { string } layerId - */ -proto.stopSessionChildren = function(layerId) { - const layer = this.getLayerById(layerId); - const relations = this._filterRelationsInEditing({ - relations: layer.getRelations() ? layer.getRelations().getArray() : [], - layerId - }); - relations - .filter(relation => relation.getFather() === layerId) - .forEach(relation => { - const relationId = this._getRelationId({ layerId, relation }); - // In case of no editing is started (click on pencil of relation layer) need to stop (unlock) features - if (!this.getToolBoxById(relationId).inEditing()) { - this._sessions[relationId].stop(); - } - }) -}; - -/** - * Check if father relation is editing and has commit feature - * - * @param { string } layerId - * - * @returns father in editing - */ -proto.fathersInEditing = function(layerId) { - return this.getLayerById(layerId) - .getFathers() - .filter(id => { - const toolbox = this.getToolBoxById(id); - if (toolbox && toolbox.inEditing() && toolbox.isDirty()) { - //get temporary relations object - const {relations={}} = toolbox.getSession().getCommitItems(); - //check if layerId has some changes - return Object - .keys(relations) - .find(relationLayerId => layerId === relationLayerId); - } - }); -}; - -/** - * Based on layerId and relation, - * extract field of relation. - * ownField are array of fields related to relation and belong to layerId - * relationField area array of fields related to relation thar belong to other layer in relation with layerId - * @param { Object } opts - * @param opts.layerId - * @param opts.relation - * - * @returns {{ ownField: [], relationField: [] }} `ownField` and `relationField` are Arrays since g3w-client-plugin-editing@v3.7.0 - * - * @private - */ -proto._getRelationFieldsFromRelation = function({ - layerId, - relation, -} = {}) { - /** {String} @type */ - const childId = relation.getChild ? - relation.getChild() : - relation.child; - - /** {Boolean} @type check if is child*/ - const isChild = childId !== layerId; - /** { Array } @type array of fields */ - const _fatherField = relation.getFatherField ? - relation.getFatherField() : - relation.fatherField; - - /** { Array } @type array of fields */ - const _childField = relation.getChildField ? - relation.getChildField() : - relation.childField; - - return { - ownField: isChild ? _fatherField : _childField, - relationField: isChild ? _childField : _fatherField - } -}; - -/** - * @param { 'all' | 'bbox' | 'field' | 'fid' | '1:1' } filterType - * @param { Object } options - * @param options.feature - * @param options.relation - * @param options.field - * @param options.layerId - * @param options.operator - */ -proto.createEditingDataOptions = function(filterType = 'all', options = {}) { - let filter; - - switch (filterType) { - - case 'all': - filter = undefined; - break; - - case 'bbox': - filter = { - bbox: this._mapService.getMapBBOX(), - }; - break; - case 'field': - filter = { - field: { - field: options.field, - type: 'editing' - } - }; - break; + if (undefined === layer.__uniqueFieldsValuesRelations) { + layer.__uniqueFieldsValuesRelations = {}; + } - case 'fid': - if ('not' !== options.operator) { // get relations of current feature - filter = { - fid: { - fid: options.feature.getId(), - layer: { id: options.layerId }, - type: 'editing', - relation: options.relation.state, - formatter: 0, // 0 = retrieve stored value - } - }; - } - break; + if (undefined === layer.__uniqueFieldsValuesRelations[layerId]) { + layer.__uniqueFieldsValuesRelations[layerId] = {}; + } - // relation 1:1 - case '1:1': - filter = { - field: options.relation.getChildField()[0] + '|eq|' + options.feature.get(options.relation.getFatherField()[0]), - type: 'editing', - } - break; + const values = new Set(this.layersUniqueFieldsValues[layerId][field.name]); + values.delete(oldValue); + values.add(newValue); + + layer.__uniqueFieldsValuesRelations[layerId][field.name] = values; } - return { - registerEvents: true, // usefult to get register vent on toolbox example mapmoveend - editing: true, - filter - }; + /** + * @param { Object } opts + * @param { string } opts.layerId + * @param opts.field + * @param opts.value + */ + addLayerUniqueFieldValue({ + layerId, + field, + value, + }) { + this.layersUniqueFieldsValues[layerId][field.name].add(value); + } -}; - -/** - * @param { string } layerId - * - * @returns {*} - * - * @private - */ -proto._getFeaturesByLayerId = function(layerId) { - return this.getLayerById(layerId).readEditingFeatures(); -}; - -/** - * @param { Object } opts - * @param { string } opts.layerId - * @param opts.relation - * @param opts.feature - * @param { string } [opts.operator='eq'] - * - * @returns { Promise } - */ -proto.getLayersDependencyFeaturesFromSource = function({ - layerId, - relation, - feature, - operator = 'eq', -} = {}) { - return new Promise(resolve => { - // skip when .. - if ('eq' !== operator) { - return resolve(false); - } + /** + * @param { Object } opts + * @param { string } opts.layerId + * @param opts.field + * @param opts.value + */ + deleteLayerUniqueFieldValue({ + layerId, + field, + value, + }) { + this.layersUniqueFieldsValues[layerId][field.name].delete(value); + } - const { ownField, relationField } = this._getRelationFieldsFromRelation({ layerId, relation }); - const features = this._getFeaturesByLayerId(layerId); - const featureValues = relationField.map(field => feature.get(field)); + /** + * @param { Object } opts + * @param { string } opts.layerId + * @param { Array } opts.sessionItems + * @param opts.action + */ + undoRedoLayerUniqueFieldValues({ + layerId, + sessionItems = [], + action, + }) { - resolve(ownField.every((field, i) => features.find(f => f.get(field) == featureValues[i]))) - }) -}; - -/** - * Based on layer id and relation, return the layer id - * of the other layer that is in relation with layerId - * @param { Object } opts - * @param opts.layerId - * @param opts.relation - * - * @returns {*|{configurable: boolean}|{configurable}|boolean|(function(): *)} - * - * @private - */ -proto._getRelationId = function({ - layerId, - relation, -} = {}) { - const fatherId = relation.getFather ? relation.getFather() : relation.father; - const childId = relation.getChild ? relation.getChild() : relation.child; - - return fatherId === layerId ? childId: fatherId; -}; - -/** - * @param { string } layerId - * @param opts - * - * @returns { Promise[]> } - */ -proto.getLayersDependencyFeatures = function(layerId, opts = {}) { - const promises = []; - const layer = this.getLayerById(layerId); - const relations = opts.relations ? - opts.relations : - layer.getChildren().length && layer.getRelations() ? - this._filterRelationsInEditing({ - relations: layer.getRelations() - .getArray() - .filter(relation => relation.getFather() === layerId), - layerId - }) : - []; - const online = ApplicationState.online; - relations.forEach(relation => { - if (relation.setLoading) { - relation.setLoading(true); - } else { - relation.loading = true; + // if not set + if (undefined === this.layersUniqueFieldsValues[layerId]) { + return; } - const id = this._getRelationId({ layerId, relation }); - //Promise - const promise = new Promise(resolve => { - opts.relation = relation; - opts.layerId = layerId; - //In case of relation 1:1 - opts.filterType = 'ONE' === relation.getType() ? '1:1' : opts.filterType; - const filterType = opts.filterType || 'fid'; - const options = this.createEditingDataOptions(filterType, opts); - const session = this._sessions[id]; - const toolbox = this.getToolBoxById(id); - //check if si online and it has session - if (online && session) { - //show bar loading - toolbox.startLoading(); - //check is session is already start - if (session.isStarted()) { - //try to get feature from source - //without server reques - this.getLayersDependencyFeaturesFromSource({ - layerId: id, - relation, - feature: opts.feature, - operator: opts.operator - }) - .then(find => { - //if found - if (find) { - resolve(id); - toolbox.stopLoading(); - } else { - //request features from server - session.getFeatures(options) - .always(promise => { - promise.always(() => { - toolbox.stopLoading(); - resolve(id); - }); - }); - } - }) - } else { - //start session and get features - session.start(options) - .always(promise => { - promise.always(() => { - toolbox.stopLoading(); - resolve(id); - }) - }); - } - } else { - //try to get feature from source - this.getLayersDependencyFeaturesFromSource({ - layerId: id, - relation, - feature: opts.feature, - operator: opts.operator - }).then(() => resolve(id)) - } - }); - promises.push(promise); - }); - // at the end se loading false - Promise.all(promises) - .finally(() => relations.forEach(relation => { - if (relation.setLoading) { - relation.setLoading(false); - } else { - relation.loading = false; - } - })); - return Promise.all(promises); -}; - -/** - * @param { string } layerId - * - * @returns { Promise } - */ -proto.commitDirtyToolBoxes = function(layerId) { - return new Promise((resolve, reject) => { - const toolbox = this.getToolBoxById(layerId); - if (toolbox.isDirty() && toolbox.hasDependencies()) { - this.commit({toolbox}) - .then(() => { - resolve(toolbox); - }) - .fail(() => { - toolbox.revert() - .then(() => { - toolbox.getDependencies() - .forEach((layerId) => { - if (this.getLayerById(layerId).getChildren().indexOf(layerId) !== -1) { - this.getToolBoxById(layerId).revert(); - } - }) - }) - reject(toolbox); - }) - } else - resolve(toolbox); - }); -}; - -/** - * @param commitItems - * - * @returns { string } - * - * @private - */ -proto._createCommitMessage = function(commitItems) { - function create_changes_list_dom_element(add, update, del) { - const changeIds = {}; - changeIds[`${t('editing.messages.commit.add')}`] = add.length; - changeIds[`${t('editing.messages.commit.update')}`] = `[${update.map((item)=> item.id).join(',')}]`; - changeIds[`${t('editing.messages.commit.delete')}`] = `[${del.join(',')}]`; - let dom = `

${t('editing.messages.commit.header')}

`; - dom+=`
${t('editing.messages.commit.header_add')}
`; - dom+=`
${t('editing.messages.commit.header_update_delete')}
`; - dom+= `
    `; - Object.entries(changeIds).forEach(([action, ids]) => { - dom += `
  • ${action} : ${ids}
  • `; + sessionItems.forEach(item => { + + Object + .keys(this.layersUniqueFieldsValues[layerId]) + .forEach(name => { + const is_array = Array.isArray(item); + let oldVal, newVal; + if (is_array) { // 0 = old feature, 1 = new feature + const has_change = item[1].feature.get(name) != item[0].feature.get(name); + // update feature that contains "new" and "old" values of feature + oldVal = has_change ? (action === 'undo' ? item[1].feature.get(name) : item[0].feature.get(name)) : undefined; + newVal = has_change ? (action === 'undo' ? item[0].feature.get(name) : item[1].feature.get(name)) : undefined; + } else { + oldVal = 'add' === item.feature.getState() ? item.feature.get(name) : undefined; + newVal = 'delete' === item.feature.getState() ? item.feature.get(name) : undefined; + } + if (undefined !== oldVal) { + this.deleteLayerUniqueFieldValue({ layerId, field: { name }, value: oldVal }); + } + if (undefined !== newVal) { + this.addLayerUniqueFieldValue({ layerId, field: { name }, value: newVal }); + } + }); }); - dom += `
`; - return dom; - } - - let message = ""; - message += create_changes_list_dom_element(commitItems.add, commitItems.update, commitItems.delete); - if (!_.isEmpty(commitItems.relations)) { - message += "
"; - message += "

"+ t('editing.relations') +"

"; - Object.entries(commitItems.relations).forEach(([ relationName, commits]) => { - message += "
" + relationName + "
"; - message += create_changes_list_dom_element(commits.add, commits.update, commits.delete); - }) } - return message; -}; - -/** - * @param { Object } opts - * @param opts.layer - * @param opts.commitItems - * @param opts.close - * @param opts.commitPromise - * - * @returns { Promise } - */ -proto.showCommitModalWindow = function({ - layer, - commitItems, - close, - commitPromise, -}) { - // messages set to commit - const messages = { - success: { - message: "plugins.editing.messages.saved", - autoclose: true - }, - error: {} - }; - return new Promise((resolve, reject) =>{ - - /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/commitfeaturesworkflow.js@v3.7.1 */ - const workflow = new EditingWorkflow({ - type: 'commitfeatures', - steps: [ new ConfirmStep({ type: 'commit' }) ] - }) - - workflow.start({ - inputs: { - layer, - message: this._createCommitMessage(commitItems), - close - } - }) - .then(() => { - const dialog = GUI.dialog.dialog({ - message: `

${t('editing.messages.saving')}

`, - closeButton: false + /** + * @param { Object } opts + * @param opts.relationSessionItems + * @param opts.action + */ + undoRedoRelationUniqueFieldValues({ + relationSessionItems, + action, + }) { + Object + .entries(relationSessionItems) + .forEach(([layerId, {own:sessionItems, dependencies:relationSessionItems}]) => { + this.undoRedoLayerUniqueFieldValues({ + layerId, + sessionItems, + action }); - resolve(messages); - commitPromise.always(() => dialog.modal('hide')) // hide saving dialog + this.undoRedoRelationUniqueFieldValues({ + relationSessionItems, + action + }) }) - .fail(async error => { - const promises = []; - const rollbackRelations = (relations={}) => { - Object - .entries(relations) - .forEach(([ layerId, {add, delete:del, update, relations = {}}]) => { - const layer = this.getLayerById(layerId); - const sourceLayer = layer.getEditingSource(); - // check if the relation layer has some features - if (sourceLayer.readFeatures().length > 0) { - - //add a need to remove - add.forEach(({id}) => { - sourceLayer.removeFeature(sourceLayer.getFeatureById(id)) - }) + } - // //need to get original values - update.forEach(({ id }) => { - promises.push( - new Promise((resolve, reject) => { - this.getProjectLayerFeatureById({ layerId, fid: id }) - .then(f => { - const feature = sourceLayer.getFeatureById(id); - feature.setProperties(f.properties); - feature.setGeometry(f.geometry); - resolve(); - }).catch(reject); - }) - ) - }) - } + /** + * end unique fields + */ + getProjectLayerById(layerId) { + return CatalogLayersStoresRegistry.getLayerById(layerId); + } - //need to add again. - del.forEach(id => { - promises.push( - new Promise((resolve, reject) => { - this.getProjectLayerFeatureById({ layerId, fid: id }) - .then(f => { - const feature = new ol.Feature({ geometry: f.geometry }) - feature.setProperties(f.properties); - feature.setId(id); - // need to add again to source because it is for relation layer is locked - sourceLayer.addFeature(new Feature({ feature })); - resolve(); - }).catch(reject); - }) - ) - }) - rollbackRelations(relations); - }) - } - rollbackRelations(commitItems.relations); - await Promise.allSettled(promises); - reject(error) - }) - .always(() => workflow.stop()) - }) -}; - -/** - * Function called very single change saved temporary - */ -proto.saveChange = async function() { - switch (this.saveConfig.mode) { - case 'autosave': - return this.commit({ - modal: false // set to not show modal ask window + /** + * @param { Object } opts + * @param { string } opts.layerId + * @param opts.fid + * + * @returns {Promise<*>} + */ + async getProjectLayerFeatureById({ + layerId, + fid, + }) { + let feature; + + try { + const response = await XHR.get({ + url: this.getProjectLayerById(layerId).getUrl('data'), + params: {fids: fid}, }); + const features = getFeaturesFromResponseVectorApi(response); + if (features.length > 0) { + feature = features[0]; + } + } catch(e) { + console.warn(e); + } + return feature; } -}; - -/** - * @param { Object } opts - * @param { string } opts.layerId - * @param { Array } opts.fids - */ -proto.addLayersFeaturesToShowOnResult = function({ - layerId, - fids = [], -}) { - if (undefined === this.loadLayersFeaturesToResultWhenCloseEditing[layerId]) { - this.loadLayersFeaturesToResultWhenCloseEditing[layerId] = new Set(); - } - fids.forEach(fid => this.loadLayersFeaturesToResultWhenCloseEditing[layerId].add(fid)) -}; - -/** - * Called on close editingpanel panel - */ -proto.onCloseEditingPanel = async function() { - await this.showChangesToResult(); - this.getToolBoxes().forEach(toolbox => toolbox.resetDefault()); -}; - -/** - * Show feature that are updated or created with editing on result content - * - * @returns { Promise } - */ -proto.showChangesToResult = async function() { - const layerIdChanges = Object.keys(this.loadLayersFeaturesToResultWhenCloseEditing); - if (layerIdChanges.length) { - const inputs = { - layers: [], - fids: [], - formatter: 1 - }; - layerIdChanges - .forEach(layerId => { - const fids = [...this.loadLayersFeaturesToResultWhenCloseEditing[layerId]]; - if (fids.length) { - const layer = CatalogLayersStoresRegistry.getLayerById(layerId); - inputs.layers.push(layer); - inputs.fids.push(fids); - } - }); - const promise = inputs.layers.length ? - DataRouterService.getData('search:layersfids', { - inputs, - outputs: { - title: 'plugins.editing.editing_changes', - show: {loading: false} - } - }) : - Promise.resolve(); - try { - await promise; - } catch(err) {} - } - this.loadLayersFeaturesToResultWhenCloseEditing = {}; -}; - -/** - * Commit and save changes on server persistently - * - * @param { Object } commit - * @param commit.toolbox - * @param commit.commitItems - * @param commit.messages - * @param commit.done - * @param { boolean } commit.modal - * @param { boolean } commit.close - * - * @returns {*} - */ -proto.commit = function({ - toolbox, - commitItems, - modal = true, - close = false, -} = {}) { - const d = $.Deferred(); - const commitPromise = d.promise(); - const { - cb = {}, - messages = { - success:{}, - error:{} - }, - } = this.saveConfig; - toolbox = toolbox || this.state.toolboxselected; - let session = toolbox.getSession(); - let layer = toolbox.getLayer(); - const layerType = layer.getType(); - const items = commitItems; - commitItems = commitItems || session.getCommitItems(); - const { - add = [], - delete: cancel = [], - update = [], - relations = {}, - } = commitItems; - - //check if there are some changes to commit - if ( - [ - ...add, - ...cancel, - ...update, - ...Object.keys(relations) - ].length === 0 - ) { - GUI.showUserMessage({ - type: 'info', - message: 'Nothing to save', - autoclose: true, - closable: false + /** + * @param layer + * @param { Object } options + * @param { Array } options.exclude + * + * @returns {*} + */ + getProjectLayersWithSameGeometryOfLayer(layer, options = { exclude: [] }) { + const { exclude = [] } = options; + const geometryType = layer.getGeometryType(); + return CatalogLayersStoresRegistry + .getLayers() + .filter(layer => { + return ( + layer.isGeoLayer() && + layer.getGeometryType && + layer.getGeometryType() && + -1 === exclude.indexOf(layer.getId()) + ) && ( + layer.getGeometryType() === geometryType || + ( + isSameBaseGeometryType(layer.getGeometryType(), geometryType) && + Geometry.isMultiGeometry(geometryType) + ) + ) }); - - d.resolve(toolbox); - - return d.promise(); } - const promise = modal ? this.showCommitModalWindow({ - layer, - commitItems, - close, - commitPromise // add a commit promise - }) : Promise.resolve(messages); - - promise - .then(messages => { - //check if application is online - if (ApplicationState.online) { - session.commit({items: items || commitItems}) - .then((commitItems, response) => { - //@TODO need to double check why ApplicationState.online is repeated - if (ApplicationState.online) { - //if result is true - if (response.result) { - const {autoclose=true, message="plugins.editing.messages.saved"} = messages.success; - if (messages && messages.success) { - GUI.showUserMessage({ - type: 'success', - message, - duration: 3000, - autoclose - }); - } + /** + * return (geometryType === featureGeometryType) + * || Geometry.isMultiGeometry(geometryType) + * || !Geometry.isMultiGeometry(featureGeometryType); + */ + getExternalLayersWithSameGeometryOfLayer(layer) { + const geometryType = layer.getGeometryType(); + return this._mapService + .getExternalLayers() + .filter(externalLayer => { + const features = externalLayer.getSource().getFeatures(); + // skip when .. + if (!(features && features.length > 0) || (features && features[0] && !features[0].getGeometry())) { + return false; + } + const type = features[0].getGeometry().getType(); + return geometryType === type || isSameBaseGeometryType(geometryType, type); + }); + } - //In case of vector layer need to refresh map commit changes - if (layerType === Layer.LayerTypes.VECTOR) { - this._mapService.refreshMap({force: true}); - } + /** + * Finalize "formatter" value for any kind of field + * + * @param { string } opts.layerId + * @param {ol.Feature} opts.feature + * @param { string } opts.property + * + * @returns (field.key) or (field.value) + * + * @since g3w-client-plugin-editing@v3.7.0 + */ + getFeatureTableFieldValue({ + layerId, + feature, + property + } = {}) { - if (cb.done && cb.done instanceof Function) { - cb.done(toolbox); - } + // get editable fields + const { fields } = this.getLayerById(layerId).config.editing; - //add items when close editing to results to show changes - this.addLayersFeaturesToShowOnResult({ - layerId: toolbox.getId(), - fids: [ - ...response.response.new.map(({id}) => id), - ...commitItems.update.map(update => update.id) - ] - }); - //@since 3.7.2 - //it is useful when click on save all disk icon in editing forma for relation purpose - this.emit('commit', response.response); - - } else { //result is false. An error occurs - const parser = new serverErrorParser({ - error: response.errors - }); - - const errorMessage = parser.parse({ - type: 'String' - }); - - const {autoclose=false, message} = messages.error; - - GUI.showUserMessage({ - type: 'alert', - message: message || errorMessage, - textMessage: !message, - autoclose - }); - - if (cb.error && cb.error instanceof Function) { - cb.error(toolbox, message || errorMessage); - } - } + // get field value (raw) + let value = feature.get(property); - d.resolve(toolbox); - } - }) - .fail((error={}) => { - //parse error server - const parser = new serverErrorParser({ - error: error.errors ? error.errors : error - }); - //set type string - const errorMessage = parser.parse({ - type: 'String' - }); + // get key-value fields implicated into: https://github.com/g3w-suite/g3w-client-plugin-editing/pull/64 + const values = (null !== value) && (fields + .filter(field => ['select_autocomplete', 'select'].includes(field.input.type)) || [] ) + .reduce((kv, field) => { kv[field.name] = field.input.options.values; return kv; }, {}); - const {autoclose = false, message} = messages.error; + // get last key-value feature add to + const kv_field = values && values[property] && values[property].find(kv => value == kv.value); - GUI.showUserMessage({ - type: 'alert', - message: message || errorMessage, - textMessage: !message, - autoclose - }); + // return key for key-values fields (raw field value otherwise) + return kv_field ? kv_field.key : value; + } - d.reject(toolbox); + /** + * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 + * + * Show editing panel + * + * @param options + * @param options.toolboxes + * + * @since g3w-client-plugin-editing@v3.7.2 + */ + showPanel(options = {}) { + if (options.toolboxes && Array.isArray(options.toolboxes)) { + this.getToolBoxes().forEach(tb => tb.setShow(-1 !== options.toolboxes.indexOf(tb.getId()))); + } + this.getPlugin().showEditingPanel(options); + } - if (cb.error && cb.error instanceof Function) { - cb.error(toolbox, message || errorMessage); - } - }); - //case offline - } else { - this.saveOfflineItem({ - data: { - [session.getId()]: commitItems - }, - id: OFFLINE_ITEMS.CHANGES - }) - .then(() => { - GUI.showUserMessage({ - type: 'success', - message: "plugins.editing.messages.saved_local", - autoclose: true - }); - session.clearHistory(); - d.resolve(toolbox); - }) - .catch(error => { - GUI.showUserMessage({ - type: 'alert', - message: error, - textMessage: true, - }); + /** + * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 + * + * Hide Editing Panel + * + * @param options + * + * @since g3w-client-plugin-editing@v3.7.2 + */ + hidePanel(options = {}) { + this.getPlugin().hideEditingPanel(options); + } - d.reject(toolbox); - }) - } - }) - .catch((e) => { - console.warn(e); - d.reject(toolbox) - }); - - return commitPromise; -}; - -/** - * Clear all unique values fields related to layer (after closing editing panel). - */ -proto.clearAllLayersUniqueFieldsValues = function() { - this.layersUniqueFieldsValues = {}; -}; - -/** - * Clear single layer unique field values (when stopping toolbox editing). - * - * @param { string } layerId - */ -proto.clearLayerUniqueFieldsValues = function(layerId) { - this.layersUniqueFieldsValues[layerId] = {}; -}; - -/** - * Remove unique values from unique fields of a layer (when deleting a feature) - * - * @param { Object } opts - * @param { string } opts.layerId - * @param opts.feature - */ -proto.removeLayerUniqueFieldValuesFromFeature = function({ - layerId, - feature, -}) { - const fields = this.layersUniqueFieldsValues[layerId]; - if (fields) { - Object - .keys(feature.getProperties()) - .filter(field => undefined !== fields[field]) - .forEach(field => fields[field].delete(feature.get(field))); - } -}; - -/** - * @param { Object } opts - * @param { string } opts.layerId - * @param { string } opts.relationLayerId - * @param opts.feature - */ -proto.removeRelationLayerUniqueFieldValuesFromFeature = function({ - layerId, - relationLayerId, - feature, -}) { - - const layer = this.layersUniqueFieldsValues[relationLayerId]; - const fields = this.layersUniqueFieldsValues[layerId]; - - /** @FIXME add description */ - if (undefined === layer || undefined === fields) { - return; - } - - /** @FIXME add description */ - if (undefined === layer.__uniqueFieldsValuesRelations) { - layer.__uniqueFieldsValuesRelations = {}; - } - - Object - .keys(feature.getProperties()) - .forEach(property => { - /** @FIXME add description */ - if (undefined === layer.__uniqueFieldsValuesRelations[layerId]) { - layer.__uniqueFieldsValuesRelations[layerId] = {}; + /** + * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 + * + * Start editing API + * + * @param layerId + * @param { Object } options + * @param { boolean } [options.selected=true] + * @param { boolean } [options.disablemapcontrols=false] + * @param { boolean } [options.showselectlayers=true] + * @param options.title + * + * @returns { Promise } + * + * @since g3w-client-plugin-editing@v3.7.2 + */ + startEditing(layerId, options = {}, data = false) { + options.selected = undefined === options.selected ? true : options.selected; + options.showselectlayers = undefined === options.showselectlayers ? true : options.showselectlayers; + options.disablemapcontrols = undefined === options.disablemapcontrols ? false : options.showselectlayers; + return new Promise((resolve, reject) => { + // get toolbox related to layer id + const toolbox = this.getToolBoxById(layerId); + // set show select layers input visibility + this.setShowSelectLayers(options.showselectlayers); + // skip when .. + if (!toolbox) { + return reject(); } - /** @FIXME add description */ - if (undefined !== fields[property]) { - const values = new Set(fields[property]); - values.delete(feature.get(property)); - layer.__uniqueFieldsValuesRelations[layerId][property] = values; + // set selected + toolbox.setSelected(options.selected); + // set seletcted toolbox + if (options.selected) { + this.setSelectedToolbox(toolbox); } - }); -}; - -/** - * @param { string } layerId - * - * @returns { Promise<*> } - */ -proto.setLayerUniqueFieldValues = async function(layerId) { - const promises = []; - const layer = CatalogLayersStoresRegistry.getLayerById(layerId); - layer - .getEditingFields() - .forEach(field => { - // skip when .. - if (!(field.validate.unique && undefined === this.getLayerUniqueFieldValues({ layerId, field }))) { - return; + if (options.title) { + toolbox.setTitle(options.title); } - promises.push( - layer - .getFilterData({ unique: field.name }) - .then((values = []) => { - if (undefined === this.layersUniqueFieldsValues[layerId]) { - this.layersUniqueFieldsValues[layerId] = {}; - } - this.layersUniqueFieldsValues[layerId][field.name] = new Set(values); - }) - ); - }); - await Promise.allSettled(promises); - - return this.layersUniqueFieldsValues[layerId]; -}; - -/** - * Save temporary relation feature changes on father (root) layer feature - * - * @param { string } layerId - */ -proto.saveTemporaryRelationsUniqueFieldsValues = function(layerId) { - const relations = ( - this.layersUniqueFieldsValues[layerId] && - this.layersUniqueFieldsValues[layerId].__uniqueFieldsValuesRelations - ); - - // skip when no relation unique fields values are stored - if (undefined === relations) { - return; - } - - Object - .keys(relations) - .forEach(relationLayerId => { - Object - .entries(relations[relationLayerId]) - .forEach(([fieldName, uniqueValues]) => { - this.layersUniqueFieldsValues[relationLayerId][fieldName] = uniqueValues; + // start editing toolbox (options contain also filter type) + toolbox + .start(options) + .then(data => { + // disablemapcontrols in conflict + if (options.disablemapcontrols) { + this.disableMapControlsConflict(true); + } + // opts contain information about start editing has features loaded + resolve(data ? { toolbox, data } : toolbox); }) + .fail(reject); }); - - this.clearTemporaryRelationsUniqueFieldsValues(layerId); -}; - -/** - * @param { string } layerId - */ -proto.clearTemporaryRelationsUniqueFieldsValues = function(layerId) { - if (this.layersUniqueFieldsValues[layerId]) { - delete this.layersUniqueFieldsValues[layerId].__uniqueFieldsValuesRelations; - } -}; - -/** - * Get layer unique field value - * @param { Object } opts - * @param { string } opts.layerId layer id - * @param opts.field filed name - * - * @returns {*} - */ -proto.getLayerUniqueFieldValues = function({ - layerId, - field, -}) { - - return this.layersUniqueFieldsValues[layerId] ? - this.layersUniqueFieldsValues[layerId][field.name] : - []; -}; - -/** - * @param { Object } opts - * @param { string } opts.layerId - * @param { string } opts.relationLayerId - * @param opts.field - * - * @returns {*} - */ -proto.getChildLayerUniqueFieldValues = function({ - layerId, - relationLayerId, - field, -}) { - const relations = ( - this.layersUniqueFieldsValues[relationLayerId] && - this.layersUniqueFieldsValues[relationLayerId].__uniqueFieldsValuesRelations - ); - const has_values = ( - undefined !== relations && - undefined !== relations[layerId] && - undefined !== relations[layerId][field.name] - ); - return has_values ? relations[layerId][field.name] : this.getLayerUniqueFieldValues({ layerId, field }); -}; - -/** - * @param { Object } opts - * @param { string } opts.layerId - * @param opts.field - * @param opts.oldValue - * @param opts.newValue - */ -proto.changeLayerUniqueFieldValues = function({ - layerId, - field, - oldValue, - newValue, -}) { - if ( - undefined === this.layersUniqueFieldsValues[layerId] || - undefined === this.layersUniqueFieldsValues[layerId][field.name] - ) { - return; - } - const values = this.layersUniqueFieldsValues[layerId][field.name]; - values.delete(oldValue); - values.add(newValue); -}; - -/** - * @param { Object } opts - * @param { string } opts.layerId - * @param { string } opts.relationLayerId - * @param opts.field - * @param opts.oldValue - * @param opts.newValue - */ -proto.changeRelationLayerUniqueFieldValues = function({ - layerId, - relationLayerId, - field, - oldValue, - newValue, -}) { - const layer = this.layersUniqueFieldsValues[relationLayerId]; - - if (undefined === layer) { - return; - } - - if (undefined === layer.__uniqueFieldsValuesRelations) { - layer.__uniqueFieldsValuesRelations = {}; - } - - if (undefined === layer.__uniqueFieldsValuesRelations[layerId]) { - layer.__uniqueFieldsValuesRelations[layerId] = {}; - } - - const values = new Set(this.layersUniqueFieldsValues[layerId][field.name]); - - values.delete(oldValue); - values.add(newValue); - - layer.__uniqueFieldsValuesRelations[layerId][field.name] = values; -}; - -/** - * @param { Object } opts - * @param { string } opts.layerId - * @param opts.field - * @param opts.value - */ -proto.addLayerUniqueFieldValue = function({ - layerId, - field, - value, -}) { - this.layersUniqueFieldsValues[layerId][field.name].add(value); -}; - -/** - * @param { Object } opts - * @param { string } opts.layerId - * @param opts.field - * @param opts.value - */ -proto.deleteLayerUniqueFieldValue = function({ - layerId, - field, - value, -}) { - this.layersUniqueFieldsValues[layerId][field.name].delete(value); -}; - -/** - * @param { Object } opts - * @param { string } opts.layerId - * @param { Array } opts.sessionItems - * @param opts.action - */ -proto.undoRedoLayerUniqueFieldValues = function({ - layerId, - sessionItems = [], - action, -}) { - - // if not set - if (undefined === this.layersUniqueFieldsValues[layerId]) { - return; } - sessionItems.forEach(item => { + /** + * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 + * + * Stop editing on layerId + * + * @param layerId + * @param options + * + * @returns { Promise } + * + * @since g3w-client-plugin-editing@v3.7.2 + */ + stopEditing(layerId, options = {}) { + return new Promise((resolve, reject) => { + this.getToolBoxById(layerId) + .stop(options) + .then(resolve) + .fail(reject) + }) + } - Object - .keys(this.layersUniqueFieldsValues[layerId]) - .forEach(name => { - const is_array = Array.isArray(item); - let oldVal, newVal; - if (is_array) { // 0 = old feature, 1 = new feature - const has_change = item[1].feature.get(name) != item[0].feature.get(name); - // update feature that contains "new" and "old" values of feature - oldVal = has_change ? (action === 'undo' ? item[1].feature.get(name) : item[0].feature.get(name)) : undefined; - newVal = has_change ? (action === 'undo' ? item[0].feature.get(name) : item[1].feature.get(name)) : undefined; - } else { - oldVal = 'add' === item.feature.getState() ? item.feature.get(name) : undefined; - newVal = 'delete' === item.feature.getState() ? item.feature.get(name) : undefined; - } - if (undefined !== oldVal) { - this.deleteLayerUniqueFieldValue({ layerId, field: { name }, value: oldVal }); - } - if (undefined !== newVal) { - this.addLayerUniqueFieldValue({ layerId, field: { name }, value: newVal }); - } - }); - }); -}; - -/** - * @param { Object } opts - * @param opts.relationSessionItems - * @param opts.action - */ -proto.undoRedoRelationUniqueFieldValues = function({ - relationSessionItems, - action, -}) { - Object - .entries(relationSessionItems) - .forEach(([layerId, {own:sessionItems, dependencies:relationSessionItems}]) => { - this.undoRedoLayerUniqueFieldValues({ - layerId, - sessionItems, - action - }); - this.undoRedoRelationUniqueFieldValues({ - relationSessionItems, - action + /** + * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 + * + * Add Feature + * + * @param { Object } opts + * @param opts.layerId + * @param opts.feature + * + * @since g3w-client-plugin-editing@v3.7.2 + */ + addLayerFeature({ + layerId, + feature, + } = {}) { + // skip when mandatory params are missing + if (undefined === feature || undefined === layerId) { + return Promise.reject(); + } + return new Promise((resolve, reject) => { + const layer = this.getLayerById(layerId); + // get session + const session = this.getSessionById(layerId); + // exclude an eventually attribute pk (primary key) not editable (mean autoincrement) + const attributes = layer + .getEditingFields() + .filter(attr => !(attr.pk && !attr.editable)); + // start session (get no features but set layer in editing) + session.start({ + filter: { + nofeatures: true, // no feature + nofeatures_field: attributes[0].name // get first field in editing form + }, + editing: true, }) - }) -}; - -/** - * end unique fields - */ -proto.getProjectLayerById = function(layerId) { - return CatalogLayersStoresRegistry.getLayerById(layerId); -}; - -/** - * @param { Object } opts - * @param { string } opts.layerId - * @param opts.fid - * - * @returns {Promise<*>} - */ -proto.getProjectLayerFeatureById = async function({ - layerId, - fid, -}) { - let feature; - - try { - const response = await XHR.get({ - url: this.getProjectLayerById(layerId).getUrl('data'), - params: {fids: fid}, + + /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/easyaddfeatureworkflow.js@v3.7.1 */ + // create workflow + const workflow = new EditingWorkflow({ + type: 'addfeature', + steps: [ + new OpenFormStep({ + push: true, + showgoback: false, + saveAll: false, + }) + ], }); - const features = getFeaturesFromResponseVectorApi(response); - if (features.length > 0) { - feature = features[0]; - } - } catch(e) { - console.warn(e); - } - return feature; -}; - -/** - * @param layer - * @param { Object } options - * @param { Array } options.exclude - * - * @returns {*} - */ -proto.getProjectLayersWithSameGeometryOfLayer = function(layer, options = { exclude: [] }) { - const { exclude = [] } = options; - const geometryType = layer.getGeometryType(); - return CatalogLayersStoresRegistry - .getLayers() - .filter(layer => { - return ( - layer.isGeoLayer() && - layer.getGeometryType && - layer.getGeometryType() && - -1 === exclude.indexOf(layer.getId()) - ) && ( - layer.getGeometryType() === geometryType || - ( - isSameBaseGeometryType(layer.getGeometryType(), geometryType) && - Geometry.isMultiGeometry(geometryType) - ) - ) - }); -}; - -/** - * return (geometryType === featureGeometryType) - * || Geometry.isMultiGeometry(geometryType) - * || !Geometry.isMultiGeometry(featureGeometryType); - */ -proto.getExternalLayersWithSameGeometryOfLayer = function(layer) { - const geometryType = layer.getGeometryType(); - return this._mapService - .getExternalLayers() - .filter(externalLayer => { - const features = externalLayer.getSource().getFeatures(); - // skip when .. - if (!(features && features.length > 0) || (features && features[0] && !features[0].getGeometry())) { - return false; + + const stop = cb => { + workflow.stop(); + session.stop(); + return cb(); + }; + + try { + //check if feature has property of layer + attributes.forEach(a => { + if (undefined === feature.get(a.name)) { + feature.set(a.name, null); + } + }) + + //set feature as g3w feature + feature = new Feature({ feature, properties: attributes.map(a => a.name) }); + //set new + feature.setTemporaryId(); + + // add to session and source as new feature + session.pushAdd(layerId, feature, false); + layer.getEditingLayer().getSource().addFeature(feature); + + //start workflow + workflow.start({ + inputs: { layer, features: [feature] }, + context: { session }, + }) + .then(() => { + session.save(); + this + .commit({ modal: false, toolbox: this.getToolBoxById(layerId) }) + .then(() => stop(resolve)) + .fail(() => stop(reject)) + }) + .fail(() => stop(reject)); + + } catch(e) { + console.warn(e); + reject(); } - const type = features[0].getGeometry().getType(); - return geometryType === type || isSameBaseGeometryType(geometryType, type); - }); -}; - -/** - * Finalize "formatter" value for any kind of field - * - * @param { string } opts.layerId - * @param {ol.Feature} opts.feature - * @param { string } opts.property - * - * @returns (field.key) or (field.value) - * - * @since g3w-client-plugin-editing@v3.7.0 - */ -proto.getFeatureTableFieldValue = function({ - layerId, - feature, - property -} = {}) { - - // get editable fields - const { fields } = this.getLayerById(layerId).config.editing; - - // get field value (raw) - let value = feature.get(property); - - // get key-value fields implicated into: https://github.com/g3w-suite/g3w-client-plugin-editing/pull/64 - const values = (null !== value) && (fields - .filter(field => ['select_autocomplete', 'select'].includes(field.input.type)) || [] ) - .reduce((kv, field) => { kv[field.name] = field.input.options.values; return kv; }, {}); - - // get last key-value feature add to - const kv_field = values && values[property] && values[property].find(kv => value == kv.value); - - // return key for key-values fields (raw field value otherwise) - return kv_field ? kv_field.key : value; -} - -/** - * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 - * - * Show editing panel - * - * @param options - * @param options.toolboxes - * - * @since g3w-client-plugin-editing@v3.7.2 - */ -proto.showPanel = function(options = {}) { - if (options.toolboxes && Array.isArray(options.toolboxes)) { - this.getToolBoxes().forEach(tb => tb.setShow(-1 !== options.toolboxes.indexOf(tb.getId()))); - } - this.getPlugin().showEditingPanel(options); -}; - -/** - * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 - * - * Hide Editing Panel - * - * @param options - * - * @since g3w-client-plugin-editing@v3.7.2 - */ -proto.hidePanel = function(options = {}) { - this.getPlugin().hideEditingPanel(options); -}; - -/** - * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 - * - * Start editing API - * - * @param layerId - * @param { Object } options - * @param { boolean } [options.selected=true] - * @param { boolean } [options.disablemapcontrols=false] - * @param { boolean } [options.showselectlayers=true] - * @param options.title - * - * @returns { Promise } - * - * @since g3w-client-plugin-editing@v3.7.2 - */ -proto.startEditing = function(layerId, options = {}, data = false) { - options.selected = undefined === options.selected ? true : options.selected; - options.showselectlayers = undefined === options.showselectlayers ? true : options.showselectlayers; - options.disablemapcontrols = undefined === options.disablemapcontrols ? false : options.showselectlayers; - return new Promise((resolve, reject) => { - // get toolbox related to layer id - const toolbox = this.getToolBoxById(layerId); - // set show select layers input visibility - this.setShowSelectLayers(options.showselectlayers); - // skip when .. - if (!toolbox) { - return reject(); - } - // set selected - toolbox.setSelected(options.selected); - // set seletcted toolbox - if (options.selected) { - this.setSelectedToolbox(toolbox); - } - if (options.title) { - toolbox.setTitle(options.title); - } - // start editing toolbox (options contain also filter type) - toolbox - .start(options) - .then(data => { - // disablemapcontrols in conflict - if (options.disablemapcontrols) { - this.disableMapControlsConflict(true); - } - // opts contain information about start editing has features loaded - resolve(data ? { toolbox, data } : toolbox); - }) - .fail(reject); - }); -}; - -/** - * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 - * - * Stop editing on layerId - * - * @param layerId - * @param options - * - * @returns { Promise } - * - * @since g3w-client-plugin-editing@v3.7.2 - */ -proto.stopEditing = function(layerId, options = {}) { - return new Promise((resolve, reject) => { - this.getToolBoxById(layerId) - .stop(options) - .then(resolve) - .fail(reject) - }) -}; - -/** - * ORIGINAL SOURCE: g3w-client-plugin-editing/api/index.js@v3.7.1 - * - * Add Feature - * - * @param { Object } opts - * @param opts.layerId - * @param opts.feature - * - * @since g3w-client-plugin-editing@v3.7.2 - */ -proto.addLayerFeature = function({ - layerId, - feature, -} = {}) { - // skip when mandatory params are missing - if (undefined === feature || undefined === layerId) { - return Promise.reject(); - } - return new Promise((resolve, reject) => { - const layer = this.getLayerById(layerId); - // get session - const session = this.getSessionById(layerId); - // exclude an eventually attribute pk (primary key) not editable (mean autoincrement) - const attributes = layer - .getEditingFields() - .filter(attr => !(attr.pk && !attr.editable)); - // start session (get no features but set layer in editing) - session.start({ - filter: { - nofeatures: true, // no feature - nofeatures_field: attributes[0].name // get first field in editing form - }, - editing: true, - }) - - /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/easyaddfeatureworkflow.js@v3.7.1 */ - // create workflow - const workflow = new EditingWorkflow({ - type: 'addfeature', - steps: [ - new OpenFormStep({ - push: true, - showgoback: false, - saveAll: false, - }) - ], - }); - - const stop = cb => { - workflow.stop(); - session.stop(); - return cb(); - }; - - try { - //check if feature has property of layer - attributes.forEach(a => { - if (undefined === feature.get(a.name)) { - feature.set(a.name, null); - } - }) - - //set feature as g3w feature - feature = new Feature({ feature, properties: attributes.map(a => a.name) }); - //set new - feature.setTemporaryId(); - - // add to session and source as new feature - session.pushAdd(layerId, feature, false); - layer.getEditingLayer().getSource().addFeature(feature); - - //start workflow - workflow.start({ - inputs: { layer, features: [feature] }, - context: { session }, - }) - .then(() => { - session.save(); - this - .commit({ modal: false, toolbox: this.getToolBoxById(layerId) }) - .then(() => stop(resolve)) - .fail(() => stop(reject)) - }) - .fail(() => stop(reject)); - - } catch(e) { - console.warn(e); - reject(); - } - }) -}; - -EditingService.EDITING_FIELDS_TYPE = ['unique']; - -module.exports = new EditingService; \ No newline at end of file + }) + } + +}); \ No newline at end of file diff --git a/services/relationservice.js b/services/relationservice.js index 8df42656..4e69ec92 100644 --- a/services/relationservice.js +++ b/services/relationservice.js @@ -1,4 +1,8 @@ -import { EditingWorkflow } from '../g3wsdk/workflow/workflow'; +import { EditingWorkflow } from '../g3wsdk/workflow/workflow'; +import { setAndUnsetSelectedFeaturesStyle } from '../utils/setAndUnsetSelectedFeaturesStyle'; +import { promisify } from '../utils/promisify'; +import { VM } from '../eventbus'; + import { OpenFormStep, LinkRelationStep, @@ -7,624 +11,572 @@ import { AddTableFeatureStep, OpenTableStep, AddFeatureStep, -} from '../workflows'; + ModifyGeometryVertexStep, + MoveFeatureStep, +} from '../workflows'; const { GUI } = g3wsdk.gui; const { tPlugin:t } = g3wsdk.core.i18n; const { Layer } = g3wsdk.core.layer; const { WorkflowsStack } = g3wsdk.core.workflow; - -// what we can do with each type of relation element -const RELATIONTOOLS = { - default: ['editattributes', 'deletefeature'], - 'table' : [], - 'Point': ['movefeature'], - 'LineString': ['movevertex'], - 'Polygon': ['movefeature', 'movevertex'] -}; - -const RelationService = function(layerId, options = {}) { - // layerId is id of the parent of relation - this._parentLayerId = layerId; - this._parentWorkFlow = this.getCurrentWorkflow(); - this._parentLayer = this._parentWorkFlow.getLayer(); - /** - * relation: contain information about relation from parent layer and current relation layer (ex. child, fields, relationid, etc....) - * relations: Array of relations object id and fields linked to current parent feature that is in editing - * - */ - const { relation, relations } = options; - this.relation = relation; - // relation feature link to current parent feature - this.relations = relations; - //editing service (main service of plugin) - this._editingService; - this._isExternalFieldRequired = false; - // this._relationLayerId is layer id of relation layer - this._relationLayerId = this.relation.child === this._parentLayerId - ? this.relation.father - : this.relation.child; - // layer in relation - const relationLayer = this.getLayer(); - this._layerType = relationLayer.getType(); - //get type of relation - const relationLayerType = Layer.LayerTypes.VECTOR === this._layerType - ? relationLayer.getGeometryType() - : Layer.LayerTypes.TABLE; - // - const { - ownField: fatherRelationField - } = this.getEditingService()._getRelationFieldsFromRelation({ layerId: this._parentLayerId, relation: this.relation}); - - // Store father relation fields editable and pk - this._fatherRelationFields = { - //get editable fields - editable: fatherRelationField.filter(fRField => this._parentLayer.isEditingFieldEditable(fRField)), - //check if father field is a pk and is not editable - pk: fatherRelationField.find(fRField => { - return this._parentLayer.isPkField(fRField) && !this._parentLayer.isEditingFieldEditable(fRField) - }), - } - //check if external fields - this._isExternalFieldRequired = this._checkIfExternalFieldRequired(); - // check if parent field is editable. If not get the id of parent feature so the server can generate the right value - // to fill the field with relation layer feature when commit - - this._currentParentFeatureRelationFieldsValue = fatherRelationField - .reduce((accumulator, fField) => { - accumulator[fField] = fField === this._fatherRelationFields.pk //check if isPk - ? this.getCurrentWorkflowData().feature.getId() - : this.getCurrentWorkflowData().feature.get(fField); - return accumulator; - }, {}) - - - /////////////////////////////////////// - this._relationTools = []; - this._add_link_workflow = null; - //get editing constraint type - this.capabilities = { - parent: this._parentLayer.getEditingCapabilities(), - relation: this._parentLayer.getEditingCapabilities() - }; - //check if relationLayer is a TABLE Layer and with capabilities value check add tools - if (Layer.LayerTypes.TABLE === relationLayerType) { - if (undefined !== this.capabilities.relation.find(capability => 'delete_feature' === capability )) { - this._relationTools.push({ - state: { - icon: 'deleteTableRow.png', - id: 'deletefeature', - name: "editing.tools.delete_feature" - } - }); - } - if (undefined !== this.capabilities.relation.find(capability => 'change_attr_feature' === capability)) { - this._relationTools.push({ - state: { - icon: 'editAttributes.png', - id: 'editattributes', - name: "editing.tools.update_feature" - } - }) +const { Geometry } = g3wsdk.core.geometry; +const { FormService } = g3wsdk.gui.vue.services; + + +const color = 'rgb(255,89,0)'; +// Vector styles for selected relation +const SELECTED_STYLES = { + 'Point': new ol.style.Style({ image: new ol.style.Circle({ radius: 8, fill: new ol.style.Fill({ color }) }) }), + 'MultiPoint': new ol.style.Style({ image: new ol.style.Circle({ radius: 8, fill: new ol.style.Fill({ color }) }) }), + 'Linestring': new ol.style.Style({ stroke: new ol.style.Stroke({ width: 8, color }) }), + 'MultiLinestring': new ol.style.Style({ stroke: new ol.style.Stroke({ width: 8, color }) }), + 'Polygon': new ol.style.Style({ stroke: new ol.style.Stroke({ width: 8, color }), fill: new ol.style.Fill({ color }) }), + 'MultiPolygon': new ol.style.Style({ stroke: new ol.style.Stroke({ width: 8, color }), fill: new ol.style.Fill({ color }) }), +} + +module.exports = class RelationService { + + constructor(layerId, options = {}) { + + const parentLayer = this.getEditingService().getCurrentWorkflow().getLayer(); + + /** + * Contain information about relation from parent layer and current relation layer (ex. child, fields, relationid, etc....) + */ + this.relation = options.relation; + + /** + * @type { Array } of a relations object id and fields linked to current parent feature (that is in editing) + */ + this.relations = options.relations; + + /** + * Current relation feature (in editing) + * + * @since g3w-client-plugin*editing@3.8.0 + */ + this.currentRelationFeatureId = null; + + /** + * layer id of relation layer + */ + this._relationLayerId = this.relation.child === layerId ? this.relation.father : this.relation.child; + + /** + * layer in relation type + */ + this._layerType = this.getLayer().getType(); + + const { ownField: fatherRelationField } = this.getEditingService()._getRelationFieldsFromRelation({ layerId, relation: this.relation }); + + const pk = fatherRelationField.find(fRField => parentLayer.isPkField(fRField)) + + /** + * Father relation fields (editable and pk) + */ + this.parent = { + // layerId is id of the parent of relation + layerId, + // get editable fields + editable: fatherRelationField.filter(fRField => parentLayer.isEditingFieldEditable(fRField)), + // check if father field is a pk and is not editable + pk, + // Check if the parent field is editable. + // If not, get the id of parent feature so the server can generate the right value + // to fill the field with the relation layer feature when commit + values: fatherRelationField + .reduce((accumulator, fField) => { + //get feature + const feature = this.getEditingService().getCurrentWorkflow().getCurrentFeature(); + //get fields of form because contains values that have temporary changes not yet saved + // in case of form fields + const fields = this.getEditingService().getCurrentWorkflow().getInputs().fields; + accumulator[fField] = (fField === pk && feature.isNew()) //check if isPk and parent feature isNew + ? feature.getId() + //check if fields are set (parent workflow is a form) + // or for example, for feature property field value + : fields ? fields.find(f => fField === f.name).value: feature.get(fField); + return accumulator; + }, {}), } - } else { - const allrelationtools = this.getEditingService().getToolBoxById(this._relationLayerId).getTools(); - allrelationtools.forEach(tool => { - if (_.concat(RELATIONTOOLS[relationLayerType], RELATIONTOOLS.default).indexOf(tool.getId()) !== -1) - this._relationTools.push(_.cloneDeep(tool)); - }); - } - this._add_link_workflow = ({ - [Layer.LayerTypes.TABLE]: { + /////////////////////////////////////// - /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/edittableworkflow.js@v3.7.1 */ - link(options = {}) { - const w = new EditingWorkflow({ - ...options, - type: 'edittable', - backbuttonlabel: 'plugins.editing.form.buttons.save_and_back_table', - steps: [ new OpenTableStep() ], - }); - return w; - }, + /** + * editing a constraint type + */ + this.capabilities = parentLayer.getEditingCapabilities(); - /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/addtablefeatureworkflow.js@v3.7.1 */ - add(options = {}) { - return new EditingWorkflow({ - ...options, - type: 'addtablefeature', - steps: [ - new AddTableFeatureStep(), - new OpenFormStep(), - ], - }); - }, + /** + * relation tools + */ + this.tools = []; - }, - [Layer.LayerTypes.VECTOR]: { + this._add_link_workflow = ({ + [Layer.LayerTypes.TABLE]: { - /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/linkrelationworkflow.js@v3.7.1 */ - link() { - return new EditingWorkflow({ - type: 'linkrelation', - steps: [ - new LinkRelationStep() - ] - }); - }, + /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/edittableworkflow.js@v3.7.1 */ + link(options = {}) { + return new EditingWorkflow({ + ...options, + type: 'edittable', + backbuttonlabel: 'plugins.editing.form.buttons.save_and_back_table', + steps: [ new OpenTableStep() ], + }); + }, + + /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/addtablefeatureworkflow.js@v3.7.1 */ + add(options = {}) { + return new EditingWorkflow({ + ...options, + type: 'addtablefeature', + steps: [ + new AddTableFeatureStep(), + new OpenFormStep(), + ], + }); + }, - /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/addfeatureworkflow.js@v3.7.1 */ - add(options = {}) { - const w = new EditingWorkflow({ - ...options, - type: 'addfeature', - steps: [ - new AddFeatureStep(options), - new OpenFormStep(options), - ], - }); - w.addToolsOfTools({ step: w.getStep(0), tools: ['snap', 'measure'] }); - return w; }, + [Layer.LayerTypes.VECTOR]: { + + /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/linkrelationworkflow.js@v3.7.1 */ + link(options = {}) { + return new EditingWorkflow({ + type: 'linkrelation', + steps: [ + new LinkRelationStep(options) + ] + }); + }, + + /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/addfeatureworkflow.js@v3.7.1 */ + add(options = {}) { + const w = new EditingWorkflow({ + ...options, + type: 'addfeature', + steps: [ + new AddFeatureStep(options), + new OpenFormStep(options), + ], + }); + w.addToolsOfTools({ step: w.getStep(0), tools: ['snap', 'measure'] }); + return w; + }, + + /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/selectandcopyfeaturesfromotherlayerworkflow.js@v3.7.1 */ + selectandcopy(options = {}) { + return new EditingWorkflow({ + type: 'selectandcopyfeaturesfromotherlayer', + steps: [ + new PickProjectLayerFeaturesStep(options), + new CopyFeaturesFromOtherProjectLayerStep(options), + new OpenFormStep(options), + ], + registerEscKeyEvent: true, + }); + }, - /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/selectandcopyfeaturesfromotherlayerworkflow.js@v3.7.1 */ - selectandcopy(options) { - return new EditingWorkflow({ - type: 'selectandcopyfeaturesfromotherlayer', - steps: [ - new PickProjectLayerFeaturesStep(options), - new CopyFeaturesFromOtherProjectLayerStep(options), - new OpenFormStep(options), - ], - registerEscKeyEvent: true, - }); }, + })[this._layerType]; + + // add tools for each relation + this.relations.forEach((r) => this.addTools(r.id) ); + + } + + /** + * Enable/Disable elements + * + * @param { Boolean } bool true enabled + * + * @since g3w-client-plugin-editing@3.8.0 + */ + enableDOMElements(bool = true) { + + document.querySelectorAll('.editing-save-all-form').forEach(c => { + + if (bool && c.classList.contains('g3w-disabled')) { c.classList.remove('g3w-disabled'); } + + if (!bool && !c.querySelector('.save-all-icon').classList.contains('g3w-disabled')) { c.classList.add('g3w-disabled'); } - }, - })[this._layerType]; - -}; - -const proto = RelationService.prototype; - -/** - * Return editing capabilities - * @returns {*|{parent: *, relation: *}} - */ -proto.getEditingCapabilities = function() { - return this.capabilities; -}; - -/** - * - * @returns {[]} - */ -proto.getRelationTools = function() { - return this._relationTools -}; - -/** - * - * @param relation - * @returns {*} - * @private - */ -proto._highlightRelationSelect = function(relation) { - const originalStyle = this.getLayer().getEditingLayer().getStyle(); - const geometryType = this.getLayer().getGeometryType(); - let style; - if ('LineString' === geometryType || 'MultiLineString' === geometryType) { - style = new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: 'rgb(255,255,0)', - width: 4 - }) - }); - } else if ('Point' === geometryType || 'MultiPoint' === geometryType) { - style = new ol.style.Style({ - image: new ol.style.Circle({ - radius: 8, - fill: new ol.style.Fill({ - color: 'rgb(255,255,0)' - }) - }) - }); - } else if ('Polygon' === geometryType || 'MultiPolygon' === geometryType) { - style = new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: 'rgb(255,255,0)', - width: 4 - }), - fill: new ol.style.Fill({ - color: 'rgba(255, 255, 0, 0.5)' - }) }); + + document.querySelectorAll('.g3w-editing-relations-add-link-tools, .g3wform_footer').forEach(c => c.classList.toggle('g3w-disabled', !bool)) + } - relation.setStyle(style); + /** + * Return editing capabilities + * @returns {*|{parent: *, relation: *}} + */ + getEditingCapabilities() { + return this.capabilities; + } - return originalStyle; -}; + /** + * Add relation tools + */ + addTools(id) { + + const tools = [ + + // edit attributes + this.capabilities.includes('change_attr_feature') && { + state: Vue.observable({ + icon: 'editAttributes.png', + id: `${id}_editattributes`, + name: 'editing.tools.update_feature', + enabled: true, + active: false, + }), + type: 'editfeatureattributes', + }, -/** - * - * @param relationtool - * @param index - * @returns {Promise} - */ -proto.startTool = function(relationtool, index) { - if ('movefeature' === relationtool.state.id ) { - GUI.hideContent(true); + // delete feature + this.capabilities.includes('delete_feature') && { + state: Vue.observable({ + icon: 'deleteTableRow.png', + id: `${id}_deletefeature`, + name: 'editing.tools.delete_feature', + enabled: true, + active: false, + }), + type: 'deletefeature', + }, + + // other vector tools (e.g., move feature) + this.capabilities.includes('change_feature') && Layer.LayerTypes.VECTOR === this._layerType && ( + this + .getEditingService() + .getToolBoxById(this._relationLayerId) + .getTools() + .filter(t => Geometry.isPointGeometryType(this.getLayer().getGeometryType()) + ? 'movefeature' === t.getId() // Point geometry + : ['movefeature', 'movevertex'].includes(t.getId()) // Line or Polygon + ) + .map(tool => ({ + state: Vue.observable({ ...tool.state, id: `${id}_${tool.state.id}` }), + type: tool.getOperator().type, + })) + ) + + ].flat().filter(Boolean); + + this.tools.push(tools); + return tools; + } + + /** + * @returns {[]} + */ + getTools(index) { + return this.tools[index] || this.addTools(this.relations[index].id); } - return new Promise((resolve, reject) => { - const toolPromise = ( - (Layer.LayerTypes.VECTOR === this._layerType && this.startVectorTool(relationtool, index)) - || - (Layer.LayerTypes.TABLE === this._layerType && this.startTableTool(relationtool, index)) - ); - toolPromise - .then(() => { - this.emitEventToParentWorkFlow(); - resolve(); - }) - .fail(err => reject(err)) - .always(() => GUI.hideContent(false)); - }) -}; - -/** - * force parent workflow form service to update - */ -proto.forceParentsFromServiceWorkflowToUpdated = function() { - const workflowParents = WorkflowsStack.getParents() || [this.getCurrentWorkflow()]; - workflowParents.forEach(workflow => { - //check if workflow has service (form service) - if (workflow.getContextService()) { - workflow - .getContextService() - .setUpdate(true, {force: true}) + + /** + * @param relationtool + * @param index + * + * @returns {Promise} + */ + async startTool(relationtool, index) { + relationtool.state.active = !relationtool.state.active; + + // skip when .. + if (!relationtool.state.active) { + return Promise.resolve(); } - }); -}; - -/** - * Method to start table tool - * @param relationtool - * @param index - * @returns {*} - */ -proto.startTableTool = function(relationtool, index) { - const d = $.Deferred(); - const relation = this.relations[index]; - const featurestore = this.getLayer().getEditingSource(); - const relationfeature = featurestore.getFeatureById(relation.id); - const options = this._createWorkflowOptions({ features: [relationfeature] }); - // delete feature - if ('deletefeature' === relationtool.state.id) { - GUI.dialog.confirm(t("editing.messages.delete_feature"), result => { - if (result) { - this.getCurrentWorkflowData().session.pushDelete(this._relationLayerId, relationfeature); - this.relations.splice(index, 1); - this.getEditingService().removeRelationLayerUniqueFieldValuesFromFeature({ - layerId: this._relationLayerId, - relationLayerId: this._parentLayerId, - feature: relationfeature - }); - featurestore.removeFeature(relationfeature); - this.forceParentsFromServiceWorkflowToUpdated(); - d.resolve(result); - } else { - d.reject(result); - } + + this.tools.forEach(tools => { + tools.forEach(t => { if (relationtool.state.id !== t.state.id) { t.state.active = false; } }) }); - } - //edit attributes feature - if ('editattributes' === relationtool.state.id) { - /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/edittablefeatureworkflow.js@v3.7.1 */ - const workflow = new EditingWorkflow({ type: 'edittablefeature', steps: [ new OpenFormStep() ] }); - workflow.start(options) - .then(() => { + + await VM.$nextTick(); + + // do something with map features + + const d = {}; + const promise = new Promise((resolve, reject) => { Object.assign(d, { resolve, reject }) }) + + const is_vector = Layer.LayerTypes.VECTOR === this._layerType; + const relation = this.relations[index]; + const toolId = relationtool.state.id.split(`${relation.id}_`)[1]; + const relationfeature = this.getLayer().getEditingSource().getFeatureById(relation.id); + const featurestore = this.getLayer().getEditingSource(); + const selectStyle = is_vector && SELECTED_STYLES[this.getLayer().getGeometryType()]; // get selected vector style + const options = this._createWorkflowOptions({ features: [relationfeature] }); + + // DELETE FEATURE RELATION + if ('deletefeature' === toolId) { + + setAndUnsetSelectedFeaturesStyle({ promise, inputs: { features: [ relationfeature ], layer: this.getLayer() }, style: selectStyle }) + + GUI.dialog.confirm( + t("editing.messages.delete_feature"), + res => { + //confirm to delete + if (res) { + this.getEditingService().getCurrentWorkflowData().session.pushDelete(this._relationLayerId, relationfeature); + this.relations.splice(index, 1); // remove feature from relation features + this.tools.splice(index, 1); // remove tool from relation tools + this.getEditingService().removeRelationLayerUniqueFieldValuesFromFeature({ + layerId: this._relationLayerId, + relationLayerId: this.parent.layerId, + feature: relationfeature + }); + featurestore.removeFeature(relationfeature); + // check if relation feature delete is new. + // In this case, we need to check if there are temporary changes not related to this current feature + if ( + relationfeature.isNew() + && undefined === WorkflowsStack + ._workflows + .find(w => w.getSession()._temporarychanges.filter(({ feature }) => relationfeature.getUid() !== feature.getUid()).length > 0) + ) { + WorkflowsStack._workflows + .filter(w => w.getContextService() instanceof FormService) + .forEach(w => setTimeout(() => w.getContextService().state.update = false)); + } else { + //set parent workflow update + this.updateParentWorkflows(); + } + d.resolve(res); + } + + if (!res) { + d.reject(); + } + + } + ); + } + + // EDIT ATTRIBUTE FEATURE RELATION + if ('editattributes' === toolId) { + /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/edittablefeatureworkflow.js@v3.7.1 */ + const workflow = new EditingWorkflow({ type: 'edittablefeature', steps: [ new OpenFormStep({ selectStyle }) ] }); + + try { + await promisify(workflow.start(options)); + //get relation layer fields - this._getRelationFieldsValue(relationfeature) - .forEach(_field => { + this + .getLayer() + .getFieldsWithValues(relationfeature, { relation: true }) + .forEach(f => { relation.fields - .forEach(field => { - if (field.name === _field.name) { - //in case of sync feature get data value of sync feature - field.value = _field.value; - } + .forEach(rf => { + //in case of sync feature get data value of sync feature + if (rf.name === f.name) { rf.value = f.value; } }) }); d.resolve(true); - }) - .fail(err => d.reject(false)) - .always(() => workflow.stop()); - } - return d.promise() -}; - -/** - * - * @param relationtool - * @param index - * @returns {*} - */ -proto.startVectorTool = function(relationtool, index) { - const d = $.Deferred(); - const relation = this.relations[index]; - const relationfeature = this._getRelationFeature(relation.id); - - const options = this._createWorkflowOptions({ - features: [relationfeature] - }); - - const workflow = Object.create(Object.getPrototypeOf(relationtool.getOperator())) - const originalStyle = this._highlightRelationSelect(relationfeature); - - GUI.setModal(false); - - workflow.bindEscKeyUp(() => relationfeature.setStyle(this._originalLayerStyle)); - - ( - workflow.isType(['deletefeature', 'editattributes']) && - workflow.startFromLastStep(options) - || - workflow.start(options) - ) - .then(outputs => { - if ('deletefeature' === relationtool.getId()) { - relationfeature.setStyle(this._originalLayerStyle); - this.getCurrentWorkflowData().session.pushDelete(this._relationLayerId, relationfeature); - this.relations.splice(index, 1); - this.forceParentsFromServiceWorkflowToUpdated(); + } catch(e) { + console.warn(e); + d.reject(e); } - if ('editattributes' === relationtool.getId()) { - const fields = this._getRelationFieldsValue(relationfeature); - fields - .forEach(_field => { - relation.fields - .forEach(field => { - if (field.name === _field.name) { - field.value = _field.value - } - }) - }); - } - d.resolve(outputs) - }) - .fail(err => d.reject(err)) - .always(() => { + workflow.stop(); - GUI.hideContent(false); - workflow.unbindEscKeyUp(); - GUI.setModal(true); - relationfeature.setStyle(originalStyle); - }); + } + + // zoom to relation vector feature + if (['movevertex', 'movefeature'].includes(toolId) && this.currentRelationFeatureId !== relationfeature.getId()) { + this.currentRelationFeatureId = relationfeature.getId(); + GUI.getService('map').zoomToFeatures([ relationfeature ]); + } - return d.promise() -}; - -/** - * - * @returns {*} - */ -proto.getLayer = function() { - return this.getEditingService().getLayerById(this._relationLayerId); -}; - -/** - * - * @returns {*} - */ -proto.getEditingLayer = function() { - return this.getEditingService().getEditingLayer(this._relationLayerId); -}; - -/** - * - * @returns {*|EditingService|{}} - */ -proto.getEditingService = function() { - this._editingService = this._editingService || require('./editingservice'); - return this._editingService; -}; - -/** - * function that change the relation field value when and if parent change the value of relation field - * @param input - */ -proto.updateExternalKeyValueRelations = function(input) { - const session = this.getEditingService().getToolBoxById(this._relationLayerId).getSession(); - //ownFiled is the field of relation feature link to parent feature layer - const {ownField, relationField} = this.getEditingService()._getRelationFieldsFromRelation({ - layerId: this._relationLayerId, - relation: this.relation - }); - // get if parent form input that is changing - // is the field in relation of the current feature relation Layer - if (this._fatherRelationFields.editable.length > 0 && relationField.find(rField => rField === input.name)) { - // change currentParentFieature relation value - this._currentParentFeatureRelationFieldsValue[input.name] = input.value; - // loop all features relations - this.relations.forEach(relation => { - //get relation fields - const fields = relation.fields; - // field relation field of current relation feature - const field = fields.find(field => ownField.indexOf(field.name) !== -1); - //if field is find - if (field) { - field.value = this._currentParentFeatureRelationFieldsValue[field.name]; - relation = this._getRelationFeature(relation.id); - const originalRelation = relation.clone(); - relation.set(field.name, input.value); - if (!relation.isNew()) { - session.pushUpdate(this._relationLayerId, relation, originalRelation); + // MOVE vertex or MOVE feature tool + if (['movevertex', 'movefeature'].includes(toolId)) { + // disable modal and buttons (saveAll and back) + GUI.setModal(false); + this.enableDOMElements(false); + const workflow = new EditingWorkflow({ + type: relationtool.type, + steps: [ new { + 'movevertex': ModifyGeometryVertexStep, + 'movefeature': MoveFeatureStep, + }[toolId]({ selectStyle }) ] + }); + + // watch eventually deactive when another tool is activated + const unwatch = VM.$watch( + () => relationtool.state.active, + bool => { + if (!bool) { + //need to enable saveAll and back + this.enableDOMElements(true); + GUI.setModal(true); + workflow.unbindEscKeyUp(); + workflow.stop(); + unwatch(); + d.reject(false); + } } + ) + // bind listen esc key + workflow.bindEscKeyUp(() => { + GUI.setModal(true); + unwatch(); + d.reject(false); + }); + + try { + await promisify(workflow.start(options)); + + WorkflowsStack + .getParents() + .filter(w => w.getContextService().setUpdate) + .forEach(w => w.getContextService().setUpdate(true, { force: true })); + d.resolve(true); + setTimeout(() => this.startTool(relationtool, index)); + } catch(e) { + console.warn(e); + d.reject(e); } - }) + + workflow.unbindEscKeyUp(); + workflow.stop(); + unwatch(); + } + + try { + await promise; + } catch (e) { + console.trace('START TOOL FAILED', e); + return Promise.reject(e); + } finally { + relationtool.state.active = false; + } } -}; - -/** - * - * @param relation - * @returns {*} - * @private - */ -proto._getRelationFieldsValue = function(relation) { - return this.getLayer() - .getFieldsWithValues(relation, {relation: true}); -}; - -/** - * - * @param relation - * @returns {{id: *, fields: *}} - * @private - */ -proto._createRelationObj = function(relation) { - return { - fields: this._getRelationFieldsValue(relation), - id: relation.getId() + + /** + * force parent workflow form service to update + */ + updateParentWorkflows() { + (WorkflowsStack.getParents() || [this.getEditingService().getCurrentWorkflow()]) + .forEach(workflow => { + //check if workflow has service (form service) + if (workflow.getContextService()) { + workflow + .getContextService() + .setUpdate(true, {force: true}) + } + }); } -}; - -/** - * - * @param type - * @param options - */ -proto.emitEventToParentWorkFlow = function(type, options={}) { - //type=set-main-component event name to set table parent visible - if (type) { - this._parentWorkFlow.getContextService().getEventBus().$emit(type, options); + + /** + * @returns {*} + */ + getLayer() { + return this.getEditingService().getLayerById(this._relationLayerId); } -}; - -/** - * - * @param type - * @returns {*|string} - * @private - */ -proto._getRelationAsFatherStyleColor = function(type) { - const fatherLayer = this.getEditingLayer(this._parentLayerId); - const fatherLayerStyle = fatherLayer.getStyle(); - let fatherLayerStyleColor; - switch (type) { - case 'Point': - fatherLayerStyleColor = fatherLayerStyle.getImage() && fatherLayerStyle.getImage().getFill(); - break; - case 'Line': - fatherLayerStyleColor = fatherLayerStyle.getStroke() || fatherLayerStyle.getFill(); - break; - case 'Polygon': - fatherLayerStyleColor = fatherLayerStyle.getFill() || fatherLayerStyle.getStroke(); - break; + + /** + * @returns {*} + */ + getEditingLayer() { + return this.getEditingService().getEditingLayer(this._relationLayerId); } - return ( - ( - fatherLayerStyleColor && - fatherLayerStyleColor.getColor() - ) || - '#000000' - ); -}; - -/** - * Add Relation from project layer - * @param layer - */ -proto.addRelationFromOtherLayer = function({layer, external}){ - let workflow; - let isVector = false; - if (external || layer.isGeoLayer() ) { - isVector = true; - workflow = new this._add_link_workflow.selectandcopy({ - copyLayer: layer, - isVector, - external, - help: 'editing.steps.help.copy', - }); + + /** + * @returns {*|EditingService|{}} + */ + getEditingService() { + return require('./editingservice'); } - this.runAddRelationWorkflow({ - workflow, - isVector - }) -}; - -/** - * add relation method - */ -proto.addRelation = function() { - this.runAddRelationWorkflow({ - workflow: new this._add_link_workflow.add(), - isVector: Layer.LayerTypes.VECTOR === this._layerType - }) -}; - -/** - * Common method to add a relation - */ -proto.runAddRelationWorkflow = function({workflow, isVector=false}={}){ - if (isVector) { - GUI.setModal(false); - GUI.hideContent(true); + /** + * Add Relation from project layer + * + * @param layer + * @param external + */ + addRelationFromOtherLayer({ layer, external }) { + let workflow; + let isVector = false; + if (external || layer.isGeoLayer() ) { + isVector = true; + workflow = new this._add_link_workflow.selectandcopy({ + copyLayer: layer, + isVector, + external, + help: 'editing.steps.help.copy', + }); + } + + this.runAddRelationWorkflow({ + workflow, + isVector + }) } - const options = this._createWorkflowOptions(); - const session = options.context.session; - const {ownField, relationField} = this.getEditingService()._getRelationFieldsFromRelation({ - layerId: this._relationLayerId, - relation: this.relation - }); - - const { parentFeature } = options; - const promise = workflow.start(options); - if (isVector) { - workflow.bindEscKeyUp(); + + /** + * add relation method + */ + addRelation() { + + this.runAddRelationWorkflow({ + workflow: new this._add_link_workflow.add(), + isVector: Layer.LayerTypes.VECTOR === this._layerType, + }) } - promise - .then(outputs => { - const {newFeatures, originalFeatures} = outputs.relationFeatures; - - /** - * Set Relation child feature value - * @param oIndex - * @param value - */ - const setRelationFieldValue = ({oIndex, value}) => { - newFeatures.forEach((newFeature, index) => { - const originalFeature = originalFeatures[index]; + + /** + * Common method to add a relation + */ + async runAddRelationWorkflow({ workflow, isVector = false } = {} ) { + + if (isVector) { + GUI.setModal(false); + GUI.hideContent(true); + } + + const options = this._createWorkflowOptions(); + + const { ownField, relationField } = this.getEditingService()._getRelationFieldsFromRelation({ + layerId: this._relationLayerId, + relation: this.relation + }); + + try { + const outputs = await promisify(workflow.start(options)); + + if (isVector) { workflow.bindEscKeyUp(); } + + const { newFeatures, originalFeatures } = outputs.relationFeatures; + + // Set Relation child feature value + const setRelationFieldValue = ({ oIndex, value }) => { + newFeatures.forEach((newFeature, i) => { newFeature.set(ownField[oIndex], value); - if (parentFeature.isNew()) { - originalFeature.set(ownField[oIndex], value); + if (options.parentFeature.isNew()) { + originalFeatures[i].set(ownField[oIndex], value); } this.getLayer().getEditingSource().updateFeature(newFeature); - session.pushUpdate(this._relationLayerId, newFeature, originalFeature); + options.context.session.pushUpdate(this._relationLayerId, newFeature, originalFeatures[i]); }) }; + Object - .entries(this._currentParentFeatureRelationFieldsValue) + .entries(this.parent.values) .forEach(([field, value]) => { setRelationFieldValue({ oIndex: relationField.findIndex(rField => field === rField), value }); - }) - //check if parent features is new and if parent layer has editable fields - if (parentFeature.isNew() && this._fatherRelationFields.editable.length > 0) { - const keyRelationFeatureChange = parentFeature.on('propertychange', evt => { - if (parentFeature.isNew()) { + }); + + //check if parent feature is new and if parent layer has editable fields + if (options.parentFeature.isNew() && this.parent.editable.length > 0) { + const keyRelationFeatureChange = options.parentFeature.on('propertychange', evt => { + if (options.parentFeature.isNew()) { //check if input is relation field - if (relationField.indexOf(evt.key) !== -1) { - //get input value - const value = evt.target.get(evt.key); + if (relationField.find(evt.key)) { //set value to relation field setRelationFieldValue({ oIndex: relationField.findIndex(rField => evt.key === rField), - value + value: evt.target.get(evt.key) }); } } else { @@ -632,386 +584,202 @@ proto.runAddRelationWorkflow = function({workflow, isVector=false}={}){ } }) } - newFeatures.forEach(newFeature => { - this.relations.push(this._createRelationObj(newFeature)); - }); - this.emitEventToParentWorkFlow(); - }) - .fail(inputs => { + this.relations.push( + ...(newFeatures || []).map(f => ({ id: f.getId(), fields: this.getLayer().getFieldsWithValues(f, { relation: true }) })) + ) + } catch(inputs) { + console.warn(inputs); + + // in case of save all pressed on openformtask if (inputs && inputs.relationFeatures) { - /** - * needed in case of save all pressed on openformtask - */ - const {relationFeatures: { newFeatures=[] } } = inputs; - newFeatures.forEach(newFeature => { - this.relations.push(this._createRelationObj(newFeature)); - }); + this.relations.push( + ...(inputs.relationFeatures.newFeatures || []).map(f => ({ id: f.getId(), fields: this.getLayer().getFieldsWithValues(f, { relation: true }) })) + ) } - session.rollbackDependecies([this._relationLayerId]) - }) - .always(() => { - - workflow.stop(); - if (isVector) { - - workflow.unbindEscKeyUp(); - GUI.hideContent(false); - GUI.setModal(true); + options.context.session.rollbackDependecies([this._relationLayerId]) + } + + workflow.stop(); - } - }) -}; - -/** - * Link relation (bind) to parent feature layer - */ -proto.linkRelation = function() { - const isVector = Layer.LayerTypes.VECTOR === this._layerType; - const workflow = new this._add_link_workflow.link(); - const options = this._createWorkflowOptions(); - const session = options.context.session; - const {ownField, relationField} = this.getEditingService()._getRelationFieldsFromRelation({ - layerId: this._relationLayerId, - relation: this.relation - }); - - //add options to exclude features from link - options.context.excludeFeatures = relationField.reduce((accumulator, rField, index) => { - accumulator[ownField[index]] = this._currentParentFeatureRelationFieldsValue[rField]; - return accumulator; - }, {}); - - - //check if a vector layer - if (isVector) { - GUI.setModal(false); - GUI.hideContent(true); - options.context.style = this.getUnlinkedStyle(); + if (isVector) { + workflow.unbindEscKeyUp(); + GUI.hideContent(false); + GUI.setModal(true); + } } - const {feature} = this.getCurrentWorkflowData(); + /** + * Link relation (bind) to parent feature layer + */ + async linkRelation() { + const is_vector = Layer.LayerTypes.VECTOR === this._layerType; + const workflow = new this._add_link_workflow.link( is_vector ? { + selectStyle: SELECTED_STYLES[this.getLayer().getGeometryType()] + } : {}); + const options = this._createWorkflowOptions(); + const { ownField, relationField } = this.getEditingService()._getRelationFieldsFromRelation({ + layerId: this._relationLayerId, + relation: this.relation + }); - const dependencyOptions = { - relations: [this.relation], - feature, - operator: 'not', - filterType: isVector ? 'bbox' : 'fid' - }; - const getRelationFeatures = () => this.getEditingService().getLayersDependencyFeatures(this._parentLayerId, dependencyOptions); + //add options to exclude features from a link + options.context.excludeFeatures = relationField.reduce((accumulator, rField, index) => { + accumulator[ownField[index]] = this.parent.values[rField]; + return accumulator; + }, {}); - let preWorkflowStart; - if (isVector) { - const mapService = this.getEditingService().getMapService(); - options.context.beforeRun = async () => { - //show map spinner - mapService.showMapSpinner(); + //check if a vector layer + if (is_vector) { + GUI.setModal(false); + } - await new Promise((resolve) => setTimeout(resolve)); + const { feature } = this.getEditingService().getCurrentWorkflowData(); - await getRelationFeatures(); - //hide mapSpinner - mapService.hideMapSpinner(); - - GUI.showUserMessage({ - type: 'info', - size: 'small', - message: t('editing.messages.press_esc'), - closable: false - }) + const getRelationFeatures = () => this.getEditingService().getLayersDependencyFeatures(this.parent.layerId, { + relations: [this.relation], + feature, + operator: 'not', + filterType: is_vector ? 'bbox' : 'fid' + }); + + let response = { + promise: undefined, + showContent: false, }; - preWorkflowStart = new Promise((resolve) => { + if (is_vector) { + options.context.beforeRun = async () => { + await new Promise((resolve) => setTimeout(resolve)); + await getRelationFeatures(); + }; workflow.bindEscKeyUp(); - resolve({ - promise: workflow.start(options), + response = { + promise: workflow.start(options), showContent: true - }) - }); - } else { - - preWorkflowStart = new Promise((resolve) => { - getRelationFeatures() - .then(() => resolve({})) - }); - } + }; - preWorkflowStart.then(({promise, showContent=false}={})=> { - let linked = false; + this.enableDOMElements(false); - promise = promise || workflow.start(options); + } else { + await getRelationFeatures(); + } - promise - .then(outputs => { + let linked = false; - if (outputs.features.length > 0) { + try { + const outputs = await promisify(response.promise || workflow.start(options)); + // loop on features selected + (outputs.features || []).forEach(relation => { + if (undefined === this.relations.find(rel => rel.id === relation.getId())) { + linked = linked || true; + const originalRelation = relation.clone(); + Object + .entries(this.parent.values) + .forEach(([field, value]) => { + relation.set(ownField[relationField.findIndex(rF => field === rF)], value); + }) + this.getEditingService().getCurrentWorkflowData().session.pushUpdate(this._relationLayerId , relation, originalRelation); + this.relations.push({ + fields: this.getLayer().getFieldsWithValues(relation, { relation: true }), + id: relation.getId() + }); + } else { + // in case already present + GUI.notify.warning(t("editing.relation_already_added")); + } + }); + } catch (e) { + console.warn(e); + options.context.session.rollbackDependecies([this._relationLayerId]); + } - //loop on features selected - outputs.features.forEach(relation => { + if (is_vector) { + this.enableDOMElements(true); + } - if (undefined === this.relations.find(rel => rel.id === relation.getId())) { + if (response.showContent) { + GUI.closeUserMessage(); + workflow.unbindEscKeyUp(); + } - linked = linked || true; + if (linked) { + this.updateParentWorkflows(); + } - const originalRelation = relation.clone(); + workflow.stop(); + } - Object - .entries(this._currentParentFeatureRelationFieldsValue) - .forEach(([field, value]) => { - relation.set(ownField[relationField.findIndex(rField => field === rField)], value); - }) + /** + * Method to unlink relation + * @param index + * @param dialog + * @returns JQuery Promise + */ + unlinkRelation(index, dialog = true) { + const d = $.Deferred(); + const { ownField } = this.getEditingService()._getRelationFieldsFromRelation({ + layerId: this._relationLayerId, + relation: this.relation + }); + const unlink = () => { + const relation = this.relations[index]; + const feature = this.getLayer().getEditingSource().getFeatureById(relation.id); + const originalRelation = feature.clone(); + //loop on ownField (Array field child relation) + ownField.forEach(oField => feature.set(oField, null)) + + this.getEditingService().getCurrentWorkflowData().session.pushUpdate(this._relationLayerId, feature, originalRelation); + this.relations.splice(index, 1); + this.updateParentWorkflows(); + d.resolve(true); + }; + if (dialog) { - this.getCurrentWorkflowData() - .session - .pushUpdate(this._relationLayerId , relation, originalRelation); + GUI.dialog.confirm( + t("editing.messages.unlink_relation"), + result => { + if (result) { unlink() } + else { d.reject(false) } + } + ) + } else { unlink() } - this.relations.push(this._createRelationObj(relation)); + return d.promise(); + } - this.emitEventToParentWorkFlow(); - } else { - // in case already present - GUI.notify.warning(t("editing.relation_already_added")); - } - }); - } - }) - .fail(() => session.rollbackDependecies([this._relationLayerId])) - .always(() => { - if (showContent) { - GUI.closeUserMessage(); - GUI.hideContent(false); - workflow.unbindEscKeyUp(); - } - if (linked) { - this.forceParentsFromServiceWorkflowToUpdated(); - } - workflow.stop(); - }); - }) -}; - -/** - * Check if relation layer fields has at least one field required - * @returns {Boolean} - * @private - */ -proto._checkIfExternalFieldRequired = function() { - // own Fields is relation Fields array of Relation Layer - const { ownField } = this.getEditingService()._getRelationFieldsFromRelation({ - layerId: this._relationLayerId, - relation: this.relation, - }); - - //check if at least one filed is required - return ownField.some(field => this.getEditingService().isFieldRequired(this._relationLayerId, field)); -}; - -/** - * - * @returns {boolean|*} - */ -proto.isRequired = function() { - return this._isExternalFieldRequired; -}; - -/** - * - * @param featureId - * @returns {*} - * @private - */ -proto._getRelationFeature = function(featureId) { - return this.getLayer().getEditingSource().getFeatureById(featureId); -}; - -/** - * Get value from feature if layer has key value - * - * @since g3w-client-plugin-editing@v3.7.0 - */ -proto.getRelationFeatureValue = function(featureId, property) { - return this - .getEditingService() - .getFeatureTableFieldValue({ - layerId: this._relationLayerId, - feature: this._getRelationFeature(featureId), - property, + /** + * @param options + * + * @returns {{parentFeature, inputs: {features: *[], layer: *}, context: {fatherValue: *, session: *, fatherField: *, excludeFields: *[]}}} + * + * @private + */ + _createWorkflowOptions(options = {}) { + const fields = this.getEditingService()._getRelationFieldsFromRelation({ + layerId: this._relationLayerId, + relation: this.relation }); -}; - -/** - * Method to unlink relation - * @param index - * @param dialog - * @returns JQuery Promise - */ -proto.unlinkRelation = function(index, dialog=true) { - const d = $.Deferred(); - const { ownField } = this.getEditingService()._getRelationFieldsFromRelation({ - layerId: this._relationLayerId, - relation: this.relation - }); - const unlink = () => { - const relation = this.relations[index]; - const feature = this.getLayer().getEditingSource().getFeatureById(relation.id); - const originalRelation = feature.clone(); - //loop on ownField (Array field child relation) - ownField.forEach(oField => feature.set(oField, null)) - - this.getCurrentWorkflowData().session.pushUpdate(this._relationLayerId, feature, originalRelation); - this.relations.splice(index, 1); - this.forceParentsFromServiceWorkflowToUpdated(); - d.resolve(true); - }; - if (dialog) { - - GUI.dialog.confirm( - t("editing.messages.unlink_relation"), - result => { - if (result) { - unlink() ; - } else { - d.reject(false); - } + const data = this.getEditingService().getCurrentWorkflowData(); + const parent = Object.entries(this.parent.values); + return { + parentFeature: data.feature, // get parent feature + context: { + session: data.session, // get parent workflow + excludeFields: fields.ownField, // array of fields to be excluded + fatherValue: parent.map(([_, value]) => value), + fatherField: parent.map(([field]) => fields.ownField[fields.relationField.findIndex(rField => field === rField)]), + }, + inputs: { + features: options.features || [], + layer: this.getLayer() } - ) - } else { - unlink(); - } - - return d.promise(); -}; - -/** - * - * @returns {*} - */ -proto.getCurrentWorkflow = function() { - return this.getEditingService().getCurrentWorkflow(); -}; - -/** - * - * @returns {*} - */ -proto.getCurrentWorkflowData = function() { - return this.getEditingService().getCurrentWorkflowData(); -}; - -/** - * - * @param options - * @returns {{parentFeature, inputs: {features: *[], layer: *}, context: {fatherValue: *, session: *, fatherField: *, excludeFields: *[]}}} - * @private - */ -proto._createWorkflowOptions = function(options={}) { - - const {ownField, relationField} = this.getEditingService()._getRelationFieldsFromRelation({ - layerId: this._relationLayerId, - relation: this.relation - }); - const { - feature: parentFeature, - session - } = this.getCurrentWorkflowData(); - - const fatherValue = []; - const fatherField = []; - - Object - .entries(this._currentParentFeatureRelationFieldsValue) - .forEach(([field, value]) => { - const index = relationField.findIndex(rField => field === rField); - fatherField.push(ownField[index]); - fatherValue.push(value); - }) - - const workflow_options = { - parentFeature, //get parent feature - context: { - session, //get parent workfow - excludeFields: ownField, //ownField is an Array to exclude - fatherValue, - fatherField - }, - inputs: { - features: options.features || [], - layer: this.getLayer() - } - }; - - return workflow_options; -}; - -/** - * - * @returns {ol.style.Style} - */ -proto.getUnlinkedStyle = function() { - let style; - const geometryType = this.getLayer().getGeometryType(); - switch (geometryType) { - case 'Point': - case 'MultiPoint': - style = new ol.style.Style({ - image: new ol.style.Circle({ - radius: 8, - fill: new ol.style.Fill({ - color: this._getRelationAsFatherStyleColor('Point') - }), - stroke: new ol.style.Stroke({ - width: 5, - color: 'yellow' - }) - }) - }); - break; - case 'Line': - case 'MultiLine': - style = new ol.style.Style({ - fill: new ol.style.Fill({ - color: this._getRelationAsFatherStyleColor('Line') - }), - stroke: new ol.style.Stroke({ - width: 5, - color: 'yellow' - }) - }); - break; - case 'Polygon': - case 'MultiPolygon': - style = new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: 'yellow' , - width: 5 - }), - fill: new ol.style.Fill({ - color: this._getRelationAsFatherStyleColor('Polygon'), - opacity: 0.5 - }) - }) + }; } - - return style; -}; - -/** - * - * @param relation - * @returns {*[]} - */ -proto.relationFields = function(relation) { - return relation.fields.map(({label, name, value}) => ({ - name, - label, - value - })) -}; - -module.exports = RelationService; +}; \ No newline at end of file diff --git a/services/tableservice.js b/services/tableservice.js index 6d504ec3..7ed55d0e 100644 --- a/services/tableservice.js +++ b/services/tableservice.js @@ -19,49 +19,47 @@ const { G3WObject } = g3wsdk.core; const { GUI } = g3wsdk.gui; const t = g3wsdk.core.i18n.tPlugin; -/** - * - * @param options - * @constructor - */ -const TableService = function(options = {}) { - this._features = options.features || []; // original features - this._promise = options.promise; - this._context = options.context; - this._inputs = options.inputs; - this._layerId = options.inputs.layer.getId(); - this._fatherValue = options.fatherValue; - this._foreignKey = options.foreignKey; - this._workflow = null; - this._deleteFeaturesIndexes = []; - this._isrelation = options.isrelation || false; +module.exports = class TableService extends G3WObject { - const { - capabilities, - headers=[], - title='Link relation', - push:isrelation - } = options; - - this.state = { - headers, - features: [], - title , - isrelation, - capabilities - }; + /** + * @param options + */ + constructor(options = {}) { + super(); + + this._features = options.features || []; // original features + this._promise = options.promise; + this._context = options.context; + this._inputs = options.inputs; + this._layerId = options.inputs.layer.getId(); + this._fatherValue = options.fatherValue; + this._workflow = null; + this._deleteFeaturesIndexes = []; + this._isrelation = options.isrelation || false; + + const { + capabilities, + headers=[], + title='Link relation', + push:isrelation + } = options; + + this.state = { + headers, + features: [], + title , + isrelation, + capabilities + }; + + this.init(); + } /** * Init function service - * */ - this.init = function() { + init() { const EditingService = require('./editingservice'); - //filter the original feature based on if is a relation - if (this._isrelation) { - this._features.filter(feature => feature.get(this._foreignKey) !== this._fatherValue); - } - // set values if (this._features.length > 0) { const baseFeature = this._features[0]; @@ -91,185 +89,177 @@ const TableService = function(options = {}) { return orderedProperties; }); } - }; - - this.init(); - - base(this); -}; - -inherit(TableService, G3WObject); - -const proto = TableService.prototype; + } -/** - * - * @param name - * @returns {boolean} - */ -proto.isMediaField = function(name) { - let isMedia = false; - for (let i=0; i < this.state.headers.length; i++) { - const header = this.state.headers[i]; - if (header.name === name && header.input.type === 'media' ) { - isMedia = true; - break; + /** + * @param name + * + * @returns {boolean} + */ + isMediaField(name) { + let isMedia = false; + for (let i=0; i < this.state.headers.length; i++) { + const header = this.state.headers[i]; + if (header.name === name && header.input.type === 'media' ) { + isMedia = true; + break; + } } + return isMedia; } - return isMedia; -}; - -/** - * - */ -proto.save = function() { - this._promise.resolve(); -}; - -/** - * - */ -proto.cancel = function() { - this._promise.reject(); -}; -/** - * - * @param uid feature uid - * @returns {Promise} - */ -proto.deleteFeature = function(uid) { - const EditingService = require('./editingservice'); - const layer = this._inputs.layer; - const layerId = layer.getId(); - const childRelations = layer.getChildren(); - const relationinediting = childRelations.length && EditingService._filterRelationsInEditing({ - layerId, - relations: layer.getRelations().getArray() - }).length > 0; + /** + * + */ + save() { + this._promise.resolve(); + } - return new Promise((resolve, reject) =>{ - GUI.dialog.confirm( - `

${t('editing.messages.delete_feature')}

-
${ relationinediting ?t('editing.messages.delete_feature_relations') : ''}
`, - (result) => { - if (result) { - const index = this._features.findIndex(f => f.getUid() === uid); - const feature = this._features[index]; - const session = this._context.session; - const layerId = this._inputs.layer.getId(); - this._inputs.layer.getEditingSource().removeFeature(feature); - session.pushDelete(layerId, feature); - this.state.features.splice(index, 1); - resolve() - } else { - reject() - } - }); - }) -}; + /** + * + */ + cancel() { + this._promise.reject(); + } -/** - *Copy feature tool from another table feature - * @param uid - * @returns {Promise} - */ -proto.copyFeature = function(uid) { - const EditingService = require('./editingservice'); - return new Promise((resolve, reject) => { - const feature = cloneFeature( - this._features.find(f => f.getUid() === uid), - this._inputs.layer.getEditingLayer() - ); - /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/addtablefeatureworkflow.js@v3.7.1 */ - this._workflow = new EditingWorkflow({ - type: 'addtablefeature', - steps: [ - new AddTableFeatureStep(), - new OpenFormStep(), - ], + /** + * @param uid feature uid + * + * @returns {Promise} + */ + deleteFeature(uid) { + const EditingService = require('./editingservice'); + const layer = this._inputs.layer; + const layerId = layer.getId(); + const childRelations = layer.getChildren(); + const relationinediting = childRelations.length && EditingService._filterRelationsInEditing({ + layerId, + relations: layer.getRelations().getArray() + }).length > 0; + + return new Promise((resolve, reject) =>{ + GUI.dialog.confirm( + `

${t('editing.messages.delete_feature')}

+
${ relationinediting ?t('editing.messages.delete_feature_relations') : ''}
`, + (result) => { + if (result) { + const index = this._features.findIndex(f => f.getUid() === uid); + const feature = this._features[index]; + const session = this._context.session; + const layerId = this._inputs.layer.getId(); + this._inputs.layer.getEditingSource().removeFeature(feature); + session.pushDelete(layerId, feature); + this.state.features.splice(index, 1); + resolve() + } else { + reject() + } }); - this._inputs.features.push(feature); - this._workflow.start({ - context: this._context, - inputs: this._inputs }) - .then(outputs => { - const feature = outputs.features[outputs.features.length -1]; - const newFeature = {}; - Object.entries(this.state.features[0]).forEach(([key, value]) => { - newFeature[key] = EditingService.getFeatureTableFieldValue({ - layerId: this._layerId, - feature, - property: key - }); + } + + /** + * Copy feature tool from another table feature + * + * @param uid + * + * @returns {Promise} + */ + copyFeature(uid) { + const EditingService = require('./editingservice'); + return new Promise((resolve, reject) => { + const feature = cloneFeature( + this._features.find(f => f.getUid() === uid), + this._inputs.layer.getEditingLayer() + ); + /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/addtablefeatureworkflow.js@v3.7.1 */ + this._workflow = new EditingWorkflow({ + type: 'addtablefeature', + steps: [ + new AddTableFeatureStep(), + new OpenFormStep(), + ], }); - newFeature.__gis3w_feature_uid = feature.getUid(); - this.state.features.push(newFeature); - resolve(newFeature) - }) - .fail(reject) - .always(() => { - this._workflow.stop(); - /** @TODO check input.features that grow in number */ - console.log('here we are') + this._inputs.features.push(feature); + this._workflow.start({ + context: this._context, + inputs: this._inputs }) + .then(outputs => { + const feature = outputs.features[outputs.features.length -1]; + const newFeature = {}; + Object.entries(this.state.features[0]).forEach(([key, value]) => { + newFeature[key] = EditingService.getFeatureTableFieldValue({ + layerId: this._layerId, + feature, + property: key + }); + }); + newFeature.__gis3w_feature_uid = feature.getUid(); + this.state.features.push(newFeature); + resolve(newFeature) + }) + .fail(reject) + .always(() => { + this._workflow.stop(); + /** @TODO check input.features that grow in number */ + console.log('here we are') + }) - }) -}; + }) + } -/** - * - * @param uid - */ -proto.editFeature = function(uid) { + /** + * @param uid + */ + editFeature(uid) { - const EditingService = require('./editingservice'); + const EditingService = require('./editingservice'); - const index = this._features.findIndex(f => f.getUid() === uid); + const index = this._features.findIndex(f => f.getUid() === uid); - const feature = this._features[index]; + const feature = this._features[index]; - /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/edittablefeatureworkflow.js@v3.7.1 */ - this._workflow = new EditingWorkflow({ type: 'edittablefeature', steps: [ new OpenFormStep() ] }); + /** ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/edittablefeatureworkflow.js@v3.7.1 */ + this._workflow = new EditingWorkflow({ type: 'edittablefeature', steps: [ new OpenFormStep() ] }); - const inputs = this._inputs; + const inputs = this._inputs; - inputs.features.push(feature); + inputs.features.push(feature); - this._workflow - .start({ - context: this._context, - inputs - }) - .then(outputs => { - const feature = outputs.features[outputs.features.length -1]; - Object - .entries(this.state.features[index]) - .forEach(([key, _]) => { - this.state.features[index][key] = EditingService.getFeatureTableFieldValue({ - layerId: this._layerId, - feature, - property: key - }); - }); - }) - .fail(console.warn) - .always(() => this._workflow.stop()) -}; + this._workflow + .start({ + context: this._context, + inputs + }) + .then(outputs => { + const feature = outputs.features[outputs.features.length -1]; + Object + .entries(this.state.features[index]) + .forEach(([key, _]) => { + this.state.features[index][key] = EditingService.getFeatureTableFieldValue({ + layerId: this._layerId, + feature, + property: key + }); + }); + }) + .fail(console.warn) + .always(() => this._workflow.stop()) + } -/** - * @param ids features indexes - */ -proto.linkFeatures = function(ids = []) { - this._promise.resolve({ features: ids.map(index => this._features[index]) }) -}; + /** + * @param ids features indexes + */ + linkFeatures(ids = []) { + this._promise.resolve({ features: ids.map(index => this._features[index]) }) + } -/** - * @param index - */ -proto.linkFeature = function(index) { - this._promise.resolve({ features: [this._features[index]] }); -}; + /** + * @param index + */ + linkFeature(index) { + this._promise.resolve({ features: [this._features[index]] }); + } -module.exports = TableService; +}; \ No newline at end of file diff --git a/utils/promisify.js b/utils/promisify.js new file mode 100644 index 00000000..7aa5014d --- /dev/null +++ b/utils/promisify.js @@ -0,0 +1,13 @@ +/** + * Migrate your consumer code away from jQuery promises. + * + * @param promise jquery promise + */ +export function promisify(promise) { + if (promise instanceof Promise) { + return promise; + } + return new Promise((resolve, reject) => { + promise.then(resolve).fail(reject); + }); +} \ No newline at end of file diff --git a/utils/setAndUnsetSelectedFeaturesStyle.js b/utils/setAndUnsetSelectedFeaturesStyle.js index 9bcdafd7..89aa48b0 100644 --- a/utils/setAndUnsetSelectedFeaturesStyle.js +++ b/utils/setAndUnsetSelectedFeaturesStyle.js @@ -1,4 +1,5 @@ -import { setFeaturesSelectedStyle } from './setFeaturesSelectedStyle'; +import { promisify } from '../utils/promisify'; +import { setFeaturesSelectedStyle } from '../utils/setFeaturesSelectedStyle'; const { WorkflowsStack } = g3wsdk.core.workflow; const { Layer } = g3wsdk.core.layer; @@ -10,8 +11,10 @@ const { Layer } = g3wsdk.core.layer; * reset original style when workflow (tool) is done. * * @param promise jQuery promise + * @param { Object } inputs + * @param { ol.style.Style } style */ -export function setAndUnsetSelectedFeaturesStyle({ promise, inputs } = {}) { +export function setAndUnsetSelectedFeaturesStyle({ promise, inputs, style } = {}) { /** @FIXME temporary add in order to fix issue on pending promise (but which issue ?) */ const { @@ -26,8 +29,8 @@ export function setAndUnsetSelectedFeaturesStyle({ promise, inputs } = {}) { * need to wait. */ const selectOriginalStyleHandle = () => { - const originalStyle = setFeaturesSelectedStyle(features); - promise.always(() => { features.flat().forEach((feature => feature.setStyle(originalStyle))) }); + const originalStyle = setFeaturesSelectedStyle(features, style); + promisify(promise).finally(() => { features.flat().forEach((feature => feature.setStyle(originalStyle))) }); }; const is_vector = Layer.LayerTypes.VECTOR === layer.getType(); @@ -35,7 +38,7 @@ export function setAndUnsetSelectedFeaturesStyle({ promise, inputs } = {}) { if (is_vector && is_single) { setTimeout(() => { selectOriginalStyleHandle(); }); - } else if(is_vector) { + } else if (is_vector) { selectOriginalStyleHandle(); } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/utils/setFeaturesSelectedStyle.js b/utils/setFeaturesSelectedStyle.js index 7e9fb06e..5cc8fb5c 100644 --- a/utils/setFeaturesSelectedStyle.js +++ b/utils/setFeaturesSelectedStyle.js @@ -5,29 +5,31 @@ * * @param feature * - * @returns {{ originalStyle: *, selectedStyle: * }} selected style based on geometry type + * @returns {{ originalStyle: *, selectedStyle: * }} selected style based on a geometry type */ function getSelectedStyle(feature) { return { originalStyle: feature.getStyle(), selectedStyle: g3wsdk.core.geoutils.createSelectedStyle({ geometryType: feature.getGeometry().getType() }) } -}; +} /** * ORIGINAL SOURCE: g3w-client-plugin-editing/workflows/tasks/editingtask.js@3.7.1 * - * Set selected style to features and return original feature style + * Set selected style to feature and return original feature style * * @param { Array } features + * @param { ol.style.Style } style @since 3.8.0 custom select style * * @returns { ol.style.Style } */ -export function setFeaturesSelectedStyle(features=[]) { - if (features.length > 0) { // copy feature from other layers when selecting multiple features +export function setFeaturesSelectedStyle(features=[], style) { + if (features.length > 0) { + // copy feature from other layers when selecting multiple features const arr = features.flat(); // flat nested features - const style = getSelectedStyle(arr[0]); - arr.forEach(feature => feature.setStyle(style.selectedStyle)); - return style.originalStyle; + const { originalStyle, selectedStyle } = getSelectedStyle(arr[0]); + arr.forEach(feature => feature.setStyle(style || selectedStyle)); + return originalStyle; } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/workflows/index.js b/workflows/index.js index c6675a82..4a7200bb 100644 --- a/workflows/index.js +++ b/workflows/index.js @@ -15,7 +15,6 @@ import { listenRelation1_1FieldChange } from '../utils/listenRel import CopyFeatureFromOtherLayersComponent from '../components/CopyFeaturesFromOtherLayers.vue'; import CopyFeatureFromOtherProjectLayersComponent from '../components/CopyFeaturesFromOtherProjectLayer.vue'; -import SaveAll from '../components/SaveAll.vue'; import TableVueObject from '../components/Table.vue'; import { PickFeaturesInteraction } from '../interactions/pickfeaturesinteraction'; @@ -57,6 +56,7 @@ const { const { G3W_FID } = g3wsdk.constant; const { CatalogLayersStoresRegistry } = g3wsdk.core.catalog; const { Component } = g3wsdk.gui.vue; +const { FormService } = g3wsdk.gui.vue.services; const EditingFormComponent = require('../form/editingform'); @@ -184,7 +184,7 @@ export class AddFeatureStep extends EditingTask { } /** @since g3w-client-plugin-editing@v3.8.0 */ - setAndUnsetSelectedFeaturesStyle({ promise: this._stopPromise, inputs }); + setAndUnsetSelectedFeaturesStyle({ promise: this._stopPromise, inputs, style: this.selectStyle }); const originalGeometryType = originalLayer.getEditingGeometryType(); @@ -454,7 +454,7 @@ export class ConfirmStep extends EditingTask { run(inputs, context) { const promise = this._dialog.fnc(inputs, context); if (inputs.features) { - setAndUnsetSelectedFeaturesStyle({ promise, inputs }); + setAndUnsetSelectedFeaturesStyle({ promise, inputs, style: this.selectStyle }); } return promise; } @@ -538,7 +538,7 @@ export class CopyFeaturesFromOtherLayerStep extends EditingTask { const promisesFeatures = []; selectedFeatures.forEach(selectedFeature => { /** - * check if layer belong to project or not + * check if the layer belongs to project or not */ if (this.getEditingService().getProjectLayerById(selectedFeature.__layerId)) { promisesFeatures.push(this.getEditingService().getProjectLayerFeatureById({ @@ -800,7 +800,7 @@ export class DeleteFeatureStep extends EditingTask { const session = context.session; const feature = inputs.features[0]; - //get all relations of current editing layer that are in editing + //get all relations of the current editing layer that are in editing const relations = EditingService._filterRelationsInEditing({ layerId, relations: originaLayer.getRelations() ? @@ -1011,7 +1011,7 @@ export class GetVertexStep extends EditingTask { this._stopPromise = $.Deferred(); /** @since g3w-client-plugin-editing@v3.8.0 */ - setAndUnsetSelectedFeaturesStyle({ promise: this._stopPromise, inputs }); + setAndUnsetSelectedFeaturesStyle({ promise: this._stopPromise, inputs, style: this.selectStyle }); this._snapIteraction = new ol.interaction.Snap({ features: new ol.Collection(features), @@ -1062,36 +1062,44 @@ export class LinkRelationStep extends EditingTask { return new EditingStep(options); } + /** + * + * + * @param inputs + * @param context + * @return { JQuery Promise } + */ run(inputs, context) { GUI.setModal(false); const d = $.Deferred(); const editingLayer = inputs.layer.getEditingLayer(); this._originalLayerStyle = editingLayer.getStyle(); - const beforeRun = context.beforeRun; - const promise = beforeRun && typeof beforeRun === 'function' ? beforeRun() : Promise.resolve(); + const promise = context.beforeRun && typeof context.beforeRun === 'function' ? context.beforeRun() : Promise.resolve(); const { excludeFeatures } = context; - const style = context.style; - - this._features = editingLayer.getSource().getFeatures(); - - if (excludeFeatures) { - this._features = this._features - .filter(feature => Object - .entries(excludeFeatures) - .reduce((bool, [field, value]) => bool && feature.get(field) != value, true) - ) - } - - if (style) { - this._features.forEach(feature => feature.setStyle(style)); - } promise .then(() => { + let features = editingLayer.getSource().getFeatures(); + + if (excludeFeatures) { + features = features + .filter(feature => Object + .entries(excludeFeatures) + .reduce((bool, [field, value]) => bool && feature.get(field) != value, true) + ) + } + this._stopPromise = $.Deferred(); + + setAndUnsetSelectedFeaturesStyle({ + promise: this._stopPromise.promise(), + inputs: { layer: inputs.layer, features }, + style: this.selectStyle + }); + this.pickFeatureInteraction = new PickFeatureInteraction({ layers: [editingLayer], - features: this._features + features }); this.addInteraction(this.pickFeatureInteraction); this.pickFeatureInteraction.on('picked', evt => { @@ -1101,17 +1109,20 @@ export class LinkRelationStep extends EditingTask { d.resolve(inputs); }); }) - .catch(err => d.reject(err)); - return d.promise() + .catch(e => { + console.warn(e); + d.reject(e); + }); + + return d.promise(); } stop() { GUI.setModal(true); this.removeInteraction(this.pickFeatureInteraction); - this._features.forEach(feature => feature.setStyle(this._originalLayerStyle)); this.pickFeatureInteraction = null; - this._features = null; - this._originalLayerStyle = null; + this._originalLayerStyle = null; + if (this._stopPromise) { this._stopPromise.resolve(true)} return true; } } @@ -1235,7 +1246,6 @@ export class ModifyGeometryVertexStep extends EditingTask { const layerId = originalLayer.getId(); const feature = this._feature = inputs.features[0]; this._originalStyle = editingLayer.getStyle(); - this.deleteVertexKey; const style = function() { const image = new ol.style.Circle({ @@ -1247,8 +1257,14 @@ export class ModifyGeometryVertexStep extends EditingTask { new ol.style.Style({ image, geometry(feature) { - const coordinates = feature.getGeometry().getCoordinates()[0]; - return new ol.geom.MultiPoint(coordinates); + return new ol + .geom + .MultiPoint( + ( // in the case of multipolygon geometry + Geometry.isPolygonGeometryType(originalLayer.getGeometryType()) && + Geometry.isMultiGeometry(originalLayer.getGeometryType()) + ) ? feature.getGeometry().getCoordinates()[0][0] : feature.getGeometry().getCoordinates()[0] + ) } }), new ol.style.Style({ @@ -1288,10 +1304,11 @@ export class ModifyGeometryVertexStep extends EditingTask { }); /** * - * end of evaluate + * end of evaluating */ } }); + return d.promise(); } @@ -1389,7 +1406,7 @@ export class MoveElementsStep extends EditingTask { const session = context.session; /** @since g3w-client-plugin-editing@v3.8.0 */ - setAndUnsetSelectedFeaturesStyle({ promise: d, inputs }); + setAndUnsetSelectedFeaturesStyle({ promise: d, inputs, style: this.selectStyle }); this._snapIteraction = new ol.interaction.Snap({source, edge: false}); @@ -1485,9 +1502,9 @@ export class MoveFeatureStep extends EditingTask { } run(inputs, context) { - /** Need two different promise: One for stop() method and clean selected feature, - * and other one for run task. If we use the same promise, when stop task without move feature, - * this.promise.resolve(), it fires also thenable method listen resolve promise of run task, + /** Need two different promises: One for stop() method and clean-selected feature, + * and another one for a run task. If we use the same promise, when stop a task without move feature, + * this.promise.resolve(), it fires also thenable method listens to resolve promise of a run task, * that call stop task method.*/ this.promise = $.Deferred(); const d = $.Deferred(); @@ -1498,8 +1515,7 @@ export class MoveFeatureStep extends EditingTask { let originalFeature = null; this.changeKey = null; // let isGeometryChange = false; // changed if geometry is changed - - setAndUnsetSelectedFeaturesStyle({ promise: this.promise, inputs }); + setAndUnsetSelectedFeaturesStyle({ promise: this.promise, inputs, style: this.selectStyle }); this._translateInteraction = new ol.interaction.Translate({ features, @@ -1588,11 +1604,6 @@ export class OpenFormStep extends EditingTask { */ this._edit_relations = undefined === options.edit_relations || options._edit_relations; - /** - * @FIXME add description - */ - this._formIdPrefix = 'form_'; - /** * @FIXME set a default value + add description */ @@ -1633,22 +1644,11 @@ export class OpenFormStep extends EditingTask { */ this._fields; - /** - * @FIXME set a default value + add description - */ - this._session; - - /** - * @FIXME set a default value + add description - */ - this._editorFormStructure; - /** * @FIXME set a default value + add description */ this.promise; - /** * @since g3w-client-plugin-editing@v3.7.0 */ @@ -1666,28 +1666,104 @@ export class OpenFormStep extends EditingTask { this._multi = bool; } + /** + * @param fields + * @param fields Array of fields + * + * @returns {Promise} + */ + async saveAll(fields) { + const { session } = this.getContext(); + fields = this._multi ? fields.filter(field => null !== field.value) : fields; + + // skip when .. + if (!fields.length) { + return; + } + + await WorkflowsStack.getCurrent().getContextService().saveDefaultExpressionFieldsNotDependencies(); + + const newFeatures = []; + + this._features.forEach(f => { + this._originalLayer.setFieldsWithValues(f, fields); + newFeatures.push(f.clone()); + }); + + if (this._isContentChild) { + this.getInputs().relationFeatures = { newFeatures, originalFeatures: this._originalFeatures }; + } + + await this.fireEvent('saveform', { newFeatures, originalFeatures: this._originalFeatures }); + + newFeatures.forEach((f, i) => session.pushUpdate(this.layerId, f, this._originalFeatures[i])); + + //check and handle if layer has relation 1:1 + await handleRelation1_1LayerFields({ + layerId: this.layerId, + features: newFeatures, + fields, + task: this, + }) + + this.fireEvent('savedfeature', newFeatures); // called after saved + this.fireEvent(`savedfeature_${this.layerId}`, newFeatures); // called after saved using layerId + + session.save(); + + return { promise: this.promise }; + } + /** * @param inputs * @param context * - * @returns {Promise<*>} - * - * @private + * @returns {*} + */ + run(inputs, context) { + const d = $.Deferred(); + const { + layer, + features + } = inputs; + + this.promise = d; + this._isContentChild = WorkflowsStack.getLength() > 1; + this.layerId = layer.getId(); + + GUI.setLoadingContent(false); + + this.getEditingService().disableMapControlsConflict(true); + setAndUnsetSelectedFeaturesStyle({ promise: d, inputs, style: this.selectStyle }); + + if (!this._multi && Array.isArray(features[features.length -1])) { + d.resolve(); + } else { + this.startForm({ inputs, context, promise: d }); + } + + return d.promise(); + } + + /** + * Build form */ - async _getForm(inputs, context) { - this._session = context.session; + async startForm({ inputs, context, promise }) { + const EditingService = this.getEditingService(); + + EditingService.setCurrentLayout(); + this._originalLayer = inputs.layer; this._editingLayer = this._originalLayer.getEditingLayer(); this._layerName = this._originalLayer.getName(); this._features = this._multi ? inputs.features : [inputs.features[inputs.features.length - 1]]; - this._originalFeatures = this._features.map(feature => feature.clone()); - const feature = this._features[0]; - /** - * In case of create a child relation feature set a father relation field value - */ + this._originalFeatures = this._features.map(f => f.clone()); + let feature = this._features[0]; + + // create a child relation feature set a father relation field value if (this._isContentChild) { //Are array - const {fatherValue=[], fatherField=[]} = context; + const { fatherValue=[], fatherField=[] } = context; fatherField.forEach((fField, index) => { feature.set(fField, fatherValue[index]); this._originalFeatures[0].set(fField, fatherValue[index]); @@ -1695,204 +1771,199 @@ export class OpenFormStep extends EditingTask { } this._fields = await getFormFields({ inputs, context, feature, isChild: this._isContentChild }); - // in case of multi editing set all field to null // - this._fields = this._multi - ? this._fields - .map(field => { - const _field = JSON.parse(JSON.stringify(field)); - _field.value = null; - _field.forceNull = true; - _field.validate.required = false; - return _field; - }) - .filter(field => !field.pk) - : this._fields; - if (this._originalLayer.hasFormStructure()) { - const editorFormStructure = this._originalLayer.getEditorFormStructure(); - this._editorFormStructure = editorFormStructure.length ? editorFormStructure : null; + // in the case of multi editing set all fields to null + if (this._multi) { + this._fields = this._fields + .map(field => { + const f = JSON.parse(JSON.stringify(field)); + f.value = null; + f.forceNull = true; + f.validate.required = false; + return f; + }) + .filter(field => !field.pk) } - return GUI.showContentFactory('form'); - } - - /** - * @param fields - * @param fields Array of fields - * - * @returns {Promise} - */ - saveAll(fields) { - return new Promise(async (resolve, reject) => { - const { session } = this.getContext(); - const inputs = this.getInputs(); - fields = this._multi ? fields.filter(field => null !== field.value) : fields; - if (fields.length) { - await WorkflowsStack.getCurrent().getContextService().saveDefaultExpressionFieldsNotDependencies(); - const newFeatures = []; - this._features - .forEach(feature => { - this._originalLayer.setFieldsWithValues(feature, fields); - newFeatures.push(feature.clone()); - }); + // set fields. Useful getParentFormData + WorkflowsStack + .getCurrent() + .setInput({ key: 'fields', value: this._fields }); + + const formService = GUI.showForm({ + feature: this._originalFeatures[0], + formComponent: /*opts.formComponent ||*/ EditingFormComponent, + title: "plugins.editing.editing_attributes", + name: this._layerName, + crumb: { title: this._layerName }, + id: 'form_' + this._layerName, + dataid: this._layerName, + layer: this._originalLayer, + isnew: this._originalFeatures.length > 1 ? false : this._originalFeatures[0].isNew(), // specify if is a new feature + parentData: getParentFormData(), + fields: this._fields, + context_inputs: !this._multi && this._edit_relations && { context, inputs }, + formStructure: this._originalLayer.hasFormStructure() && this._originalLayer.getEditorFormStructure().length ? this._originalLayer.getEditorFormStructure() : undefined, + modal: true, + push: this.push || this._isContentChild, //@since v3.7 need to take in account this.push value + showgoback: undefined !== this.showgoback ? this.showgoback : !this._isContentChild, + /** @TODO make it straightforward: `headerComponent` vs `buttons` ? */ + headerComponent: this._saveAll && { + template: /* html */ ` +
+ +
+ + + +
+
`, + + name: 'Saveall', + + /** @TODO figure out who populate these props (ie. core client code?) */ + props: { + update: { type: Boolean }, + valid: { type: Boolean }, + }, - if (this._isContentChild) { - inputs.relationFeatures = { newFeatures, originalFeatures: this._originalFeatures }; - } + data() { + return { + loading: false, + enabled: true, + }; + }, - this.fireEvent('saveform', { - newFeatures, - originalFeatures: this._originalFeatures - }).then(() => { - newFeatures - .forEach((newFeature, index) => { - session.pushUpdate(this.layerId, newFeature, this._originalFeatures[index]); - }); + computed: { + /** + * Disable save all buttons when it is not enabled (a case of parent form is not valid, + * or when current form is not valid or valid but not updated) + * @return {boolean} + */ + disabled() { + return !this.enabled || !(this.valid && this.update); + }, - //check and handle if layer has relation 1:1 - handleRelation1_1LayerFields({ - layerId: this.layerId, - features: newFeatures, - fields, - task: this, - }).then(() => { - this.fireEvent('savedfeature', newFeatures); // called after saved - this.fireEvent(`savedfeature_${this.layerId}`, newFeatures); // called after saved using layerId - session.save(); - resolve({ promise: this.promise }); - }) - }) - } - }) - } + }, - /** - * @param fields - * @param promise - * @param session - * @param inputs - * - * @returns {Promise} - * - * @private - */ - async _saveFeatures({fields, promise, session, inputs}) { - fields = this._multi ? fields.filter(field => field.value !== null) : fields; - if (fields.length) { - const newFeatures = []; + methods: { - // @since 3.5.15 - GUI.setLoadingContent(true); - GUI.disableContent(true); + async save() { + this.loading = true; - await WorkflowsStack.getCurrent().getContextService().saveDefaultExpressionFieldsNotDependencies(); + await Promise.allSettled( + [...WorkflowsStack._workflows] + .reverse() + .filter( w => "function" === typeof w.getLastStep().getTask().saveAll) // need to filter only workflow that + .map(w => w.getLastStep().getTask().saveAll(w.getContext().service.state.fields)) + ) - GUI.setLoadingContent(false); - GUI.disableContent(false); + EditingService + .commit({ modal: false }) + .then(() => { WorkflowsStack._workflows.forEach(w => w.getContext().service.setUpdate(false, { force: false })); }) + .fail((e) => console.warn(e)) + .always(() => { this.loading = false }); + }, - this._features.forEach(feature => { - this._originalLayer.setFieldsWithValues(feature, fields); - newFeatures.push(feature.clone()); - }); - if (this._isContentChild) { - inputs.relationFeatures = { - newFeatures, - originalFeatures: this._originalFeatures - }; - } - this.fireEvent('saveform', { - newFeatures, - originalFeatures: this._originalFeatures - }).then(() => { - newFeatures - .forEach((newFeature, index) => session.pushUpdate(this.layerId, newFeature, this._originalFeatures[index])); - - //check and handle if layer has relation 1:1 - //async - handleRelation1_1LayerFields({ - layerId: this.layerId, - features: newFeatures, - fields, - task: this, - }).then(() => { - GUI.setModal(false); - this.fireEvent('savedfeature', newFeatures); // called after saved - this.fireEvent(`savedfeature_${this.layerId}`, newFeatures); // called after saved using layerId - // In case of save of child it means that child is updated so also parent - if (this._isContentChild) { - WorkflowsStack - .getParents() - .forEach(workflow => workflow.getContextService().setUpdate(true, {force: true})); - } - promise.resolve(inputs); - }) - }) - } else { + }, - GUI.setModal(false); + created() { + // set enabled to true as default value; + // this.enabled = true; - promise.resolve(inputs); - } - } + // skip when workflow tasks are less than 2 + if (WorkflowsStack.getLength() < 2) { + return; + } - /** - * Build form - * - * @param options - * - * @returns {Promise} - */ - async startForm(options = {}) { - this.getEditingService().setCurrentLayout(); - const { - inputs, - context, - promise - } = options; - const { session } = context; - const formComponent = options.formComponent || EditingFormComponent; - const Form = await this._getForm(inputs, context); - const feature = this._originalFeatures[0]; + this.enabled = WorkflowsStack + ._workflows + .slice(0, WorkflowsStack.getLength() - 1) + .reduce((bool, w) => { + const { service } = w.getContext(); + const { valid = true } = (service instanceof FormService) ? service.getState() : {}; + return bool && valid; + }, true); + }, - /** - * set fields. Useful getParentFormData - */ - WorkflowsStack - .getCurrent() - .setInput({key: 'fields', value: this._fields}); - - const formService = Form({ - formComponent, - title: "plugins.editing.editing_attributes", - name: this._layerName, - crumb: {title: this._layerName}, - id: this._generateFormId(this._layerName), - dataid: this._layerName, - layer: this._originalLayer, - isnew: this._originalFeatures.length > 1 ? false : feature.isNew(), // specify if is a new feature - feature, - parentData: getParentFormData(), - fields: this._fields, - context_inputs: !this._multi && this._edit_relations && {context, inputs}, - formStructure: this._editorFormStructure, - modal: true, - push: this.push || this._isContentChild, //@since v3.7 need to take in account this.push value - showgoback: undefined !== this.showgoback ? this.showgoback : !this._isContentChild, - headerComponent: this._saveAll && SaveAll, - buttons: [ + }, + buttons: [ { id: 'save', - title: this._isContentChild ? - ( - //check if parent has custom back label set - WorkflowsStack.getParent().getBackButtonLabel() || - "plugins.editing.form.buttons.save_and_back" - ) : - "plugins.editing.form.buttons.save", + title: this._isContentChild + ? WorkflowsStack.getParent().getBackButtonLabel() || "plugins.editing.form.buttons.save_and_back" // get custom back label from parent + : "plugins.editing.form.buttons.save", type: "save", class: "btn-success", - cbk: (fields) => { - this._saveFeatures({ fields, promise, inputs, session: context.session }); + // save features + cbk: async (fields) => { + fields = this._multi ? fields.filter(field => null !== field.value) : fields; + + // skip when .. + if (!fields.length) { + GUI.setModal(false); + promise.resolve(inputs); + return; + } + + const newFeatures = []; + + // @since 3.5.15 + GUI.setLoadingContent(true); + GUI.disableContent(true); + + await WorkflowsStack.getCurrent().getContextService().saveDefaultExpressionFieldsNotDependencies(); + + GUI.setLoadingContent(false); + GUI.disableContent(false); + + this._features.forEach(feature => { + this._originalLayer.setFieldsWithValues(feature, fields); + newFeatures.push(feature.clone()); + }); + + if (this._isContentChild) { + inputs.relationFeatures = { + newFeatures, + originalFeatures: this._originalFeatures + }; + } + + this + .fireEvent('saveform', { newFeatures, originalFeatures: this._originalFeatures}) + .then(async () => { + newFeatures.forEach((f, i) => context.session.pushUpdate(this.layerId, f, this._originalFeatures[i])); + + // check and handle if layer has relation 1:1 + await handleRelation1_1LayerFields({ + layerId: this.layerId, + features: newFeatures, + fields, + task: this, + }); + + GUI.setModal(false); + this.fireEvent('savedfeature', newFeatures); // called after saved + this.fireEvent(`savedfeature_${this.layerId}`, newFeatures); // called after saved using layerId + // In case of save of child it means that child is updated so also parent + if (this._isContentChild) { + WorkflowsStack + .getParents() + .forEach(workflow => workflow.getContextService().setUpdate(true, {force: true})); + } + promise.resolve(inputs); + }); } }, { @@ -1921,75 +1992,29 @@ export class OpenFormStep extends EditingTask { } ] }); - //fire openform event + + // fire openform event this.fireEvent('openform', { - layerId:this.layerId, - session, + layerId: this.layerId, + session: context.session, feature: this._originalFeature, formService } ); - const currentWorkflow = WorkflowsStack.getCurrent(); - // in case of called single task no workflow is set - if (currentWorkflow) { - //set context service to form Service - currentWorkflow.setContextService(formService); + // set context service to form Service in case of single task (ie. no workflow) + if (WorkflowsStack.getCurrent()) { + WorkflowsStack.getCurrent().setContextService(formService); } //listen eventually field relation 1:1 changes value this._unwatchs = await listenRelation1_1FieldChange({ layerId: this.layerId, fields: this._fields, - }) - } - - /** - * @param inputs - * @param context - * - * @returns {*} - */ - run(inputs, context) { - const d = $.Deferred(); - const { - layer, - features - } = inputs; - - this.promise = d; - this._isContentChild = WorkflowsStack.getLength() > 1; - this.layerId = layer.getId(); - - GUI.setLoadingContent(false); - - this.getEditingService().disableMapControlsConflict(true); - - setAndUnsetSelectedFeaturesStyle({ promise: d, inputs }); - - if (!this._multi && Array.isArray(features[features.length -1])) { - d.resolve(); - } else { - this.startForm({ - inputs, - context, - promise: d - }); - this.disableSidebar(true); - } - - return d.promise(); - } + }); - /** - * @param layerName - * @returns {string} - * - * @private - */ - _generateFormId(layerName) { - return this._formIdPrefix + layerName; + this.disableSidebar(true); } /** @@ -1998,24 +2023,24 @@ export class OpenFormStep extends EditingTask { stop() { this.disableSidebar(false); - const service = this.getEditingService(); - let contextService; - - // when the last feature of features is Array - // and is resolved without setting form service - // Ex. copy multiple feature from other layer - if ( - false === this._isContentChild || // no child workflow + const service = this.getEditingService(); + //Check if form coming from the parent table component + const is_parent_table = false === this._isContentChild || // no child workflow ( - //case edit feature of a table (edit layer alphanumeric) + // case edit feature of a table (edit layer alphanumeric) 2 === WorkflowsStack.getLength() && //open features table WorkflowsStack.getParent().isType('edittable') - ) - ) { + ); + + // when the last feature of features is Array + // and is resolved without setting form service + // Ex. copy multiple features from another layer + if (is_parent_table) { service.disableMapControlsConflict(false); - contextService = WorkflowsStack.getCurrent().getContextService(); } + const contextService = is_parent_table && WorkflowsStack.getCurrent().getContextService(); + // force update parent form update if (contextService && false === this._isContentChild) { contextService.setUpdate(false, { force: false }); @@ -2026,13 +2051,11 @@ export class OpenFormStep extends EditingTask { service.resetCurrentLayout(); this.fireEvent('closeform'); - this.fireEvent(`closeform_${this.layerId}`); // need to check layerId + this.fireEvent(`closeform_${this.layerId}`); this.layerId = null; this.promise = null; - // class unwatch this._unwatchs.forEach(unwatch => unwatch()); - //reset to Empty Array this._unwatchs = []; } @@ -2056,6 +2079,7 @@ const TableComponent = function(options={}) { return g3wsdk.core.utils.base(this, 'unmount'); }; }; + g3wsdk.core.utils.inherit(TableComponent, Component); /** @@ -2069,8 +2093,6 @@ export class OpenTableStep extends EditingTask { super(options); - this._formIdPrefix = 'form_'; - options.task = this; return new EditingStep(options); } @@ -2090,16 +2112,14 @@ export class OpenTableStep extends EditingTask { const layerName = originalLayer.getName(); const headers = originalLayer.getEditingFields(); this._isContentChild = WorkflowsStack.getLength() > 1; - const foreignKey = this._isContentChild && context.excludeFields ? context.excludeFields[0] : null; - const exclude = this._isContentChild && context.exclude; + const excludeFields = this._isContentChild ? (context.excludeFields || []) : []; const capabilities = originalLayer.getEditingCapabilities(); const editingLayer = originalLayer.getEditingLayer(); //get editing features let features = editingLayer.readEditingFeatures(); - - if (exclude && features.length > 0) { - const {value} = exclude; - features = features.filter(feature => feature.get(foreignKey) != value); + if (excludeFields.length > 0 && features.length > 0) { + //filter features already bind to parent feature + features = features.filter(feat => !excludeFields.reduce((a, f, i) => a && context.fatherValue[i] === `${feat.get(f)}` , true)); } const content = new TableComponent({ @@ -2112,7 +2132,6 @@ export class OpenTableStep extends EditingTask { inputs, capabilities, fatherValue: context.fatherValue, - foreignKey }); GUI.disableSideBar(true); @@ -2125,9 +2144,7 @@ export class OpenTableStep extends EditingTask { }); setTimeout(() => { - content.once('ready', () => setTimeout(()=> { - GUI.closeUserMessage(); - })); + content.once('ready', () => setTimeout(() => { GUI.closeUserMessage() })); GUI.showContent({ content, @@ -2202,7 +2219,7 @@ export class PickFeatureStep extends EditingTask { inputs.features = features; inputs.coordinate = coordinate; } - setAndUnsetSelectedFeaturesStyle({ promise: d, inputs }); + setAndUnsetSelectedFeaturesStyle({ promise: d, inputs, style: this.selectStyle }); if (this._steps) { this.setUserMessageStepDone('select'); @@ -2699,7 +2716,7 @@ export class SplitFeatureStep extends EditingTask { /** @since g3w-client-plugin-editing@v3.8.0 */ this._stopPromise = $.Deferred(); - setAndUnsetSelectedFeaturesStyle({ promise: this._stopPromise, inputs }); + setAndUnsetSelectedFeaturesStyle({ promise: this._stopPromise, inputs, style: this.selectStyle }); this._drawInteraction = new ol.interaction.Draw({