web-mojo
Version:
WEB-MOJO - A lightweight JavaScript framework for building data-driven web applications
352 lines (351 loc) • 10.7 kB
JavaScript
import { V as View } from "./WebApp-B2r2EDj7.js";
class Page extends View {
constructor(options = {}) {
options.tagName = options.tagName || "main";
options.className = options.className || "mojo-page";
const pageName = options.pageName || "";
if (pageName && !options.id) {
options.id = "page_" + pageName.toLowerCase().replace(/\s+/g, "_");
}
super(options);
this.pageName = options.pageName || this.constructor.pageName || "";
this.route = options.route || this.constructor.route || "";
this.title = options.title || this.pageName || "";
if (!this.id && this.constructor.pageName && !options.pageName) {
this.id = "page_" + this.constructor.pageName.toLowerCase().replace(/\s+/g, "_");
}
this.pageIcon = options.icon || options.pageIcon || this.constructor.pageIcon || "bi bi-file-text";
this.displayName = options.displayName || this.constructor.displayName || this.pageName || "";
this.pageDescription = options.pageDescription || this.constructor.pageDescription || "";
this.params = {};
this.query = {};
this.matched = false;
this.isActive = false;
this.pageOptions = {
title: options.title || this.pageName || "Untitled Page",
description: options.description || "",
requiresAuth: options.requiresAuth || false,
...options.pageOptions
};
this.savedState = null;
console.log(`Page ${this.pageName} constructed with route: ${this.route}`);
}
/**
* Handle route parameters - from design doc
* @param {object} params - Route parameters
* @param {object} query - Query string parameters
*/
async onParams(params = {}, query = {}) {
this.params = params;
this.query = query;
}
canEnter() {
if (this.options.permissions) {
const user = this.getApp().activeUser;
if (!user || !user.hasPermission(this.options.permissions)) {
return false;
}
}
if (this.options.requiresGroup && !this.getApp().activeGroup) {
return false;
}
return true;
}
/**
* Called when entering this page (before render)
* Override this method for initialization logic
*/
async onEnter() {
this.isActive = true;
await this.onInitView();
if (this.savedState) {
this.restoreState(this.savedState);
this.savedState = null;
}
if (this.pageOptions && this.pageOptions.title && typeof document !== "undefined") {
document.title = this.pageOptions.title;
}
this.emit("activated", {
page: this.getMetadata()
});
console.log(`Page ${this.pageName} entered`);
}
/**
* Called when leaving this page (before cleanup)
* Override this method for cleanup logic like removing listeners, clearing timers, etc.
*/
async onExit() {
this.savedState = this.captureState();
this.isActive = false;
this.emit("deactivated", {
page: this.getMetadata()
});
console.log(`Page ${this.pageName} exiting`);
}
/**
* Get page metadata for display and events
* @returns {object} Page metadata
*/
getMetadata() {
return {
name: this.pageName,
displayName: this.displayName || this.pageName,
icon: this.pageIcon,
description: this.pageDescription,
route: this.route,
isActive: this.isActive
};
}
/**
* Handle default action - fallback from design doc
*/
async onActionDefault(action) {
console.log(`Default action '${action}' triggered on page: ${this.pageName}`);
}
async makeActive() {
this.getApp().showPage(this);
}
async onActionNavigate(event, element) {
event.preventDefault();
const page = element.dataset.page;
this.getApp().showPage(page);
}
/**
* Capture current page state for preservation
* @returns {object|null} Captured state
*/
captureState() {
if (!this.element) return null;
return {
scrollTop: this.element.scrollTop,
formData: this.captureFormData(),
custom: this.captureCustomState()
};
}
/**
* Restore saved state
* @param {object} state - State to restore
*/
restoreState(state) {
if (!state || !this.element) return;
this.element.scrollTop = state.scrollTop || 0;
this.restoreFormData(state.formData);
if (state.custom) {
this.restoreCustomState(state.custom);
}
}
/**
* Capture form data from page
* @returns {object} Form data
*/
captureFormData() {
const data = {};
if (!this.element) return data;
this.element.querySelectorAll("input, select, textarea").forEach((field) => {
if (field.name) {
if (field.type === "checkbox") {
data[field.name] = field.checked;
} else if (field.type === "radio") {
if (field.checked) {
data[field.name] = field.value;
}
} else {
data[field.name] = field.value;
}
}
});
return data;
}
/**
* Restore form data to page
* @param {object} formData - Form data to restore
*/
restoreFormData(formData) {
if (!formData || !this.element) return;
Object.entries(formData).forEach(([name, value]) => {
const field = this.element.querySelector(`[name="${name}"]`);
if (field) {
if (field.type === "checkbox") {
field.checked = value;
} else if (field.type === "radio") {
const radio = this.element.querySelector(`[name="${name}"][value="${value}"]`);
if (radio) radio.checked = true;
} else {
field.value = value;
}
}
});
}
/**
* Capture custom state - override in subclasses
* @returns {object} Custom state
*/
captureCustomState() {
return {};
}
/**
* Restore custom state - override in subclasses
* @param {object} state - Custom state to restore
*/
restoreCustomState(state) {
}
/**
* Set page metadata
* @param {object} meta - Metadata object
*/
setMeta(meta = {}) {
if (typeof document === "undefined") {
return;
}
if (meta.title) {
document.title = meta.title;
this.pageOptions.title = meta.title;
}
if (meta.description) {
let descMeta = document.querySelector('meta[name="description"]');
if (!descMeta) {
descMeta = document.createElement("meta");
descMeta.name = "description";
document.head.appendChild(descMeta);
}
descMeta.content = meta.description;
this.pageOptions.description = meta.description;
}
Object.entries(meta).forEach(([key, value]) => {
if (key !== "title" && key !== "description") {
let metaEl = document.querySelector(`meta[name="${key}"]`);
if (!metaEl) {
metaEl = document.createElement("meta");
metaEl.name = key;
document.head.appendChild(metaEl);
}
metaEl.content = value;
}
});
}
/**
* Show error message with page context
* @param {string} message - Error message
*/
showError(message) {
super.showError(message);
if (this.element) {
const errorDiv = document.createElement("div");
errorDiv.className = "alert alert-danger alert-dismissible fade show";
errorDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
this.element.insertBefore(errorDiv, this.element.firstChild);
setTimeout(() => {
if (errorDiv.parentNode) {
errorDiv.parentNode.removeChild(errorDiv);
}
}, 5e3);
}
}
/**
* Show success message with page context
* @param {string} message - Success message
*/
showSuccess(message) {
super.showSuccess(message);
if (this.element) {
const successDiv = document.createElement("div");
successDiv.className = "alert alert-success alert-dismissible fade show";
successDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
this.element.insertBefore(successDiv, this.element.firstChild);
setTimeout(() => {
if (successDiv.parentNode) {
successDiv.parentNode.removeChild(successDiv);
}
}, 3e3);
}
}
/**
* Page-specific before render hook
*/
async onBeforeRender() {
await super.onBeforeRender();
this.setMeta({
title: this.pageOptions.title,
description: this.pageOptions.description
});
}
/**
* Page-specific after mount hook
*/
async onAfterMount() {
await super.onAfterMount();
if (typeof document !== "undefined" && this.pageName) {
document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\s+/g, "-")}`);
}
}
/**
* Page-specific before destroy hook
*/
async onBeforeDestroy() {
await super.onBeforeDestroy();
if (typeof document !== "undefined" && this.pageName) {
document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\s+/g, "-")}`);
}
}
/**
* Navigate to another page using the app's router
* @param {string} route - Route to navigate to
* @param {object} params - Route parameters
* @param {object} options - Navigation options
*/
navigate(route, params = {}, options = {}) {
if (this.app && this.app.router) {
return this.app.router.navigate(route, options);
}
if (typeof window !== "undefined" && window.MOJO?.router) {
return window.MOJO.router.navigate(route, options);
}
console.error("No router available for navigation");
}
getRoute() {
if (this.route) {
let route = this.route;
if (typeof route === "string" && route.startsWith("/")) {
route = route.substring(1);
}
return route;
}
return this.pageName;
}
syncUrl(force = true) {
this.updateBrowserUrl(this.query, false, false);
}
updateBrowserUrl(query = null, replace = false, trigger = false) {
this.getApp();
this.app.router.updateBrowserUrl(this.getRoute(), query, replace, trigger);
}
/**
* Static method to define a page class with metadata
* @param {object} definition - Page class definition
* @returns {class} Page class
*/
static define(definition) {
class DefinedPage extends Page {
constructor(options = {}) {
super({
...definition,
...options
});
}
}
DefinedPage.template = definition.template;
DefinedPage.pageName = definition.pageName;
DefinedPage.route = definition.route;
return DefinedPage;
}
}
export {
Page as P
};
//# sourceMappingURL=Page-tbcVc_Wl.js.map