Building SaaS Website #10: Understanding Total.js Definition Files
Welcome back to our TotalGPT development series! In this installment, we'll explore definition files in Total.js v5 - a crucial aspect of organizing and structuring your application. Definition files help create clean, reusable workflows and define or modify framework features.
What Are Definition Files?
Definition files in Total.js reside in the /definitions
directory and serve as the configuration backbone of your application. They allow you to:
- Define global functionality
- Set up middleware and authentication
- Configure external services
- Create utility functions
- Initialize application settings
Common Definition Files Structure
/definitions/
├── auth.js # Authorization configuration
├── db.js # Database connections
├── localize.js # Localization settings
├── api.js # External API integrations
├── func.js # Utility functions
├── init.js # Application initialization
└── *.js # Other custom definitions
Creating Utility Functions with FUNC
// FUNC is a global object provided by Total.js
// It allows us to create functions that can be accessed from anywhere in our application
// Simple synchronous example
FUNC.calculate_total = function(price, quantity) {
// This function will be available globally as FUNC.calculate_total
return price * quantity;
};
// More complex synchronous example with callback
FUNC.get_list = function(callback) {
// Sample data - in real applications, this might come from a database
var list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// Check if callback exists before calling it
// The '&&' operator here is a shorthand way to check existence
// This is equivalent to: if(callback) { callback(list); }
callback && callback(list);
};
// Asynchronous example using Promises
FUNC.get_item = function(index) {
// Sample data array
var list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// Return a new Promise for asynchronous operation
return new Promise(function(resolve) {
// Simulate async operation by resolving with array item
resolve(list[index]);
});
};
Real-World Examples from TotalGPT
1. Country Data Loading
// definitions/func.js
// Function to load countries from a JSON file
FUNC.load_countries = function() {
// Total.Fs is the Total.js file system module
// PATH.public() gets the path to the public directory
Total.Fs.readFile(PATH.public('countries.json'), 'utf8', function(err, res) {
// Check for errors when reading file
if (err) {
console.error('Error loading countries:', err);
return;
}
// Parse JSON data
// The 'true' parameter makes the parsing more forgiving of malformed JSON
var data = res.parseJSON(true);
// Store countries in the global MAIN object for later use
// MAIN is another global object provided by Total.js
MAIN.countries = data;
});
};
// Framework initialization event
// This runs when Total.js is fully loaded and ready
ON('ready', function() {
// Load countries when the framework starts
FUNC.load_countries();
});
2. Authentication Workflow
// definitions/auth.js
// Define default admin user object
var User = {
id: 'admin', // Unique identifier
name: 'ADMIN', // Display name
sa: true // Super admin flag
};
// Authentication function
FUNC.auth = function($) {
// $ is the controller instance
// It contains request and response objects
// Check for token in headers or query parameters
// This provides flexibility in how the token can be sent
var token = $.headers['token'] || $.query.token;
// Validate token exists
if (!token) {
$.invalid(); // Return 401 Unauthorized
return;
}
// Compare with configured token
// CONF is a global object containing application configuration
if (token !== CONF.token) {
$.invalid(); // Return 401 Unauthorized
return;
}
// Get chat ID from query parameters (specific to TotalGPT)
var chatid = $.query.chatid;
// Add chat ID to user object
User.chatid = chatid;
// Return success with user object
$.success(User);
};
// Register the authentication function with the framework
AUTH(FUNC.auth);
3. Localization Configuration
// definitions/localize.js
// Define localization function
LOCALIZE(function($) {
// Check multiple sources for language preference:
// 1. Request language (from Accept-Language header)
// 2. Query parameter 'language'
// 3. Query parameter 'lang' (shorter alternative)
var lang = $.req.language || $.req.query.language || req.query.lang;
// Return the determined language code
// If none found, framework will use default language
return lang;
});
4. WhatsApp Integration
// definitions/api.js
// Global publish function for notifications
global.PUB = function(name, obj, broker, lang) {
// Use API endpoint to publish message
// 'publish' is the action name
// 'totalgpt_notify' is the endpoint
API('publish', 'totalgpt_notify', {
id: name, // Event identifier
data: obj, // Message data
broker: broker, // Message broker (e.g., 'redis')
lang: lang // Language code
}).callback(console.log); // Log response
};
// Function to send WhatsApp messages
FUNC.sendWhatsAppMessage = async function(phone, message, lang) {
return new Promise(function(resolve) {
// Prepare and send message
PUB('gpt_reply', {
chatid: phone.trim(), // Clean phone number
content: TRANSLATOR(lang || 'en', message), // Translate message
isvoice: false // Text message flag
}, 'redis', false);
// Resolve promise immediately
resolve();
});
};
// Function to send media messages
FUNC.sendWhatsAppMediaMessage = async function(phone, mediaUrl, message) {
return new Promise(function(resolve) {
// Prepare message object
var model = {
chatid: phone.trim(), // Clean phone number
type: 'url', // Media type
content: mediaUrl, // Media URL
caption: message // Optional caption
};
// Publish media message
PUB('gpt_media', model, 'redis', false);
resolve();
});
};
5. Application Initialization
// definitions/init.js
// Set framework configuration
CONF.$customtitles = true; // Enable custom titles
CONF.version = '1'; // App version
CONF.op_icon = 'ti ti-rss-square'; // Default icon
CONF.op_path = '/admin/'; // Admin panel path
// Framework ready event handler
ON('ready', function() {
// Load required UI components
// The 'true' parameter indicates these are required components
COMPONENTATOR('ui', [
'fileuploader', // File upload component
'locale', // Localization support
'exec', // Command execution
'page', // Page handling
// ... more components
].join(','), true);
// Initialize admin notification system
FUNC.notify_admin = function(message) {
// Split superadmin string into array of admin IDs
for (var m of CONF.superadmin.split(',')) {
// Send notification to each admin
PUB('gpt_reply', {
chatid: m.trim(), // Clean admin ID
content: message.toString(), // Convert message to string
isvoice: false // Text message flag
}, 'redis', false);
}
};
});
Best Practices
- Organization:
- Keep related functionality in appropriate definition files
- Use clear, descriptive function names
- Group similar utilities together
- Error Handling:
- Always include proper error handling in async functions
- Use callbacks or promises consistently
- Documentation:
- Comment complex logic
- Document function parameters and return values
- Explain any side effects
- Initialization:
- Use the
ON('ready')
event for setup tasks
- Keep configuration separate from logic
- Initialize in the correct order
Why Use Definition Files?
- Global Access: Functions defined in definition files are accessible throughout your application
- Organization: Keeps related functionality together
- Maintainability: Easier to update and maintain centralized utilities
- Reusability: Functions can be reused across different parts of your application
- Configuration: Central location for application settings and initialization
What's Next?
In our next blog post, we'll explore how to integrate payment gateways into our TotalGPT platform, building on the foundation we've created with our definition files.
Remember that well-organized definition files are crucial for maintaining a clean and scalable codebase. Take time to plan your utility functions and choose appropriate locations for different types of functionality.
Stay tuned for more Total.js development insights! 🚀