JavaScript Coding Standards for Scenescape
Code Style
Linting & Formatting
- Linter: ESLint (config in
.github/resources/eslint.config.js) - Formatter: Prettier (config in
.github/resources/.prettierrc.json) - Commands:
make lint-javascript # Run ESLint make prettier-write # Auto-format all code make prettier-check # Check formatting
Indentation
- Use 2 spaces (never tabs)
- Consistent with Prettier configuration
Line Length
- Enforced by Prettier configuration
- Typically 80-100 characters
Semicolons
- Use semicolons consistently (enforced by Prettier)
Quotes
- Prefer double quotes for strings (enforced by Prettier)
Naming Conventions
- Classes:
PascalCase(e.g.,MapboxPlugin,GoogleMapsPlugin,MapInterface) - Functions/Methods:
camelCase(e.g.,generateSnapshot,saveSnapshotToServer) - Constants:
UPPER_SNAKE_CASE(e.g.,DEFAULT_ZOOM,MAP_CENTER) - Variables:
camelCase(e.g.,sceneData,cameraList) - Private members: Prefix with underscore
_(convention only, not enforced)
Architecture Patterns
Class-Based Components
Scenescape uses ES6 classes for map plugins and UI components:
class MapboxPlugin extends MapInterface {
constructor() {
super();
this.map = null;
this.accessToken = null;
}
async initialize(containerId, config = {}) {
// Implementation
}
generateSnapshot() {
// Implementation
}
}
Plugin Pattern
Map providers implement a common interface:
class MapInterface {
async initialize(containerId, config) {}
moveToLocation(input) {}
generateBounds() {}
generateSnapshot() {}
getBounds() {}
getCenter() {}
getZoom() {}
}
Async/Await
Prefer async/await over promises for readability:
// Good
async function fetchSceneData(sceneId) {
try {
const response = await fetch(`/api/v1/scenes/${sceneId}/`);
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch scene:", error);
return null;
}
}
// Avoid
function fetchSceneData(sceneId) {
return fetch(`/api/v1/scenes/${sceneId}/`)
.then((response) => response.json())
.catch((error) => console.error(error));
}
DOM Manipulation
jQuery Usage
Scenescape uses jQuery for DOM manipulation:
$(document).ready(function () {
$("#login-submit").on("click", handleLogin);
$(".roi-color").each(function () {
// Process each element
});
});
Event Handlers
// Attach handlers
$("#button-id").on("click", function (event) {
event.preventDefault();
// Handle click
});
// Delegate for dynamic elements
$(document).on("click", ".dynamic-class", function () {
// Handle click on dynamically added elements
});
Element Selection
// By ID
const element = document.getElementById("map");
const $element = $("#map");
// By class
const elements = document.querySelectorAll(".roi-item");
const $elements = $(".roi-item");
AJAX Requests
Fetch API with CSRF
async function saveData(data) {
const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]");
const response = await fetch("/api/v1/endpoint/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken.value,
},
body: JSON.stringify(data),
});
if (response.ok) {
const result = await response.json();
return result;
} else {
console.error("Request failed:", response.status);
return null;
}
}
FormData for File Uploads
const formData = new FormData();
formData.append("image_data", imageData);
formData.append("csrfmiddlewaretoken", csrfToken.value);
const response = await fetch("/api/v1/save-snapshot/", {
method: "POST",
headers: {
"X-CSRFToken": csrfToken.value,
},
body: formData,
});
Error Handling
Try-Catch with Async
async function processOperation() {
try {
const result = await riskyOperation();
console.log("Success:", result);
return result;
} catch (error) {
console.error("Operation failed:", error);
showErrorMessage(error.message);
return null;
}
}
Null Checks
// Good - early return
function processScene(scene) {
if (!scene) {
console.error("Scene is null");
return;
}
// Process scene
}
// Good - optional chaining
const cameraCount = scene?.cameras?.length ?? 0;
Canvas & Graphics
Canvas Manipulation
const canvas = document.createElement("canvas");
canvas.width = 1280;
canvas.height = 1280;
const ctx = canvas.getContext("2d");
// Draw image
ctx.drawImage(image, 0, 0);
// Get base64 data
const imageData = canvas.toDataURL("image/png");
SVG Manipulation
// Create SVG element
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "100");
svg.setAttribute("height", "100");
// Add to DOM
$("#svgout").append(svg);
Console Logging
Development Logging
// Debug information
console.log("Processing scene:", sceneId);
// Warnings
console.warn("Camera not found:", cameraId);
// Errors
console.error("Failed to load scene:", error);
// Structured data
console.log("Response:", { status, data, timestamp });
Production Considerations
Remove or guard verbose logging in production code:
if (DEBUG_MODE) {
console.log("Detailed debug info:", debugData);
}
Comments
Function Documentation
/**
* Generate a snapshot of the current map view
* @returns {Promise<string>} Base64-encoded image data
*/
async function generateSnapshot() {
// Implementation
}
Inline Comments
// Hide map controls before screenshot
const style = document.createElement("style");
style.textContent = `...`;
// Process each ROI point
points.forEach((point, index) => {
// Transform coordinates to map space
const transformed = transformPoint(point);
});
Common Patterns
Show/Hide Elements
// Show
$("#element").show();
$("#element").css("display", "block");
// Hide
$("#element").hide();
$("#element").css("display", "none");
Add/Remove Classes
$("#element").addClass("active");
$("#element").removeClass("disabled");
$("#element").toggleClass("selected");
Update Content
// Text
$("#message").text("Operation complete");
// HTML
$("#container").html("<div>New content</div>");
// Attributes
$("#image").attr("src", imageUrl);
Animation & Effects
// Fade in/out
$("#element").fadeIn(300);
$("#element").fadeOut(300);
// Scroll into view
element.scrollIntoView({ behavior: "smooth" });
Map Provider Integration
Mapbox
mapboxgl.accessToken = apiKey;
const map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/satellite-v9",
center: [lng, lat],
zoom: 15,
});
Google Maps
const map = new google.maps.Map(document.getElementById("map"), {
center: { lat, lng },
zoom: 15,
mapTypeId: "satellite",
});
File Organization
Manager Static Files
manager/src/static/js/
├── geospatial/
│ ├── mapbox-plugin.js
│ ├── google-maps-plugin.js
│ └── map-interface.js
└── sscape.js
Script Loading
Scripts are loaded via Django templates:
{% load static %}
<script src="{% static 'js/sscape.js' %}"></script>
<script src="{% static 'js/geospatial/mapbox-plugin.js' %}"></script>
Anti-Patterns to Avoid
❌ Don't use var:
// Bad
var count = 0;
// Good
const count = 0;
let counter = 0;
❌ Don't modify global scope unnecessarily:
// Bad
window.myGlobalVar = "value";
// Good - use module pattern or classes
const MyModule = {
value: "value",
};
❌ Don't use == for comparisons:
// Bad
if (value == null) {
}
// Good
if (value === null) {
}
if (value == null) {
} // OK only for null/undefined check
❌ Don't create functions in loops:
// Bad
for (let i = 0; i < items.length; i++) {
$("#item-" + i).on("click", function () {
process(i); // i will be wrong
});
}
// Good
items.forEach((item, index) => {
$(`#item-${index}`).on("click", () => process(index));
});
Browser Compatibility
- Target modern browsers (ES6+ support)
- Use polyfills where necessary
- Test in Chrome, Firefox, Edge, Safari
Performance Tips
- Minimize DOM queries (cache selectors)
- Use event delegation for dynamic content
- Debounce/throttle frequent events (scroll, resize)
- Load scripts at bottom of page or use
defer
// Cache selector
const $container = $("#container");
$container.addClass("active");
$container.append(element);
// Don't repeat
$("#container").addClass("active");
$("#container").append(element);