# The Events Calendar v5.0.8 Best Practices Guide

## Overview
This guide documents the best practices for working with The Events Calendar (TEC) v5.0.8 and Community Events 5.0.8, based on comprehensive testing and field validation.

## Verified Working Configuration

### Plugin Versions (Staging Environment)
- **The Events Calendar**: 6.14.2
- **The Events Calendar Community Events**: 5.0.8  
- **Events Calendar Pro**: 7.6.3
- **WordPress**: Latest compatible version

## Field Selector Best Practices

### 1. Core Fields (Always Available)
These fields are standard WordPress fields and always work:

```javascript
const coreFields = {
    title: '#title',                    // Event title
    content: '#content',                // Event description
    excerpt: '#excerpt',                // Event excerpt
    status: '#post_status'              // Post status
};
```

### 2. Date/Time Fields (Verified Working)
```javascript
const dateTimeFields = {
    startDate: '#EventStartDate',       // Format: MM/DD/YYYY
    endDate: '#EventEndDate',           // Format: MM/DD/YYYY
    startTime: '#EventStartTime',       // Format: HH:MM am/pm
    endTime: '#EventEndTime',           // Format: HH:MM am/pm
    allDay: '#EventAllDay',             // Checkbox
    timezone: 'select[name="EventTimezone"]'  // Dropdown
};
```

### 3. Event Details (Verified Working)
```javascript
const eventDetails = {
    eventURL: '#EventURL',              // External event website
    showMap: '#EventShowMap',           // Checkbox
    showMapLink: '#EventShowMapLink'    // Checkbox
};
```

### 4. Cost Fields (May Be Hidden)
**Note**: Cost fields may be hidden if Events Tickets plugin is active.

```javascript
const costFields = {
    cost: '#EventCost',                 // May not be visible
    currencySymbol: '#EventCurrencySymbol',
    currencyCode: '#EventCurrencyCode'
};

// Check visibility before interacting
async function checkCostField(page) {
    const costField = await page.$('#EventCost');
    if (costField) {
        const isVisible = await costField.isVisible();
        if (!isVisible) {
            console.log('Cost field is hidden - Events Tickets may be active');
        }
    }
}
```

### 5. Venue Fields (Complex Selectors)
Venue fields use array notation in their name attributes:

```javascript
const venueFields = {
    venueDropdown: 'select[name="venue[VenueID]"]',
    venueName: 'input[name="venue[Venue]"]',
    venueAddress: 'input[name="venue[Address]"]',
    venueCity: 'input[name="venue[City]"]',
    venueState: 'input[name="venue[State]"]',
    venueZip: 'input[name="venue[Zip]"]',
    venueCountry: 'select[name="venue[Country]"]'
};
```

### 6. Organizer Fields (Complex Selectors)
```javascript
const organizerFields = {
    organizerDropdown: 'select[name="organizer[OrganizerID]"]',
    organizerName: 'input[name="organizer[Organizer]"]',
    organizerEmail: 'input[name="organizer[Email]"]',
    organizerPhone: 'input[name="organizer[Phone]"]',
    organizerWebsite: 'input[name="organizer[Website]"]'
};
```

## JavaScript Implementation Patterns

### Pattern 1: Flexible Field Access
Always check multiple possible selectors for maximum compatibility:

```javascript
async function getTECField(page, selectors) {
    for (const selector of selectors) {
        const element = await page.$(selector);
        if (element && await element.isVisible()) {
            return element;
        }
    }
    return null;
}

// Usage
const startDateField = await getTECField(page, [
    '#EventStartDate',
    'input[name="EventStartDate"]',
    '.tribe-datetime-block input[name*="StartDate"]'
]);
```

### Pattern 2: Safe Field Updates
Always verify field exists and is visible before updating:

