/** * ============================================================================= * DYNAMIC ARTIST NEWS API SCRIPT FOR WEBFLOW (CLONING & PAGINATION) * ============================================================================= * This script fetches news from a GraphQL API, populates a list, and * implements a "Load More" button for pagination. * * Instructions: * 1. List Element: Attribute [news="list"]. * 2. Template Item: Inside the list, one item with attribute [news="item"]. * Set this item to "display: none". * 3. Load More Button: A button or link with attribute [news="loadmore"]. * 4. Data Binding Attributes (inside template item): * - news="heading" * - news="image" * - news="link" * 5. Options (on the list element): * - [news-options-limit="9"]: Sets items per page. Defaults to 9. * ============================================================================= */ document.addEventListener('DOMContentLoaded', () => { // State management variables let currentPage = 1; let isFetching = false; // Prevents multiple simultaneous requests /** * Fetches artist news from the Hasura GraphQL API. * (This function remains unchanged) */ async function fetchArtistNews(endpoint, token, query, variables) { const headers = { 'Content-Type': 'application/json', Accept: 'application/json', }; if (token) { headers['Authorization'] = `Bearer ${token}`; } const payload = { query, variables }; try { const response = await axios.post(endpoint, payload, { headers }); const responseData = response.data; if (responseData.errors) { console.error('GraphQL API returned errors:', responseData.errors); throw new Error('GraphQL query failed.'); } let artistNewsData = responseData.data; if ( artistNewsData?.getArtistNews?.metadata && typeof artistNewsData.getArtistNews.metadata === 'string' ) { try { artistNewsData.getArtistNews.metadata = JSON.parse( artistNewsData.getArtistNews.metadata ); } catch (parseError) { console.error('Failed to parse metadata string:', parseError); } } return artistNewsData; } catch (error) { console.error('API request failed:', error); throw new Error('API request failed. Check browser console for details.'); } } /** * Populates the DOM, now with an 'append' option. * (This function remains unchanged) */ function populateNewsItems(articles, append = false) { const template = document.querySelector('[news="item"]'); const list = document.querySelector('[news="list"]'); if (!template || !list) { console.error( 'Cloning failed: Template [news="item"] or List [news="list"] not found.' ); return; } if (!append) { const children = Array.from(list.children); children.forEach(child => { if (child !== template) { list.removeChild(child); } }); } articles.forEach(article => { const clone = template.cloneNode(true); clone.style.display = 'flex'; const headingEl = clone.querySelector('[news="heading"]'); const imageEl = clone.querySelector('[news="image"]'); const linkEl = clone.querySelector('[news="link"]'); if (headingEl) { headingEl.textContent = article.title; if (headingEl.tagName === 'A') { headingEl.href = article.link; } } if (imageEl && imageEl.tagName === 'IMG') { if (article.media_thumbnail_url) { imageEl.src = article.media_thumbnail_url; imageEl.srcset = article.media_thumbnail_url; } imageEl.alt = article.title; } if (linkEl && linkEl.tagName === 'A') { linkEl.href = article.link; } list.appendChild(clone); }); } /** * --- MODIFIED: Handles the logic for loading more articles. --- * Now accepts an 'event' object to prevent default browser behavior. */ async function loadMoreArticles(event) { // --- BUG FIX: Prevent the link/button's default scroll-to-top behavior --- event.preventDefault(); const loadMoreButton = document.querySelector('[news="loadmore"]'); if (!loadMoreButton || isFetching) { return; } isFetching = true; currentPage++; const originalButtonText = loadMoreButton.textContent; loadMoreButton.textContent = 'Loading...'; loadMoreButton.disabled = true; try { const newsListElement = document.querySelector('[news="list"]'); const limitAttribute = newsListElement.getAttribute('news-options-limit'); const determinedLimit = parseInt(limitAttribute, 10) || 9; const queryVariables = { dateFilter: '24hrs', limit: determinedLimit.toString(), page: currentPage.toString(), newsType: 'trending', }; const result = await fetchArtistNews( 'https://trusted-dassie-2137.ddn.hasura.app/graphql', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMuand0Lmhhc3VyYS5pbyI6eyJ4LWhhc3VyYS1yb2xlIjoicHVibGljIiwieC1oYXN1cmEtYWxsb3dlZC1yb2xlcyI6WyJwdWJsaWMiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoicHVibGljIn0sImlhdCI6MTc1MTg4MjMyNywiZXhwIjo0MDcwOTA4ODAwLCJ2ZXJzaW9uIjoxfQ.bsP-d6OQKrsxnGe93sATOuiN16B7dozvLPv-fniM308', ` query getArtistNews($dateFilter: String!, $limit: String!, $page: String!, $newsType: String) { getArtistNews(dateFilter: $dateFilter, limit: $limit, page: $page, newsType: $newsType) { success message metadata } } `, queryVariables ); const articles = result?.getArtistNews?.metadata?.data || []; if (articles.length > 0) { populateNewsItems(articles, true); } if (articles.length < determinedLimit) { loadMoreButton.style.display = 'none'; console.log('No more articles to load.'); } } catch (error) { console.error('❌ An error occurred while loading more articles:', error); } finally { isFetching = false; // Only restore button state if it hasn't been hidden if (loadMoreButton.style.display !== 'none') { loadMoreButton.textContent = originalButtonText; loadMoreButton.disabled = false; } } } /** * --- MODIFIED: Main function now correctly passes the event to the handler. --- * The addEventListener setup is what makes this work. */ async function main() { const newsListElement = document.querySelector('[news="list"]'); if (!newsListElement) { console.error( "Execution stopped: Element with attribute [news='list'] not found." ); return; } const loadMoreButton = document.querySelector('[news="loadmore"]'); if (loadMoreButton) { loadMoreButton.style.display = 'none'; } const API_ENDPOINT = 'https://trusted-dassie-2137.ddn.hasura.app/graphql'; const API_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMuand0Lmhhc3VyYS5pbyI6eyJ4LWhhc3VyYS1yb2xlIjoicHVibGljIiwieC1oYXN1cmEtYWxsb3dlZC1yb2xlcyI6WyJwdWJsaWMiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoicHVibGljIn0sImlhdCI6MTc1MTg4MjMyNywiZXhwIjo0MDcwOTA4ODAwLCJ2ZXJzaW9uIjoxfQ.bsP-d6OQKrsxnGe93sATOuiN16B7dozvLPv-fniM308'; const GQL_QUERY = ` query getArtistNews($dateFilter: String!, $limit: String!, $page: String!, $newsType: String) { getArtistNews(dateFilter: $dateFilter, limit: $limit, page: $page, newsType: $newsType) { success message metadata } } `; const defaultLimit = 9; const limitAttribute = newsListElement.getAttribute('news-options-limit'); const determinedLimit = parseInt(limitAttribute, 10) || defaultLimit; const queryVariables = { dateFilter: '24hrs', limit: determinedLimit.toString(), page: '1', newsType: 'trending', }; console.log( `Fetching initial artist news with a limit of ${determinedLimit}...` ); try { const result = await fetchArtistNews( API_ENDPOINT, API_TOKEN, GQL_QUERY, queryVariables ); const articles = result?.getArtistNews?.metadata?.data || []; if (articles.length > 0) { console.log( `Successfully fetched ${articles.length} articles. Populating page...` ); populateNewsItems(articles, false); console.log('Page population complete.'); if (loadMoreButton && articles.length === determinedLimit) { loadMoreButton.style.display = 'block'; // This syntax automatically passes the 'event' object to our function loadMoreButton.addEventListener('click', loadMoreArticles); } } else { console.log( 'No news articles were returned from the API for the initial load.' ); } } catch (error) { console.error( '❌ An error occurred during the initial news fetch:', error ); } } main(); });