contact-form-demo.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // ==========================================================================
  2. // CONTACT FORM DEMO VERSION - contact-form-demo.js
  3. // Perfect for portfolios, GitHub Pages, and client demonstrations
  4. // Shows all functionality without needing a server
  5. // ==========================================================================
  6. document.addEventListener('DOMContentLoaded', function() {
  7. // Get main form elements
  8. const contactForm = document.getElementById('contactForm');
  9. const submitBtn = contactForm.querySelector('.form-submit-btn');
  10. const formSuccess = document.getElementById('form-success');
  11. const formError = document.getElementById('form-error');
  12. /**
  13. * Display field-specific error messages
  14. */
  15. function showFieldError(fieldName, message) {
  16. const errorElement = document.getElementById(`${fieldName}-error`);
  17. if (errorElement) {
  18. errorElement.textContent = message;
  19. errorElement.classList.add('show');
  20. }
  21. }
  22. /**
  23. * Clear all field error messages
  24. */
  25. function clearFieldErrors() {
  26. const errorElements = contactForm.querySelectorAll('.form-error');
  27. errorElements.forEach(element => {
  28. element.textContent = '';
  29. element.classList.remove('show');
  30. });
  31. }
  32. /**
  33. * Show success message with personalization
  34. */
  35. function showSuccessMessage(message) {
  36. formError.style.display = 'none';
  37. formSuccess.style.display = 'flex';
  38. if (message) {
  39. formSuccess.querySelector('span').textContent = message;
  40. }
  41. // Smooth scroll to success message
  42. setTimeout(() => {
  43. formSuccess.scrollIntoView({ behavior: 'smooth', block: 'center' });
  44. }, 100);
  45. }
  46. /**
  47. * Setup real-time validation for form fields
  48. */
  49. function setupRealTimeValidation() {
  50. const fields = {
  51. name: {
  52. element: contactForm.querySelector('#name'),
  53. validate: (value) => {
  54. if (!value.trim()) return 'Name is required';
  55. if (value.trim().length < 2) return 'Name must be at least 2 characters';
  56. return null;
  57. }
  58. },
  59. email: {
  60. element: contactForm.querySelector('#email'),
  61. validate: (value) => {
  62. if (!value.trim()) return 'Email is required';
  63. const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  64. if (!emailRegex.test(value)) return 'Please enter a valid email address';
  65. return null;
  66. }
  67. },
  68. phone: {
  69. element: contactForm.querySelector('#phone'),
  70. validate: (value) => {
  71. if (value.trim() && !/^[\+]?[1-9][\d\s\-\(\)]{0,15}$/.test(value)) {
  72. return 'Please enter a valid phone number';
  73. }
  74. return null;
  75. }
  76. },
  77. message: {
  78. element: contactForm.querySelector('#message'),
  79. validate: (value) => {
  80. if (!value.trim()) return 'Message is required';
  81. if (value.trim().length < 10) return 'Message must be at least 10 characters';
  82. if (value.trim().length > 2000) return 'Message must be less than 2000 characters';
  83. return null;
  84. }
  85. }
  86. };
  87. // Attach event listeners to each field
  88. Object.keys(fields).forEach(fieldName => {
  89. const field = fields[fieldName];
  90. const errorElement = document.getElementById(`${fieldName}-error`);
  91. // Validate on blur
  92. field.element.addEventListener('blur', function() {
  93. const error = field.validate(this.value);
  94. if (error) {
  95. showFieldError(fieldName, error);
  96. } else if (errorElement) {
  97. errorElement.textContent = '';
  98. errorElement.classList.remove('show');
  99. }
  100. });
  101. // Clear error on input if field becomes valid
  102. field.element.addEventListener('input', function() {
  103. if (errorElement && errorElement.classList.contains('show')) {
  104. const error = field.validate(this.value);
  105. if (!error) {
  106. errorElement.textContent = '';
  107. errorElement.classList.remove('show');
  108. }
  109. }
  110. });
  111. });
  112. // Privacy policy checkbox validation
  113. const privacyCheckbox = contactForm.querySelector('input[name="privacy"]');
  114. privacyCheckbox.addEventListener('change', function() {
  115. const errorElement = document.getElementById('privacy-error');
  116. if (this.checked && errorElement) {
  117. errorElement.textContent = '';
  118. errorElement.classList.remove('show');
  119. }
  120. });
  121. }
  122. /**
  123. * Handle form submission - DEMO SIMULATION
  124. */
  125. contactForm.addEventListener('submit', function(e) {
  126. e.preventDefault();
  127. // Clear previous messages
  128. clearFieldErrors();
  129. formSuccess.style.display = 'none';
  130. formError.style.display = 'none';
  131. // Get form data
  132. const formData = new FormData(contactForm);
  133. // Client-side validation
  134. let isValid = true;
  135. if (!formData.get('name').trim()) {
  136. showFieldError('name', 'Name is required');
  137. isValid = false;
  138. }
  139. if (!formData.get('email').trim()) {
  140. showFieldError('email', 'Email is required');
  141. isValid = false;
  142. } else {
  143. const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  144. if (!emailRegex.test(formData.get('email'))) {
  145. showFieldError('email', 'Please enter a valid email address');
  146. isValid = false;
  147. }
  148. }
  149. if (!formData.get('message').trim()) {
  150. showFieldError('message', 'Message is required');
  151. isValid = false;
  152. } else if (formData.get('message').trim().length < 10) {
  153. showFieldError('message', 'Message must be at least 10 characters');
  154. isValid = false;
  155. }
  156. if (!formData.get('privacy')) {
  157. showFieldError('privacy', 'You must accept the privacy policy');
  158. isValid = false;
  159. }
  160. // If validation fails, scroll to first error
  161. if (!isValid) {
  162. const firstError = contactForm.querySelector('.form-error.show');
  163. if (firstError) {
  164. firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
  165. }
  166. return;
  167. }
  168. // Simulate successful submission
  169. const userName = formData.get('name').trim();
  170. const userService = formData.get('service') || 'our services';
  171. // Personalized success message
  172. const messages = [
  173. `Thank you ${userName}! Your message has been received. We'll get back to you within 24 hours.`,
  174. `Hi ${userName}! Thanks for your interest in ${userService}. We'll contact you soon!`,
  175. `Great to hear from you, ${userName}! Your inquiry is important to us and we'll respond shortly.`
  176. ];
  177. const randomMessage = messages[Math.floor(Math.random() * messages.length)];
  178. showSuccessMessage(randomMessage);
  179. // Reset form after delay
  180. setTimeout(() => {
  181. contactForm.reset();
  182. updateCharCounter();
  183. }, 1500);
  184. });
  185. // Initialize validation
  186. setupRealTimeValidation();
  187. // Character counter for message textarea
  188. const messageTextarea = contactForm.querySelector('#message');
  189. const messageGroup = messageTextarea.closest('.form-group');
  190. const charCounter = document.createElement('div');
  191. charCounter.className = 'char-counter';
  192. charCounter.style.cssText = 'font-size: 12px; color: #6b7280; margin-top: 5px; text-align: right;';
  193. messageGroup.appendChild(charCounter);
  194. function updateCharCounter() {
  195. const length = messageTextarea.value.length;
  196. const maxLength = 2000;
  197. charCounter.textContent = `${length}/${maxLength} characters`;
  198. if (length > maxLength * 0.9) {
  199. charCounter.style.color = '#f59e0b';
  200. } else if (length > maxLength) {
  201. charCounter.style.color = '#dc3545';
  202. } else {
  203. charCounter.style.color = '#6b7280';
  204. }
  205. }
  206. messageTextarea.addEventListener('input', updateCharCounter);
  207. updateCharCounter();
  208. // Auto-resize textarea
  209. messageTextarea.addEventListener('input', function() {
  210. this.style.height = 'auto';
  211. this.style.height = Math.min(this.scrollHeight, 200) + 'px';
  212. });
  213. // Enhanced UX features
  214. const formInputs = contactForm.querySelectorAll('input, select, textarea');
  215. formInputs.forEach((input, index) => {
  216. // Focus styling
  217. input.addEventListener('focus', function() {
  218. this.closest('.form-group').classList.add('focused');
  219. });
  220. input.addEventListener('blur', function() {
  221. this.closest('.form-group').classList.remove('focused');
  222. });
  223. // Keyboard navigation
  224. if (input.type !== 'textarea') {
  225. input.addEventListener('keydown', function(e) {
  226. if (e.key === 'Enter') {
  227. e.preventDefault();
  228. const nextInput = formInputs[index + 1];
  229. if (nextInput) {
  230. nextInput.focus();
  231. } else {
  232. submitBtn.click();
  233. }
  234. }
  235. });
  236. }
  237. });
  238. // Auto-focus budget when service is selected
  239. const serviceSelect = contactForm.querySelector('#service');
  240. const budgetSelect = contactForm.querySelector('#budget');
  241. serviceSelect.addEventListener('change', function() {
  242. if (this.value && !budgetSelect.value) {
  243. setTimeout(() => budgetSelect.focus(), 100);
  244. }
  245. });
  246. // Analytics tracking (demo mode)
  247. let formStartTime = null;
  248. let fieldsInteracted = new Set();
  249. contactForm.addEventListener('focusin', function() {
  250. if (!formStartTime) {
  251. formStartTime = Date.now();
  252. }
  253. }, { once: true });
  254. formInputs.forEach(input => {
  255. input.addEventListener('input', function() {
  256. fieldsInteracted.add(this.name || this.id);
  257. });
  258. });
  259. contactForm.addEventListener('submit', function() {
  260. if (formStartTime) {
  261. const completionTime = Date.now() - formStartTime;
  262. }
  263. });
  264. });