≑ Menu

Have you ever wanted to add a basic comment section to your static web pages or quickly prototype a discussion feature?

While a full-fledged, persistent commenting system requires a backend server and a database, you can create a surprisingly functional front-end only version with just HTML, CSS, and JavaScript.

This allows users to post comments that are immediately visible on the page, perfect for demonstrations, simple internal tools, or learning the fundamentals of DOM manipulation.

Just remember, these comments won’t survive a page refresh!

Let’s dive into the code.

The Structure: HTML (index.html)

First, we’ll set up our basic HTML structure. We’ll need a section for the comment input form and another section to display the comments.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Simple Commenting System</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Leave a Comment</h1>
        <div class="comment-form">
            <input type="text" id="commenterName" placeholder="Your Name">
            <textarea id="commentText" placeholder="Your Comment"></textarea>
            <button id="postComment">Post Comment</button>
        </div>

        <div class="comments-section" id="commentsSection">
            <h2>Comments</h2>
            </div>
    </div>

    <script src="script.js"></script>
</body>
</html>

Key HTML Points:

  • comment-form: Contains input fields for the user’s name and comment text, plus a button to submit.
  • comments-section: This div (with id="commentsSection") is where our JavaScript will dynamically inject the posted comments.
  • link rel="stylesheet" and script src="script.js": These lines link our HTML to separate CSS and JavaScript files, keeping our code organized.

The Style: CSS (style.css)

Next, let’s make it look decent. Our CSS will provide basic styling for the layout, input fields, button, and individual comment boxes.

body {
    font-family: Arial, sans-serif;
    background-color: #f4f4f4;
    margin: 0;
    padding: 20px;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    min-height: 100vh;
}

.container {
    background-color: #fff;
    padding: 30px;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    width: 100%;
    max-width: 700px;
}

h1, h2 {
    color: #333;
    text-align: center;
    margin-bottom: 20px;
}

.comment-form {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 30px;
    border: 1px solid #ddd;
    padding: 20px;
    border-radius: 5px;
}

/* ... (rest of the CSS for inputs, buttons, comments, etc.) ... */

.comment {
    background-color: #f9f9f9;
    border: 1px solid #eee;
    padding: 15px;
    border-radius: 5px;
    margin-bottom: 15px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

.comment-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 8px;
    font-size: 0.9em;
    color: #555;
}

.comment-author {
    font-weight: bold;
    color: #007bff;
}

.comment-date {
    color: #888;
}

.comment-content {
    line-height: 1.6;
    color: #333;
    white-space: pre-wrap;
}

.no-comments {
    text-align: center;
    color: #777;
    font-style: italic;
    margin-top: 20px;
}

CSS Highlights:

  • Uses Flexbox for easy layout of the form and general page centering.
  • Provides clear styling for input fields, buttons, and individual comment cards.
  • Includes styles for a .no-comments class, which we’ll use for a placeholder message.

The Logic: JavaScript (script.js)

This is where the magic happens! Our JavaScript will handle user input, create new comment elements, and dynamically add them to the page.