```javascript
async function updateTECField(page, selector, value) {
    try {
        const element = await page.$(selector);
        if (!element) {
            console.log(`Field not found: ${selector}`);
            return false;
        }
        
        const isVisible = await element.isVisible();
        if (!isVisible) {
            console.log(`Field not visible: ${selector}`);
            return false;
        }
        
        await element.fill(value);
        return true;
    } catch (error) {
        console.log(`Error updating field: ${error.message}`);
        return false;
    }
}
```

### Pattern 3: Date Format Handling
TEC accepts multiple date formats. Be flexible:

```javascript
function formatDateForTEC(date) {
    // TEC typically expects MM/DD/YYYY in the UI
    const d = new Date(date);
    const month = String(d.getMonth() + 1).padStart(2, '0');
    const day = String(d.getDate()).padStart(2, '0');
    const year = d.getFullYear();
    return `${month}/${day}/${year}`;
}

// For meta updates (database), use YYYY-MM-DD HH:MM:SS
function formatDateForMeta(date) {
    const d = new Date(date);
    return d.toISOString().slice(0, 19).replace('T', ' ');
}
```

## PHP Implementation Patterns

### Pattern 1: Direct Meta Updates
When updating events programmatically:

```php
function update_tec_event($event_id, $data) {
    // Update core post data
    wp_update_post([
        'ID' => $event_id,
        'post_title' => $data['title'],
        'post_content' => $data['description']
    ]);
    
    // Update TEC meta fields
    update_post_meta($event_id, '_EventStartDate', $data['start_date']);
    update_post_meta($event_id, '_EventEndDate', $data['end_date']);
    update_post_meta($event_id, '_EventStartDateUTC', get_gmt_from_date($data['start_date']));
    update_post_meta($event_id, '_EventEndDateUTC', get_gmt_from_date($data['end_date']));
    
    // Calculate duration in seconds
    $start = strtotime($data['start_date']);
    $end = strtotime($data['end_date']);
    $duration = $end - $start;
    update_post_meta($event_id, '_EventDuration', $duration);
    
    // Update venue if provided
    if (!empty($data['venue_id'])) {
        update_post_meta($event_id, '_EventVenueID', $data['venue_id']);
    }
    
    // Update organizer if provided
    if (!empty($data['organizer_id'])) {
        update_post_meta($event_id, '_EventOrganizerID', $data['organizer_id']);
    }
}
```

### Pattern 2: Creating Events with Full Metadata
```php
function create_tec_event($data) {
    // Create the event post
    $event_id = wp_insert_post([
        'post_type' => 'tribe_events',
        'post_title' => $data['title'],
        'post_content' => $data['description'],
        'post_status' => 'publish',
        'post_author' => get_current_user_id()
    ]);
    
    if (!is_wp_error($event_id)) {
        // Add all TEC meta fields
        $meta_fields = [
            '_EventStartDate' => $data['start_date'],
            '_EventEndDate' => $data['end_date'],
            '_EventStartDateUTC' => get_gmt_from_date($data['start_date']),
            '_EventEndDateUTC' => get_gmt_from_date($data['end_date']),
            '_EventCost' => $data['cost'] ?? '',
            '_EventCurrencySymbol' => '$',
            '_EventURL' => $data['url'] ?? '',
            '_EventShowMap' => '1',
            '_EventShowMapLink' => '1',
            '_EventTimezone' => get_option('timezone_string'),
            '_EventTimezoneAbbr' => date('T'),
            '_EventOrigin' => 'events-calendar'
        ];
        
        foreach ($meta_fields as $key => $value) {
            update_post_meta($event_id, $key, $value);
        }
    }
    
    return $event_id;
}
```

## Testing Best Practices

