Implement ticket selection with Stimulus controller and improve code documentation
- Add ticket selection functionality to event show page using Stimulus - Create ticket_selection_controller.js for handling ticket quantity changes - Update ticket card component and event show view to work with Stimulus - Add comprehensive comments to all JavaScript files for better maintainability - Remove dependent: :destroy from event ticket_types association
This commit is contained in:
@@ -1,31 +1,41 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
// Counter controller for animating number increments
|
||||
// Used for statistics and numerical displays that animate when they come into view
|
||||
export default class extends Controller {
|
||||
// Define controller values with defaults
|
||||
static values = {
|
||||
target: { type: Number, default: 0 },
|
||||
decimal: { type: Boolean, default: false },
|
||||
duration: { type: Number, default: 2000 }
|
||||
target: { type: Number, default: 0 }, // Target number to count to
|
||||
decimal: { type: Boolean, default: false }, // Whether to display decimal values
|
||||
duration: { type: Number, default: 2000 } // Animation duration in milliseconds
|
||||
}
|
||||
|
||||
// Set up the intersection observer when the controller connects
|
||||
connect() {
|
||||
// Create an intersection observer to trigger animation when element is visible
|
||||
this.observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
// Start animation when element is 50% visible
|
||||
if (entry.isIntersecting) {
|
||||
this.animate()
|
||||
// Stop observing after animation starts
|
||||
this.observer.unobserve(this.element)
|
||||
}
|
||||
})
|
||||
}, { threshold: 0.5 })
|
||||
|
||||
// Begin observing this element
|
||||
this.observer.observe(this.element)
|
||||
}
|
||||
|
||||
// Clean up the observer when the controller disconnects
|
||||
disconnect() {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
// Animate the counter from 0 to the target value
|
||||
animate() {
|
||||
// Find the target element with data-target-value
|
||||
const targetElement = this.element.querySelector('.stat-number');
|
||||
@@ -37,15 +47,17 @@ export default class extends Controller {
|
||||
const startValue = 0;
|
||||
const startTime = performance.now();
|
||||
|
||||
// Update counter function using requestAnimationFrame for smooth animation
|
||||
const updateCounter = (currentTime) => {
|
||||
const elapsedTime = currentTime - startTime;
|
||||
const progress = Math.min(elapsedTime / this.durationValue, 1);
|
||||
|
||||
// Easing function for smooth animation
|
||||
// Easing function for smooth animation (ease-out quartic)
|
||||
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
|
||||
|
||||
let currentValue = startValue + (this.targetValue - startValue) * easeOutQuart;
|
||||
|
||||
// Format value based on decimal setting
|
||||
if (this.decimalValue && this.targetValue < 10) {
|
||||
currentValue = currentValue.toFixed(1);
|
||||
} else {
|
||||
@@ -55,9 +67,11 @@ export default class extends Controller {
|
||||
// Update only the text content of the target element
|
||||
targetElement.textContent = currentValue;
|
||||
|
||||
// Continue animation until complete
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(updateCounter);
|
||||
} else {
|
||||
// Ensure final value is exactly the target
|
||||
const finalValue = this.decimalValue && this.targetValue < 10
|
||||
? this.targetValue.toFixed(1)
|
||||
: this.targetValue;
|
||||
@@ -65,6 +79,7 @@ export default class extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// Start the animation
|
||||
requestAnimationFrame(updateCounter);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user