v2.0.8
ScopeJS es ese amigo desarrollador que siempre quisiste tener: confiable, ligero y que nunca te complica la vida. Perfecto para cuando quieres crear algo genial sin dramas.
No necesitas ser un ninja del desarrollo. Con ScopeJS, en 2 minutos ya estás creando maravillas 🎯
Para los que prefieren lo sencillo: copia, pega y a volar 🚀
<!-- En el <head> de tu HTML -->
<script src="https://unpkg.com/@pablotheblink/scopejs@2.0.8/js/ScopeJS.js"></script>
<script>
// Las funciones están disponibles globalmente
const MiComponente = ScopeJS.Component({
controller: class {
constructor() {
this.mensaje = "¡Hola Mundo!";
}
},
render() {
return `<h1>${this.mensaje}</h1>`;
}
});
// Renderizar cuando la página cargue
window.addEventListener('load', () => {
const container = document.getElementById('app');
MiComponente.render(container);
});
</script> Para los que les gusta presumir con ES6 modules y bundlers 😎
<!-- En el <head> de tu HTML -->
<script type="module">
import { Component, Modal, Router } from 'https://unpkg.com/@pablotheblink/scopejs@2.0.8/js/ScopeJS.js';
const MiComponente = Component({
controller: class {
constructor() {
this.mensaje = "¡Hola Mundo!";
}
},
render() {
return `<h1>${this.mensaje}</h1>`;
}
});
// Renderizar cuando la página cargue
window.addEventListener('load', () => {
const container = document.getElementById('app');
MiComponente.render(container);
});
</script> Directo desde la nube, más rápido que tu café matutino:
Para los proyectos serios que merecen lo mejor:
Aquí tienes un ejemplo completo de cómo estructurar tu página HTML:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi App con ScopeJS</title>
<!-- Opción 1: Script normal -->
<script src="js/ScopeJS.js"></script>
<!-- Opción 2: ES6 Module -->
<!-- <script type="module" src="js/app.js"></script> -->
</head>
<body>
<div id="app">
<!-- Tu aplicación se renderizará aquí -->
</div>
<script>
// Tu código de la aplicación aquí
const App = ScopeJS.Component({
controller: class {
constructor() {
this.titulo = "Mi Primera App ScopeJS";
this.contador = 0;
}
incrementar() {
this.contador++;
this.apply();
}
},
render() {
return `
<div style="text-align: center; padding: 2rem;">
<h1>${this.titulo}</h1>
<p>Contador: ${this.contador}</p>
<button onclick="incrementar()">
Incrementar
</button>
</div>
`;
}
});
// Inicializar cuando la página cargue
window.addEventListener('load', () => {
const container = document.getElementById('app');
App.render(container);
});
</script>
</body>
</html> Crea piezas de código que funcionan como un reloj suizo: precisas, elegantes y reutilizables. Porque la vida es muy corta para escribir código repetitivo 🕰️
Los componentes en ScopeJS son como ladrillos LEGO inteligentes que encapsulan todo lo que necesitas:
Botones, tarjetas, formularios... Todo lo que necesitas para impresionar.
Gráficos, métricas y paneles que hacen que los datos cobren vida.
Layouts complejos que se comunican entre sí como por arte de magia.
import { Component } from './js/ScopeJS.js';
// Definir el componente
const Counter = Component({
// Lógica del controlador
controller: class {
constructor() {
this.count = 0;
this.step = 1;
}
increment() {
this.count += this.step;
this.apply(); // Re-renderizar
}
decrement() {
this.count -= this.step;
this.apply();
}
reset() {
this.count = 0;
this.apply();
}
},
// Template HTML
render() {
return `
<div class="counter-widget">
<h3>Contador: ${this.count}</h3>
<div class="controls">
<button onclick="decrement()">-</button>
<button onclick="increment()">+</button>
</div>
<button onclick="reset()">Reset</button>
</div>
`;
},
// Estilos CSS scoped
style: `
padding: 1rem;
border: 2px solid #9333ea;
border-radius: 0.5rem;
text-align: center;
background: white;
`,
// Tag personalizado
tagName: 'my-counter'
});
// Usar el componente
const container = document.getElementById('app');
Counter.render(container); import { Component } from './js/ScopeJS.js';
const TodoList = Component({
controller: class {
constructor() {
this.todos = [];
this.newTodo = '';
this.filter = 'all';
}
addTodo() {
if (this.newTodo.trim()) {
this.todos.push({
id: Date.now(),
text: this.newTodo,
completed: false
});
this.newTodo = '';
this.apply();
}
}
toggleTodo(id) {
const todo = this.todos.find(t => t.id == id);
if (todo) {
todo.completed = !todo.completed;
this.apply();
}
}
deleteTodo(id) {
this.todos = this.todos.filter(t => t.id != id);
this.apply();
}
get filteredTodos() {
if (this.filter === 'completed') {
return this.todos.filter(t => t.completed);
}
if (this.filter === 'active') {
return this.todos.filter(t => !t.completed);
}
return this.todos;
}
},
render() {
return `
<div class="todo-app">
<h3>Lista de Tareas</h3>
<div class="add-todo">
<input type="text" model="newTodo"
placeholder="Nueva tarea...">
<button onclick="addTodo()">Agregar</button>
</div>
<div class="filters">
<button onclick="filter='all'; apply()"
class="${this.filter === 'all' ? 'active' : ''}">
Todas
</button>
<button onclick="filter='active'; apply()"
class="${this.filter === 'active' ? 'active' : ''}">
Activas
</button>
<button onclick="filter='completed'; apply()"
class="${this.filter === 'completed' ? 'active' : ''}">
Completadas
</button>
</div>
<ul class="todo-list">
${this.filteredTodos.map(todo => `
<li class="todo-item ${todo.completed ? 'completed' : ''}">
<input type="checkbox"
${todo.completed ? 'checked' : ''}
onclick="toggleTodo(${todo.id})">
<span>${todo.text}</span>
<button onclick="deleteTodo(${todo.id})">❌</button>
</li>
`).join('')}
</ul>
</div>
`;
},
style: `
padding: 1rem;
border: 2px solid #9333ea;
border-radius: 0.5rem;
background: white;
`,
tagName: 'todo-list'
}); ScopeJS no es de esos frameworks pesados que actualizan toda la página cada vez que cambias una coma. Nosotros somos más listos: solo tocamos lo que realmente ha cambiado. Como un cirujano del DOM.
Precisión ninja: Si cambias un texto, solo se actualiza ese texto. Nada más.
Eficiencia máxima: Si cambias una clase CSS, solo se modifica esa clase. Punto.
✨ Resultado: Animaciones fluidas, formularios que no se resetean y una experiencia de usuario que enamora
Con el atributo model los datos se sincronizan solos. ¡Magia pura!
Manejo de eventos que funciona como esperas, con parámetros y todo
Estilos que se portan bien y no molestan al resto
Re-renderizado eficiente solo cuando hace falta. Ni más, ni menos
Crea ventanas emergentes que no molestan, sino que encantan. Porque un modal feo es como una cita a ciegas mal planificada 💫
import { Modal } from './js/ScopeJS.js';
// Abrir modal simple
function openModal() {
Modal({
controller: class {
constructor() {
this.message = "¡Hola desde el modal!";
this.userName = "";
}
saveUser() {
if (this.userName.trim()) {
alert(`Usuario guardado: ${this.userName}`);
this.close();
}
}
},
render() {
return `
<div class="p-6">
<h3 class="text-xl font-bold mb-4">${this.message}</h3>
<div class="mb-4">
<label class="block text-sm font-medium mb-2">
Nombre de usuario:
</label>
<input type="text" model="userName"
class="w-full px-3 py-2 border rounded"
placeholder="Escribe tu nombre">
</div>
<div class="flex gap-2">
<button onclick="saveUser()"
class="bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700">
Guardar
</button>
<button onclick="close()"
class="bg-gray-300 text-gray-700 px-4 py-2 rounded hover:bg-gray-400">
Cancelar
</button>
</div>
</div>
`;
},
hideWhenClickOverlay: true
});
} Prueba los diferentes tipos de modales: normal (con overlay), redimensionable, ventanas arrastrables y múltiples ventanas simultáneas
Entrada y salida con estilo, nada de aparecer a lo bruto
Control total sobre todo: posición, comportamiento, estilo... Tú mandas
Se adaptan a cualquier pantalla como un camaleón digital
Modales que crecen y se adaptan como tú quieras
Ventanas que se mueven por la pantalla como quieras
Abre varias ventanas simultáneamente como un escritorio
Sistema de navegación para SPAs que funciona como debe: sin dramas, sin bugs raros y con todas las funciones que necesitas. El router que siempre quisiste 🗺️
import { Router, Component } from './js/ScopeJS.js';
// Definir las vistas/componentes
const HomePage = Component({
controller: class {
constructor() {
this.title = "Página de Inicio";
this.message = "¡Bienvenido a ScopeJS!";
}
},
render() {
return `
<div class="p-6 text-center">
<h1 class="text-3xl font-bold text-purple-800 mb-4">
${this.title}
</h1>
<p class="text-gray-600">${this.message}</p>
<a href="/usuario/123" class="text-purple-600 hover:underline">
Ver perfil de usuario
</a>
</div>
`;
}
});
const UserProfile = Component({
controller: class {
constructor() {
this.userId = null;
this.userName = "";
}
init(params) {
this.userId = params.id;
this.userName = `Usuario ${params.id}`;
this.apply();
}
},
render() {
return `
<div class="p-6">
<h1 class="text-2xl font-bold text-purple-800 mb-4">
Perfil de ${this.userName}
</h1>
<p class="text-gray-600 mb-4">ID: ${this.userId}</p>
<button onclick="history.back()"
class="bg-purple-600 text-white px-4 py-2 rounded">
Volver
</button>
</div>
`;
}
});
// Configurar el router
const AppRouter = Router([
{
path: "/",
component: HomePage,
alias: "inicio"
},
{
path: "/inicio",
component: HomePage,
alias: "home"
},
{
path: "/usuario/:id",
component: UserProfile,
alias: "perfil-usuario",
middleware: (params, next) => {
// Validar que el ID sea un número
if (isNaN(params.id)) {
alert("ID de usuario inválido");
return false;
}
next();
}
},
{
path: "/productos",
alias: "catalogo-productos",
component: Component({
render() {
return `
<div class="p-6">
<h1 class="text-2xl font-bold text-purple-800 mb-4">
Catálogo de Productos
</h1>
<div class="grid grid-cols-2 gap-4">
<div class="border p-4 rounded">Producto 1</div>
<div class="border p-4 rounded">Producto 2</div>
</div>
</div>
`;
}
})
}
]);
// Renderizar el router en el DOM
const container = document.getElementById('app');
AppRouter.render(container);
// Navegación programática
function navigateToUser(userId) {
AppRouter.navigate(`/usuario/${userId}`);
}
function navigateToProducts() {
AppRouter.navigate('/productos');
}
// Escuchar cambios de ruta
AppRouter.listen((route, params) => {
console.log('Navegando a:', route, 'Parámetros:', params);
}); Selecciona una ruta para ver la navegación
Parámetros en las rutas que funcionan de verdad
Control de acceso y validación sin complicaciones
Navegación fluida sin recargas molestas
Cambios de vista que enamoran
Crea aplicaciones complejas con layouts compartidos y navegación jerárquica. Las rutas hijas heredan automáticamente el path del padre.
import { Router, Component } from './js/ScopeJS.js';
// Layout Principal (Componente Padre)
const AdminLayout = Component({
controller: class {
constructor() {
this.title = "Panel Admin";
}
},
render() {
return `
<div class="admin-layout">
<nav class="navbar">
<h2>🛡️ ${this.title}</h2>
<div class="nav-links">
<a href="#/admin/dashboard">📊 Dashboard</a>
<a href="#/admin/usuarios">👥 Usuarios</a>
<a href="#/admin/productos">📦 Productos</a>
</div>
</nav>
<main class="content">
<!-- Aquí se renderizan las rutas hijas -->
<router-outlet></router-outlet>
</main>
</div>
`;
}
});
// Componente Dashboard (Hijo)
const Dashboard = Component({
controller: class {
constructor() {
this.stats = { users: 150, products: 45 };
}
},
render() {
return `
<div>
<h3>📊 Dashboard</h3>
<div class="stats-grid">
<div class="stat-card">
<h4>👥 Usuarios</h4>
<p>${this.stats.users}</p>
</div>
<div class="stat-card">
<h4>📦 Productos</h4>
<p>${this.stats.products}</p>
</div>
</div>
</div>
`;
}
});
// Configuración con Rutas Anidadas
const routes = [
{
path: "/",
controller: HomePage
},
{
path: "/admin", // Ruta padre
controller: AdminLayout, // Layout compartido
children: [ // Rutas hijas
{
path: "/dashboard", // = "/admin/dashboard"
controller: Dashboard
},
{
path: "/usuarios", // = "/admin/usuarios"
controller: UsersList
},
{
path: "/usuarios/:id", // = "/admin/usuarios/:id"
controller: UserDetail
}
]
}
];
// Inicializar Router
const AppRouter = Router(routes, { useHash: true });
AppRouter.render(document.getElementById('app')); 1. Navegar al layout padre:
2. Navegar a rutas hijas (se renderizan dentro del layout):
Haz clic en los botones para ver las rutas anidadas en acción
Las rutas hijas heredan automáticamente el path del padre
Usa <router-outlet> para renderizar componentes hijos
Mantén navegación y estilos consistentes
Cambia solo el contenido, no todo el layout
Todo lo que necesitas saber para dominar ScopeJS como un ninja del código. Aquí está la documentación que realmente vas a leer 📚
| Propiedad | Tipo | Descripción |
|---|---|---|
| controller | Class | Clase controladora del componente |
| render | Function | Función que retorna el HTML del componente |
| style | String | CSS scoped para el componente |
| tagName | String | Nombre del elemento personalizado |
| postRender | Function | Callback ejecutado después del render |
| Propiedad | Tipo | Descripción |
|---|---|---|
| controller | Class | Controlador del modal |
| render | Function | Función de renderizado |
| hideWhenClickOverlay | Boolean | Cerrar al hacer clic fuera |
| className | String | Clase CSS adicional |
| referrer | Element | Elemento de referencia para posición |
| resizable | Boolean | Permite redimensionar arrastrando (solo desktop) |
| draggable | Boolean | Permite mover el modal arrastrando desde título |
| windowMode | Boolean | Modo ventana: sin overlay, múltiples permitidos |
| title | String | Título mostrado en la barra superior (windowMode) |
| position | Object | Posición inicial {x, y} para windowMode |
| Método | Parámetros | Descripción |
|---|---|---|
| navigate | path, body | Navegar a una ruta específica |
| render | container | Renderizar el router en un contenedor |
| listen | callback | Escuchar cambios de ruta |
| unlisten | uuid | Remover listener de cambios |
| Función | Parámetros | Descripción |
|---|---|---|
| enableDebugger | boolean | Activar/desactivar modo debug |
| apply | - | Re-renderizar componente |
| close | ...args | Cerrar modal (disponible en modales) |
Tu sandbox personal para experimentar con ScopeJS. Aquí puedes romper cosas, crear maravillas y aprender sin miedo. ¡Es tu momento de brillar! ⚡
¡Vamos! Escribe algo genial y verás cómo cobra vida aquí ✨
this.apply() después de cambiar datosmodel es tu mejor amigo para formulariosonclick="miMetodo()"Desarrollador Full Stack con un poquito de locura 🤪 por crear herramientas que hagan que los usuarios digan "¡WOW!" Creé ScopeJS porque estaba cansado de frameworks pesados que convierten proyectos simples en monstruos complicados 🐉
Si este proyecto te ha robado el corazón ❤️ (y algunos cafés ☕), considera invitarme a una pizza 🍕 para seguir creando cositas geniales
Tu apoyo me ayuda a seguir desarrollando herramientas útiles y gratuitas para la comunidad