source: main/trunk/greenstone3/web/interfaces/default/js/map-scripts-editor.js@ 33099

Last change on this file since 33099 was 33099, checked in by wy59, 5 years ago

In previous commits, the autocompletelist was a member variable of the mapeditor: one autocomplete labels list per map. Now we want one list per whole document/page, i.e. one autocomplete list covering all the maps on one page. This means making the autocomplete list 'global' to the file instead of a member var of mapeditor. It had as further consequence that there needed to be a global hashmap to maintain unique labels, from which the autocomplete labels list would get constituted. Another consequence was that the jquery handler, which would setup the autocomplete to source from our autocomplete labels array, would need to be global to the file and no longer called from within a map editor method. So this jquery method likewise needs to be called only once per document and not for each map in a doc. The mapeditor LOAD function now still keeps a local labels hashmap to work out unique labels per loaded mapeditor, then adds these to our global labels hashmap (by calling jquery extend), to ensure uniqueness of the sum of all labels on the page. And finally, the LOAD function converts that recalculated global hashmap into an array to reset our autocomplete labels list/array.

File size: 32.0 KB
Line 
1var global_autocompleteLabelsList = [];
2var global_labels_hashmap = {};
3
4function MapEditor(id) {
5 // TODO: investigate const, see https://www.w3schools.com/js/js_const.asp and check it will work on older browsers,
6 // https://stackoverflow.com/questions/4271566/how-do-i-know-which-version-of-javascript-im-using
7 this.MAX_THICKNESS = 5.0;
8 this.MIN_THICKNESS = 0.0;
9 this.MAX_OPACITY = 100.00;
10 this.MIN_OPACITY = 0.00;
11
12 // WORK-IN-PROGRESS FEATURE: label on Map (to be changed to a label associated with each shape later)
13 // Also uncomment import of label-overlay-class.js in document.xsl
14 //this.labelOverlay = null;
15
16
17 this.id = id;
18 this.shiftKeyPressed = false;
19 this.beingDragged = false;
20 this.allowDeselect = true;
21 this.colors = ['#1E90FF', '#FF1493', '#4B0082', '#32CD32', '#FF8C00', '#000000'];
22 this.selectedColor;
23 this.colorButtons = {};
24 this.thicknessValue = 1;
25 this.opacityValue = 40;
26 this.overlays = [];
27 this.selectedShapes = [];
28 this.listenersArray = [];
29 this.mapsArray = [];
30 this.drawingManager;
31 this.selectedShape;
32 this.savedOverlays = null;
33 this.map = null;
34 this.counter = 0;
35 this.branchNum = 1;
36 this.mouseState = "up";
37 this.thicknessRangeListener = this.thicknessValue; // ????
38 this.resizable = false;
39 this.dontResize = false;
40
41
42 this.shapeOptions = {
43 suppressUndo: true,
44 fillColor: '#CA4A2F',
45 strokeWeight: this.thicknessValue,
46 fillOpacity: this.opacityValue / 100,
47 editable: true,
48 geodesic: false,
49 draggable: true,
50 description: ""
51 };
52 this.mapEditorHistory = new MapEditorHistory(this);
53}
54
55//draggable checkbox control
56MapEditor.prototype.initMapEditorControls = function () {
57 var that = this;
58
59 var draggableCB = document.getElementById("draggableCB-"+ this.id);
60 draggableCB.addEventListener('change', function () {
61 if (this.checked) {
62 for (var i = 0; i < that.overlays.length; i++) {
63 that.overlays[i].draggable = false;
64 that.shapeOptions.draggable = false;
65 }
66 } else {
67 for (var i = 0; i < that.overlays.length; i++) {
68 that.overlays[i].draggable = true;
69 that.shapeOptions.draggable = true;
70 }
71 }
72 });
73
74
75 //Update thickness
76 var thicknessSliderOutput = document.getElementById("thicknessRangeVal" + "-" + this.id);
77 var thicknessSlider = document.getElementById("thicknessRange" + "-" + this.id);
78 var thicknessValue = ((thicknessSlider.value / 20) * 100) / 100;
79 thicknessSliderOutput.value = thicknessValue.toFixed(2);
80
81 thicknessSlider.oninput = function () {
82 that.shapeSpecsChangeMD();
83 var thicknessVal = ((this.value / 20) * 100) / 100;
84 thicknessSliderOutput.value = thicknessVal.toFixed(2);
85 that.thicknessValue = this.value / 20;
86 that.shapeOptions.strokeWeight = that.thicknessValue;
87 that.setSelectedThickness(that.thicknessValue);
88 }
89
90 thicknessSliderOutput.oninput = function () {
91 that.shapeSpecsChangeMD(); // TODO: DO WE NEED THIS LINE? (LINE COPIED & PASTED FROM ABOVE)
92 if(this.value > that.MAX_THICKNESS) this.value = that.MAX_THICKNESS;
93 if(this.value < that.MIN_THICKNESS) this.value = that.MIN_THICKNESS;
94 var thicknessVal = this.value * 20;
95 thicknessSlider.value = thicknessVal.toFixed(2);
96 that.thicknessValue = this.value;
97 that.shapeOptions.strokeWeight = that.thicknessValue;
98 that.setSelectedThickness(that.thicknessValue);
99 }
100 //Update opacity
101 // TODO: https://stackoverflow.com/questions/469357/html-text-input-allow-only-numeric-input?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
102 var opacitySlider = document.getElementById("colourOpacity" + "-" + this.id);
103 var opacitySliderOutput = document.getElementById("opacityRangeVal" + "-" + this.id);
104
105 opacitySlider.oninput = function () {
106 that.shapeSpecsChangeMD();
107 opacitySliderOutput.value = this.value;
108 that.opacityValue = this.value / 100;
109 that.shapeOptions.fillOpacity = that.opacityValue;
110 that.setSelectedOpacity(that.opacityValue);
111 }
112 opacitySliderOutput.oninput = function () {
113 that.shapeSpecsChangeOnInput();
114 if(this.value > that.MAX_OPACITY) this.value = that.MAX_OPACITY;
115 if(this.value < that.MIN_OPACITY) this.value = that.MIN_OPACITY;
116 opacitySlider.value = this.value;
117 that.opacityValue = this.value / 100;
118 that.shapeOptions.fillOpacity = that.opacityValue;
119 that.setSelectedOpacity(that.opacityValue);
120 }
121
122 var descriptionInput = document.getElementById("description" + "-" + this.id);
123 // don't use oninput, use onchange, because with autocomplete a newly entered or pasted value
124 // doesn't always get stored properly when using oninput.
125 descriptionInput.onchange = function () {
126 that.shapeSpecsChangeOnInput(); // takes care of history (undo/redo)
127 var description = this.value;
128 that.shapeOptions.description = description;
129 that.setSelectedDescription(that.shapeOptions.description);
130 }
131
132 // Also add a COMPLETED description (i.e. when description input box loses focus)
133 // to the autocomplete list of descriptions/labels
134 descriptionInput.onblur = function () {
135 var description = this.value;
136 that.addToAutocompleteLabelsList(description);
137 }
138
139 // TODO: Do we need these listeners, when we already have onInput methods above? Test.
140 document.getElementById("color-palette1" + "-" + this.id).addEventListener("mousedown", function() { that.shapeSpecsChangeMD() });
141 document.getElementById("thicknessRange" + "-" + this.id).addEventListener("mouseup", function () { that.shapeSpecsChangeMU() });
142 document.getElementById("colourOpacity" + "-" + this.id).addEventListener("mouseup", function () { that.shapeSpecsChangeMU() });
143 //document.getElementById("description" + "-" + this.id).addEventListener("keypress", function () { that.shapeSpecsChangeOnInput() });
144
145 document.onmousedown = function (ev) {
146 that.mouseState = "down";
147 // console.log('Down State you can now start dragging');
148 //do not write any code here in this function
149 }
150
151 document.onmouseup = function (ev) {
152 that.mouseState = "up";
153 // console.log('up state you cannot drag now because you are not holding your mouse')
154 //do not write any code here in this function
155 }
156
157
158 //prompts the user to save the changes before reloading/leaving the page
159 window.onbeforeunload = function (e) {
160 var currentOverlays = JSON.stringify(ShapesUtil.overlayToJSON(that.overlays));
161 var enableMessage = currentOverlays !== that.savedOverlays;
162 var message = "Changes are not saved. Are you sure you want to leave?";
163
164
165 // Comment out following section in entirety -- from "e = e || window.event" to end of "if(e)" -- if
166 // you don't want to see the popup about changes that haven't been saved yet
167 e = e || window.event;
168 // For IE and Firefox
169 if (e) {
170
171 if(enableMessage){
172 if(currentOverlays !== "[]") {
173 alert(message);
174 e.returnValue = message;
175
176 // For Safari
177 return message;
178 }
179 }
180 }
181 }
182}
183
184
185// Ensure only unique labels are added to our autocomplete list
186MapEditor.prototype.addToAutocompleteLabelsList = function (newLabel) {
187
188 if (newLabel !== "" && !global_autocompleteLabelsList.includes(newLabel)) {
189 // add to autocomplete list and sort alphabetically
190 global_autocompleteLabelsList.push(newLabel);
191 global_autocompleteLabelsList.sort();
192 }
193
194 console.log("the added new label to global autocomplete array:"); console.log(global_autocompleteLabelsList);
195}
196
197MapEditor.prototype.settingThePath = function () {
198 var that = this;
199 this.listenersArray = []
200 this.counter = 0;
201 this.branchNum = 1;
202
203 for (var i = 0; i < this.selectedShapes.length * 2; i++) {
204 for (var j = 1; j < 6; j++) {
205 var path = "//*[@id='map-" + this.id + "']/div/div/div[1]/div[3]/div/div[3]/div[" + this.branchNum + "]/div[" + j + "]/div";
206 this.listenersArray[this.counter] = this.getElementByXpath(path);
207 if (this.listenersArray[this.counter] !== (undefined || null)) {
208 this.listenersArray[this.counter].addEventListener("mousemove", function () {
209 that.resizable = true;
210 that.shapeResize();
211 });
212 this.listenersArray[this.counter].addEventListener("mouseout", function () {
213 if (this.mouseDown) {
214 that.resizable = true;
215 that.shapeResize();
216
217 }
218 });
219 }
220 this.counter++;
221 }
222 this.branchNum++;
223 }
224}
225
226MapEditor.prototype.shapeResize = function () {
227 if (this.mouseState == "down") {
228 if (this.selectedShapes.length > 0) {
229 if (this.resizable) {
230 if (this.dontResize == false) {
231 this.mapEditorHistory.historyOverlayPush();
232 }
233
234 }
235 }
236 }
237}
238
239MapEditor.prototype.shapeSpecsChangeMD = function () {
240 if (this.selectedShapes.length > 0) {
241 this.mapEditorHistory.historyOverlayPush();
242 }
243}
244
245MapEditor.prototype.shapeSpecsChangeMU = function () {
246 if (this.selectedShapes.length > 0) {
247 this.mapEditorHistory.presentOverlayPush();
248 }
249}
250
251MapEditor.prototype.shapeSpecsChangeOnInput = function () {
252 if (this.selectedShapes.length > 0) {
253 this.mapEditorHistory.presentOverlayPush();
254 }
255}
256
257MapEditor.prototype.makeColorButton = function (color) {
258 var that = this;
259
260 var button = document.createElement('span');
261 button.className = 'color-buttons1';
262 button.style.backgroundColor = color;
263 google.maps.event.addDomListener(button, 'click', function () {
264 that.selectColor(color);
265 that.setSelectedShapeColor(color);
266 that.shapeSpecsChangeMU();
267 });
268 return button;
269}
270
271MapEditor.prototype.buildColorPalette = function () {
272 var colorPalette = document.getElementById("color-palette1" + "-" + this.id);
273 for (var i = 0; i < this.colors.length; ++i) {
274 var currColor = this.colors[i];
275 var colorButton = this.makeColorButton(currColor);
276 colorPalette.appendChild(colorButton);
277 this.colorButtons[currColor] = colorButton;
278 }
279 this.selectColor(this.colors[0]);
280};
281
282MapEditor.prototype.selectColor = function (color) {
283 this.selectedColor = color;
284 for (var i = 0; i < this.colors.length; ++i) {
285 var currColor = this.colors[i];
286 this.colorButtons[currColor].style.border = currColor == color ? '2px solid #789' : '2px solid #fff';
287 }
288
289 // Retrieves the current options from the drawing manager and replaces the
290 // stroke or fill color as appropriate.
291 var polylineOptions = this.drawingManager.get('polylineOptions');
292 polylineOptions.strokeColor = color;
293 this.drawingManager.set('polylineOptions', polylineOptions);
294
295 var rectangleOptions = this.drawingManager.get('rectangleOptions');
296 rectangleOptions.fillColor = color;
297 this.drawingManager.set('rectangleOptions', rectangleOptions);
298
299 var circleOptions = this.drawingManager.get('circleOptions');
300 circleOptions.fillColor = color;
301 this.drawingManager.set('circleOptions', circleOptions);
302
303 var polygonOptions = this.drawingManager.get('polygonOptions');
304 polygonOptions.fillColor = color;
305 this.drawingManager.set('polygonOptions', polygonOptions);
306}
307
308MapEditor.prototype.initMapEditor = function () {
309 var that = this;
310
311 this.map = new google.maps.Map(document.getElementById("map-" + this.id), {
312 center: {
313 lat: -37.7891,
314 lng: 175.3180
315 },
316 zoom: 14,
317 mapId: this.id,
318 });
319
320 // WORK-IN-PROGRESS FEATURE: label on Map (to be changed to a label associated with each shape later)
321 // let's associate a label with the map (for now, later associate labels for each shape)
322 //this.labelOverlay = new LabelOverlay(this.map);
323
324 this.mapsArray.push(this.map);
325 // Add a style-selector control to the map.
326 var styleControl = document.getElementById('style-selector-control' + "-" + this.id);
327 this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(styleControl);
328
329 /*
330 // Set the map's style to the initial value of the selector.
331 var styleSelector = document.getElementById('style-selector' + "-" + this.id);
332 //console.log(styleSelector);
333 //map = google.maps.Map(document.getElementById('map' +"-"+ this.id));
334 this.map.setOptions({
335 styles: styles[styleSelector.value]
336
337 });
338
339 // Apply new JSON when the user selects a different style.
340 styleSelector.addEventListener('change', function () {
341 that.map.setOptions({
342 styles: styles[styleSelector.value]
343 });
344 });
345 */
346
347 this.drawingManager = new google.maps.drawing.DrawingManager({
348 //drawingMode: google.maps.drawing.OverlayType.RECTANGLE,
349 drawingControl: true,
350 drawingControlOptions: {
351 position: google.maps.ControlPosition.TOP_CENTER,
352 drawingModes: ['marker', 'circle', 'polygon', 'polyline', 'rectangle']
353 },
354 markerOptions: {
355 draggable: true
356 },
357 circleOptions: this.shapeOptions,
358 polylineOptions: this.shapeOptions,
359 polygonOptions: this.shapeOptions,
360 rectangleOptions: this.shapeOptions,
361 });
362
363 this.drawingManager.setMap(this.map);
364
365 google.maps.event.addListener(this.drawingManager, "drawingmode_changed", function () {
366 if (that.shiftKeyPressed != true && that.drawingManager.drawingMode !== null) {
367 that.deselectAll();
368 }
369 that.settingThePath();
370
371 })
372
373 // store reference to added overlay
374 google.maps.event.addListener(this.drawingManager, 'overlaycomplete', function (e) {
375 that.allowDeselect = true;
376 that.mapEditorHistory.historyOverlayPush();
377 that.overlays.push(e.overlay); // store reference to added overlay
378 var newShape = e.overlay;
379 newShape.type = e.type;
380 that.mapEditorHistory.presentOverlayPush();
381
382 if (e.type !== google.maps.drawing.OverlayType.MARKER) {
383 that.addShapeListeners(newShape, e);
384 that.setSelection(newShape, e);
385 } else {
386 that.addMarkerListeners(newShape, e);
387 that.setSelection(newShape, e);
388 }
389 });
390
391 //Clears selection if clicked on the map when shift is not presseed
392 google.maps.event.addListener(this.map, 'click', function (e) {
393 var c = document.body.childNodes;
394 if (e.target && e.target.matches("a.classA")) {
395 console.log("Anchor element clicked!");
396 }
397 if (that.shiftKeyPressed == false) {
398 that.clearSelection();
399 that.selectedShape = null;
400 }
401 });
402
403 google.maps.event.addListener(this.map, 'mousedown', function (e) {
404 that.dontResize = true;
405 });
406
407 google.maps.event.addListener(this.map, 'mouseup', function (e) {
408 that.dontResize = false;
409 });
410
411 //Keyboard shortcuts
412 var mapAndControls = document.getElementById("map-and-controls-" + this.id);
413 var thicknessField = document.getElementById ("thicknessRangeVal-" + this.id);
414 var opacityField = document.getElementById ("opacityRangeVal-" + this.id);
415 var openMapFunction = function() {
416 //Sets shift as unpressed
417 mapAndControls.addEventListener('keyup', function (event) {
418 if (event.keyCode == '16') {
419 that.shiftKeyPressed = false;
420 }
421 });
422
423 mapAndControls.addEventListener('keydown', function (event) {
424
425 // https://stackoverflow.com/questions/2220196/how-to-decode-character-pressed-from-jquerys-keydowns-event-handler
426 var keyCode = String.fromCharCode(event.which);
427 //console.log("Key pressed: " + keyCode);
428
429 //disable keyboard shortcut within the number input field
430 var activeElement = $(document.activeElement);
431 if(activeElement.attr('type') == 'number' || activeElement.attr('type') == 'text'){
432 //console.log('number detected')
433 return;
434 }
435 //Sets shift as pressed
436 if (event.keyCode == '16') {
437 that.shiftKeyPressed = true;
438 }
439 else if (keyCode == 'Y' && (event.ctrlKey || event.metaKey)) {
440 that.mapEditorHistory.redo();
441 }
442 else if (keyCode == 'Z' && (event.ctrlKey || event.metaKey) ) {
443 if (that.shiftKeyPressed == false) {
444 that.mapEditorHistory.undo();
445 }
446 }
447 else if (keyCode == 'A' && (event.ctrlKey || event.metaKey)) {
448 event.preventDefault();
449 that.drawingManager.setDrawingMode(null);
450 that.selectAll();
451 }
452 else if (keyCode == 'D' && (event.ctrlKey || event.metaKey)) {
453 event.preventDefault();
454 that.deselectAll();
455 }
456
457 else if (keyCode == '0' || keyCode == 'À' || (keyCode == 'G'&& (event.ctrlKey || event.metaKey))) {
458 event.preventDefault();
459 that.drawingManager.setDrawingMode(null);
460 } else if (keyCode == '1') {
461 that.drawingManager.setDrawingMode('marker');
462 } else if (keyCode == '2') {
463 that.drawingManager.setDrawingMode('circle');
464 } else if (keyCode == '3') {
465 that.drawingManager.setDrawingMode('polygon');
466 } else if (keyCode == '4') {
467 that.drawingManager.setDrawingMode('polyline');
468 } else if (keyCode == '5') {
469 that.drawingManager.setDrawingMode('rectangle');
470 }
471
472 //else if (keyCode == 'S') {
473 // that.saveToArchives();
474 //}
475 else if (keyCode == 'Q') { // for debugging information, press Q (easy to hit key)
476 that.printHistory();
477 }
478 else if (keyCode == '.') {
479 that.deleteSelectedShapes();
480 }
481 // console.log(keyCode);
482 });
483 };
484
485 openMapFunction();
486
487 this.buildColorPalette();
488
489
490 var collection = gs.cgiParams.c;
491 var site_name = gs.xsltParams.site_name;
492 var nodeID = this.id; // documentID, hopefully contains section ID too
493 var metaname = gps_metadata_name;
494
495 // collection, site, documentID, metadataName, metadataPosition, responseFunction
496 gs.functions.getArchivesMetadata(collection, site_name, nodeID, metaname, 0, function(responseText){
497 // responseText is of type GSMetadata
498
499 // called when data has been retrieved from archives
500 var JSONString = responseText.getValue();
501 if(JSONString !== "")
502 {
503 that.LOAD(JSONString, nodeID);
504 that.savedOverlays = JSONString;
505 }
506 }
507 ); // TODO: responseFunction in setMeta call
508}
509
510//Deletes a vertex if clicked on it
511MapEditor.prototype.vertexAndPolyDel = function (e, newShape) {
512 var vertex = e.vertex;
513 if (e.vertex !== undefined) {
514 if (newShape.type === google.maps.drawing.OverlayType.POLYGON) {
515 var path = newShape.getPaths().getAt(e.path);
516 path.removeAt(e.vertex);
517 if (path.length < 3) {
518 newShape.setMap(null);
519 }
520 }
521 if (newShape.type === google.maps.drawing.OverlayType.POLYLINE) {
522 var path = newShape.getPath();
523 path.removeAt(e.vertex);
524 if (path.length < 2) {
525 newShape.setMap(null);
526 }
527 }
528 }
529}
530
531MapEditor.prototype.addMarkerListeners = function (newShape, e) {
532 var that = this;
533 //Click event if a marker is created
534 google.maps.event.addListener(newShape, 'click', function (e) {
535 if(that.shiftKeyPressed){
536
537 } else {
538 that.mapEditorHistory.historyOverlayPush();
539 newShape.setMap(null);
540 that.mapEditorHistory.presentOverlayPush();
541 }
542
543 });
544
545 google.maps.event.addListener(newShape, 'dragstart', function (e) {
546 that.beingDragged = true;
547 that.mapEditorHistory.historyOverlayPush();
548
549 });
550
551 google.maps.event.addListener(newShape, 'dragend', function () {
552 that.beingDragged = false;
553 that.mapEditorHistory.presentOverlayPush();
554 that.allowDeselect = false;
555 });
556}
557
558MapEditor.prototype.addShapeListeners = function (newShape, e) {
559 var that = this;
560 // Add an event listener that selects the newly-drawn shape when the user
561 // mouses down on it.
562 google.maps.event.addListener(newShape, 'click', function (e) {
563 that.vertexAndPolyDel(e, newShape);
564 });
565
566 google.maps.event.addListener(newShape, 'dragstart', function (e) {
567 that.allowDeselect = false;
568 that.mapEditorHistory.historyOverlayPush();
569 });
570
571 google.maps.event.addListener(newShape, 'dragend', function () {
572 that.beingDragged = false;
573 that.mapEditorHistory.presentOverlayPush();
574 that.settingThePath();
575
576 that.allowDeselect = false;
577 that.setSelection(newShape, e);
578 });
579
580 //Store information after the event ends
581 google.maps.event.addListener(newShape, 'bounds_changed', function (e) {
582 if (that.beingDragged == false) {
583 that.mapEditorHistory.presentOverlayPush();
584 }
585 });
586
587 //Add an event listener to select a shape if the mouse hovers over it
588 google.maps.event.addListener(newShape, 'mousedown', function (e) {
589 if (e.target && e.target.matches("a.classA")) {
590 console.log("Anchor element clicked!");
591 }
592 if (e.vertex !== undefined || e.edge !== undefined) {
593 that.mapEditorHistory.historyOverlayPush()
594 }
595 if (that.drawingManager.drawingMode == null) {
596 that.setSelection(newShape, e);
597 }
598 });
599
600 google.maps.event.addListener(newShape, 'mouseup', function (e) {
601 if (e.vertex !== undefined || e.edge !== undefined) {
602 that.mapEditorHistory.presentOverlayPush()
603 } else {
604 //that.setSelection(newShape, e);
605 }
606
607 });
608}
609MapEditor.prototype.clearSelection = function () {
610 if (this.selectedShape) {
611 if (this.shiftKeyPressed == false) {
612 for (var i = 0; i < this.selectedShapes.length; i++) {
613 if(this.selectedShapes[i].type !== 'marker') {
614 this.selectedShapes[i].setEditable(false);
615 }
616 }
617 this.selectedShapes = [];
618 }
619 this.selectedShape = null;
620 }
621}
622
623//Set selection for the selected overlay
624MapEditor.prototype.setSelection = function (shape, e) {
625 //var that = this;
626 if (shape.type !== 'marker') {
627 if (this.shiftKeyPressed == false) {
628 if (e !== null) {
629 if (e.vertex == undefined) {
630 if (e.edge == undefined) {
631 this.clearSelection();
632 shape.setEditable(true);
633 }
634 }
635 }
636 }
637 if (this.selectedShapes.includes(shape)) {
638 if (e !== null) {
639 if (e.vertex == undefined) {
640 if (e.edge == undefined) {
641 this.allowDeselect = true;
642 this.removeFromSelectedShapes(shape);
643 }
644 }
645 }
646 } else {
647 this.allowDeselect = false;
648 shape.setEditable(true);
649 this.selectedShapes.push(shape);
650 }
651
652 //Send the values to be updated
653 var thi = shape.strokeWeight;
654 var opa = shape.fillOpacity;
655 var fCol = shape.fillColor;
656 var sCol = shape.strokeColor;
657 var description = shape.description;
658 this.updateMenuValues(thi, opa, fCol, sCol, description);
659
660 } else if (shape.type == 'marker') {
661 this.allowDeselect = false;
662 this.selectedShapes.push(shape);
663 }
664 this.selectedShape = shape;
665 this.settingThePath();
666}
667
668MapEditor.prototype.removeFromSelectedShapes = function (shape) {
669 if (this.selectedShapes.includes(shape)) {
670 if (this.allowDeselect) {
671 const index = this.selectedShapes.indexOf(shape);
672 this.selectedShapes.splice(index, 1);
673 shape.setEditable(false);
674 }
675 this.allowDeselect = true;
676 }
677}
678
679//Set selected label
680MapEditor.prototype.setSelectedDescription = function (label) {
681 if (this.selectedShapes.length > 0) {
682 for (var i = 0; i < this.selectedShapes.length; i++) {
683 this.selectedShapes[i].set('description', label); //SAME: this.selectedShapes[i].description = label;
684 }
685 }
686}
687
688//Set selected thickness
689MapEditor.prototype.setSelectedThickness = function (sWeight) {
690 if (this.selectedShapes.length > 0) {
691 for (var i = 0; i < this.selectedShapes.length; i++) {
692 this.selectedShapes[i].set('strokeWeight', sWeight);
693 }
694 }
695}
696
697//Set selected opacity
698MapEditor.prototype.setSelectedOpacity = function (fOpacity) {
699
700 if (this.selectedShapes.length > 0) {
701 for (var i = 0; i < this.selectedShapes.length; i++) {
702 this.selectedShapes[i].set('fillOpacity', fOpacity);
703 }
704 }
705}
706
707//set selected fill colour
708MapEditor.prototype.setSelectedShapeColor = function (color) {
709 if (this.selectedShapes.length > 0) {
710 for (var i = 0; i < this.selectedShapes.length; i++) {
711 this.selectedShapes[i].set('fillColor', color);
712 this.selectedShapes[i].set('strokeColor', color);
713 }
714 }
715}
716
717MapEditor.prototype.getElementByXpath = function (path) {
718 return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
719}
720
721MapEditor.prototype.updateMenuValues = function (thi, opa, fCol, sCol, description) {
722 //Update thickness slider and value on the settings menu
723 var thicknessSliderOutput = document.getElementById("thicknessRangeVal" + "-" + this.id);
724 // update the thickness innerHTML's value to always have 2 decimal places, https://www.w3schools.com/js/js_number_methods.asp
725 thi = parseFloat(thi); //Ensure the thi is a number
726 thicknessSliderOutput.value = thi.toFixed(2);
727 document.getElementById("thicknessRange" + "-" + this.id).value = Math.round((thi * 20) * 100) / 100;
728
729 //Update the opacity slider and value on the settings menu
730 var opacitySliderOutput = document.getElementById("opacityRangeVal" + "-" + this.id);
731 opacitySliderOutput.value = opa * 100;
732 document.getElementById("colourOpacity" + "-" + this.id).value = opa * 100;
733
734 // Show the description in the description field
735 var descriptionInput = document.getElementById("description" + "-" + this.id);
736 descriptionInput.value = description;
737
738 if (this.drawingManager.drawingMode == null) {
739 this.selectColor(fCol);
740 }
741}
742MapEditor.prototype.selectAll = function () {
743 this.shiftKeyPressed = true;
744 var e = new Object();
745 e.vertex = undefined;
746 this.selectedShapes = [];
747 for (var i = 0; i < this.overlays.length; i++) {
748 this.setSelection(this.overlays[i], e);
749 }
750 this.shiftKeyPressed = false;
751}
752MapEditor.prototype.deselectAll = function () {
753 for (var i = 0; i < this.selectedShapes.length; i++) {
754 if (this.selectedShapes[i].type !== google.maps.drawing.OverlayType.MARKER) {
755 this.selectedShapes[i].setEditable(false);
756 }
757
758 }
759 this.selectedShapes = [];
760}
761
762// event handler for s being pressed *when map editor has the focus*
763// For saving and rebuilding, see map_scripts
764MapEditor.prototype.saveToArchives = function () {
765 var that = this;
766 console.log("Save pressed");
767
768 var json_overlays = JSON.stringify(ShapesUtil.overlayToJSON(this.overlays));
769 that.savedOverlays = json_overlays; // save the old version to compare with future changes
770 var collection = gs.cgiParams.c;
771 var site_name = gs.xsltParams.site_name;
772 var nodeID = this.id; // documentID, hopefully contains section ID too
773 var metaname = "GPS.mapOverlay";
774
775 // collection, site, documentID, metadataName, metadataPosition, metadataValue, prevMetadataValue, metamode, responseFunction
776 gs.functions.setArchivesMetadata(collection, site_name, nodeID, metaname, 0, json_overlays, null, "override", function(){
777 console.log("SAVED");
778 }
779 );
780}
781
782// TODO: When finished testing, can remove this debug function that just prints to console
783MapEditor.prototype.printHistory = function () {
784 console.log("prev", this.mapEditorHistory.prevOverlays);
785 console.log("present ", this.mapEditorHistory.presentOverlays);
786 console.log("undone ", this.mapEditorHistory.undoneOverlays);
787 console.log("@@@@ allShapes: ", this.overlays);
788 console.log("@@@@ selectedShapes: ", this.selectedShapes);
789}
790
791// to be called after reading back in stored JSON from archives meta
792MapEditor.prototype.LOAD = function (json_str, nodeID) {
793 this.mapEditorHistory.historyOverlayPush();
794
795 // This seems to convert the map_store object into an array and forces array index access, instead of convenient property access using nodeID
796 //Object.values(gsmap_store)[0]; // Always gets top level section's map-editor, not what we want.
797
798 // Get the map editor for the nodeID, as we're asked to load that editor
799 var map_editor = gsmap_store["map-"+nodeID];
800
801 var new_overlays = ShapesUtil.JSONToOverlays(json_str);
802 for (var i=0; i<map_editor.overlays.length; i++) {
803 map_editor.overlays[i].setMap(null);
804 }
805
806 map_editor.overlays = new_overlays;
807
808 var local_labels_hashmap = {};
809 for (var i=0; i<map_editor.overlays.length; i++) {
810 var shape = map_editor.overlays[i];
811
812 // set up the autocomplete list using saved labels/descriptions
813 //map_editor.addToAutocompleteLabelsList(shape.description); // inefficient in ensuring uniqueness of values, use (hash)map
814 if (shape.description !== "") {
815 local_labels_hashmap[shape.description] = 1; // we just want the shape.description added to the map as key, don't care about value
816 // so that we can maintain a list of unique descriptions
817 }
818
819 // make the shapes selectable on load:
820 if (ShapesUtil.overlayItemIsShape(shape)) {
821 map_editor.addShapeListeners(shape, null); // don't have an overlay event!
822 } else {
823 map_editor.addMarkerListeners(shape, null); // don't have an overlay event!
824 }
825 shape.setMap(map_editor.map);
826 }
827
828 this.mapEditorHistory.presentOverlayPush();
829
830
831 // DO NOT assign "this.autocompleteLabelsList = keys(local_labels_hashmap);"
832 // Because Javascript is like Java and (in this) like C, not like C++. JavaScript uses Call-By-Sharing for objects:
833 // when reference addresses that are overwritten inside functions,
834 // the new address local to the function is not remembered/visible back outside the function
835 // https://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language
836 // Instead, push.apply=addAll elements of keys to our autocomplete list, see
837 // https://stackoverflow.com/questions/35658464/what-is-the-equivalent-of-java-collection-addall-for-javascript-arrays/35658514
838
839 // Combining contents of one JavaScript Object/hashmap (or associative 'array') into another is also described as merging our JavaScript Objects, so see
840 // https://stackoverflow.com/questions/929776/merging-objects-associative-arrays
841 // We've decided to use the jQuery based suggested solution for merging hashmaps:
842 // $.extend(obj1, obj2); means obj1 = obj1 + obj2. Overwriting any existing properties in obj1 with new values in obj2.
843
844 console.log("@@@@ Adding local contents to global: "); console.log(local_labels_hashmap);
845 console.log("@@@@ global map before: "); console.log(global_labels_hashmap);
846
847
848 $.extend(global_labels_hashmap, local_labels_hashmap);
849
850 console.log("@@@@ global map after: "); console.log(global_labels_hashmap);
851
852 // important to empty our global autocomplete labels list array
853 //See https://stackoverflow.com/questions/1232040/how-do-i-empty-an-array-in-javascript
854 // and https://www.jstips.co/en/javascript/two-ways-to-empty-an-array/
855 global_autocompleteLabelsList.length = 0;
856
857 var keys = Object.keys(global_labels_hashmap);
858 global_autocompleteLabelsList.push.apply(global_autocompleteLabelsList, keys); // addAll keys to our global_autocompleteLabelsList (turns map's keys into array)
859
860 console.log("@@@@ global ARRAY after setting to global map: "); console.log(global_autocompleteLabelsList);
861 global_autocompleteLabelsList.sort();
862}
863
864MapEditor.prototype.deleteSelectedShapes = function () {
865 if(this.selectedShapes.length !== 0) {
866 this.mapEditorHistory.historyOverlayPush();
867 for (var i = 0; i < this.selectedShapes.length; i++) {
868 this.selectedShapes[i].setMap(null);
869
870 if (this.overlays.includes(this.selectedShapes[i])) {
871 const index = this.overlays.indexOf(this.selectedShapes[i]);
872 this.overlays.splice(index, 1);
873// this.selectedShapes[i].setEditable(false);
874 }
875 }
876 this.selectedShapes = [];
877 this.mapEditorHistory.presentOverlayPush();
878 }
879}
880
881MapEditor.prototype.draggableState = function () {
882
883 var draggableCB = document.querySelector("input[name=draggableCB]");
884 draggableCB.addEventListener('change', function () {
885 if (this.checked) {
886 this.overlays.draggable = false;
887 } else {
888 this.overlays.draggable = false;
889 }
890 });
891}
892
893MapEditor.prototype.deleteAllShapes = function () {
894 if(this.overlays.length !== 0) {
895 //console.log("deleteAllShape() this.id = " + this.id);
896 this.mapEditorHistory.historyOverlayPush();
897 for (var i = 0; i < this.overlays.length; i++) {
898 this.overlays[i].setMap(null);
899 }
900 this.overlays = [];
901 this.mapEditorHistory.presentOverlayPush();
902 }
903}
904
905
906// Having got our unique (set) of descriptions/labels, we're ready to set it up as the source for our autocomplete list
907$(function setupAutocompleteLabelsList() {
908 // Overrides the default autocomplete filter function to
909 // search only from the beginning of the string
910 //resource: https://miroslavpopovic.com/posts/2012/06/jqueryui-autocomplete-filter-words-starting-with-term
911 $.ui.autocomplete.filter = function (array, term) {
912 var matcher = new RegExp("^" + $.ui.autocomplete.escapeRegex(term), "i");
913 return $.grep(array, function (value) {
914 return matcher.test(value.label || value.value || value);
915 });
916 };
917
918 $(".description").autocomplete({
919 source: global_autocompleteLabelsList
920 });
921});
Note: See TracBrowser for help on using the repository browser.