document.addEventListener('DOMContentLoaded', () => {
    const commenterNameInput = document.getElementById('commenterName');
    const commentTextInput = document.getElementById('commentText');
    const postCommentButton = document.getElementById('postComment');
    const commentsSection = document.getElementById('commentsSection');

    // Function to display comments
    function displayComment(name, text, date) {
        // 1. Create the main comment div
        const commentDiv = document.createElement('div');
        commentDiv.classList.add('comment');

        // 2. Create header (author and date)
        const commentHeader = document.createElement('div');
        commentHeader.classList.add('comment-header');

        const commentAuthor = document.createElement('span');
        commentAuthor.classList.add('comment-author');
        commentAuthor.textContent = name || 'Anonymous'; // Default to Anonymous if no name

        const commentDate = document.createElement('span');
        commentDate.classList.add('comment-date');
        commentDate.textContent = date;

        commentHeader.appendChild(commentAuthor);
        commentHeader.appendChild(commentDate);

        // 3. Create comment content
        const commentContent = document.createElement('p');
        commentContent.classList.add('comment-content');
        commentContent.textContent = text; // Sets the actual comment text

        // 4. Assemble the full comment structure
        commentDiv.appendChild(commentHeader);
        commentDiv.appendChild(commentContent);

        // 5. Manage "No comments yet" message and insert new comment
        if (commentsSection.querySelector('.no-comments')) {
            // If the "No comments yet" message is there, remove it
            commentsSection.innerHTML = '<h2>Comments</h2>';
        }
        // Insert the new comment after the H2 heading (at index 0)
        // This puts new comments at the top (reverse chronological order)
        commentsSection.insertBefore(commentDiv, commentsSection.children[1]);
    }

    // Function to handle posting a comment when the button is clicked
    postCommentButton.addEventListener('click', () => {
        const name = commenterNameInput.value.trim();
        const text = commentTextInput.value.trim();

        if (text === '') {
            alert('Please enter a comment before posting.');
            return;
        }

        const now = new Date();
        const dateString = now.toLocaleString(); // Formats date and time nicely

        displayComment(name, text, dateString); // Call our function to add the comment to the DOM

        // Clear the form fields after posting
        commenterNameInput.value = '';
        commentTextInput.value = '';
    });

    // Function to display the "No comments yet" message
    function showNoCommentsMessage() {
        const noCommentsDiv = document.createElement('div');
        noCommentsDiv.classList.add('no-comments');
        noCommentsDiv.textContent = 'No comments yet. Be the first to comment!';
        commentsSection.appendChild(noCommentsDiv);
    }

    // Initial check: Show "No comments yet" if no user comments exist
    if (commentsSection.children.length <= 1) { // Remember, H2 is at index 0
        showNoCommentsMessage();
    }
});

JavaScript Breakdown:

  1. DOMContentLoaded: This ensures our script runs only after the entire HTML document is loaded, preventing errors from trying to select elements that don’t exist yet.

 

  1. Element Selection: We get references to our input fields, button, and the comments section using document.getElementById().

 

  1. displayComment(name, text, date) Function:
    • This function dynamically creates new HTML elements (div, span, p) for each comment.
    • It populates these elements with the provided name, text, and date.
    • Crucially, it handles the “No comments yet” message:
      • if (commentsSection.querySelector('.no-comments')): This checks if the .no-comments placeholder is currently visible.
      • commentsSection.innerHTML = '<h2>Comments</h2>';: If present, it clears the entire content of commentsSection (effectively removing the placeholder) and re-adds just the <h2> heading.
    • commentsSection.insertBefore(commentDiv, commentsSection.children[1]);: This is the key to displaying comments in reverse chronological order (newest first). Since commentsSection.children[0] is always our <h2> heading, inserting commentDiv before commentsSection.children[1] means the new comment goes right after the heading and before any previously added comments.
  2. postCommentButton Event Listener:
    • Attached to the “Post Comment” button, this function runs when clicked.
    • It retrieves the values from the input fields.
    • Performs a basic check to ensure the comment text isn’t empty.
    • Generates a timestamp using new Date().toLocaleString().
    • Calls displayComment() to add the new comment to the page.
    • Clears the input fields for the next comment.
  3. showNoCommentsMessage(): A utility function to create and append the “No comments yet” message.
  4. Initial Check:
    • if (commentsSection.children.length <= 1): When the page loads, this condition checks if the commentsSection only contains its <h2> heading (meaning no comments have been posted yet).
    • If true, showNoCommentsMessage() is called to display the initial placeholder.

How to Run This Code

  1. Create a new folder (e.g., my-comment-system).
  2. Inside this folder, create three files: index.html, style.css, and script.js.
  3. Copy and paste the respective code blocks above into their corresponding files.
  4. Open index.html in your web browser.

You now have a fully functional, front-end-only commenting system!

