| name | javascript |
| description | JavaScript coding standards for Scenescape — code style, conventions, and frontend patterns. |
JavaScript Coding Standards for Scenescape
Code Style
Linting & 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 = {}) {
}
generateSnapshot() {
}
}
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:
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;
}
}
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 () {
});
});
Event Handlers
$("#button-id").on("click", function (event) {
event.preventDefault();
});
$(document).on("click", ".dynamic-class", function () {
});
Element Selection
const element = document.getElementById("map");
const $element = $("#map");
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
function processScene(scene) {
if (!scene) {
console.error("Scene is null");
return;
}
}
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");
ctx.drawImage(image, 0, 0);
const imageData = canvas.toDataURL("image/png");
SVG Manipulation
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "100");
svg.setAttribute("height", "100");
$("#svgout").append(svg);
Console Logging
Development Logging
console.log("Processing scene:", sceneId);
console.warn("Camera not found:", cameraId);
console.error("Failed to load scene:", error);
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
async function generateSnapshot() {
}
Inline Comments
const style = document.createElement("style");
style.textContent = `...`;
points.forEach((point, index) => {
const transformed = transformPoint(point);
});
Common Patterns
Show/Hide Elements
$("#element").show();
$("#element").css("display", "block");
$("#element").hide();
$("#element").css("display", "none");
Add/Remove Classes
$("#element").addClass("active");
$("#element").removeClass("disabled");
$("#element").toggleClass("selected");
Update Content
$("#message").text("Operation complete");
$("#container").html("<div>New content</div>");
$("#image").attr("src", imageUrl);
Animation & Effects
$("#element").fadeIn(300);
$("#element").fadeOut(300);
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:
var count = 0;
const count = 0;
let counter = 0;
❌ Don't modify global scope unnecessarily:
window.myGlobalVar = "value";
const MyModule = {
value: "value",
};
❌ Don't use == for comparisons:
if (value == null) {
}
if (value === null) {
}
if (value == null) {
}
❌ Don't create functions in loops:
for (let i = 0; i < items.length; i++) {
$("#item-" + i).on("click", function () {
process(i);
});
}
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
const $container = $("#container");
$container.addClass("active");
$container.append(element);
$("#container").addClass("active");
$("#container").append(element);