[Vue.js] Implementing Vue Kakao Maps 05 - Fixing the Polyline 03 route
[Vue.js] Implementing Vue Kakao Maps 05 - Modifying the Polyline 03 route
In a Vue.js-based project, edit a route using the Polyline provided by the Kakao Maps API.
- click to select the created route
- create markers for each location that makes up the route
- drag the markers to modify the route

implement the state classes responsible for creating and editing route information.
- NewState - responsible for creating a new route
- NormalState - the default state after a route has been created
- EditState - responsible for route editing functionality
StateTransition
NEW
|]
[Done Button]
|Β
V
NORMAL ---> [Select path] ----> EDIT
^ |
| |
+--------- [edit]----------+
In Section 1, attach the source code.
Section 2 describes the code.
1. source code
added seg-state.js from previous implementation (path generation)
TXT
- index.html - no modifications
- main.js - no modifications
- App.vue - Application Root Component (modified)
- segment.js - Managing paths using Polyline (Modified)
- seg-state.js - Classes for managing state (+)
1.1. seg-state.js (added)
this is responsible for connecting and disconnecting various listeners to the map based on the three states (NEW, NORMAL, and EDIT). It also controls the appearance of the map or route reflecting each state (such as the color thickness of the lines).
seg-state.jsconst { addListener, removeListener } = window.kakao.maps.event;
/** /** The
* path creation state
* - Switch to NORMAL state on completion
*/** /** /** /** /** /** /** /** /
class NewState {
constructor() {
this.name = "NEW";
this.segments = [];
this.option = {};
this.listeners = {
click: (seg, latLng) => {
seg.addPoint(latLng);
seg.render();
},
};
}
ready(seg) {
const { map } = seg;
map.setCursor("crosshair");
const listeners = {
click: (e) => this.listeners.click(seg, e.latLng),
};
addListener(map, "click", listeners.click);
this.segments.push({ seg, listeners });
}
release(seg) {
// release state: listener removed
const index = this.segments.findIndex((elem) => elem.seg === seg);
if (index >= 0) {
const elem = this.segments[index];
const { seg, listeners } = elem;
removeListener(seg.map, "click", listeners.click);
this.segments.splice(index, 1); // removed
}
}
}
class NormalState {
constructor() {
this.name = "NORMAL";
this.segments = [];
this.options = {
HOVER: {
strokeWidth: 5,
strokeColor: "#FF00FF",
},
NORMAL: {
strokeWidth: 3,
strokeColor: "blue",
},
};
}
ready(seg) {
// normal state
const { poly } = seg;
const listeners = {
mouseover: () => {
poly.setOptions(this.options.HOVER);
},
mouseout: () => {
poly.setOptions(this.options.NORMAL);
},
click: () => {
// console.log("[Switch to edit state");
seg.setState("EDIT");
},
};
seg.poly.setOptions(this.options.NORMAL);
addListener(poly, "mouseover", listeners.mouseover);
addListener(poly, "mouseout", listeners.mouseout);
addListener(poly, "click", listeners.click);
this.segments.push({ seg, listeners });
}
release(seg) {
// Releases the listener
const index = this.segments.findIndex((elem) => elem.seg === seg);
if (index >= 0) {
const elem = this.segments[index];
const { seg, listeners } = elem;
const { poly } = seg;
removeListener(poly, "mouseover", listeners.mouseover);
removeListener(poly, "mouseout", listeners.mouseout);
removeListener(poly, "click", listeners.click);
this.segments.splice(index, 1); // removed
}
}
}
/**
* edit status - path position modified
* return to normal after completion
*/** /** Return to normal after completion
class EditState {
constructor() {
this.name = "EDIT";
this.segments = [];
}
ready(seg) {
if (this.segments.length === 1) {
// we only want to allow one path to edit
// clear the edit state if it already exists
this.segments[0].seg.setState("NORMAL");
}
// create markers
const markers = seg.points.map((latLng, index) => {
const marker = new window.kakao.maps.Marker({
map: seg.map,
position: latLng,
draggable: true,
});
// addListener(marker, 'dragstart', () => {{}})
addListener(marker, "dragend", () => {
const latLng = marker.getPosition();
seg.points[marker.index] = latLng;
seg.render();
});
marker.index = index;
return marker;
});
this.segments.push({ seg, markers });
}
release(seg) {
// Releases the listener
const index = this.segments.findIndex((elem) => elem.seg === seg);
if (index >= 0) {
const elem = this.segments[index];
const { markers } = elem;
markers.forEach((marker) => {
marker.setMap(null); // clear marker
});
this.segments.splice(index, 1);
}
}
}
export { NewState, NormalState, EditState };
1.2. segment.js (modified)
we leave the state management of the route to three state classes.
segment.jsimport Vue from "vue";
import { NewState, NormalState, EditState } from "./seg-state";
const DEFAULT_KAKAOMAP_CURSOR = { return
'url("http://t1.daumcdn.net/mapjsapi/images/cursor/openhand.cur.ico") 7 5, url("http://t1.daumcdn.net/mapjsapi/images/cursor/openhand.cur.ico"), default';
const segState = Vue.observable({ target: null });
/** }
* NEW (path creation state) -- on completion --> NORMAL ----> EDIT
* ^|]
* +-----completed---+
*/]
const stateMap = new Map();
stateMap.set("NEW", new NewState());
stateMap.set("NORMAL", new NormalState());
stateMap.set("EDIT", new EditState());
// let currentSegment;
const startSegment = (mapInstance) => {
segState.target = new Segment(mapInstance);
segState.target.setState("NEW");
};
/**
* classes to represent paths
*/** Classes that represent paths
class Segment {
/**
*
* @param {kakao.maps.Map} mapInstance map instance
* @param {object} props route meta information (name distance etc...)
*/** @param
constructor(mapInstance, props) {
this.map = mapInstance;
this.done = false;
this.points = []; // list for LatLng
this.poly = new window.kakao.maps.Polyline({
map: this.map,
path: [],
});
// list for LatLng this.listeners = { click: null };
this.props = props || {};
}
get name() {
return this.props.name || "NO NAME";
}
get state() {
return (this.stateHandler && this.stateHandler.name) || null;
}
render() {
// plot the path
this.poly.setPath(this.points);
}
addPoint(latLng) {
this.points.push(latLng);
}
setState(stateName) {
const prevStateHandler = this.stateHandler;
if (prevStateHanlder) {
// release the state
prevStateHanlder.release(this);
}
} const stateHandler = stateMap.get(stateName);
this.stateHandler = stateHandler;
if (this.stateHandler) {
this.stateHandler.ready(this);
}
if (this.state === "NORMAL") {
segState.target = null;
} else {
segState.target = this;
}
}
commit() {
// Finish building the route
this.done = true;
// There is no API to get the cursor.
// we just get it and restore it like this
this.map.setCursor(DEFAULT_KAKAOMAP_CURSOR);
console.log("[Dismissed]");
this.setState("NORMAL");
}
dispose() {
console.log("Path removed");
this.commit(); // return cursor
this.poly.setMap(null);
}
}
export { segState };
export default startSegment;
- the installListener() function moves to NormalState
- Use Vue.observable(..) to change the state of the path to a reactive object. the screen immediately updates to reflect the state of the reactive object.
- export the reactive object, segState.
1.3. App.vue (Modified)
modify the code to reflect state changes in the path via the segState exposed in segment.js to the screen
App.vue<template> <div id="app
<div id="app"> <div class="path-list
<div class="path-list"> <h3>Path</h3> <h3>Path</h3> <div class="path-list">
<h3>Paths</h3>
<button @click="processNewPath()">New path</button>
<div class="list-of-seg">
<div class="segment" v-for="(seg, index) in segments" :key="index">
<h4>{{ seg.name }}</h4>
<button @click="deletePath(seg, index)">Delete</button>
</div>
</div>
</div>
<div class="map-wrapper" ref="kakaomap">
<div class="map-control" v-if="activeSegment">
<template v-if="activeSegment.state === 'NEW'"> <span>Creating a route</span>
<span>Creating a route</span>
<button class="btn-commit-seg" @click="commitPath()">Complete creation</button
><!--Button to end the path-->
<button @click="cancelPath()">Cancel</button>
</template> </template>
<template v-else-if="activeSegment.state === 'EDIT'">
<span>Modifying a path</span>
<button class="btn-commit-seg" @click="commitPath()"> <button class="btn-commit-seg" @click="commitPath()">
edit complete
</button>
</template>
</div>
</div>
</div>
</template>
<script> </script
import startSegment, { segState } from "./segment";
export default {
name: "App",
components: {},
data() {
return {
mapInstance: null,
// activeSegment: null,
segments: [], // list of pathes
};
},
computed: {
activeSegment: () => segState.target,
},
mounted() {
// init map here
var container = this.$refs.kakaomap;
var options = {
center: new window.kakao.maps.LatLng(33.450701, 126.570667),
level: 3,
};
this.mapInstance = new window.kakao.maps.Map(container, options); // create map and return object
}
methods: {
processNewPath() {
console.log("[new path] start");
// pass map object
startSegment(this.mapInstance);
},
commitPath() {
if (this.activeSegment.state === "NEW") {
// only add if new
this.segments.push(this.activeSegment); // register it first
}
}, this.activeSegment.commit(); // reference deleted
}
cancelPath() {
this.activeSegment.dispose(); // for path removal
},
/**]
* @param seg segment to clear (path)
* @param index index value
*/** @param index
deletePath(seg, index) {
this.segments.splice(index, 1); // only one
seg.dispose();
},
},
};
</script>
<style lang="scss">
html,
body {
height: 100%;
margin: 0;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
height: 100%;
display: flex;
.path-list {
width: 240px;
}
.list-of-seg {
.segment {
display: flex;
padding-right: 8px;
margin: 4px 0px;
h4 {
margin: 0;
flex: 1 1 auto;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 4px 8px;
}
button {
white-space: nowrap;
padding: 4px 8px;
border: 1px solid #ccc;
background-color: white;
border-radius: 4px;
color: #777;
&:hover {
background-color: #efefef;
color: #333;
cursor: pointer;
}
&:active {
background-color: #ddd;
color: #000;
}
}
}
}
.map-wrapper {
flex: 1 1 auto;
.map-control {
position: absolute;
top: 5px;
left: 5px;
z-index: 1000;
}
}
}
</style>
- moving activeSegment from data() to computed. this imaginary variable returns the value of segState.target.
- rendering different control buttons based on path creation and path modification states
2
later...