Lesson 17: HTML Forms Part 3

Master file uploads, hidden fields, form security, and advanced form styling techniques

Start Learning

File Uploads

The <input type="file"> element allows users to select one or more files from their device storage to upload to a server.

Key Insight: When using file uploads, you must set the form's enctype attribute to multipart/form-data and the method to POST.

Basic File Upload

<form action="/upload" method="post" enctype="multipart/form-data">
  <div class="form-group">
    <label for="avatar">Upload Profile Picture:</label>
    <input type="file" id="avatar" name="avatar" accept="image/*">
  </div>
  <button type="submit">Upload</button>
</form>

Result:

Multiple File Upload

Add the multiple attribute to allow selecting multiple files:

<form action="/upload" method="post" enctype="multipart/form-data">
  <div class="form-group">
    <label for="photos">Upload Photos:</label>
    <input type="file" id="photos" name="photos" accept="image/*" multiple>
  </div>
  <button type="submit">Upload Files</button>
</form>

Result:

File Upload with Drag & Drop

Note: Drag and drop functionality requires JavaScript to handle the events.

<div id="drop-area" class="file-upload-container">
  <h4>Drag & Drop Files Here</h4>
  <p>or</p>
  <input type="file" id="file-input" multiple style="display:none;">
  <button class="file-upload-btn" onclick="document.getElementById('file-input').click()">
    Select Files
  </button>
  <div id="file-list" class="file-list"></div>
</div>

<script>
  const dropArea = document.getElementById('drop-area');
  const fileInput = document.getElementById('file-input');
  const fileList = document.getElementById('file-list');

  // Prevent default drag behaviors
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
    dropArea.addEventListener(eventName, preventDefaults, false);
  });

  function preventDefaults(e) {
    e.preventDefault();
    e.stopPropagation();
  }

  // Highlight drop area when item is dragged over it
  ['dragenter', 'dragover'].forEach(eventName => {
    dropArea.addEventListener(eventName, highlight, false);
  });
  
  ['dragleave', 'drop'].forEach(eventName => {
    dropArea.addEventListener(eventName, unhighlight, false);
  });

  function highlight() {
    dropArea.classList.add('drag-over');
  }

  function unhighlight() {
    dropArea.classList.remove('drag-over');
  }

  // Handle dropped files
  dropArea.addEventListener('drop', handleDrop, false);

  function handleDrop(e) {
    const dt = e.dataTransfer;
    const files = dt.files;
    handleFiles(files);
  }

  // Handle selected files from input
  fileInput.addEventListener('change', function() {
    handleFiles(this.files);
  });

  function handleFiles(files) {
    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      displayFile(file);
    }
  }

  function displayFile(file) {
    const fileItem = document.createElement('div');
    fileItem.className = 'file-item';
    
    const fileInfo = document.createElement('div');
    fileInfo.style.display = 'flex';
    fileInfo.style.alignItems = 'center';
    fileInfo.style.flex = '1';
    
    const fileIcon = document.createElement('i');
    fileIcon.className = 'fas fa-file file-icon';
    
    const fileName = document.createElement('div');
    fileName.className = 'file-name';
    fileName.textContent = file.name;
    
    const fileSize = document.createElement('div');
    fileSize.className = 'file-size';
    fileSize.textContent = formatFileSize(file.size);
    
    const removeBtn = document.createElement('button');
    removeBtn.className = 'remove-file';
    removeBtn.innerHTML = '';
    removeBtn.onclick = function() {
      fileItem.remove();
    };
    
    fileInfo.appendChild(fileIcon);
    fileInfo.appendChild(fileName);
    fileInfo.appendChild(fileSize);
    
    fileItem.appendChild(fileInfo);
    fileItem.appendChild(removeBtn);
    
    fileList.appendChild(fileItem);
  }

  function formatFileSize(bytes) {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  }
</script>

Result:

Drag & Drop Files Here

or

Hidden Fields

Hidden fields allow you to include data that isn't visible to the user but is submitted with the form.

Common Uses: Session tracking, passing security tokens, storing user IDs, or preserving state between pages.

Basic Hidden Field

<form action="/submit" method="post">
  <!-- Visible fields -->
  <div class="form-group">
    <label for="name">Name:</label>
    <input type="text" id="name" name="name" required>
  </div>
  
  <!-- Hidden field -->
  <input type="hidden" id="user-id" name="user_id" value="12345">
  
  <button type="submit">Submit</button>
</form>

Result:

Hidden fields are not visible to the user but are included in form submission.

Security: CSRF Tokens

Security Alert: Always include CSRF tokens in your forms to prevent Cross-Site Request Forgery attacks.

<form action="/update-profile" method="post">
  <div class="form-group">
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required>
  </div>
  
  <!-- CSRF Token -->
  <input type="hidden" name="csrf_token" value="a1b2c3d4e5f6g7h8i9j0">
  
  <button type="submit">Update Email</button>
</form>

Result:

CSRF tokens protect against malicious form submissions.

Advanced Form Styling

Create visually appealing forms with custom styling for form controls using CSS.

Custom Checkboxes

<style>
.custom-checkbox {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
  cursor: pointer;
}

.custom-checkbox input {
  position: absolute;
  opacity: 0;
  cursor: pointer;
  height: 0;
  width: 0;
}

.checkmark {
  height: 20px;
  width: 20px;
  background-color: #eee;
  border: 1px solid #ccc;
  border-radius: 4px;
  margin-right: 10px;
  position: relative;
  transition: all 0.2s;
}

