Introduction
Juris is a modern JavaScript reactive framework that offers a unique approach to building user interfaces. Unlike traditional frameworks, Juris provides two distinct development paradigms: component-based development for building new applications from scratch, and enhancement-based development for progressively enhancing existing HTML with reactive behavior.
This comprehensive guide will walk you through 12 practical illustrations, starting from basic setup to advanced component composition. You'll learn how to create reactive components, manage state, and enhance existing DOM elements with interactive functionality.
Prerequisites:
- Intermediate JavaScript knowledge required
- Understanding of ES6+ features (arrow functions, destructuring, template literals)
- Familiarity with DOM manipulation and event handling
- Basic knowledge of functional programming concepts
- Experience with modern JavaScript frameworks is helpful but not required
What You'll Learn:
- Setting up and configuring Juris instances
- Creating and composing reactive components
- Managing application state with automatic reactivity
- Using the enhance() API for progressive enhancement
- Combining component-based and enhancement-based approaches
Let's begin building reactive applications with Juris!
Illustration 1: Include Juris in Your Project
For client-side usage, add the following script tag to your HTML:
<div id="app"></div>
<script src="https://unpkg.com/juris@0.74.0/juris.mini.js"></script>
This includes the Juris framework from the CDN and creates a div element with id="app" where your application will be rendered. The #app ID is the default container that Juris looks for when rendering your application. Developer should not remove this id all throughout the runtime.
Note: Refer to https://www.npmjs.com/package/juris to get the latest version.
Illustration 2: Create Juris Instance and Render
<script>
const jurisInstance = new Juris();
jurisInstance.layout={div:{text:'Hello World'}};
jurisInstance.render();
</script>
This creates a new Juris instance, sets a simple layout with a div containing "Hello World" text, and renders it to the #app element.
Demo: https://codepen.io/jurisauthor/pen/RNPdPLP
Illustration 3: Create Counter Component
<script>
jurisInstance.registerComponent('CounterComponent', (props, ctx) => {
return {
div: {
children: [
{div: {
text: () => ctx.getState('counter', 0)
}},//div
{button: {text: 'Increment',
onclick: () => {
const current = ctx.getState('counter', 0);
ctx.setState('counter', current + 1);
}
}}//button
]
}//div
};//return
});
</script>
This registers a component called 'CounterComponent' that receives props
and ctx
(context). The component uses state named 'counter' with default value 0. The text attribute uses a function to make it reactive, while the onclick event handler updates the counter state when clicked.
Illustration 4: Use Counter Component
<script>
const jurisInstance = new Juris();
jurisInstance.layout={CounterComponent:{}};
jurisInstance.render();
</script>
This replaces the simple "Hello World" layout with the CounterComponent we created. The empty object {}
represents no props being passed to the component.
Illustration 5: Putting It All Together
<div id="app"></div>
<script src="https://unpkg.com/juris@0.74.0/juris.mini.js"></script>
<script>
const jurisInstance = new Juris();
jurisInstance.registerComponent('CounterComponent', (props, ctx) => {
return {
div: {
children: [
{div: {
text: () => ctx.getState('counter', 0)
}},//div
{button: {text: 'Increment',
onclick: () => {
const current = ctx.getState('counter', 0);
ctx.setState('counter', current + 1);
}
}}//button
]
}//div
};//return
});
jurisInstance.layout={CounterComponent:{}};
jurisInstance.render();
</script>
This complete example combines all the previous illustrations into a working Juris application. It includes the framework, creates an instance, registers the CounterComponent, sets it as the layout, and renders the reactive counter to the page.
Demo: https://codepen.io/jurisauthor/pen/yyNwNxE
Illustration 6: Component Composition
<script>
// AppLayout Component
jurisInstance.registerComponent('AppLayout', (props, ctx) => {
return {
div: {className: 'app-container',
children: [
{h1: {text: 'Counter Application'}},//h1
{CounterApp: {}}//CounterApp
]
}//div
};//return
});
// CounterDisplayComponent
jurisInstance.registerComponent('CounterDisplayComponent', (props, ctx) => {
return {
div: {className: 'counter-display',
text: () => `Count: ${ctx.getState('counter', 0)}`
}//div
};//return
});
// IncrementComponent
jurisInstance.registerComponent('IncrementComponent', (props, ctx) => {
return {
button: {text: 'Increment',
onclick: () => {
const current = ctx.getState('counter', 0);
ctx.setState('counter', current + 1);
}
}//button
};//return
});
// CounterApp Component
jurisInstance.registerComponent('CounterApp', (props, ctx) => {
return {
div: {className: 'counter-app',
children: [
{CounterDisplayComponent: {}},//CounterDisplayComponent
{IncrementComponent: {}}//IncrementComponent
]
}//div
};//return
});
jurisInstance.layout = {AppLayout: {}};
jurisInstance.render();
</script>
This demonstrates component composition in Juris where larger components are built from smaller, reusable components. The AppLayout serves as the main container with a title and contains CounterApp. The CounterApp component combines CounterDisplayComponent (shows the current count) and IncrementComponent (handles the increment action). All components share the same 'counter' state, making the display reactive to button clicks. This modular approach promotes code reusability and separation of concerns.
Demo: https://codepen.io/jurisauthor/pen/YPXgXdw
Illustration 7: Using enhance() API - Basic Setup
<div class="counter-display">0</div>
<button class="increment-btn">Increment</button>
<script src="https://unpkg.com/juris@0.74.0/juris.mini.js"></script>
<script>
const jurisInstance = new Juris();
jurisInstance.enhance('.counter-display', (ctx) => {
return {
text: () => ctx.getState('counter', 0)
};
});
jurisInstance.enhance('.increment-btn', (ctx) => {
return {
onclick: () => {
const current = ctx.getState('counter', 0);
ctx.setState('counter', current + 1);
}
};
});
</script>
The enhance() API allows you to add reactive behavior to existing HTML elements without recreating them. Instead of using components, you enhance existing DOM elements by targeting them with CSS selectors. The .counter-display
element gets reactive text that updates when the counter state changes, while the .increment-btn
gets a click handler that modifies the state.
Demo: https://codepen.io/jurisauthor/pen/QwbobPm
Illustration 8: enhance() with Multiple Elements
<div class="app-container">
<h1>Counter Application</h1>
<div class="counter-section">
<div class="counter-display">0</div>
<button class="increment-btn">Increment</button>
<button class="decrement-btn">Decrement</button>
</div>
</div>
<script>
const jurisInstance = new Juris();
jurisInstance.enhance('.counter-display', (ctx) => {
return {
text: () => `Count: ${ctx.getState('counter', 0)}`
};
});
jurisInstance.enhance('.increment-btn', (ctx) => {
return {
onclick: () => {
const current = ctx.getState('counter', 0);
ctx.setState('counter', current + 1);
}
};
});
jurisInstance.enhance('.decrement-btn', (ctx) => {
return {
onclick: () => {
const current = ctx.getState('counter', 0);
ctx.setState('counter', current - 1);
}
};
});
</script>
This example shows how to enhance multiple elements independently. Each enhance() call targets a specific CSS selector and adds reactive behavior. The counter display shows formatted text, while both increment and decrement buttons modify the same counter state. All elements automatically stay synchronized through Juris's reactive state management.
Demo: https://codepen.io/jurisauthor/pen/ogXVXrX
Illustration 9: enhance() with Selector-Based Enhancement
<div class="counter-app">
<h2>Enhanced Counter App</h2>
<div class="display">Loading...</div>
<div class="controls">
<button class="btn-increment">+</button>
<button class="btn-decrement">-</button>
<button class="btn-reset">Reset</button>
</div>
</div>
<script>
const jurisInstance = new Juris();
jurisInstance.enhance('.counter-app', (ctx) => {
return {
selectors: {
'.display': (ctx) => ({
text: () => `Current Value: ${ctx.getState('counter', 0)}`,
style: () => ({
color: ctx.getState('counter', 0) < 0 ? 'red' : 'green',
fontWeight: 'bold'
})
}),
'.btn-increment': (ctx) => ({
onclick: () => {
const current = ctx.getState('counter', 0);
ctx.setState('counter', current + 1);
}
}),
'.btn-decrement': (ctx) => ({
onclick: () => {
const current = ctx.getState('counter', 0);
ctx.setState('counter', current - 1);
}
}),
'.btn-reset': (ctx) => ({
onclick: () => ctx.setState('counter', 0)
})
}
};
});
</script>
This demonstrates the powerful selector-based enhancement where a single enhance() call can target multiple child elements within a container. The .counter-app
container defines a selectors
object that maps CSS selectors to their enhancements. The display element shows reactive text and dynamic styling (red for negative, green for positive), while all buttons get their respective click handlers. This approach is more organized for complex UIs with multiple related elements.
Demo: https://codepen.io/jurisauthor/pen/VYLRLoa
Illustration 10: enhance() with Dynamic Element Rendering
<div class="todo-app">
<h2>Todo List</h2>
<div class="todo-list">
<!-- Static placeholder content will be replaced -->
<div>Loading todos...</div>
</div>
<div class="add-todo">
<input type="text" class="todo-input" placeholder="Add new todo">
<button class="add-btn">Add</button>
</div>
</div>
<script>
const jurisInstance = new Juris();
jurisInstance.enhance('.todo-app', (ctx) => {
return {
selectors: {
'.todo-list': (ctx) => ({
children: () => {
const todos = ctx.getState('todos', []);
return todos.map((todo, index) => ({
div: {className: 'todo-item',
children: [
{span: {text: todo.text}},//span
{button: {text: 'Delete',
onclick: () => {
const currentTodos = ctx.getState('todos', []);
const newTodos = currentTodos.filter((_, i) => i !== index);
ctx.setState('todos', newTodos);
}
}}//button
]
}//div
}));
}
}),
'.add-btn': (ctx) => ({
onclick: () => {
const input = document.querySelector('.todo-input');
const text = input.value.trim();
if (text) {
const currentTodos = ctx.getState('todos', []);
ctx.setState('todos', [...currentTodos, { text }]);
input.value = '';
}
}
})
}
};
});
</script>
This demonstrates dynamic element rendering using enhance() where the .todo-list
container's children are completely replaced based on state changes. The children
function returns an array of virtual DOM objects that get rendered as actual DOM elements. Each todo item is dynamically created with its own delete handler. When todos are added or removed, Juris automatically updates the DOM by replacing the entire children array, making it perfect for lists and dynamic content.
Demo: https://codepen.io/jurisauthor/pen/wBaOKwP
Illustration 11: enhance() with Component Composition
<div class="dashboard">
<header class="app-header">
<h1>Enhanced Dashboard</h1>
</header>
<main class="main-content">
<section class="widgets">
<div class="widget counter-widget">
<h3>Counter Widget</h3>
<div class="counter-container"></div>
</div>
<div class="widget todo-widget">
<h3>Todo Widget</h3>
<div class="todo-container"></div>
</div>
</section>
</main>
</div>
<script>
const jurisInstance = new Juris();
// Register Counter Component
jurisInstance.registerComponent('CounterComponent', (props, ctx) => {
return {
div: {className: 'counter-comp',
children: [
{div: {className: 'counter-display',
text: () => `Count: ${ctx.getState('counter', 0)}`
}},//div
{button: {text: 'Increment',
onclick: () => {
const current = ctx.getState('counter', 0);
ctx.setState('counter', current + 1);
}
}}//button
]
}//div
};//return
});
// Register Todo Component
jurisInstance.registerComponent('TodoComponent', (props, ctx) => {
return {
div: {className: 'todo-comp',
children: [
{div: {className: 'todo-list',
children: () => {
const todos = ctx.getState('todos', []);
return todos.map((todo, index) => ({
div: {className: 'todo-item',
children: [
{span: {text: todo.text}},//span
{button: {text: '×',
onclick: () => {
const currentTodos = ctx.getState('todos', []);
ctx.setState('todos', currentTodos.filter((_, i) => i !== index));
}
}}//button
]
}//div
}));
}
}},//div
{div: {className: 'todo-controls',
children: [
{input: {type: 'text', className: 'todo-input', placeholder: 'New task'}},//input
{button: {text: 'Add',
onclick: () => {
const input = document.querySelector('.todo-input');
const text = input.value.trim();
if (text) {
const currentTodos = ctx.getState('todos', []);
ctx.setState('todos', [...currentTodos, { text }]);
input.value = '';
}
}
}}//button
]
}}//div
]
}//div
};//return
});
// Enhance the dashboard with component composition
jurisInstance.enhance('.dashboard', (ctx) => {
return {
selectors: {
'.counter-container': (ctx) => ({
children: () => [{CounterComponent: {}}]
}),
'.todo-container': (ctx) => ({
children: () => [{TodoComponent: {}}]
})
}
};
});
</script>
This sophisticated example demonstrates the powerful combination of enhance() API with component composition. Unlike previous examples, this approach registers reusable CounterComponent and TodoComponent as traditional components, then uses enhance() to dynamically inject these components into existing HTML containers. The .counter-container
and .todo-container
elements get their children replaced with the registered components through the children
function. This hybrid approach allows for progressive enhancement of static HTML while maintaining component reusability and modularity. The sophistication lies in bridging component-based architecture with DOM enhancement, enabling developers to enhance existing websites with reactive components without complete rewrites.
Demo: https://codepen.io/jurisauthor/pen/KwpEdpx
Illustration 12: Providing Initial State
To provide predefined todos as initial state to your Juris instance, modify the Juris constructor to include initial state:
const jurisInstance = new Juris({
states: {
todos: [
{ text: 'Learn Juris Framework' },
{ text: 'Build a Todo App' },
{ text: 'Explore enhance() API' }
],
counter: 5
}
});
This initializes the application with predefined todos and sets the counter to 5. The initial state is automatically available to all components and enhanced elements without requiring additional setup. When you load the application, the todo list will immediately display the predefined items, and the counter will start at 5 instead of 0. This is particularly useful for demos, testing, or providing default content for users.
Demo: https://codepen.io/jurisauthor/pen/bNdZVpv
Conclusion
This step-by-step guide has demonstrated the versatility and power of the Juris framework through 12 practical illustrations. We've covered two primary approaches:
Component-Based Development (Illustrations 1-6): Traditional component registration and composition using registerComponent()
and layout
properties. This approach is ideal for building new applications from scratch with reusable, modular components.
Enhancement-Based Development (Illustrations 7-12): Using the enhance()
API to add reactive behavior to existing HTML elements. This approach excels at progressive enhancement of legacy websites, CMS content, or server-rendered pages.
Key Takeaways:
- Juris offers flexible state management with automatic reactivity
- Components can be simple functions or complex objects with lifecycle hooks
- The enhance() API enables gradual migration to reactive interfaces
- Both approaches can be combined for maximum flexibility
- Initial state configuration allows for immediate application functionality
Key Features:
- Temporal Independent - Handle async operations seamlessly
- Automatic deep call stack branch aware dependency detection - Smart reactivity without manual subscriptions
- Smart Promise Handling - Built-in async/await support throughout the framework
- Component lazy compilation - Components compile only when needed
- Non-Blocking Rendering Pipeline - UI remains responsive during updates
- Global Non-Reactive State Management - Flexible state handling options
Resources:
- GitHub: https://github.com/jurisjs/juris
- Website: https://jurisjs.com/
- NPM: https://www.npmjs.com/package/juris
- CodePen Examples: https://codepen.io/jurisauthor
- Online Testing: https://jurisjs.com/tests/juris_pure_test_interface.html
Whether you're building a new application or enhancing an existing website, Juris provides the tools to create reactive, maintainable user interfaces with minimal boilerplate code.
Top comments (1)
I can't say how much I appreciate your posts :-)