The Element class is the foundation for all question types in RoundtableJS. It provides a flexible structure for creating various survey elements, handling data, managing styles, and implementing validation. By extending this class, you can create custom question types tailored to your specific survey needs.

Element Class Structure

The Element class includes:

  • Core properties like id, type, store_data, and required
  • Data handling methods for responses and additional data
  • Styling methods for merging and applying custom styles
  • Utility methods for validation and error handling
  • Static properties and methods for data structure and style management

Creating Custom Elements

RoundtableJS allows you to create custom elements by extending the Element class. This feature provides flexibility to create question types tailored to your specific survey needs.

Step-by-Step Guide

  1. Extend the Element Class

    Start by creating a new class that extends Element:

    class CustomQuestion extends Element {
        // Your custom code here
    }
    
  2. Define Static Properties

    Specify the style keys for your custom element:

    static styleKeys = ['root', 'label', 'input', 'errorMessage'];
    
  3. Implement the Constructor

    In the constructor, call the parent constructor and initialize any custom properties:

    constructor({ id, text, options, ...otherProps }) {
        super({ id, type: 'custom-question', ...otherProps });
        this.text = text;
        this.options = options;
        // Initialize other custom properties
    }
    
  4. Implement generateHTML Method

    This method should return the HTML structure for your custom element:

    generateHTML() {
        return `
            <div class="custom-question" id="${this.id}-container">
                <label for="${this.id}">${this.text}</label>
                <!-- Add your custom HTML here -->
                <div id="${this.id}-error" class="error-message"></div>
            </div>
        `;
    }
    
  5. Implement attachEventListeners Method

    If your element needs to respond to user interactions, implement this method:

    attachEventListeners() {
        const element = document.getElementById(this.id);
        this.addEventListenerWithTracking(element, 'change', this.handleChange.bind(this));
    }
    
    handleChange(e) {
        // Handle the change event
        this.setResponse(e.target.value);
    }
    
  6. Implement Custom Validation (Optional)

    If your element requires custom validation, override the validate method:

    validate(showError = false) {
        // Implement your custom validation logic
        const isValid = /* your validation logic */;
        if (showError && !isValid) {
            this.showValidationError('Your custom error message');
        }
        return isValid;
    }
    
  7. Add Any Additional Custom Methods

    Include any other methods specific to your custom element:

    customMethod() {
        // Your custom functionality
    }
    

Example: Custom Rating Question

Here’s an example of a custom rating question that extends the Element class:

class RatingQuestion extends Element {
    static styleKeys = ['root', 'label', 'ratingContainer', 'ratingButton', 'selectedRating', 'errorMessage'];

    constructor({ id, text, maxRating = 5, ...otherProps }) {
        super({ id, type: 'rating-question', ...otherProps });
        this.text = text;
        this.maxRating = maxRating;
    }

    generateHTML() {
        const buttons = Array.from({ length: this.maxRating }, (_, i) => 
            `<button class="rating-button" data-rating="${i + 1}">${i + 1}</button>`
        ).join('');

        return `
            <div class="rating-question" id="${this.id}-container">
                <label for="${this.id}">${this.text}</label>
                <div class="rating-container">${buttons}</div>
                <div id="${this.id}-error" class="error-message"></div>
            </div>
        `;
    }

    attachEventListeners() {
        const container = document.querySelector(`#${this.id}-container .rating-container`);
        this.addEventListenerWithTracking(container, 'click', this.handleRatingClick.bind(this));
    }

    handleRatingClick(e) {
        if (e.target.classList.contains('rating-button')) {
            const rating = parseInt(e.target.dataset.rating);
            this.setResponse(rating);
            this.updateSelectedRating(rating);
        }
    }

    updateSelectedRating(rating) {
        const buttons = document.querySelectorAll(`#${this.id}-container .rating-button`);
        buttons.forEach(button => {
            if (parseInt(button.dataset.rating) <= rating) {
                button.classList.add('selected');
            } else {
                button.classList.remove('selected');
            }
        });
    }

    validate(showError = false) {
        const isValid = this.data.response !== undefined;
        if (showError && !isValid) {
            this.showValidationError('Please select a rating');
        }
        return isValid;
    }
}

This custom RatingQuestion element creates a rating scale with clickable buttons, handles user interactions, and implements custom validation.