2af477397e
I hope that coding results will be better and more consistent
343 lines
7.3 KiB
Markdown
343 lines
7.3 KiB
Markdown
---
|
|
description: 'Instructions for writing JavaScript following project conventions and ES6+ best practices'
|
|
applyTo: '**/*.js,**/*.mjs'
|
|
---
|
|
|
|
# JavaScript Development Instructions
|
|
|
|
Follow ES6+ best practices and project-specific patterns for JavaScript code.
|
|
|
|
## Module System
|
|
|
|
### ES6 Modules
|
|
Use ES6 import/export syntax:
|
|
|
|
```js
|
|
// Named exports
|
|
export const API = axios.create({ ... })
|
|
export function helper() { ... }
|
|
|
|
// Default export
|
|
export default {
|
|
messages: { ... }
|
|
}
|
|
|
|
// Imports
|
|
import API from '../utils/api'
|
|
import { createI18n } from 'vue-i18n'
|
|
```
|
|
|
|
## API Configuration (`utils/api.js`)
|
|
|
|
### Standard Axios Instance Pattern
|
|
```js
|
|
import axios from 'axios'
|
|
|
|
const API = axios.create({
|
|
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8180'
|
|
})
|
|
|
|
// Request interceptor - adds auth token
|
|
API.interceptors.request.use(
|
|
(config) => {
|
|
const token = localStorage.getItem('token')
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`
|
|
}
|
|
return config
|
|
},
|
|
(error) => Promise.reject(error)
|
|
)
|
|
|
|
// Response interceptor - handles 401
|
|
API.interceptors.response.use(
|
|
(response) => response,
|
|
(error) => {
|
|
if (error.response?.status === 401) {
|
|
localStorage.removeItem('token')
|
|
// Optional: redirect to login
|
|
}
|
|
return Promise.reject(error)
|
|
}
|
|
)
|
|
|
|
export default API
|
|
```
|
|
|
|
**Key points:**
|
|
- Single Axios instance for the entire app
|
|
- Auto-adds Authorization header from localStorage
|
|
- Auto-handles 401 responses
|
|
- Uses Vite environment variables
|
|
|
|
## Pinia Store Pattern (`stores/`)
|
|
|
|
### Standard Store Structure
|
|
```js
|
|
import { defineStore } from 'pinia'
|
|
import { createI18n } from 'vue-i18n'
|
|
import de from '@/locales/de'
|
|
import en from '@/locales/en'
|
|
|
|
export const i18n = createI18n({
|
|
legacy: false,
|
|
locale: localStorage.getItem('language') || 'de',
|
|
fallbackLocale: 'en',
|
|
messages: { de, en }
|
|
})
|
|
|
|
export const useLanguageStore = defineStore('language', {
|
|
state: () => ({
|
|
currentLanguage: localStorage.getItem('language') || 'de'
|
|
}),
|
|
actions: {
|
|
setLanguage(lang) {
|
|
this.currentLanguage = lang
|
|
i18n.global.locale.value = lang
|
|
localStorage.setItem('language', lang)
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
## Locale Files (`locales/de`, `locales/en`)
|
|
|
|
### Translation Object Structure
|
|
**Important**: Locale files use `.js` extension and export objects (not `.json`):
|
|
|
|
```js
|
|
// locales/de
|
|
export default {
|
|
char: 'Figur',
|
|
menu: {
|
|
Datasheet: 'Datenblatt',
|
|
Skill: 'Fertigkeiten'
|
|
},
|
|
export: {
|
|
selectTemplate: 'Vorlage wählen',
|
|
exportPDF: 'PDF Export',
|
|
exporting: 'Exportiere...',
|
|
pleaseSelectTemplate: 'Bitte Vorlage auswählen'
|
|
}
|
|
}
|
|
```
|
|
|
|
**Conventions:**
|
|
- Nested objects for grouping related translations
|
|
- camelCase for keys
|
|
- Always add to both `de` and `en` files simultaneously
|
|
- Keep structure identical between languages
|
|
|
|
## Async/Await Patterns
|
|
|
|
### Error Handling
|
|
```js
|
|
try {
|
|
const response = await API.get('/api/endpoint')
|
|
return response.data
|
|
} catch (error) {
|
|
console.error('Operation failed:', error)
|
|
throw error // or handle gracefully
|
|
}
|
|
```
|
|
|
|
### Multiple Parallel Requests
|
|
```js
|
|
const [characters, templates] = await Promise.all([
|
|
API.get('/api/characters'),
|
|
API.get('/api/pdf/templates')
|
|
])
|
|
```
|
|
|
|
## Browser APIs
|
|
|
|
### LocalStorage Usage
|
|
```js
|
|
// Save
|
|
localStorage.setItem('token', response.data.token)
|
|
localStorage.setItem('language', 'de')
|
|
|
|
// Retrieve
|
|
const token = localStorage.getItem('token')
|
|
const lang = localStorage.getItem('language') || 'de'
|
|
|
|
// Remove
|
|
localStorage.removeItem('token')
|
|
```
|
|
|
|
### Blob/File Handling
|
|
```js
|
|
// Create blob from response
|
|
const blob = new Blob([response.data], { type: 'application/pdf' })
|
|
const url = window.URL.createObjectURL(blob)
|
|
|
|
// Open in new window
|
|
const pdfWindow = window.open(url, '_blank')
|
|
|
|
// Clean up after use
|
|
setTimeout(() => window.URL.revokeObjectURL(url), 10000)
|
|
```
|
|
|
|
### URL Parameters
|
|
```js
|
|
// Build query string
|
|
const params = new URLSearchParams({
|
|
template: templateId,
|
|
showUserName: 'true'
|
|
})
|
|
const url = `/api/export?${params.toString()}`
|
|
|
|
// Extract from params
|
|
const queryParams = Object.fromEntries(params)
|
|
```
|
|
|
|
## Event Handling
|
|
|
|
### Debouncing/Throttling
|
|
For search inputs or resize events:
|
|
|
|
```js
|
|
let debounceTimer
|
|
function debounce(func, delay = 300) {
|
|
return (...args) => {
|
|
clearTimeout(debounceTimer)
|
|
debounceTimer = setTimeout(() => func(...args), delay)
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
const search = debounce(async (query) => {
|
|
const results = await API.get(`/api/search?q=${query}`)
|
|
}, 300)
|
|
```
|
|
|
|
### Cleanup
|
|
```js
|
|
// Save timer ID for cleanup
|
|
this.timer = setTimeout(() => { ... }, 5000)
|
|
|
|
// Clean up in component lifecycle
|
|
beforeUnmount() {
|
|
if (this.timer) clearTimeout(this.timer)
|
|
}
|
|
```
|
|
|
|
## Array/Object Operations
|
|
|
|
### Array Methods
|
|
```js
|
|
// Filter
|
|
const learned = skills.filter(s => s.Fertigkeitswert > 0)
|
|
|
|
// Map
|
|
const names = characters.map(c => c.name)
|
|
|
|
// Find
|
|
const char = characters.find(c => c.id === 18)
|
|
|
|
// Some/Every
|
|
const hasSkills = character.fertigkeiten.some(f => f.Fertigkeitswert > 0)
|
|
```
|
|
|
|
### Object Destructuring
|
|
```js
|
|
// Response destructuring
|
|
const { data, headers, status } = response
|
|
|
|
// Props destructuring
|
|
const { character, template, showUserName = false } = options
|
|
```
|
|
|
|
### Spread Operator
|
|
```js
|
|
// Merge objects
|
|
const merged = { ...defaults, ...userOptions }
|
|
|
|
// Copy array
|
|
const copy = [...originalArray]
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Loading State Management
|
|
```js
|
|
export default {
|
|
data() {
|
|
return {
|
|
isLoading: false,
|
|
data: null,
|
|
error: null
|
|
}
|
|
},
|
|
async created() {
|
|
await this.loadData()
|
|
},
|
|
methods: {
|
|
async loadData() {
|
|
this.isLoading = true
|
|
this.error = null
|
|
try {
|
|
const response = await API.get('/api/data')
|
|
this.data = response.data
|
|
} catch (error) {
|
|
this.error = error.message
|
|
} finally {
|
|
this.isLoading = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Form Validation
|
|
```js
|
|
validateForm() {
|
|
if (!this.selectedTemplate) {
|
|
alert(this.$t('export.pleaseSelectTemplate'))
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
async submit() {
|
|
if (!this.validateForm()) return
|
|
|
|
// Proceed with submission
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use `const` by default**, `let` when reassignment needed, never `var`
|
|
2. **Prefer arrow functions** for callbacks and short functions
|
|
3. **Use template literals** for string interpolation
|
|
4. **Handle promise rejections** with try/catch or .catch()
|
|
5. **Clean up timers and intervals** in component lifecycle
|
|
6. **Use optional chaining** `?.` for nested properties
|
|
7. **Use nullish coalescing** `??` instead of `||` for default values
|
|
8. **Keep functions small** and single-purpose
|
|
9. **Document complex logic** with comments
|
|
10. **Use meaningful variable names** - avoid single letters except loops
|
|
|
|
## Anti-Patterns to Avoid
|
|
|
|
❌ Don't use `var` - use `const` or `let`
|
|
❌ Don't ignore promise rejections
|
|
❌ Don't mutate function parameters
|
|
❌ Don't create memory leaks (clean up listeners, timers)
|
|
❌ Don't use `eval()` or `new Function()`
|
|
❌ Don't mix callbacks and promises
|
|
❌ Don't forget to handle edge cases (null, undefined, empty arrays)
|
|
❌ Don't use `==` - always use `===` for comparisons
|
|
|
|
## Environment Variables (Vite)
|
|
|
|
### Accessing Variables
|
|
```js
|
|
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8180'
|
|
const isDev = import.meta.env.DEV
|
|
const isProd = import.meta.env.PROD
|
|
```
|
|
|
|
**Convention**: Prefix all custom variables with `VITE_`
|