β Form Validation
OnigiriJS includes a comprehensive validation system with built-in rules and support for custom validators.
Basic Usage
const form = O('#registration-form');
const result = form.validate({
username: {
required: true,
minLength: 3,
maxLength: 20,
alphanumeric: true
},
email: {
required: true,
email: true
},
password: {
required: true,
minLength: 8
},
age: {
required: true,
numeric: true,
min: 18,
max: 120
},
website: {
url: true // Optional field
}
});
if (result.isValid) {
// Form is valid, submit
submitForm();
} else {
// Show errors
Object.keys(result.errors).forEach(field => {
const errors = result.errors[field];
console.log(\`\${field}: \${errors.join(', ')}\`);
});
}
Built-in Validation Rules
Required
username: {
required: true
}
// Error message: "This field is required"
email: {
required: true,
email: true
}
// Validates: user@example.com
// Invalid: user@, @example.com, user
URL
website: {
url: true
}
// Validates: https://example.com
// Invalid: example.com, htp://example
Numeric
quantity: {
numeric: true
}
// Validates: 123, 0, 999
// Invalid: 12.5, abc, 12a
Alpha (Letters Only)
firstName: {
alpha: true
}
// Validates: John, Mary
// Invalid: John123, John-Paul
Alphanumeric
username: {
alphanumeric: true
}
// Validates: user123, JohnDoe
// Invalid: user-123, john_doe
Min/Max Value
age: {
required: true,
numeric: true,
min: 18,
max: 120
}
price: {
min: 0.01,
max: 9999.99
}
// Error messages:
// "Value must be at least 18"
// "Value must be at most 120"
Min/Max Length
password: {
required: true,
minLength: 8,
maxLength: 64
}
bio: {
maxLength: 500
}
// Error messages:
// "Must be at least 8 characters"
// "Must be at most 64 characters"
Pattern (Regex)
password: {
required: true,
minLength: 8,
pattern: '^(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])'
}
phoneNumber: {
pattern: '^\\d{3}-\\d{3}-\\d{4}$'
}
// Error message: "Invalid format"
Custom Validation Rules
Adding Custom Rules
// Add a custom rule
Onigiri.validation.addRule(
'strongPassword',
function(value) {
const hasUpperCase = /[A-Z]/.test(value);
const hasLowerCase = /[a-z]/.test(value);
const hasNumber = /[0-9]/.test(value);
const hasSpecial = /[!@#$%^&*]/.test(value);
return hasUpperCase && hasLowerCase && hasNumber && hasSpecial;
},
'Password must contain uppercase, lowercase, number, and special character'
);
// Use the custom rule
const result = form.validate({
password: {
required: true,
minLength: 8,
strongPassword: true
}
});
Rule with Parameters
// Custom rule that accepts parameters
Onigiri.validation.addRule(
'matchesField',
function(value, fieldName) {
const otherField = document.querySelector(\`[name="\${fieldName}"]\`);
return otherField && value === otherField.value;
},
'Fields do not match'
);
// Usage
const result = form.validate({
password: {
required: true,
minLength: 8
},
confirmPassword: {
required: true,
matchesField: 'password'
}
});
Async Validation
// Async rule for username availability
Onigiri.validation.addRule(
'usernameAvailable',
async function(value) {
try {
const response = await Onigiri.get(\`/api/check-username/\${value}\`);
return response.available;
} catch (error) {
return false;
}
},
'Username is already taken'
);
// Use async validation
const result = await form.validate({
username: {
required: true,
minLength: 3,
alphanumeric: true,
usernameAvailable: true
}
});
Real-time Validation
Validate on Input
const form = O('#signup-form');
// Validate each field on input
form.on('input', 'input', function() {
const fieldName = this.name;
const value = this.value;
// Define rules for this field
const rules = {
[fieldName]: getFieldRules(fieldName)
};
// Validate
const result = Onigiri.validation.validate(
document.querySelector('#signup-form'),
rules
);
// Show/hide error
const errorEl = O(\`#\${fieldName}-error\`);
if (result.errors[fieldName]) {
errorEl.text(result.errors[fieldName][0]).show();
O(this).addClass('input-error');
} else {
errorEl.text('').hide();
O(this).removeClass('input-error');
}
});
function getFieldRules(fieldName) {
const allRules = {
username: {
required: true,
minLength: 3,
alphanumeric: true
},
email: {
required: true,
email: true
},
password: {
required: true,
minLength: 8
}
};
return allRules[fieldName] || {};
}
Validate on Blur
// Validate when user leaves field
O('#registration-form').on('blur', 'input', function() {
const fieldName = this.name;
const rules = { [fieldName]: getFieldRules(fieldName) };
const result = Onigiri.validation.validate(
document.querySelector('#registration-form'),
rules
);
if (result.errors[fieldName]) {
showFieldError(fieldName, result.errors[fieldName][0]);
} else {
clearFieldError(fieldName);
}
});
Complete Form Example
HTML
<form id="registration-form">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username">
<span class="error" id="username-error"></span>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email">
<span class="error" id="email-error"></span>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password">
<span class="error" id="password-error"></span>
</div>
<div class="form-group">
<label for="confirmPassword">Confirm Password</label>
<input type="password" id="confirmPassword" name="confirmPassword">
<span class="error" id="confirmPassword-error"></span>
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="number" id="age" name="age">
<span class="error" id="age-error"></span>
</div>
<button type="submit">Register π</button>
</form>
JavaScript
// Custom password matching rule
Onigiri.validation.addRule(
'matchesPassword',
function(value) {
const password = document.querySelector('[name="password"]').value;
return value === password;
},
'Passwords do not match'
);
const form = O('#registration-form');
// Real-time validation
form.on('input', 'input', function() {
const fieldName = this.name;
validateField(fieldName);
});
// Submit validation
form.on('submit', async function(e) {
e.preventDefault();
// Clear all errors
O('.error').text('').hide();
O('.input-error').removeClass('input-error');
// Validate entire form
const result = O(this).validate({
username: {
required: true,
minLength: 3,
maxLength: 20,
alphanumeric: true
},
email: {
required: true,
email: true
},
password: {
required: true,
minLength: 8,
pattern: '^(?=.*[A-Z])(?=.*[0-9])'
},
confirmPassword: {
required: true,
matchesPassword: true
},
age: {
required: true,
numeric: true,
min: 18,
max: 120
}
});
if (result.isValid) {
// Collect form data
const formData = {
username: O('[name="username"]').val(),
email: O('[name="email"]').val(),
password: O('[name="password"]').val(),
age: parseInt(O('[name="age"]').val())
};
// Submit with CSRF
try {
await Onigiri.post('/api/register', formData);
alert('π Registration successful!');
window.location = '/dashboard';
} catch (error) {
alert('Registration failed: ' + error.message);
}
} else {
// Show errors
Object.keys(result.errors).forEach(field => {
const errors = result.errors[field];
showFieldError(field, errors[0]);
});
// Focus first error
const firstError = Object.keys(result.errors)[0];
O(\`[name="\${firstError}"]\`).elements[0].focus();
}
});
function validateField(fieldName) {
const rules = getFieldRules(fieldName);
if (!rules) return;
const result = Onigiri.validation.validate(
document.querySelector('#registration-form'),
{ [fieldName]: rules }
);
if (result.errors[fieldName]) {
showFieldError(fieldName, result.errors[fieldName][0]);
} else {
clearFieldError(fieldName);
}
}
function showFieldError(fieldName, message) {
O(\`[name="\${fieldName}"]\`).addClass('input-error');
O(\`#\${fieldName}-error\`)
.text(message)
.show()
.css({ color: 'red', fontSize: '14px' });
}
function clearFieldError(fieldName) {
O(\`[name="\${fieldName}"]\`).removeClass('input-error');
O(\`#\${fieldName}-error\`).text('').hide();
}
function getFieldRules(fieldName) {
const allRules = {
username: {
required: true,
minLength: 3,
maxLength: 20,
alphanumeric: true
},
email: {
required: true,
email: true
},
password: {
required: true,
minLength: 8,
pattern: '^(?=.*[A-Z])(?=.*[0-9])'
},
confirmPassword: {
required: true,
matchesPassword: true
},
age: {
required: true,
numeric: true,
min: 18,
max: 120
}
};
return allRules[fieldName];
}
Advanced Patterns
Multi-Step Form Validation
const steps = ['step1', 'step2', 'step3'];
let currentStep = 0;
const stepRules = {
step1: {
firstName: { required: true, alpha: true },
lastName: { required: true, alpha: true }
},
step2: {
email: { required: true, email: true },
phone: { required: true, pattern: '^\\d{3}-\\d{3}-\\d{4}$' }
},
step3: {
password: { required: true, minLength: 8 },
confirmPassword: { required: true, matchesPassword: true }
}
};
O('.next-step').on('click', async () => {
const step = steps[currentStep];
const form = O(\`#\${step}\`);
const result = form.validate(stepRules[step]);
if (result.isValid) {
currentStep++;
showStep(currentStep);
} else {
showStepErrors(result.errors);
}
});
O('.prev-step').on('click', () => {
currentStep--;
showStep(currentStep);
});
Conditional Validation
// Only validate if checkbox is checked
O('#shipping-different').on('change', function() {
const isDifferent = this.checked;
if (isDifferent) {
O('#shipping-fields').show();
} else {
O('#shipping-fields').hide();
}
});
O('#checkout-form').on('submit', function(e) {
e.preventDefault();
const rules = {
billingAddress: { required: true },
billingCity: { required: true }
};
// Add shipping rules only if needed
if (O('#shipping-different').elements[0].checked) {
rules.shippingAddress = { required: true };
rules.shippingCity = { required: true };
}
const result = O(this).validate(rules);
if (result.isValid) {
submitOrder();
}
});
π Pro Tip: Combine validation with the security module for complete form protection - validation checks input integrity while CSRF protection verifies request authenticity!
Validation Best Practices
- Validate on both client and server side
- Provide clear, specific error messages
- Validate in real-time for better UX
- Focus the first field with an error
- Use visual indicators (colors, icons)
- Group related validation rules
- Sanitize input even after validation
- Test edge cases (empty, very long, special characters)
β Development Roadmap
Track the progress of OnigiriJS modules. Tasks are marked complete by the development team.
OnigiriJS Module Roadmap
Implementation progress of planned modules
6 / 21 completed (29%)
onigiri-state
Shared global & scoped state management
onigiri-directives
Declarative DOM bindings (o-show, o-model, etc.)
onigiri-resource
REST-style data models over AJAX
onigiri-observe
Intersection & Mutation observer helpers
onigiri-humhub-ui
Standard HumHub UI abstractions (modal, notify, confirm)
onigiri-lifecycle
Component lifecycle hooks
onigiri-guard
Debounce, throttle, single-run guards
onigiri-scroll
Scroll save/restore & helpers (PJAX-friendly)
onigiri-permission
Client-side permission awareness
onigiri-portal
DOM teleport / overlay mounting
onigiri-router
Micro router (non-SPA, PJAX-first)
onigiri-sanitize
HTML & input sanitization
onigiri-shortcut
Keyboard shortcut manager
onigiri-queue
Sequential async task runner
onigiri-gesture
Touch & swipe helpers
onigiri-devtools
Debugging & inspection helpers
onigiri-plugin
Plugin registration system
onigiri-time
Relative time & timezone utilities
onigiri-emojis
Emoji Picker and Manager
onigiri-tasks
Task Management
onigiri-polls
Polls creation and management
Note: Task completion is managed by the OnigiriJS development team.