### 1. Always Test Field Visibility
```javascript
// Before running tests, check which fields are actually available
async function auditTECFields(page) {
    const audit = {
        visible: [],
        hidden: [],
        missing: []
    };
    
    const allSelectors = {
        ...coreFields,
        ...dateTimeFields,
        ...eventDetails,
        ...costFields,
        ...venueFields,
        ...organizerFields
    };
    
    for (const [name, selector] of Object.entries(allSelectors)) {
        const element = await page.$(selector);
        if (!element) {
            audit.missing.push(name);
        } else if (await element.isVisible()) {
            audit.visible.push(name);
        } else {
            audit.hidden.push(name);
        }
    }
    
    return audit;
}
```

### 2. Test Data Persistence
Always verify that changes persist after save:

```javascript
async function testFieldPersistence(page, selector, testValue) {
    // Set value
    await page.fill(selector, testValue);
    
    // Save
    await page.click('#publish');
    await page.waitForSelector('.notice-success');
    
    // Reload page
    await page.reload();
    await page.waitForLoadState('networkidle');
    
    // Check if value persisted
    const currentValue = await page.$eval(selector, el => el.value);
    return currentValue === testValue;
}
```

### 3. Handle AJAX-Loaded Elements
Some TEC elements load via AJAX:

```javascript
async function waitForTECElements(page) {
    // Wait for TEC admin scripts to load
    await page.waitForFunction(() => {
        return window.tribe && window.tribe.events;
    }, { timeout: 10000 });
    
    // Additional wait for dynamic elements
    await page.waitForTimeout(1000);
}
```

## Common Issues and Solutions

### Issue 1: Date Format Inconsistencies
**Problem**: Different installations may use different date formats.
**Solution**: Check WordPress date format setting and adapt:

```javascript
async function getDateFormat(page) {
    return await page.evaluate(() => {
        // Check if TEC provides format
        if (window.tribe_events_calendar && window.tribe_events_calendar.date_format) {
            return window.tribe_events_calendar.date_format;
        }
        // Default to US format
        return 'MM/DD/YYYY';
    });
}
```

### Issue 2: Venue/Organizer Dropdowns Not Populating
**Problem**: Select2/Chosen dropdowns may not populate immediately.
**Solution**: Wait for initialization and trigger manually if needed:

```javascript
async function selectVenue(page, venueId) {
    // Wait for Select2 initialization
    await page.waitForFunction(() => {
        return jQuery && jQuery('select[name="venue[VenueID]"]').data('select2');
    }, { timeout: 5000 }).catch(() => {});
    
    // Try Select2 method first
    await page.evaluate((id) => {
        jQuery('select[name="venue[VenueID]"]').val(id).trigger('change');
    }, venueId);
    
    // Fallback to standard select
    await page.selectOption('select[name="venue[VenueID]"]', venueId).catch(() => {});
}
```

### Issue 3: Cost Field Hidden by Events Tickets
**Problem**: When Events Tickets is active, cost field is managed differently.
**Solution**: Check for alternative cost input methods:

```javascript
async function setCost(page, amount) {
    // Try standard cost field
    const standardField = await page.$('#EventCost');
    if (standardField && await standardField.isVisible()) {
        await standardField.fill(amount);
        return true;
    }
    
    // Check for Events Tickets price field
    const ticketField = await page.$('input[name="_tribe_ticket_price"]');
    if (ticketField && await ticketField.isVisible()) {
        await ticketField.fill(amount);
        return true;
    }
    
    console.log('Cost field not available - may need to create ticket');
    return false;
}
```

## Migration from Older Versions

### Key Changes from TEC v4.x to v5.x
1. **Block Editor Support**: v5.x includes Gutenberg block support
2. **Updated Meta Keys**: Some meta keys have changed format
3. **New REST API**: Enhanced REST API endpoints
4. **Modified Admin UI**: Updated admin interface selectors

### Backward Compatibility
Most v4.x selectors still work in v5.x, but always test:

```javascript
const legacySelectors = {
    // v4.x selectors that may still work
    'event-cost': '.tribe-event-cost input',
    'event-website': '.tribe-event-website input',
    'venue-select': '#saved_venue'
};
```

## Performance Optimization

