Overview
The BaseController class extends CoreController and provides a complete request handling layer for Loopar Framework applications. It manages routing, authentication, CRUD operations, rendering, and sidebar navigation.
Import
import { BaseController } from 'loopar';
Constructor
class MyController extends BaseController {
constructor(props) {
super(props);
}
}
Properties
Default action when no action is specified in the request
Whether the view should include sidebar navigation
Name of the document type this controller handles
Request data (POST body or query parameters)
Document name from request (for update/view/delete actions)
Current action being executed
Response data to be sent to client
HTTP method (GET, POST, PUT, DELETE)
Flag for preloaded data requests (‘true’ for API mode)
CRUD Actions
actionList()
Handles list view requests with pagination, filtering, and search.
HTTP Method: GET or POST
Route: /desk/{document}/list
// In controller
async actionList() {
const list = await loopar.getList(this.document, {
q: this.data.q,
filters: this.data.filters
});
return await this.render(list);
}
Features:
- Pagination with page state management
- Search query persistence in session
- Filtering by field values
- Returns rows and pagination metadata
Request Example:
// GET /desk/User/list?page=2&q[first_name]=John
// POST /desk/User/list
{
page: 2,
q: {
first_name: 'John',
disabled: 0
}
}
Response:
{
instance: 'user-list',
rows: [...],
pagination: {
page: 2,
pageSize: 10,
totalPages: 5,
totalRecords: 47
},
fields: ['name', 'email', 'first_name'],
labels: ['Name', 'Email', 'First Name']
}
actionCreate()
Handles document creation requests.
HTTP Method: GET (show form) or POST (save)
Route: /desk/{document}/create
// In controller
async actionCreate() {
const document = await loopar.newDocument(this.document, this.data);
if (this.hasData()) {
await document.save();
return this.redirect('update?name=' + document.name);
} else {
return await this.render(await document.__meta__());
}
}
GET Request (Show Form):
// GET /desk/User/create
// Returns form with empty document
POST Request (Create Document):
// POST /desk/User/create
{
name: 'newuser@example.com',
email: 'newuser@example.com',
first_name: 'New',
last_name: 'User'
}
// Response:
{
redirect: 'update?name=newuser@example.com'
}
Note: Single documents cannot be created (will throw 404 error).
actionUpdate()
Handles document update requests.
HTTP Method: GET (show form) or POST (save)
Route: /desk/{document}/update?name={name}
// In controller
async actionUpdate(document) {
document ??= await loopar.getDocument(
this.document,
this.name,
this.hasData() ? this.data : null
);
if (this.hasData()) {
await document.save();
return await this.success('Document saved successfully', {
name: document.name
});
} else {
return await this.render(await document.__meta__());
}
}
GET Request (Show Form):
// GET /desk/User/update?name=john@example.com
// Returns form populated with document data
POST Request (Update Document):
// POST /desk/User/update?name=john@example.com
{
first_name: 'Johnny',
last_name: 'Doe'
}
// Response:
{
status: 200,
success: true,
message: 'User john@example.com saved successfully',
name: 'john@example.com',
notify: {
type: 'success',
message: 'User john@example.com saved successfully'
}
}
actionView()
Handles read-only document view requests.
HTTP Method: GET
Route: /desk/{document}/view?name={name}
// In controller
async actionView() {
const document = await loopar.getDocument(this.document, this.name);
return await this.render(document);
}
actionDelete()
Handles document deletion requests.
HTTP Method: POST
Route: /desk/{document}/delete?name={name}
// In controller
async actionDelete() {
const document = await loopar.getDocument(this.document, this.name);
await document.delete();
return this.redirect('list');
}
Request:
// POST /desk/User/delete?name=olduser@example.com
// Response:
{
redirect: 'list'
}
actionBulkDelete()
Handles bulk deletion of multiple documents.
HTTP Method: POST
Route: /desk/{document}/bulk-delete
// In controller
async actionBulkDelete() {
const names = JSON.parse(this.names);
for (const name of names) {
const document = await loopar.getDocument(this.document, name);
await document.delete();
}
return this.success(`Documents ${names.join(', ')} deleted successfully`);
}
Request:
// POST /desk/User/bulk-delete
{
names: '["user1@example.com", "user2@example.com"]'
}
// Response:
{
status: 200,
success: true,
message: 'Documents user1@example.com, user2@example.com deleted successfully',
notify: {
type: 'success',
message: 'Documents deleted successfully'
}
}
Additional Actions
actionSearch()
Handles search requests for select elements.
HTTP Method: GET
Route: /desk/{document}/search?q={query}
// In controller
async actionSearch() {
const document = await loopar.newDocument(this.document);
return await document.getListToSelectElement(this.q);
}
Request:
// GET /desk/User/search?q=john
// Response:
{
title_fields: ['first_name', 'last_name'],
rows: [
{ name: 'john@example.com', first_name: 'John', last_name: 'Doe' },
{ name: 'johnny@example.com', first_name: 'Johnny', last_name: 'Smith' }
]
}
Returns sidebar navigation data.
HTTP Method: GET
Route: /desk/sidebar
// In controller
async actionSidebar() {
return { sidebarData: await CoreController.sidebarData() };
}
Rendering & Response
Renders a view with document metadata and UI configuration.
Document metadata and data
const document = await loopar.getDocument('User', 'john@example.com');
return await this.render(await document.__meta__());
Response Structure:
{
key: 'hash-of-route',
instance: 'user-form',
meta: {
title: 'User',
action: 'update'
},
entryPoint: 'user-form',
Entity: { ... },
data: { ... }
}
redirect(url)
Redirects to a different URL.
Relative or absolute URL to redirect to
return this.redirect('/desk/User/list');
// or
return this.redirect('update?name=newuser');
success(message, options)
Returns a success response with notification.
return this.success('Document saved successfully', {
name: document.name,
notify: {
type: 'success',
message: 'Saved!'
}
});
Response:
{
status: 200,
success: true,
message: 'Document saved successfully',
name: 'document-name',
notify: {
type: 'success',
message: 'Saved!'
}
}
error(message, options, status)
Returns an error response.
return this.error('Document not found', {}, 404);
notFound(options)
Returns a 404 not found response.
return await this.notFound({
code: 404,
title: 'Document Not Found',
description: 'The requested document does not exist'
});
Request Handling
sendAction(action)
Dispatches request to the appropriate action handler.
Action name (e.g., ‘list’, ‘create’, ‘update’)
const result = await controller.sendAction('list');
Process:
- Capitalizes action name (e.g., ‘list’ → ‘actionList’)
- Checks if action method exists
- Calls
beforeAction() hook
- Executes action method
- Returns result
beforeAction()
Hook called before any action executes. Override in subclass.
class MyController extends BaseController {
async beforeAction() {
// Custom authentication check
if (!this.isAuthenticated()) {
return this.redirect('/login');
}
}
}
hasData()
Checks if request contains data (POST body or query params).
if (this.hasData()) {
// Process submitted data
await document.save();
} else {
// Show form
return await this.render(meta);
}
Navigation & UI
Static method that returns sidebar navigation structure.
const sidebar = await BaseController.sidebarData();
// Returns loopar.modulesGroup with app/module hierarchy
clientImporter(document)
Determines the client-side entry point for a document.
const entryPoint = this.clientImporter(document);
// Returns: 'user-form', 'user-list', 'page-view', etc.
Entry Points:
{entity}-form: Create/Update actions
{entity}-list: List action
{entity}-view: View action or Page/View types
getKey(route)
Generates unique hash key for route caching.
const key = this.getKey('/desk/User/update?name=john');
getInstance(route)
Generates unique instance identifier for component.
const instance = this.getInstance('/desk/User/list');
// Returns: 'user-list'
Private File Serving
servePrivateFile(file)
Serves private files with access control.
await controller.servePrivateFile('document-attachment-123');
Custom Controller Example
import { BaseController } from 'loopar';
import { loopar } from 'loopar';
class OrderController extends BaseController {
defaultAction = 'list';
hasSidebar = true;
async beforeAction() {
// Custom authentication
if (!loopar.currentUser?.name) {
return this.redirect('/login');
}
}
// Override update to add custom logic
async actionUpdate() {
const document = await loopar.getDocument(
this.document,
this.name,
this.data
);
if (this.hasData()) {
// Custom validation
if (document.status === 'Submitted' && !this.canApprove()) {
return this.error('You do not have permission to approve orders');
}
await document.save();
// Send notification email
if (document.status === 'Submitted') {
await this.sendOrderNotification(document);
}
return this.success('Order saved successfully', {
name: document.name
});
}
return await this.render(await document.__meta__());
}
// Custom action
async actionApprove() {
const document = await loopar.getDocument(this.document, this.name);
if (!this.canApprove()) {
return this.error('Insufficient permissions', {}, 403);
}
document.status = 'Approved';
document.approved_by = loopar.currentUser.name;
document.approved_at = new Date();
await document.save();
return this.success('Order approved successfully');
}
canApprove() {
return loopar.currentUser?.role === 'Manager';
}
async sendOrderNotification(order) {
// Email notification logic
}
}
export default OrderController;
Complete Workflow Example
import { BaseController } from 'loopar';
// Extend BaseController
class UserController extends BaseController {
constructor(props) {
super(props);
}
}
// Usage in routes:
// GET /desk/User/list
// → Calls actionList()
// → Returns paginated user list
// GET /desk/User/create
// → Calls actionCreate()
// → Returns empty user form
// POST /desk/User/create with data
// → Calls actionCreate() with this.hasData() = true
// → Creates user, redirects to update
// GET /desk/User/update?name=john@example.com
// → Calls actionUpdate()
// → Returns form with user data
// POST /desk/User/update?name=john@example.com with data
// → Calls actionUpdate() with this.hasData() = true
// → Saves changes, returns success response
// POST /desk/User/delete?name=john@example.com
// → Calls actionDelete()
// → Deletes user, redirects to list