Going Further (for Persistent Comments)

As mentioned, this system is temporary. If you close the browser or refresh the page, the comments will disappear. To make them persistent, you would need to:

  • Backend Server: Use a technology like Node.js (with Express), Python (with Flask/Django), PHP, Ruby on Rails, etc.
  • Database: Store comments in a database like MongoDB, PostgreSQL, MySQL, SQLite.
  • API Endpoints: Your JavaScript would send Workspace requests to a POST endpoint on your server to save new comments and make GET requests to retrieve existing comments when the page loads.

This simple front-end system is an excellent starting point for understanding dynamic web content and client-side interactions. Happy coding!

{ 1 comment }

In today’s interconnected applications, it’s common to need data from various API endpoints.

Doing this efficiently and, more importantly, reliably is crucial.

I wanted to share a JavaScript async function I’ve been using that tackles this challenge head-on, ensuring concurrent data fetching and comprehensive error handling.

This function is a game-changer for scenarios where your application depends on data from several independent APIs and demands a robust way to manage potential failures.

async function fetchMultipleResources(urls) {
  try {
    const promises = urls.map(url => fetch(url));
    const responses = await Promise.all(promises);

    // Check if all responses are successful
    const allSuccess = responses.every(response => response.ok);

    if (!allSuccess) {
      const errorDetails = await Promise.all(responses.map(async response => {
        if (!response.ok) {
          return `${response.url}: ${response.status} - ${await response.text()}`;
        }
        return null;
      })).then(errors => errors.filter(error => error !== null).join(', '));
      throw new Error(`Failed to fetch all resources. Details: ${errorDetails}`);
    }

    // Parse the JSON data from all successful responses
    const data = await Promise.all(responses.map(response => response.json()));
    return data;
  } catch (error) {
    console.error('Error fetching resources:', error);
    throw error; // Re-throw the error for the calling function to handle
  }
}

How It Works and Why It’s Powerful

Let’s break down the key aspects that make this function so effective:

  • Concurrency with Promise.all(): The heart of its efficiency lies in Promise.all(). By mapping each URL to a Workspace promise and then awaiting them all simultaneously, we initiate and await multiple requests in parallel. This significantly boosts performance compared to fetching data sequentially, especially when dealing with many API calls.

  • Comprehensive Error Handling: This isn’t just about catching generic errors. The function specifically checks if all individual API requests were successful. If any fail, it goes the extra mile to gather detailed error information for each failure, including the URL, status code, and even the response body. This level of detail makes debugging much easier, as you immediately know what went wrong and where.

  • Data Integrity: By only proceeding with parsing the JSON data if allSuccess is true, we ensure that you only process data when all API calls are successful. This prevents unexpected issues that could arise from partial or incomplete data sets.

  • Ordered Results: A fantastic benefit is that the returned data array maintains the order of the input URLs. This makes it incredibly straightforward to associate the results with their original requests, eliminating any guesswork.

Example Usage

Here’s how you can put WorkspaceMultipleResources into action:

const apiEndpoints = [
  'https://jsonplaceholder.typicode.com/todos/1', // Example Todo API
  'https://jsonplaceholder.typicode.com/posts/1', // Example Post API
  'https://jsonplaceholder.typicode.com/users/1'  // Example User API
];

fetchMultipleResources(apiEndpoints)
  .then(results => {
    console.log('Successfully fetched data from all APIs:', results);

    // results will be an array containing the JSON data from each API in order
    console.log('Todo:', results[0]);
    console.log('Post:', results[1]);
    console.log('User:', results[2]);
  })
  .catch(error => {
    console.error('Failed to fetch data:', error);
  });

Note: The original example links were not publicly accessible, so I’ve substituted them with widely available JSON Placeholder API endpoints for demonstration purposes.


This WorkspaceMultipleResources function provides a robust and efficient solution for managing multiple API calls in your JavaScript applications. Have you encountered similar challenges in your projects, and what strategies have you found most effective?

{ 0 comments }