v2.0.8
ScopeJS is that developer friend you always wanted: reliable, lightweight, and never complicates your life. Perfect for when you want to create something great without drama.
You don't need to be a development ninja. With ScopeJS, you're creating marvels in 2 minutes 🎯
For those who prefer it simple: copy, paste and fly 🚀
<!-- 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> For those who like to show off with ES6 modules and 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> Straight from the cloud, faster than your morning coffee:
For serious projects that deserve the best:
Here's a complete example of how to structure your HTML page:
<!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> Create pieces of code that work like a Swiss watch: precise, elegant and reusable. Because life is too short to write repetitive code 🕰️
Components in ScopeJS are like smart LEGO bricks that encapsulate everything you need:
Buttons, cards, forms... Everything you need to impress.
Charts, metrics and panels that bring data to life.
Complex layouts that communicate with each other like magic.
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 is not one of those heavy frameworks that updates the entire page every time you change a comma. We're smarter: we only touch what has really changed. Like a DOM surgeon.
Ninja precision: If you change a text, only that text is updated. Nothing more.
Maximum efficiency: If you change a CSS class, only that class is modified. Period.
✨ Result: Smooth animations, forms that don't reset and a user experience that makes you fall in love
With the model attribute, data syncs itself. Pure magic!
Event handling that works as you expect, with parameters and all
Styles that behave well and don't bother the rest
Efficient re-rendering only when needed. No more, no less
Create pop-up windows that don't annoy, but enchant. Because an ugly modal is like a poorly planned blind date 💫
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
});
} Try the different types of modals: normal (with overlay), resizable, draggable windows and multiple simultaneous windows
Stylish entrance and exit, no sudden appearances
Total control over everything: position, behavior, style... You're in charge
Se adaptan a cualquier pantalla como un camaleón digital
Modals that grow and adapt as you want
Windows that move around the screen as you like
Open multiple windows simultaneously like a desktop
Navigation system for SPAs that works as it should: no drama, no weird bugs and all the features you need. The router you always wanted 🗺️
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
Route parameters that actually work
Control de acceso y validación sin complicaciones
Navegación fluida sin recargas molestas
Cambios de vista que enamoran
Create complex applications with shared layouts and hierarchical navigation. Child routes automatically inherit the parent's path.
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
Changes only the content, not the entire layout
Everything you need to know to master ScopeJS like a code ninja. Here's the documentation you'll actually read 📚
| 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) |
Your personal sandbox to experiment with ScopeJS. Here you can break things, create wonders and learn without fear. It's your time to shine! ⚡
¡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()"Full Stack developer with a bit of craziness 🤪 for creating tools that make users say "WOW!" I created ScopeJS because I was tired of heavy frameworks that turn simple projects into complicated monsters 🐉
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