### 1. Batch Meta Updates
When updating multiple fields, batch the updates:

```php
function batch_update_event_meta($event_id, $meta_data) {
    global $wpdb;
    
    // Begin transaction
    $wpdb->query('START TRANSACTION');
    
    try {
        foreach ($meta_data as $key => $value) {
            update_post_meta($event_id, $key, $value);
        }
        
        // Commit all changes
        $wpdb->query('COMMIT');
        
        // Clear TEC cache
        tribe_cache()->delete('', 'save_post');
        
        return true;
    } catch (Exception $e) {
        $wpdb->query('ROLLBACK');
        return false;
    }
}
```

### 2. Cache Field Selectors
Cache discovered selectors for better performance:

```javascript
class TECFieldCache {
    constructor() {
        this.cache = new Map();
    }
    
    async getField(page, fieldName, selectors) {
        // Check cache first
        if (this.cache.has(fieldName)) {
            const cached = this.cache.get(fieldName);
            const element = await page.$(cached);
            if (element && await element.isVisible()) {
                return element;
            }
        }
        
        // Find and cache working selector
        for (const selector of selectors) {
            const element = await page.$(selector);
            if (element && await element.isVisible()) {
                this.cache.set(fieldName, selector);
                return element;
            }
        }
        
        return null;
    }
}
```

## Security Considerations

### 1. Always Sanitize Input
```php
// Sanitize event data before saving
$clean_data = [
    'title' => sanitize_text_field($data['title']),
    'description' => wp_kses_post($data['description']),
    'start_date' => sanitize_text_field($data['start_date']),
    'cost' => floatval($data['cost']),
    'url' => esc_url_raw($data['url'])
];
```

### 2. Verify Capabilities
```php
// Check user can edit events
if (!current_user_can('edit_tribe_events')) {
    wp_die('Insufficient permissions');
}
```

### 3. Validate Nonces
```php
// Always verify nonces in AJAX handlers
if (!wp_verify_nonce($_POST['nonce'], 'tec_event_edit')) {
    wp_die('Security check failed');
}
```

## Debugging Tips

### 1. Enable TEC Debug Mode
Add to wp-config.php:
```php
define('TRIBE_EVENTS_DEBUG', true);
```

### 2. Log Field Discovery
```javascript
async function debugFieldDiscovery(page) {
    const results = await page.evaluate(() => {
        const fields = {};
        
        // Find all inputs with Event in name/id
        document.querySelectorAll('input[id*="Event"], input[name*="Event"]').forEach(el => {
            fields[el.id || el.name] = {
                type: el.type,
                value: el.value,
                visible: el.offsetParent !== null
            };
        });
        
        return fields;
    });
    
    console.log('Discovered fields:', JSON.stringify(results, null, 2));
    return results;
}
```

### 3. Monitor Meta Updates
```php
// Hook to log meta updates
add_action('updated_post_meta', function($meta_id, $object_id, $meta_key, $meta_value) {
    if (get_post_type($object_id) === 'tribe_events') {
        error_log("TEC Meta Update: Post $object_id, Key: $meta_key, Value: " . print_r($meta_value, true));
    }
}, 10, 4);
```

## Conclusion

Working with TEC v5.0.8 requires understanding:
1. The correct field selectors for each component
2. How to handle dynamic/AJAX-loaded elements
3. The proper meta key structure for database operations
4. Date format handling across different contexts
5. Compatibility considerations with other Events Calendar plugins

By following these best practices, you can reliably interact with TEC events both through the UI and programmatically.

## References
- [TEC Developer Documentation](https://theeventscalendar.com/knowledgebase/)
- [TEC GitHub Repository](https://github.com/the-events-calendar/the-events-calendar)
- [WordPress Codex - Custom Post Types](https://codex.wordpress.org/Post_Types)
- [WordPress Developer - Meta API](https://developer.wordpress.org/reference/functions/update_post_meta/)