.custom-checkbox:hover input ~ .checkmark {
  background-color: #ddd;
}

.custom-checkbox input:checked ~ .checkmark {
  background-color: var(--primary);
  border-color: var(--primary-dark);
}

.checkmark:after {
  content: "";
  position: absolute;
  display: none;
  left: 6px;
  top: 2px;
  width: 5px;
  height: 10px;
  border: solid white;
  border-width: 0 3px 3px 0;
  transform: rotate(45deg);
}

.custom-checkbox input:checked ~ .checkmark:after {
  display: block;
}
</style>

<div class="form-group">
  <label>Notification Preferences:</label>
  
  <label class="custom-checkbox">
    <input type="checkbox" name="email_notifications" checked>
    <span class="checkmark"></span>
    Email Notifications
  </label>
  
  <label class="custom-checkbox">
    <input type="checkbox" name="push_notifications">
    <span class="checkmark"></span>
    Push Notifications
  </label>
  
  <label class="custom-checkbox">
    <input type="checkbox" name="sms_notifications">
    <span class="checkmark"></span>
    SMS Notifications
  </label>
</div>

Result:

Custom Radio Buttons

<style>
.custom-radio {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
  cursor: pointer;
}

.custom-radio input {
  position: absolute;
  opacity: 0;
  cursor: pointer;
  height: 0;
  width: 0;
}

.radiomark {
  height: 20px;
  width: 20px;
  background-color: #eee;
  border: 1px solid #ccc;
  border-radius: 50%;
  margin-right: 10px;
  position: relative;
  transition: all 0.2s;
}

.custom-radio:hover input ~ .radiomark {
  background-color: #ddd;
}

.custom-radio input:checked ~ .radiomark {
  background-color: var(--primary);
  border-color: var(--primary-dark);
}

.radiomark:after {
  content: "";
  position: absolute;
  display: none;
  top: 5px;
  left: 5px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: white;
}

.custom-radio input:checked ~ .radiomark:after {
  display: block;
}
</style>

<div class="form-group">
  <label>Preferred Contact Method:</label>
  
  <label class="custom-radio">
    <input type="radio" name="contact" value="email" checked>
    <span class="radiomark"></span>
    Email
  </label>
  
  <label class="custom-radio">
    <input type="radio" name="contact" value="phone">
    <span class="radiomark"></span>
    Phone
  </label>
  
  <label class="custom-radio">
    <input type="radio" name="contact" value="mail">
    <span class="radiomark"></span>
    Postal Mail
  </label>
</div>

Result:

Custom Select Dropdown

<style>
.custom-select {
  position: relative;
  margin-bottom: 15px;
}

.custom-select select {
  appearance: none;
  background-color: white;
  padding: 10px 40px 10px 15px;
  width: 100%;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
}

.custom-select::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 15px;
  width: 0;
  height: 0;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-top: 6px solid var(--primary);
  transform: translateY(-50%);
  pointer-events: none;
}
</style>

<div class="form-group">
  <label for="country">Country:</label>
  <div class="custom-select">
    <select id="country" name="country">
      <option value="">Select a country</option>
      <option value="us">United States</option>
      <option value="ca">Canada</option>
      <option value="uk">United Kingdom</option>
      <option value="au">Australia</option>
      <option value="de">Germany</option>
      <option value="fr">France</option>
      <option value="jp">Japan</option>
    </select>
  </div>
</div>

Result:

Working with Form Data

JavaScript provides the FormData API for easily working with form data, especially when submitting forms via AJAX.

FormData API Example

<form id="user-form">
  <div class="form-group">
    <label for="username">Username:</label>
    <input type="text" id="username" name="username" required>
  </div>
  
  <div class="form-group">
    <label for="user-email">Email:</label>
    <input type="email" id="user-email" name="email" required>
  </div>
  
  <div class="form-group">
    <label for="user-avatar">Avatar:</label>
    <input type="file" id="user-avatar" name="avatar" accept="image/*">
  </div>
  
  <div class="form-actions">
    <button type="submit">Submit</button>
  </div>
</form>

<div id="form-data-output" style="margin-top: 20px; padding: 15px; background: #f9f9f9; border-radius: 8px;">
  <h4>Form Data Output:</h4>
  <pre id="output"></pre>
</div>

<script>
  document.getElementById('user-form').addEventListener('submit', function(e) {
    e.preventDefault();
    
    const form = e.target;
    const formData = new FormData(form);
    
    // Display form data
    let output = '';
    for (let [key, value] of formData.entries()) {
      output += `${key}: ${value}\n`;
    }
    document.getElementById('output').textContent = output;
    
    // In a real application, you would send the data to a server
    // Example using fetch:
    /*
    fetch('/submit', {
      method: 'POST',
      body: formData
    })
    .then(response => response.json())
    .then(data => {
      console.log('Success:', data);
    })
    .catch(error => {
      console.error('Error:', error);
    });
    */
  });
</script>

Result:

Form Data Output:


                                

Practical Exercise: File Upload Form

Create a file upload form with the following features:

Your Task:

Create a document upload form with:

  1. Personal information fields (name, email)
  2. Document type selection (dropdown)
  3. File upload with drag & drop support
  4. Custom styled checkboxes for consent
  5. CSRF token for security
  6. Form validation
  7. AJAX submission using FormData

Solution:

Drag & Drop Document Here

or