Announcement
Launch Your Website Today – Just $300!
eman9's profile
eman9

November 27, 2025

ttj

<!doctype html>
<html lang="en">
<head>
 <meta charset="utf-8" />
 <title>Create Blog Post - Validation Demo</title>
 <meta name="viewport" content="width=device-width,initial-scale=1" />
 <style>
   body { font-family: Arial, sans-serif; padding: 20px; max-width:800px; margin:auto; }
   label{ display:block; margin-top:12px; font-weight:600 }
   input[type="text"], textarea, select { width:100%; padding:8px; margin-top:6px; box-sizing:border-box; }
   .small { font-size:0.9rem; color:#555; }
   .error { color:#b00020; font-size:0.9rem; margin-top:6px; }
   .ok { color:green; font-size:0.9rem; margin-top:6px; }
   button { margin-top:12px; padding:10px 16px; }
   .counters { margin-top:6px; font-size:0.9rem; color:#333; }
 </style>
</head>
<body>

 <h2>Create a Blog Post</h2>
 <p class="small">Fill in details below to publish your post.</p>

 <form id="blogForm" novalidate>
   <label for="title">Title</label>
   <input id="title" name="title" type="text" maxlength="150" placeholder="Enter title" />
   <div id="titleError" class="error" aria-live="polite"></div>

   <label for="content">Content</label>
   <textarea id="content" name="content" rows="8" maxlength="10000" placeholder="Write your post..."></textarea>
   <div class="counters">
     <span id="wordCount">Words: 0</span> &nbsp;|&nbsp; <span id="charCount">Characters: 0</span>
   </div>
   <div id="contentError" class="error" aria-live="polite"></div>

   <label for="category">Category</label>
   <select id="category" name="category">
     <option value="">-- Select category --</option>
     <option value="refrigerator">Refrigerator</option>
     <option value="dishwasher">Dishwasher</option>
     <option value="cleaning">Cleaning</option>
     <!-- add your categories -->
   </select>
   <div id="categoryError" class="error" aria-live="polite"></div>

   <button type="submit">Publish</button>
   <div id="formMessage" role="status" style="margin-top:10px;"></div>
 </form>

 <script>
   (function() {
     // Configurable rules
     const TITLE_MIN = 3;
     const TITLE_MAX = 150;
     const CONTENT_MIN_WORDS = 10;
     const CONTENT_MAX_CHARS = 10000;

     // Disallowed special characters (adjust as needed). Note: emojis are outside ASCII range and won't match here.
     // We purposely do NOT block unicode emoji ranges here.
     const DISALLOWED_CHARS_REGEX = /[<>\\{}[\]^%$#@*+=~`|]/;

     const form = document.getElementById('blogForm');
     const titleEl = document.getElementById('title');
     const contentEl = document.getElementById('content');
     const categoryEl = document.getElementById('category');

     const titleError = document.getElementById('titleError');
     const contentError = document.getElementById('contentError');
     const categoryError = document.getElementById('categoryError');
     const formMessage = document.getElementById('formMessage');

     const wordCountEl = document.getElementById('wordCount');
     const charCountEl = document.getElementById('charCount');

     // Utility: count words (splits on whitespace, ignores empty tokens)
     function countWords(text) {
       return (text.trim().split(/\s+/).filter(Boolean)).length;
     }

     // Basic client-side sanitization for display/preview; server must re-sanitize.
     function sanitizeForSubmission(text) {
       // replace angle brackets to avoid accidental HTML injection on client-side preview
       return text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
     }

     // Validate title
     function validateTitle() {
       const v = titleEl.value.trim();
       titleError.textContent = '';
       if (!v) {
         titleError.textContent = 'Title is required.';
         return false;
       }
       if (v.length < TITLE_MIN) {
         titleError.textContent = `Title must be at least ${TITLE_MIN} characters.`;
         return false;
       }
       if (v.length > TITLE_MAX) {
         titleError.textContent = `Title must not exceed ${TITLE_MAX} characters.`;
         return false;
       }
       if (DISALLOWED_CHARS_REGEX.test(v)) {
         titleError.textContent = 'Title contains invalid characters.';
         return false;
       }
       return true;
     }

     // Validate content
     function validateContent() {
       const v = contentEl.value;
       contentError.textContent = '';
       const words = countWords(v);
       const chars = v.length;
       if (!v.trim()) {
         contentError.textContent = 'Content is required.';
         return false;
       }
       if (words < CONTENT_MIN_WORDS) {
         contentError.textContent = `Content must have at least ${CONTENT_MIN_WORDS} words (current: ${words}).`;
         return false;
       }
       if (chars > CONTENT_MAX_CHARS) {
         contentError.textContent = `Content must not exceed ${CONTENT_MAX_CHARS} characters.`;
         return false;
       }
       if (DISALLOWED_CHARS_REGEX.test(v)) {
         contentError.textContent = 'Content contains invalid characters.';
         return false;
       }
       return true;
     }

     // Validate category
     function validateCategory() {
       categoryError.textContent = '';
       if (!categoryEl.value) {
         categoryError.textContent = 'Please select a category.';
         return false;
       }
       return true;
     }

     // Live counters
     function updateCounters() {
       const txt = contentEl.value;
       wordCountEl.textContent = 'Words: ' + countWords(txt);
       charCountEl.textContent = 'Characters: ' + txt.length;
     }

     // Hook events
     titleEl.addEventListener('input', () => {
       titleError.textContent = '';
     });
     contentEl.addEventListener('input', () => {
       contentError.textContent = '';
       updateCounters();
     });
     categoryEl.addEventListener('change', () => {
       categoryError.textContent = '';
     });

     // On submit
     form.addEventListener('submit', function(e) {
       e.preventDefault();
       formMessage.textContent = '';
       formMessage.className = '';

       const okTitle = validateTitle();
       const okContent = validateContent();
       const okCategory = validateCategory();

       if (!okTitle || !okContent || !okCategory) {
         formMessage.textContent = 'Please fix the highlighted errors and try again.';
         formMessage.className = 'error';
         return;
       }

       // Prepare data for sending (example)
       const payload = {
         title: sanitizeForSubmission(titleEl.value.trim()),
         content: sanitizeForSubmission(contentEl.value.trim()),
         category: categoryEl.value
       };

       // Example: show success and simulated submit (replace with actual fetch/AJAX)
       formMessage.textContent = 'Validation passed. Ready to submit.';
       formMessage.className = 'ok';

       // TODO: send payload to server using fetch/ajax and handle server-side validation/response
       // fetch('/api/posts', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) })
       //   .then(r => r.json()).then(...)

       console.log('Payload ready to submit:', payload);
     });

     // initialize counters
     updateCounters();

     // Optional: block paste of disallowed chars (UX improvement)
     contentEl.addEventListener('paste', function(ev) {
       // let paste data through, but we can sanitize or warn after paste in validateContent
       setTimeout(updateCounters, 50);
     });

   })();
 </script>
</body>
</html>
 

0

0

Share

Log in to comment.

Comments