Now that we have a solid settings foundation, let’s make our script interact with the WME map itself! This is where scripts become truly useful - by providing visual feedback and enhancing the editing experience.
In this section, we’ll create a segment highlighter that:
This introduces core concepts you’ll use in almost every WME script: event handling, map layers, geometry manipulation, and real-time visual feedback.
At this point we have a script that reads & loads some user information into a tab, and saves & loads the checked state of our Enabled checkbox. Now we’ll learn how to make your script react when you select segments on the map: it will highlight the selected segments in red, and remove the highlights when nothing is selected or the script is turned off.
Before you start, you’ll want to pick a name for your highlight layer and something to track if your event listener is active.
This makes your code easier to update later and helps keep track of things.
(function () {
'use strict';
const SCRIPT_NAME = GM_info.script.name; // Get the Script name from the @name tag of the UserScript Meta Keys
const STORAGE_KEY = 'wazedev_Settings'; // Local Storage key
let wmeSDK; // Declare wmeSDK globally
let settings = {}; // Global settings object
const SELECTED_LAYER_NAME = "wazedev_SelectedLayer"; // Declare map layer name globally
let selectionSubscription = null; // Declare variable to store the event listener state
// <rest of the code>
})();
What/Why:
Now, create a layer that will display your highlights. You only need to do this once when your script starts, we can put this inside the main() method after setupEventHandlers();
async function main() {
...
// Setup event handlers for checkboxes
setupEventHandlers();
// Add the Layer to the Map!
wmeSDK.Map.addLayer({
layerName: SELECTED_LAYER_NAME,
styleRules: [{
style: {
strokeColor: "red", // Highlight color (red)
strokeWidth: 12, // Thickness (12px)
strokeLinecap: "round", // Rounded ends
fillOpacity: 0 // No fill needed for lines
}
}]
});
console.log(`${SCRIPT_NAME}: Settings initialized!`);
}
💡 Tip: Full list of FeatureStyles can be found at SDK: Interface FeatureStyle
What/Why:
Now, set up your script to listen when a user selects (or unselects) something.
This should be done when your script is enabled (e.g. a checkbox is on).
function enableSelectionHighlighting() {
if (selectionSubscription) {
selectionSubscription(); // Safely cleanup previous before subscribing!
selectionSubscription = null;
}
selectionSubscription = wmeSDK.Events.on({
eventName: 'wme-selection-changed',
eventHandler: drawSelectionHighlight,
});
}
When your script is turned off, undo any listening and clear highlights:
function disableSelectionHighlighting() {
if (selectionSubscription) {
selectionSubscription(); // This unregisters the event handler!
selectionSubscription = null;
}
wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
}
What/Why:
This function actually does the work of displaying highlights.
When the selection changes, it erases the old highlight and draws a new one, if needed, but only if the Enable checkbox is “on”.
function drawSelectionHighlight() {
// Check if the script's "Enable" setting is ON
if (!settings.Enabled) {
wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
return;
}
// Get the current selection object from the SDK
const selection = wmeSDK.Editing.getSelection();
// Always remove all previous features from our layer
wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
// Check if there are any segments actively selected in the selection object
if (!selection || !selection.ids || selection.objectType !== 'segment') return;
// Add the selected segments as features to the layer
selection.ids.forEach((segmentId) => {
const segment = wmeSDK.DataModel.Segments.getById({ segmentId });
if (segment && segment.geometry) {
wmeSDK.Map.addFeatureToLayer({
layerName: SELECTED_LAYER_NAME,
feature: {
id: `highlighted-${segmentId}`,
type: 'Feature',
geometry: segment.geometry,
properties: {},
},
});
}
});
}
What/Why:
Connect highlighting to your script’s on/off setting.
Whenever your enable checkbox or option is changed, activate or deactivate highlighting.
function onEnabledCheckboxChanged(value) {
if (settings.Enabled) {
enableSelectionHighlighting();
drawSelectionHighlight();
} else {
disableSelectionHighlighting();
}
}
So now let’s hook it up in the setupEventHandlers() method:
function setupEventHandlers() {
// Find all checkboxes with the settings class in the DOM
const checkboxes = document.querySelectorAll('.wazedevSettingsCheckbox');
// For each settings checkbox, add a change event listener
checkboxes.forEach((checkbox) => {
checkbox.addEventListener('change', function () {
// Extract the setting name from the checkbox ID by removing the 'wazedev' prefix
// Example: 'wazedevEnabled' -> 'Enabled'
const settingName = this.id.substring(7);
// Update the corresponding value in the settings object
settings[settingName] = this.checked;
// Save the updated settings (persist changes, e.g., to storage)
saveSettings();
// If the 'Enabled' setting changed, trigger additional actions to update highlighting
if (settingName === 'Enabled') {
onEnabledCheckboxChanged(this.checked);
}
});
});
}
What/Why:
Whenever your script disables (checkbox off), disableSelectionHighlighting() makes sure:
What/Why:
Event-Driven Map Interaction:
wme-selection-changed)Map Layers and Styling:
Geometry and Features:
Resource Management:
After implementing these changes, test the functionality:
Expected Behavior:
Troubleshooting:
disableSelectionHighlighting() is called when disablingremoveAllFeaturesFromLayer() is called before adding new ones// ==UserScript==
// @name WazeDev First Script // Display name in Tampermonkey dashboard
// @namespace http://tampermonkey.net/ // Unique identifier (use your GreasyFork profile for updates)
// @version 0.1 // Version number for update tracking
// @description Learning to script! // Brief description of functionality
// @author You // Your name or username
// @include https://beta.waze.com/* // Run on beta WME (testing environment)
// @include https://www.waze.com/editor* // Run on production WME
// @include https://www.waze.com/*/editor* // Run on localized WME URLs
// @exclude https://www.waze.com/user/editor* // Don't run on user profile pages
// @exclude https://www.waze.com/editor/sdk/* // Don't run on SDK documentation
// @grant none // No special permissions needed
// ==/UserScript==
(function () {
'use strict';
const SCRIPT_NAME = GM_info.script.name; // Get the Script name from the @name tag of the UserScript Meta Keys
const STORAGE_KEY = 'wazedev_Settings'; // Local Storage key
let wmeSDK; // Declare wmeSDK globally
let settings = {}; // Global settings object
const SELECTED_LAYER_NAME = 'wazedev_SelectedLayer'; // Declare map layer name globally
let selectionSubscription = null; // Declare variable to store the event listener state
if (window.SDK_INITIALIZED) {
console.log(`${SCRIPT_NAME}: SDK initialized...`);
window.SDK_INITIALIZED.then(bootstrap).catch((err) => {
console.error(`${SCRIPT_NAME}: SDK initialization failed`, err);
});
} else {
console.warn(`${SCRIPT_NAME}: SDK_INITIALIZED is undefined`);
}
function bootstrap() {
try {
wmeSDK = getWmeSdk({
scriptId: SCRIPT_NAME.replaceAll(' ', ''),
scriptName: SCRIPT_NAME,
});
Promise.all([wmeReady()])
.then(() => {
console.log(`${SCRIPT_NAME}: All dependencies are ready.`);
init();
})
.catch((error) => {
console.error(`${SCRIPT_NAME}: Error during bootstrap`, error);
});
} catch (error) {
console.error(`${SCRIPT_NAME}: Failed to initialize SDK`, error);
}
}
function wmeReady() {
return new Promise((resolve) => {
if (wmeSDK.State.isReady()) {
console.log(`${SCRIPT_NAME}: WME is ready.`);
resolve();
} else {
console.log(`${SCRIPT_NAME}: Waiting for WME to be ready...`);
wmeSDK.Events.once({ eventName: 'wme-ready' })
.then(() => {
console.log(`${SCRIPT_NAME}: WME is ready now.`);
resolve();
})
.catch((error) => {
console.error(`${SCRIPT_NAME}: Error while waiting for WME to be ready:`, error);
});
}
});
}
function init() {
console.log(`${SCRIPT_NAME}: Script initialized successfully!`);
// Build HTML content for our tab
const section = document.createElement('div');
section.innerHTML = `
<div>
<h2>Our First Script!</h2>
<section>
<input type="checkbox" id="wazedevEnabled" class="wazedevSettingsCheckbox">
<label for="wazedevEnabled">Enable WazeDev Script</label>
</section>
<hr>
<section>
<h3>User Info</h3>
Username: <a href="#" id="wazedevUsernameLink" target="_blank"><span id="wazedevUsername"></span></a><br>
Rank: <span id="wazedevRank"></span><br>
Area manager: <span id="wazedevAM"></span><br>
Country manager: <span id="wazedevCM"></span>
</section>
<hr>
<section>
<h3>Detailed Profile</h3>
<div id="wazedevProfileLoading">Loading profile data...</div>
<div id="wazedevProfileData" style="display: none;">
Total Edits: <span id="wazedevTotalEdits"></span><br>
Today's Edits: <span id="wazedevTodayEdits"></span>
</div>
</section>
<hr>
<section id="wazedevEditsByType" style="display: none;">
<h3>Edits by Type</h3>
Segments: <span id="wazedevSegments"></span><br>
Venues: <span id="wazedevVenues"></span><br>
Map Problems: <span id="wazedevMapProblems"></span><br>
Update Requests: <span id="wazedevUpdateRequests"></span><br>
Place Update Requests: <span id="wazedevPlaceUpdateRequests"></span><br>
Segment House Numbers: <span id="wazedevSegmentHouseNumbers"></span>
</section>
</div>
`;
// Register the script tab using the SDK
wmeSDK.Sidebar.registerScriptTab()
.then(({ tabLabel, tabPane }) => {
tabLabel.textContent = 'WazeDev';
tabLabel.title = SCRIPT_NAME;
tabPane.appendChild(section);
// Initialize the main() function after tab is created
main();
})
.catch((error) => {
console.error(`${SCRIPT_NAME}: Error creating script tab`, error);
});
}
// Save settings to localStorage
function saveSettings() {
try {
const localSettings = { Enabled: settings.Enabled };
localStorage.setItem(STORAGE_KEY, JSON.stringify(localSettings));
console.log(`${SCRIPT_NAME}: Settings saved`, localSettings);
} catch (error) {
console.error(`${SCRIPT_NAME}: Error saving settings:`, error);
}
}
// Load settings from localStorage
function loadSettings() {
try {
const loadedSettings = JSON.parse(localStorage.getItem(STORAGE_KEY));
const defaultSettings = { Enabled: false };
settings = loadedSettings ? loadedSettings : defaultSettings;
// Add missing default settings
for (const prop in defaultSettings) {
if (!settings.hasOwnProperty(prop)) {
settings[prop] = defaultSettings[prop];
}
}
console.log(`${SCRIPT_NAME}: Settings loaded`, settings);
} catch (error) {
console.error(`${SCRIPT_NAME}: Error loading settings:`, error);
settings = { Enabled: false };
}
}
// Helper to set checkbox state
function setChecked(checkboxId, checked) {
const checkbox = document.getElementById(checkboxId);
if (checkbox) checkbox.checked = checked;
}
// Setup event handlers for our controls
function setupEventHandlers() {
// Find all checkboxes with the settings class in the DOM
const checkboxes = document.querySelectorAll('.wazedevSettingsCheckbox');
// For each settings checkbox, add a change event listener
checkboxes.forEach((checkbox) => {
checkbox.addEventListener('change', function () {
// Extract the setting name from the checkbox ID by removing the 'wazedev' prefix
// Example: 'wazedevEnabled' -> 'Enabled'
const settingName = this.id.substring(7);
// Update the corresponding value in the settings object
settings[settingName] = this.checked;
// Save the updated settings (persist changes, e.g., to storage)
saveSettings();
// If the 'Enabled' setting changed, trigger additional actions to update highlighting
if (settingName === 'Enabled') {
onEnabledCheckboxChanged(this.checked);
}
});
});
}
function enableSelectionHighlighting() {
if (selectionSubscription) {
selectionSubscription(); // Safely cleanup previous before subscribing!
selectionSubscription = null;
}
selectionSubscription = wmeSDK.Events.on({
eventName: 'wme-selection-changed',
eventHandler: drawSelectionHighlight,
});
}
function disableSelectionHighlighting() {
if (selectionSubscription) {
selectionSubscription(); // This unregisters the event handler!
selectionSubscription = null;
}
wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
}
function drawSelectionHighlight() {
// Check if the script's "Enable" setting is ON
if (!settings.Enabled) {
wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
return;
}
// Get the current selection object from the SDK
const selection = wmeSDK.Editing.getSelection();
// Always remove all previous features from our layer
wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
// Check if there are any segments actively selected in the selection object
if (!selection || !selection.ids || selection.objectType !== 'segment') return;
// Add the selected segments as features to the layer
selection.ids.forEach((segmentId) => {
const segment = wmeSDK.DataModel.Segments.getById({ segmentId });
if (segment && segment.geometry) {
wmeSDK.Map.addFeatureToLayer({
layerName: SELECTED_LAYER_NAME,
feature: {
id: `highlighted-${segmentId}`,
type: 'Feature',
geometry: segment.geometry,
properties: {},
},
});
}
});
}
function onEnabledCheckboxChanged(value) {
if (settings.Enabled) {
enableSelectionHighlighting();
drawSelectionHighlight();
} else {
disableSelectionHighlighting();
}
}
async function main() {
// Load saved settings first
loadSettings();
// Set the default state of the "Enable" checkbox at script startup
setChecked('wazedevEnabled', settings.Enabled);
// Continue with user info/profile update
const userSession = wmeSDK.State.getUserInfo();
// Set basic user info
document.getElementById('wazedevUsername').textContent = userSession.userName;
document.getElementById('wazedevRank').textContent = userSession.rank;
document.getElementById('wazedevAM').textContent = userSession.isAreaManager ? 'Yes' : 'No';
document.getElementById('wazedevCM').textContent = userSession.isCountryManager ? 'Yes' : 'No';
// Set profile link
const profileLink = wmeSDK.DataModel.Users.getUserProfileLink({ userName: userSession.userName });
document.getElementById('wazedevUsernameLink').href = profileLink;
// Fetch detailed user profile
try {
const userProfile = await wmeSDK.DataModel.Users.getUserProfile({ userName: userSession.userName });
document.getElementById('wazedevProfileLoading').style.display = 'none';
document.getElementById('wazedevProfileData').style.display = 'block';
document.getElementById('wazedevEditsByType').style.display = 'block';
document.getElementById('wazedevTotalEdits').textContent = userProfile.totalEditCount.toLocaleString();
const todayEdits = userProfile.dailyEditCount[userProfile.dailyEditCount.length - 1] || 0;
document.getElementById('wazedevTodayEdits').textContent = todayEdits;
const editTypes = userProfile.editCountByType;
document.getElementById('wazedevSegments').textContent = editTypes.segments.toLocaleString();
document.getElementById('wazedevVenues').textContent = editTypes.venues.toLocaleString();
document.getElementById('wazedevMapProblems').textContent = editTypes.mapProblems.toLocaleString();
document.getElementById('wazedevUpdateRequests').textContent = editTypes.updateRequests.toLocaleString();
document.getElementById('wazedevPlaceUpdateRequests').textContent = editTypes.placeUpdateRequests.toLocaleString();
document.getElementById('wazedevSegmentHouseNumbers').textContent = editTypes.segmentHouseNumbers.toLocaleString();
console.log(`${SCRIPT_NAME}: Profile data loaded successfully`);
} catch (error) {
console.error(`${SCRIPT_NAME}: Error fetching user profile:`, error);
document.getElementById('wazedevProfileLoading').textContent = 'Error loading profile data';
}
// Setup event handlers for checkboxes
setupEventHandlers();
// Add the Layer to the Map!
wmeSDK.Map.addLayer({
layerName: SELECTED_LAYER_NAME,
styleRules: [
{
style: {
strokeColor: 'red', // Highlight color (red)
strokeWidth: 12, // Thickness (12px)
strokeLinecap: 'round', // Rounded ends
fillOpacity: 0, // No fill needed for lines
},
},
],
});
onEnabledCheckboxChanged(settings.Enabled); // Activate script effect if previously enabled
console.log(`${SCRIPT_NAME}: Settings initialized!`);
}
})();
Then check out the final version of the script wiht JSDOC support here!