• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

compassinformatics / cpsi-mapview / 15022980938

14 May 2025 02:11PM UTC coverage: 26.333% (+0.04%) from 26.29%
15022980938

push

github

geographika
Move describe to test globals

492 of 2344 branches covered (20.99%)

Branch coverage included in aggregate %.

1464 of 5084 relevant lines covered (28.8%)

1.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.56
/app/controller/LayerTreeController.js
1
/**
2
 * The controller for the {@link CpsiMapview.view.LayerTree}
3
 *
4
 * @class CpsiMapview.controller.LayerTreeController
5
 */
6
Ext.define('CpsiMapview.controller.LayerTreeController', {
1✔
7
    extend: 'Ext.app.ViewController',
8

9
    alias: 'controller.cmv_layertree',
10

11
    requires: [
12
        'BasiGX.util.Map',
13
        'BasiGX.util.Layer',
14
        'CpsiMapview.data.model.LayerTreeNode',
15
        'CpsiMapview.view.main.Map',
16
        'CpsiMapview.util.Layer'
17
    ],
18

19
    statics: {
20
        /**
21
         * Detects the original tree node config from tree.json file for a
22
         * layer identified by the given layerKey.
23
         *
24
         * @param {String} layerKey Identifier of the layer to get node conf for
25
         * @param {Object} childNodeConf Conf to start with (root if not given)
26
         */
27
        getTreeNodeConf: function (layerKey, childNodeConf) {
28
            let nodeConf = null;
×
29
            const staticMe = CpsiMapview.controller.LayerTreeController;
×
30
            if (!childNodeConf) {
×
31
                childNodeConf = CpsiMapview.treeConfig;
×
32
            }
33

34
            if (childNodeConf.id === layerKey) {
×
35
                return childNodeConf;
×
36
            } else {
37
                // exit for leafs
38
                if (childNodeConf.leaf === true) {
×
39
                    return null;
×
40
                }
41
                // iterate recursively over all child nodes
42
                for (let i = 0; i < childNodeConf.children.length; i += 1) {
×
43
                    const currentChild = childNodeConf.children[i];
×
44
                    // Search in the current child
45
                    nodeConf = staticMe.getTreeNodeConf(layerKey, currentChild);
×
46

47
                    // Return result immediately if the node conf has been found
48
                    if (nodeConf !== null) {
×
49
                        return nodeConf;
×
50
                    }
51
                }
52
            }
53

54
            return nodeConf;
×
55
        }
56
    },
57

58
    /**
59
     * Mode in order to steer how the layers in tree will be structured.
60
     * At the moment 'BASELAYER_OVERLAY' will divide the layers in 2 folders
61
     * 'Base Layers' and 'Overlays' (depending on their property 'isBaseLayer').
62
     * All other settings will result in a flat layer list.
63
     *
64
     * @cfg {String}
65
     */
66
    structureMode: null,
67

68
    /**
69
     * Shows if the event 'cmv-init-layersadded' has been fired or not.
70
     *
71
     * @property {Boolean}
72
     * @private
73
     */
74
    initLayersAdded: false,
75

76
    /**
77
     * Holds the default values for tree nodes of the config file tree.json.
78
     * Will be set in #makeLayerStore function.
79
     *
80
     * @private
81
     * @readonly
82
     */
83
    treeConfDefaults: {},
84

85
    constructor: function () {
86
        const me = this;
1✔
87

88
        const mapPanel = CpsiMapview.view.main.Map.guess();
1✔
89

90
        mapPanel.on('cmv-init-layersadded', function () {
1✔
91
            me.initLayersAdded = true;
×
92
            me.autoConnectToMap(); // connect after all the layers have been loaded to the map
×
93
        });
94

95
        Ext.GlobalEvents.on('login', function () {
1✔
96
            me.filterLayersByRole();
2✔
97
        });
98

99
        Ext.GlobalEvents.on('logout', function () {
1✔
100
            me.filterLayersByRole();
×
101
        });
102
    },
103

104
    /**
105
     * Guesses the mapcomponent and assigns the appropriate layers store, if one
106
     * could be guessed.
107
     */
108
    autoConnectToMap: function () {
109
        const me = this;
×
110

111
        const mapComp = BasiGX.util.Map.getMapComponent();
×
112
        me.map = mapComp && mapComp.getMap();
×
113

114
        if (me.map) {
×
115
            me.makeLayerStore();
×
116
        }
117
    },
118

119
    /**
120
     * This method assigns an instance of the GeoExt class
121
     * `GeoExt.data.store.LayersTree` based on the connected OL #map to the view. The layers
122
     * of the `ol.Map` are restructured and divided into groups based on the
123
     * JSON tree structure loaded in #loadTreeStructure. This assures that
124
     * the layers will appear in different folders in this TreePanel
125
     * (as defined in the tree structure JSON).
126
     */
127
    makeLayerStore: function () {
128
        const me = this;
×
129

130
        const treeJsonPromise = me.loadTreeStructure();
×
131
        treeJsonPromise.then(function (treeJson) {
×
132
            // save defaults for tree nodes from config
133
            me.treeConfDefaults = treeJson.defaults || {};
×
134

135
            // get the root layer group holding the grouped map layers
136
            const rootLayerGroup = me.getGroupedLayers(treeJson.treeConfig);
×
137

138
            me.map.set('layerTreeRoot', rootLayerGroup);
×
139
            me.map.getLayers().insertAt(0, rootLayerGroup);
×
140

141
            // create a new LayerStore from the grouped layers
142
            const groupedLayerTreeStore = Ext.create(
×
143
                'GeoExt.data.store.LayersTree',
144
                {
145
                    model: 'CpsiMapview.data.model.LayerTreeNode',
146
                    layerGroup: rootLayerGroup,
147
                    // filters are applied from bottom to top
148
                    // necessary for filtering empty layer groups
149
                    filterer: 'bottomup'
150
                }
151
            );
152
            me.getView().setStore(groupedLayerTreeStore);
×
153

154
            // update possible switchlayers when collapsing their parent folder
155
            // otherwise the node text would be wrong/empty
156
            me.getView()
×
157
                .getRootNode()
158
                .on('beforeexpand', me.updateSwitchLayerNodes, me);
159

160
            me.getView()
×
161
                .getRootNode()
162
                .cascade(function (node) {
163
                    // apply properties for tree node from corresponding tree-conf
164
                    if (node.getOlLayer()) {
×
165
                        const origTreeNodeConf =
166
                            node.getOlLayer().get('_origTreeConf') || {};
×
167
                        me.applyTreeConfigsToNode(node, origTreeNodeConf);
×
168

169
                        // We are creating the gridWindow here already, to ensure that
170
                        // any preset filter will be applied directly, without having to
171
                        // open the gridWindow first.
172
                        const gridWindow = CpsiMapview.util.Grid.getGridWindow(
×
173
                            node.getOlLayer()
174
                        );
175
                        if (gridWindow) {
×
176
                            const grid = gridWindow.down('grid');
×
177
                            if (grid) {
×
178
                                grid.fireEvent('applypresetfilters');
×
179
                            }
180
                        }
181
                    }
182
                });
183

184
            // preserve tree config to access later on
185
            CpsiMapview.treeConfig = treeJson.treeConfig;
×
186

187
            // inform subscribers that LayerTree is ready
188
            me.getView().fireEvent('cmv-init-layertree', me);
×
189
        });
190

191
        // fallback in case loading the JSON tree structure failed:
192
        // create a flat store holding all map layers at one hierarchy
193
        treeJsonPromise.catch(function () {
×
194
            Ext.Logger.warn(
×
195
                'Loading of JSON structure for LayerTree failed' +
196
                    '- creating flat layer hierarchy as fallback'
197
            );
198

199
            const layerTreeStore = Ext.create('GeoExt.data.store.LayersTree', {
×
200
                layerGroup:
201
                    me.map.get('layerTreeRoot') || me.map.getLayerGroup()
×
202
            });
203

204
            me.getView().setStore(layerTreeStore);
×
205

206
            // inform subscribers that LayerTree is ready
207
            me.getView().fireEvent('cmv-init-layertree', me);
×
208
        });
209
    },
210

211
    /**
212
     * Loads the JSON tree structure from 'resources/data/layers/tree.json'.
213
     *
214
     * @return {Ext.Promise} Promise resolving once the JSON is loaded
215
     */
216
    loadTreeStructure: function () {
217
        const app = Ext.getApplication
×
218
            ? Ext.getApplication()
219
            : Ext.app.Application.instance;
220
        return app.getResourcePaths().then(function (resourcePaths) {
×
221
            return new Ext.Promise(function (resolve, reject) {
×
222
                Ext.Ajax.request({
×
223
                    url: resourcePaths.treeConfig,
224
                    method: 'GET',
225
                    success: function (response) {
226
                        const respJson = Ext.decode(response.responseText);
×
227
                        resolve(respJson);
×
228
                    },
229
                    failure: function (response) {
230
                        reject(response.status);
×
231
                    }
232
                });
233
            });
234
        });
235
    },
236

237
    /**
238
     * Ensures tree and map only contain layers for which
239
     * the user has the required roles to see.
240
     */
241
    filterLayersByRole: function () {
242
        const me = this;
2✔
243
        const store = me.getView().getStore();
2✔
244
        if (!store) {
2!
245
            return;
×
246
        }
247
        store.filterBy(function (record) {
2✔
248
            const requiredRoles = record.get('requiredRoles');
3✔
249
            if (
3!
250
                requiredRoles &&
3!
251
                Ext.isArray(requiredRoles) &&
252
                requiredRoles.length
253
            ) {
254
                const showNode =
255
                    CpsiMapview.util.RoleManager.hasAtLeastOneRequiredRole(
×
256
                        requiredRoles
257
                    );
258
                return showNode;
×
259
            }
260
            return true;
3✔
261
        });
262
    },
263

264
    /**
265
     * Re-groups the layers of the #map, so they are put into a folder hierarchy
266
     * based on the given tree structure loaded in #loadTreeStructure.
267
     * For each folder an OL layer group is created and gets aggregated in a
268
     * root layer group.
269
     *
270
     * @param  {Object} treeJson LayerTree structure
271
     * @return {ol.layer.Group}  Root layer group
272
     */
273
    getGroupedLayers: function (treeConfJson) {
274
        const me = this;
×
275

276
        // wrapping all under the 'root' node aggregating all together
277
        const rootLayerGroup = new ol.layer.Group({
×
278
            name: 'root',
279
            layers: []
280
        });
281
        // recursively create the OL layer group by the given tree structure
282
        me.createOlLayerGroups(treeConfJson.children, rootLayerGroup);
×
283

284
        return rootLayerGroup;
×
285
    },
286

287
    /**
288
     * Creates recursively the OL layer groups for the given tree structure and
289
     * puts them all together in the given parent group so they get folders in the LayerTree.
290
     * Layers are directly put to the given parent group so they appear as "leafs" in the LayerTree.
291
     *
292
     * @param  {Object} treeNodesJson Child section of the LayerTree structure
293
     * @param  {ol.layer.Group} parentGroup The parent group to put children (another groups / layers) into
294
     */
295
    createOlLayerGroups: function (treeNodeChilds, parentGroup) {
296
        const me = this;
×
297
        // go over all passed in tree child nodes
298
        Ext.each(treeNodeChilds, function (child) {
×
299
            // apply defaults for tree nodes from config
300
            const generalDefaults = me.treeConfDefaults.general || {};
×
301
            Ext.applyIf(child, generalDefaults);
×
302

303
            // respect "isLeaf" for legacy reasons (but recommended using "leaf")
304
            const isLeaf = Ext.isDefined(child.isLeaf)
×
305
                ? child.isLeaf
306
                : child.leaf;
307
            // layer groups --> folders in tree
308
            if (isLeaf !== true) {
×
309
                // create empty layer group for this level
310
                const layerGroup = new ol.layer.Group({
×
311
                    name: child.title,
312
                    layers: []
313
                });
314

315
                // preserve the original tree JSON config to re-use it later on
316
                layerGroup.set('_origTreeConf', child);
×
317

318
                const parentLayers = parentGroup.getLayers();
×
319
                parentLayers.insertAt(0, layerGroup);
×
320

321
                // recursion
322
                me.createOlLayerGroups(child.children, layerGroup);
×
323
            } else {
324
                // layers --> leafs in tree
325
                const mapLyr = BasiGX.util.Layer.getLayerBy(
×
326
                    'layerKey',
327
                    child.id
328
                );
329

330
                if (mapLyr) {
×
331
                    // apply tree config to OL layer
332
                    // needed since the LayerTreeNode model derives them from OL layer
333
                    me.applyTreeConfigsToOlLayer(mapLyr, child);
×
334

335
                    // preserve the original tree JSON config to re-use it later on
336
                    mapLyr.set('_origTreeConf', child);
×
337

338
                    // add OL layer to parent OL LayerGroup
339
                    me.map.removeLayer(mapLyr);
×
340
                    parentGroup.getLayers().insertAt(0, mapLyr);
×
341
                } else {
342
                    //<debug>
343

344
                    // get the layers config object
345
                    const app = Ext.getApplication
×
346
                        ? Ext.getApplication()
347
                        : Ext.app.Application.instance;
348
                    const layerJson = app.layerJson;
×
349

350
                    // any switch layers not in the resolution when the app is loaded will be missing
351
                    // so we check for layer keys in the config JSON
352

353
                    const childLayers = Ext.Array.pluck(
×
354
                        layerJson.layers,
355
                        'layers'
356
                    ).filter(Boolean);
357
                    const allLayers = Ext.Array.merge(
×
358
                        Ext.Array.flatten(childLayers),
359
                        layerJson.layers
360
                    );
361
                    const layerKeys = Ext.Array.pluck(allLayers, 'layerKey');
×
362

363
                    if (!Ext.Array.indexOf(layerKeys, child.id) === -1) {
×
364
                        Ext.Logger.warn(
×
365
                            'Layer with layerKey ' +
366
                                child.id +
367
                                ' not found in map layers'
368
                        );
369
                    }
370
                    //</debug>
371
                }
372
            }
373
        });
374
    },
375

376
    /**
377
     * Applies the values from the tree layer config to OL the given
378
     * OL layer.
379
     *
380
     * @param {ol.layer.Base} olLayer The OL layer to apply tree conf values to
381
     * @param {Object} treeNodeConf The tree node layer config JSON
382
     */
383
    applyTreeConfigsToOlLayer: function (olLayer, treeNodeConf) {
384
        // name gets transformed to text on the layer tree node
385
        olLayer.set('name', treeNodeConf.text);
×
386
        // description gets transformed to qtip on the layer tree node
387
        olLayer.set('description', treeNodeConf.qtip);
×
388
        // descTitle gets transformed to qtitle on the layer tree node
389
        olLayer.set('descTitle', treeNodeConf.text);
×
390
        // changes the icon in the layer tree leaf
391
        olLayer.set('iconCls', treeNodeConf.iconCls);
×
392
    },
393

394
    /**
395
     * Applies the values from the tree layer config to the given
396
     * tree node instance.
397
     *
398
     * @param {Ext.data.NodeInterface} node The tree node to apply tree conf values to
399
     * @param {Object} treeNodeConf The tree node layer config JSON
400
     */
401
    applyTreeConfigsToNode: function (node, treeNodeConf) {
402
        node.set('cls', treeNodeConf.cls);
×
403
        node.set(
×
404
            'expandable',
405
            Ext.isDefined(treeNodeConf.expandable)
×
406
                ? treeNodeConf.expandable
407
                : true
408
        );
409
        node.set('glyph', treeNodeConf.glyph);
×
410
        node.set('icon', treeNodeConf.icon);
×
411
        node.set('qshowDelay', treeNodeConf.qshowDelay);
×
412

413
        node.set('text', treeNodeConf.text);
×
414
        node.set('requiredRoles', treeNodeConf.requiredRoles);
×
415

416
        // expand configured folders in this tree
417
        node.set('expanded', treeNodeConf.expanded);
×
418

419
        // hide checkbox on tree node if configured
420
        // setting checked to undefined has no effect since GeoExt.data.model.LayerTreeNode
421
        // overwrites this with the layer's visibility.
422
        if (treeNodeConf.checked === false) {
×
423
            node.addCls('cpsi-tree-no-checkbox');
×
424
        }
425
    },
426

427
    /**
428
     * Adds configuration to updated switch layer nodes.
429
     *
430
     * Ensures all layer information is transfered to the new
431
     * switch layer after the switch event
432
     *
433
     * @param {Ext.data.TreeModel} groupNode The folder node that is expanded
434
     */
435
    updateSwitchLayerNodes: function (groupNode) {
436
        const me = this;
×
437
        groupNode.cascade(function (child) {
×
438
            const layer = child.getOlLayer();
×
439
            if (!layer || !layer.get('isSwitchLayer')) {
×
440
                // we only care about switch layers
441
                return;
×
442
            }
443
            // the configuration for the tree node got lost during
444
            // the switch. We read it again from the layer and apply
445
            // it to the node.
446
            const origTreeNodeConf =
447
                child.getOlLayer().get('_origTreeConf') || {};
×
448
            me.applyTreeConfigsToNode(child, origTreeNodeConf);
×
449
        });
450
    },
451

452
    /**
453
     * This reacts on toggling the add wms Button in the layertree. It shows a window with an AddWmsForm.
454
     * @param {Ext.button.Button} button
455
     * @param {boolean} pressed
456
     */
457
    onAddWmsToggle: function (button, pressed) {
458
        const me = this;
×
459

460
        if (pressed) {
×
461
            if (!this.addWmsWindow) {
×
462
                this.addWmsWindow = Ext.create(me.getView().addWmsWindowConfig);
×
463
                this.addWmsWindow.on('close', function () {
×
464
                    button.setPressed(false);
×
465
                });
466
            }
467
            this.addWmsWindow.show();
×
468
        } else if (this.addWmsWindow) {
×
469
            this.addWmsWindow.close();
×
470
        }
471
    },
472

473
    /**
474
     * This reacts on toggling the add arcgisrest Button in the layertree. It shows a window with an AddArcGISRestForm.
475
     * @param {Ext.button.Button} button
476
     * @param {boolean} pressed
477
     */
478
    onAddArcGISRestToggle: function (button, pressed) {
479
        const me = this;
×
480

481
        if (pressed) {
×
482
            if (!this.addArcGISRestWindow) {
×
483
                this.addArcGISRestWindow = Ext.create(
×
484
                    me.getView().addArcGISRestWindowConfig
485
                );
486
                this.addArcGISRestWindow.on('close', function () {
×
487
                    button.setPressed(false);
×
488
                });
489
            }
490
            this.addArcGISRestWindow.show();
×
491
        } else if (this.addArcGISRestWindow) {
×
492
            this.addArcGISRestWindow.close();
×
493
        }
494
    },
495

496
    /**
497
     * Destroy any associated windows when this component gets destroyed
498
     */
499
    onBeforeDestroy: function () {
500
        if (this.addWmsWindow) {
×
501
            this.addWmsWindow.destroy();
×
502
            this.addWmsWindow = null;
×
503
        }
504
        if (this.addArcGISRestWindow) {
×
505
            this.addArcGISRestWindow.destroy();
×
506
            this.addArcGISRestWindow = null;
×
507
        }
508
    },
509

510
    /**
511
     * Updates the UI of childNodes of an expanded item
512
     * @param {CpsiMapview.data.model.LayerTreeNode} LayerTreeNode
513
     */
514
    updateExpandedItemChildNodesUI: function (layerTreeNode) {
515
        Ext.each(layerTreeNode.childNodes, function (node) {
×
516
            const layer = node.getOlLayer();
×
517
            if (layer.get('isWms') || layer.get('isWfs') || layer.get('isVt')) {
×
518
                CpsiMapview.util.Layer.updateLayerNodeUI(layer, false);
×
519
            }
520
        });
521
    }
522
});
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc