Skip to content
Open
3 changes: 2 additions & 1 deletion app-starter/WguAppTemplate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ export default {
const moduleOpts = appConfig.modules[key];
if (moduleOpts.win === target) {
moduleWins.push({
type: key + '-win',
type: (moduleOpts.moduleType ?? key) + '-win',
moduleName: key,
...moduleOpts
});
}
Expand Down
4 changes: 3 additions & 1 deletion app-starter/components/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ export default {
const moduleOpts = appConfig.modules[key];
if (moduleOpts.target === target) {
buttons.push({
type: moduleOpts.win ? 'wgu-toggle-btn' : key + '-btn',
type: moduleOpts.win
? 'wgu-toggle-btn'
: (moduleOpts.moduleType ?? key) + '-btn',
moduleName: key,
...moduleOpts
});
Expand Down
1 change: 0 additions & 1 deletion app-starter/components/SampleModule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
-->
<wgu-module-card
v-bind="$attrs"
moduleName="sample-module"
class="sample-module"
:icon="icon"
>
Expand Down
58 changes: 52 additions & 6 deletions docs/module-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,68 @@ JSON configuration objects for Wegue modules.

## General

The `modules` object contains sub-objects, whereas the key is the identifier for the module and the value is the dedicated module configuration. For example:
The modules object contains sub-objects, where the key is the identifier for the module instance and the value is the dedicated module configuration. For example:

```json
"wgu-layerlist": {
"target": "menu",
"win": "floating",
"draggable": false
}
"wgu-layerlist": {
"target": "menu",
"win": "floating",
"draggable": false
}
```

### Module identifier and module type
In general, the identifier specifies the type of module to be added.
In the example above, Wegue will look for a Vue component matching the module type `wgu-layerlist` and automatically append the `-win` suffix when resolving the component name. Internally, this results in the component `wgu-layerlist-win`, which must be registered in the application, for example:

```js
import LayerListWin from '@/components/layerlist/LayerListWin.vue';

components: {
'wgu-layerlist-win': LayerListWin
}
```
This approach allows simple module definitions where the identifier directly maps to a single module instance.

### Multiple instances of the same module type
To allow multiple instances of the same module type, the module identifier can be chosen arbitrarily. In this case, the actual module type must be specified explicitly using the `moduleType` property.
Example:
```json
"wgu-layerlist1": {
"moduleType": "wgu-layerlist",
"target": "menu",
"win": "floating",
"draggable": false
},
"wgu-layerlist2": {
"moduleType": "wgu-layerlist",
"target": "menu",
"win": "floating",
"draggable": false
}
```
In this configuration:
* `wgu-layerlist1` and `wgu-layerlist2` are instance identifiers.
* Both instances reference the same module type via `"moduleType": "wgu-layerlist"`
* Wegue will create two independent instances of the `wgu-layerlist` module.
* Each instance has its own configuration, state, and window (if applicable)
If `moduleType` is omitted, Wegue assumes that the module identifier itself represents the module type.


#### Note / Limitation:
While Wegue supports configuring multiple instances of the same module type, stock Wegue components are not guaranteed to be designed for multi-instantiation. Some built-in modules may rely on shared state, global identifiers, or singleton assumptions and may behave incorrectly or unpredictably when instantiated multiple times.

Multi-instantiation is therefore primarily intended for custom modules or stock modules that are explicitly designed and validated for this usage. When using multiple instances of a stock module, this behavior should be considered experimental unless otherwise documented.

### Common module properties

The following properties can be applied to all module types:

| Property | Meaning | Example |
|--------------------|:---------:|---------|
| **target** | Where should the button to enable/disable the module be rendered. Valid options are `menu` or `toolbar` | `"target": "menu"` |
| **win** | Value to mark if the module has a window as sub component and where to show the module UI elements. Valid options are `floating` and `sidebar`. If the value is omitted, then the module is not associated with a window. | `"win": "floating"` |
| moduleType | Optional module type. Required only if different from the module identifier (e.g. for multi-instantiation). | `"moduleType": "wgu-layerlist"`
| icon | Provide a customized icon for the module. | `"icon": "md:info"` |
| minimizable | Indicates whether the module window can be minimized. Only applies if a module window is present as indicated by the `win` parameter. | `"minimizable": true` |
| closable | Indicates whether the module window can be closed by a "X" icon in the window's header bar. Only applies if a module window is present as indicated by the `win` parameter. | `"closable": false` |
Expand Down
2 changes: 0 additions & 2 deletions src/components/attributeTable/AttributeTableWin.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<template>
<wgu-module-card v-bind="$attrs"
:moduleName="moduleName"
class="wgu-attributetable-win"
:icon="icon"
>
Expand Down Expand Up @@ -56,7 +55,6 @@ export default {
},
data () {
return {
moduleName: 'wgu-attributetable',
selLayerLid: null
}
},
Expand Down
6 changes: 0 additions & 6 deletions src/components/helpwin/HelpWin.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<template>
<wgu-module-card v-bind="$attrs"
:moduleName="moduleName"
class="wgu-helpwin"
:icon="icon"
:width="width">
Expand Down Expand Up @@ -36,11 +35,6 @@ export default {
props: {
icon: { type: String, required: false, default: 'md:help' },
width: { type: Number, required: false, default: 300 }
},
data () {
return {
moduleName: 'wgu-helpwin'
}
}
};
</script>
Expand Down
2 changes: 0 additions & 2 deletions src/components/infoclick/InfoClickWin.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<template>
<wgu-module-card v-bind="$attrs"
:moduleName="moduleName"
class="wgu-infoclick-win"
:icon="icon"
v-on:visibility-change="show">
Expand Down Expand Up @@ -105,7 +104,6 @@ export default {
},
data: function () {
return {
moduleName: 'wgu-infoclick',
attributeData: null,
coordsData: null,
featureIdx: 0,
Expand Down
1 change: 0 additions & 1 deletion src/components/layerlist/LayerListWin.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<template>
<wgu-module-card v-bind="$attrs"
moduleName="wgu-layerlist"
class="wgu-layerlist"
:icon="icon"
>
Expand Down
2 changes: 0 additions & 2 deletions src/components/maprecorder/MapRecorderWin.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<template>
<wgu-module-card v-bind="$attrs"
:moduleName="moduleName"
class="wgu-maprecorder-win"
:icon="icon"
width=350>
Expand Down Expand Up @@ -156,7 +155,6 @@ export default {
const mimeTypes = this.getSupportedMimeTypes();
return {
moduleName: 'wgu-maprecorder',
/**
* Custom canvas element for drawing the OpenLayers map.
*/
Expand Down
2 changes: 0 additions & 2 deletions src/components/measuretool/MeasureWin.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<template>
<wgu-module-card v-bind="$attrs"
:moduleName="moduleName"
class="wgu-measurewin"
:icon="icon"
v-on:visibility-change="show">
Expand Down Expand Up @@ -48,7 +47,6 @@ export default {
},
data () {
return {
moduleName: 'wgu-measuretool',
measureGeom: null,
measureType: 'distance'
}
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/specs/WguAppTemplate.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,37 @@ describe('WguAppTpl.vue', () => {
expect(moduleData[0].target).to.equal('menu');
});

it('getModuleWinData supports multiple module instances', () => {
// mock a module conf
const appConfig = {
modules: {
'wgu-helpwin1': {
moduleType: 'wgu-helpwin',
icon: 'md:help',
target: 'toolbar',
win: 'floating'
},
'wgu-helpwin2': {
moduleType: 'wgu-helpwin',
icon: 'md:help',
target: 'menu',
win: 'floating'
}
}
};
comp = createWrapper(appConfig);
vm = comp.vm;

const moduleData = vm.getModuleWinData('floating');

expect(moduleData).to.be.an('array');
expect(moduleData.length).to.equal(2);
expect(moduleData[0].type).to.equal('wgu-helpwin-win');
expect(moduleData[0].target).to.equal('toolbar');
expect(moduleData[1].type).to.equal('wgu-helpwin-win');
expect(moduleData[1].target).to.equal('menu');
})

it('has a method setGlobalAppLang', () => {
comp = createWrapper();
vm = comp.vm;
Expand Down
68 changes: 45 additions & 23 deletions tests/unit/specs/components/AppHeader.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,46 +37,68 @@ describe('AppHeader.vue', () => {
});

describe('methods', () => {
const appConfig = {
modules: {
'wgu-infoclick': {
target: 'menu'
},
'wgu-zoomtomaxextent': {
target: 'toolbar'
it('getModuleButtons(\'menu\') returns correct data', () => {
// mock a module conf
const appConfig = {
modules: {
'wgu-infoclick': {
target: 'menu'
}
}
}
};

beforeEach(() => {
};
comp = createWrapper(appConfig);
vm = comp.vm;
});

it('getModuleButtons(\'menu\') returns always an array', () => {
const moduleData = vm.getModuleButtons('menu');
expect(moduleData).to.be.an('array');
});

it('getModuleButtons(\'menu\') returns correct data', () => {
const moduleData = vm.getModuleButtons('menu');
expect(moduleData).to.be.an('array').that.has.lengthOf(1);
expect(moduleData[0].type).to.equal('wgu-infoclick-btn');
expect(moduleData[0].target).to.equal('menu');
});

it('getModuleButtons(\'toolbar\') returns always an array', () => {
const moduleData = vm.getModuleButtons('toolbar');
expect(moduleData).to.be.an('array');
});

it('getModuleButtons(\'toolbar\') returns correct data', () => {
// mock a module conf
const appConfig = {
modules: {
'wgu-zoomtomaxextent': {
target: 'toolbar'
}
}
};
comp = createWrapper(appConfig);
vm = comp.vm;

const moduleData = vm.getModuleButtons('toolbar');
expect(moduleData).to.be.an('array').that.has.lengthOf(1);
expect(moduleData[0].type).to.equal('wgu-zoomtomaxextent-btn');
expect(moduleData[0].target).to.equal('toolbar');
});

it('getModuleButtons supports multiple button instances', () => {
// mock a module conf
const appConfig = {
modules: {
'wgu-helpwin1': {
moduleType: 'wgu-helpwin',
target: 'toolbar'
},
'wgu-helpwin2': {
moduleType: 'wgu-helpwin',
target: 'toolbar'
}
}
};
comp = createWrapper(appConfig);
vm = comp.vm;

const moduleData = vm.getModuleButtons('toolbar');
expect(moduleData).to.be.an('array');
expect(moduleData.length).to.equal(2);
expect(moduleData[0].type).to.equal('wgu-helpwin-btn');
expect(moduleData[0].target).to.equal('toolbar');
expect(moduleData[1].type).to.equal('wgu-helpwin-btn');
expect(moduleData[1].target).to.equal('toolbar');
});

afterEach(() => {
comp.unmount();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { shallowMount } from '@vue/test-utils';
import AttributeTableWin from '@/components/attributeTable/AttributeTableWin.vue';

function createWrapper () {
return shallowMount(AttributeTableWin);
const moduleProps = {
moduleName: 'wgu-attributetable'
};

function createWrapper (props = moduleProps) {
return shallowMount(AttributeTableWin, {
props
});
}

describe('attributeTable/AttributeTableWin.vue', () => {
Expand Down Expand Up @@ -40,7 +46,6 @@ describe('attributeTable/AttributeTableWin.vue', () => {
});

it('has correct default data', () => {
expect(vm.moduleName).to.equal('wgu-attributetable');
expect(vm.selLayerLid).to.be.null;
});

Expand Down
13 changes: 7 additions & 6 deletions tests/unit/specs/components/helpwin/HelpWin.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import { shallowMount } from '@vue/test-utils';
import HelpWin from '@/components/helpwin/HelpWin.vue';

const moduleProps = {
moduleName: 'wgu-helpwin'
};

const confModuleProps = {
moduleName: 'wgu-helpwin',
icon: 'my-icon'
};

function createWrapper (props = {}, $appConfig = { modules: {} }) {
function createWrapper (props = moduleProps, $appConfig = { modules: {} }) {
return shallowMount(HelpWin, {
props,
global: {
Expand Down Expand Up @@ -43,7 +48,7 @@ describe('helpwin/HelpWin.vue', () => {

describe('configured', () => {
beforeEach(() => {
comp = createWrapper(moduleProps);
comp = createWrapper(confModuleProps);
vm = comp.vm;
});

Expand All @@ -62,10 +67,6 @@ describe('helpwin/HelpWin.vue', () => {
vm = comp.vm;
});

it('has correct default data', () => {
expect(vm.moduleName).to.equal('wgu-helpwin');
});

afterEach(() => {
comp.unmount();
});
Expand Down
Loading