Juris Platform Documentation

JavaScript Unified Reactive Interface Solution - A comprehensive object-first architecture that makes reactivity an intentional choice rather than an automatic behavior.

๐Ÿš€ Features

๐Ÿ”„ Reactive State Management

Automatic UI updates when state changes with precise control over reactivity

๐Ÿงฉ Component System

Reusable, composable UI components with pure JavaScript objects

๐Ÿ›ฃ๏ธ Advanced Routing

Hash-based routing with guards, parameters, and middleware

๐Ÿ’พ Multi-tier Sync

localStorage, cross-tab, and remote server synchronization

๐Ÿ” Route Guards

Authentication, authorization, data loading, and unsaved changes protection

โšก Zero Dependencies

Pure JavaScript, no external libraries required

๐Ÿ Quick Start

Download the Platofrm Core

Download Juris Core (juris.js)

Basic Setup

Pure JavaScript, no external libraries required


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JurisJS Counter Example</title>
    
<body>
    <div id="app"></div>    
    
    <script src="https://jurisjs.com/juris.js"></script>
    <script>
        const app = new Juris({
            states: {
                counter: 0
            },
            components: {
                Counter: (props, { getState, setState }) => ({
                    div: {
                        children: () => [{
                            h2: { text: () => `Count: ${getState('counter')}` }
                        }, {button: {
                                text: 'Increment',
                                onClick: () => setState('counter', getState('counter') + 1)
                            }
                        }]
                    }
                })
            },            
            layout: {
                div: {
                    children: () => [{ Counter: {} }]
                }
            }
        });

        app.render('#app');
    </script>
</body>
</html>

๐ŸŽฏ Core Concepts

State Management

Juris uses a centralized non-reactive state store:

<script>
    // Set state
    app.setState('user.name', 'John Doe');
    app.setState('todos', [...todos, newTodo]);

    // Get state
    const userName = app.getState('user.name', 'Guest');
    const todoCount = app.getState('todos', []).length;

    // Subscribe to changes
    const unsubscribe = app.subscribe('user.name', (newName, oldName) => {
        console.log(`Name changed from ${oldName} to ${newName}`);
    });
</script>

Component System

Components are pure functions that return UI objects:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JurisJS Parent-Child Components</title>
</head>
<body>
    <div id="app"></div>
    
    <script src="https://jurisjs.com/juris.js"></script>
    <script>
    // Child Component: Simple display component
    const Greeting = (props) => ({
        h2: { 
            text: () => {
                // Handle both function and string values for props.name
                return `Hello, ${props.name}!`;
            }
        }
    });

    // Child Component: Interactive button
    const Counter = (props, { getState, setState }) => ({
        div: {
            children: () => [{
                p: { text: () => `Count: ${getState('count', 0)}` }
            }, {
                button: {
                    text: 'Click me',
                    onClick: () => setState('count', getState('count', 0) + 1)
                }
            }]
        }
    });

    // Parent Component: Uses child components
    const App = (props, { getState }) => ({
        div: {
            children: () => [{
                h1: { text: 'My App' }
            }, {
                Greeting: { name: 'World' }
            }, {
                Greeting: { name: () => getState('user.name', 'Guest') + ', you current count is: ' + getState('count', 0) }
            }, {
                Counter: {}
            }]
        }
    });

    const app = new Juris({
        states: {
            count: 0,
            user: { name: 'John' }
        },
        
        components: {
            Greeting,  // Child component
            Counter,   // Child component  
            App        // Parent component
        },
        
        layout: {
            div: {
                children: () => [{ App: {} }]
            }
        }
    });

    app.render('#app');
    </script>
</body>
</html>

Reactive Attributes

Any attribute can be reactive by using a function:

<script>
    const DynamicComponent = (props, { getState }) => ({
        div: {
            // Static attributes
            className: 'container',
            
            // Reactive attributes
            style: {
                backgroundColor: () => getState('theme') === 'dark' ? '#333' : '#fff',
                color: () => getState('theme') === 'dark' ? '#fff' : '#333'
            },
            
            text: () => `Current user: ${getState('user.name', 'Guest')}`,
            
            // Reactive children
            children: () => getState('items', []).map(item => ({
                div: { text: item.name }
            }))
        }
    });
</script>

๐Ÿ”„ Progressive Enhancement API

The enhance() method allows you to progressively enhance existing HTML elements with reactive behavior, making it perfect for adding modern interactivity to existing websites without rebuilding them.

Basic Enhancement

Transform static HTML into reactive components:

<script>
    // Existing HTML works immediately
    <button class="counter-btn" data-count="0">Count: 0</button>

    // Enhance with reactivity
    app.enhance('.counter-btn', (props, { useState }) => {
        const [getCount, setCount] = useState('count', 0);
        
        return {
            textContent: () => `Count: ${getCount()}`,
            onClick: () => setCount(getCount() + 1),
            disabled: () => getCount() >= 10
        };
    });
</script>

Enhancement Syntax

<script>
    app.enhance(selector, enhancementFunction, options?)
</script>

Parameters:

  • selector - CSS selector for elements to enhance
  • enhancementFunction - Function that returns reactive properties
  • options - Optional configuration object

Enhancement Function Context

Enhancement functions receive the same context as components:

<script>
    app.enhance('.my-element', (props, context) => {
        // props: { element, dataset, index }
        // context: { useState, getState, setState, navigate, services }
        
        const { element, dataset, index } = props;
        const { useState, getState, setState } = context;
        
        return {
            // Reactive properties
            textContent: () => `Item ${index}: ${getState('data')}`,
            className: () => getState('theme') === 'dark' ? 'dark-item' : 'light-item',
            
            // Event handlers
            onClick: (event) => {
                setState('selectedItem', index);
            },
            
            // Attributes
            'data-active': () => getState('selectedItem') === index,
            disabled: () => getState('isLoading', false)
        };
    });
</script>

Real-World Examples

Shopping Cart Enhancement

<!-- Existing HTML -->
    <div class="product-card">
        <h3>Awesome Product</h3>
        <p class="price">$29.99</p>
        <button class="add-to-cart" data-product-id="123">Add to Cart</button>
    </div>
<script>
    // Enhance add-to-cart buttons
    app.enhance('.add-to-cart', (props, { useState }) => {
        const [getCart, setCart] = useState('cart.items', []);
        const productId = props.dataset.productId;
        
        const isInCart = () => getCart().some(item => item.id === productId);
        
        return {
            textContent: () => isInCart() ? 'โœ“ In Cart' : 'Add to Cart',
            className: () => isInCart() ? 'btn in-cart' : 'btn',
            disabled: () => isInCart(),
            
            onClick: () => {
                if (!isInCart()) {
                    const newItem = { 
                        id: productId, 
                        name: props.element.closest('.product-card').querySelector('h3').textContent,
                        price: 29.99 
                    };
                    setCart([...getCart(), newItem]);
                }
            }
        };
    });
</script>

Form Validation Enhancement

<!-- Existing form -->
    <form class="contact-form">
        <input type="email" class="email-input" placeholder="Your email" required>
        <textarea class="message-input" placeholder="Your message" required></textarea>
        <button type="submit" class="submit-btn">Send Message</button>
    </form>
<script>
    // Enhance form inputs with validation
    app.enhance('.email-input', (props, { useState }) => {
        const [getEmail, setEmail] = useState('form.email', '');
        const [getErrors, setErrors] = useState('form.errors', {});
        
        const isValid = () => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(getEmail());
        
        return {
            value: () => getEmail(),
            className: () => {
                if (!getEmail()) return 'form-input';
                return isValid() ? 'form-input valid' : 'form-input invalid';
            },
            
            onInput: (e) => {
                setEmail(e.target.value);
                if (getErrors().email) {
                    setErrors({ ...getErrors(), email: null });
                }
            },
            
            onBlur: () => {
                if (getEmail() && !isValid()) {
                    setErrors({ ...getErrors(), email: 'Please enter a valid email' });
                }
            }
        };
    });

    // Enhance submit button
    app.enhance('.submit-btn', (props, { getState }) => {
        const isFormValid = () => {
            const email = getState('form.email', '');
            const message = getState('form.message', '');
            const errors = getState('form.errors', {});
            
            return email && message && !errors.email && !errors.message;
        };
        
        return {
            disabled: () => !isFormValid(),
            className: () => isFormValid() ? 'btn btn-primary' : 'btn btn-disabled'
        };
    });
</script>

Live Search Enhancement

<!-- Existing search -->
    <div class="search-container">
        <input type="text" class="search-input" placeholder="Search products...">
        <div class="search-results"></div>
    </div>
<script>
    // Enhance search input with live results
    app.enhance('.search-input', (props, { useState, services }) => {
        const [getQuery, setQuery] = useState('search.query', '');
        const [getResults, setResults] = useState('search.results', []);
        
        let debounceTimer;
        
        return {
            value: () => getQuery(),
            
            onInput: (e) => {
                const query = e.target.value;
                setQuery(query);
                
                // Debounced search
                clearTimeout(debounceTimer);
                debounceTimer = setTimeout(async () => {
                    if (query.length >= 2) {
                        const results = await services.searchService.search(query);
                        setResults(results);
                    } else {
                        setResults([]);
                    }
                }, 300);
            }
        };
    });

    // Enhance results container
    app.enhance('.search-results', (props, { getState }) => {
        return {
            innerHTML: () => {
                const results = getState('search.results', []);
                const query = getState('search.query', '');
                
                if (!query) return '';
                if (results.length === 0) return '<p>No results found</p>';
                
                return results.map(item => 
                    `<div class="result-item">
                        <h4>${item.name}</h4>
                        <p>${item.description}</p>
                    </div>`
                ).join('');
            }
        };
    });
</script>

Enhancement Options

<script>
    app.enhance('.my-element', enhancementFn, {
        // Only enhance elements that match additional criteria
        filter: (element) => !element.hasAttribute('data-enhanced'),
        
        // Run after enhancement
        onEnhanced: (element, instance) => {
            console.log('Enhanced:', element);
        },
        
        // Cleanup function
        onDestroy: (element, instance) => {
            console.log('Cleaning up:', element);
        }
    });
</script>

Mass Enhancement

Enhance multiple element types at once:

<script>
    // Enhance all interactive elements
    const interactiveElements = {
        '.like-btn': (props, { useState }) => {
            const [getLikes, setLikes] = useState(`likes.${props.dataset.postId}`, 0);
            return {
                textContent: () => `โ™ฅ ${getLikes()}`,
                onClick: () => setLikes(getLikes() + 1)
            };
        },
        
        '.share-btn': (props, { getState }) => ({
            onClick: () => {
                const url = window.location.href;
                navigator.share({ url, title: document.title });
            }
        }),
        
        '.bookmark-btn': (props, { useState }) => {
            const [getBookmarks, setBookmarks] = useState('bookmarks', []);
            const itemId = props.dataset.itemId;
            
            return {
                className: () => getBookmarks().includes(itemId) ? 'bookmarked' : '',
                onClick: () => {
                    const bookmarks = getBookmarks();
                    if (bookmarks.includes(itemId)) {
                        setBookmarks(bookmarks.filter(id => id !== itemId));
                    } else {
                        setBookmarks([...bookmarks, itemId]);
                    }
                }
            };
        }
    };

    // Apply all enhancements
    Object.entries(interactiveElements).forEach(([selector, enhancement]) => {
        app.enhance(selector, enhancement);
    });
</script>

Enhancement vs Components

๐Ÿ”„ Enhancement API

For: Existing HTML, progressive enhancement, gradual adoption

  • Works with existing markup
  • SEO-friendly content first
  • Gradual feature addition
  • Zero breaking changes

๐Ÿงฉ Component System

For: New applications, complete SPAs, complex UIs

  • Full application architecture
  • Component composition
  • Advanced routing
  • Complex state management

Best Practices

  • Start with working HTML - Ensure your page works without JavaScript
  • Enhance progressively - Add features gradually without breaking existing functionality
  • Use data attributes - Store configuration data in data-* attributes
  • Handle edge cases - Check for element existence and valid data
  • Clean up properly - Use onDestroy for cleanup when elements are removed

Migration Path

Transform your existing website step-by-step:

<script>
    // Phase 1: Add basic interactivity
    app.enhance('.interactive', basicEnhancements);

    // Phase 2: Add state management  
    app.enhance('.stateful', stateEnhancements);

    // Phase 3: Add navigation features
    app.enhance('.navigation', routingEnhancements);

    // Phase 4: Convert to full components (optional)
    app.registerComponent('AdvancedFeature', fullComponent);
</script>

๐Ÿ”„ State Management

Basic and Advance Operations

<script>
    // Simple values
    app.setState('counter', 42);
    app.setState('isLoading', true);

    // Nested objects
    app.setState('user.profile.name', 'John');
    app.setState('settings.theme', 'dark');

    // Arrays
    app.setState('todos', [...currentTodos, newTodo]);

    // With context
    app.setState('data', newData, { skipPersist: true });

    // Safe handling of undefined paths
    const userName = app.getState('user.profile.name', 'Anonymous');
    const preferences = app.getState('user.preferences', {});
    const todos = app.getState('todos', []);

    // Setting null vs undefined
    app.setState('user.avatar', null);        // Explicitly null
    app.setState('user.tempData', undefined); // Remove property
    
    // Deep null checks
    const email = app.getState('user.contact.email', null);
    if (email !== null) {
        // Email exists and is not explicitly null
    }

    // Safe array operations
    const currentTodos = app.getState('todos', []);
    
    // Add item
    app.setState('todos', [...currentTodos, newTodo]);
    
    // Remove item by ID
    app.setState('todos', currentTodos.filter(todo => todo.id !== todoId));
    
    // Update item by ID
    app.setState('todos', currentTodos.map(todo => 
        todo.id === todoId ? { ...todo, completed: true } : todo
    ));
    
    // Insert at specific position
    const insertAt = (arr, index, item) => [
        ...arr.slice(0, index),
        item,
        ...arr.slice(index)
    ];
    app.setState('todos', insertAt(currentTodos, 2, newTodo));
    
    // Move item (reorder)
    const moveItem = (arr, fromIndex, toIndex) => {
        const item = arr[fromIndex];
        const filtered = arr.filter((_, i) => i !== fromIndex);
        return [
            ...filtered.slice(0, toIndex),
            item,
            ...filtered.slice(toIndex)
        ];
    };
    app.setState('todos', moveItem(currentTodos, 0, 3));

    // Deep object merging
    const currentUser = app.getState('user', {});
    app.setState('user', {
        ...currentUser,
        profile: {
            ...currentUser.profile,
            name: 'Updated Name',
            lastUpdated: new Date().toISOString()
        }
    });
    
    // Partial updates with spread
    const updateUserProfile = (updates) => {
        const current = app.getState('user.profile', {});
        app.setState('user.profile', { ...current, ...updates });
    };
    
    // Safe nested property updates
    const setNestedProperty = (path, value) => {
        const pathParts = path.split('.');
        const current = app.getState(pathParts[0], {});
        
        let updated = { ...current };
        let pointer = updated;
        
        for (let i = 1; i < pathParts.length - 1; i++) {
            pointer[pathParts[i]] = { ...pointer[pathParts[i]] };
            pointer = pointer[pathParts[i]];
        }
        
        pointer[pathParts[pathParts.length - 1]] = value;
        app.setState(pathParts[0], updated);
    };

    // Batch multiple updates for performance
    const batchUpdate = (updates) => {
        Object.entries(updates).forEach(([path, value]) => {
            app.setState(path, value);
        });
    };
    
    batchUpdate({
        'user.name': 'John Doe',
        'user.email': 'john@example.com',
        'user.lastLogin': new Date().toISOString(),
        'ui.notifications.count': 0
    });
    
    // Conditional batching
    const updateUserWithValidation = (userData) => {
        const updates = {};
        
        if (userData.name && userData.name.length > 0) {
            updates['user.name'] = userData.name;
        }
        
        if (userData.email && userData.email.includes('@')) {
            updates['user.email'] = userData.email;
        }
        
        if (Object.keys(updates).length > 0) {
            updates['user.lastUpdated'] = new Date().toISOString();
            batchUpdate(updates);
        }
    };

    / Input validation before setting state
    const setValidatedState = (path, value, validator) => {
        const isValid = validator(value);
        if (isValid) {
            app.setState(path, value);
            app.setState(`${path}.error`, null);
        } else {
            app.setState(`${path}.error`, 'Invalid value');
        }
    };
    
    // Email validation example
    setValidatedState('user.email', email, (val) => 
        val && val.includes('@') && val.includes('.')
    );
    
    // Transform data before storing
    const setNormalizedState = (path, value, transformer) => {
        const transformed = transformer(value);
        app.setState(path, transformed);
    };
    
    // Normalize user input
    setNormalizedState('user.name', rawName, (name) => 
        name.trim().toLowerCase().replace(/\s+/g, ' ')
    );

    // Normalized data patterns (recommended for large datasets)
    const normalizeEntities = (entities, idField = 'id') => {
        const byId = {};
        const allIds = [];
        
        entities.forEach(entity => {
            byId[entity[idField]] = entity;
            allIds.push(entity[idField]);
        });
        
        return { byId, allIds };
    };
    
    // Store normalized todos
    const todos = [
        { id: 1, text: 'Buy groceries', completed: false },
        { id: 2, text: 'Walk the dog', completed: true }
    ];
    
    const normalized = normalizeEntities(todos);
    app.setState('todos.byId', normalized.byId);
    app.setState('todos.allIds', normalized.allIds);
    
    // Access normalized data
    const getTodos = () => {
        const byId = app.getState('todos.byId', {});
        const allIds = app.getState('todos.allIds', []);
        return allIds.map(id => byId[id]).filter(Boolean);
    };
    
    // Update single normalized entity
    const updateTodo = (id, updates) => {
        const current = app.getState(`todos.byId.${id}`, {});
        app.setState(`todos.byId.${id}`, { ...current, ...updates });
    };

    // Safe state hydration from external sources
    const hydrateState = (data, allowedPaths = []) => {
        Object.entries(data).forEach(([path, value]) => {
            if (allowedPaths.length === 0 || allowedPaths.includes(path)) {
                try {
                    app.setState(path, value);
                } catch (error) {
                    console.warn(`Failed to hydrate path: ${path}`, error);
                }
            }
        });
    };
    
    // Serialize specific state branches
    const serializeState = (paths) => {
        const serialized = {};
        paths.forEach(path => {
            const value = app.getState(path);
            if (value !== undefined) {
                serialized[path] = value;
            }
        });
        return serialized;
    };
    
    // Example usage
    const savedState = serializeState(['user', 'preferences', 'todos']);
    localStorage.setItem('appState', JSON.stringify(savedState));
    
    // Restore on app load
    const savedData = JSON.parse(localStorage.getItem('appState') || '{}');
    hydrateState(savedData, ['user', 'preferences', 'todos']);

    // Handle state structure changes over time
    const migrateState = (currentState, fromVersion, toVersion) => {
        let migrated = { ...currentState };
        
        // Migration from v1 to v2
        if (fromVersion < 2) {
            // Move user.settings to user.preferences
            if (migrated.user?.settings) {
                migrated.user.preferences = migrated.user.settings;
                delete migrated.user.settings;
            }
        }
        
        // Migration from v2 to v3
        if (fromVersion < 3) {
            // Convert array todos to normalized structure
            if (Array.isArray(migrated.todos)) {
                const normalized = normalizeEntities(migrated.todos);
                migrated.todos = normalized;
            }
        }
        
        return migrated;
    };
    
    // Version-aware state loading
    const loadStateWithMigration = () => {
        const saved = JSON.parse(localStorage.getItem('appState') || '{}');
        const currentVersion = 3;
        const savedVersion = saved.__version__ || 1;
        
        if (savedVersion < currentVersion) {
            const migrated = migrateState(saved, savedVersion, currentVersion);
            migrated.__version__ = currentVersion;
            
            // Save migrated state
            localStorage.setItem('appState', JSON.stringify(migrated));
            hydrateState(migrated);
        } else {
            hydrateState(saved);
        }
    };

    // Debounced state updates for rapid changes
    const debouncedSetState = (() => {
        const timeouts = new Map();
        
        return (path, value, delay = 300) => {
            if (timeouts.has(path)) {
                clearTimeout(timeouts.get(path));
            }
            
            const timeoutId = setTimeout(() => {
                app.setState(path, value);
                timeouts.delete(path);
            }, delay);
            
            timeouts.set(path, timeoutId);
        };
    })();
    
    // Usage: debounced search input
    const handleSearchInput = (query) => {
        debouncedSetState('search.query', query, 500);
    };
    
    // Throttled state updates
    const throttledSetState = (() => {
        const lastCall = new Map();
        
        return (path, value, interval = 100) => {
            const now = Date.now();
            const last = lastCall.get(path) || 0;
            
            if (now - last >= interval) {
                app.setState(path, value);
                lastCall.set(path, now);
            }
        };
    })();
    
    // Usage: throttled scroll position
    const handleScroll = (scrollY) => {
        throttledSetState('ui.scrollPosition', scrollY, 16); // ~60fps
    };

    // Safe state operations with error handling
    const safeSetState = (path, value, fallback = null) => {
        try {
            app.setState(path, value);
            app.setState(`errors.${path}`, null); // Clear any previous errors
        } catch (error) {
            console.error(`Failed to set state at ${path}:`, error);
            app.setState(`errors.${path}`, error.message);
            
            if (fallback !== null) {
                app.setState(path, fallback);
            }
        }
    };
    
    // State recovery mechanisms
    const recoverState = (path, factory) => {
        const current = app.getState(path);
        
        if (current === undefined || current === null) {
            const recovered = factory();
            app.setState(path, recovered);
            return recovered;
        }
        
        return current;
    };
    
    // Usage examples
    safeSetState('user.complexData', riskyData, {});
    
    const todos = recoverState('todos', () => []);
    const user = recoverState('user', () => ({ name: 'Guest', preferences: {} }));

    // Development helpers
    const inspectState = (path = '') => {
        if (path) {
            console.log(`State at '${path}':`, app.getState(path));
        } else {
            console.log('Full state tree:', app.state);
        }
    };
    
    // State change logging
    const logStateChanges = (enabled = true) => {
        if (!enabled) return () => {};
        
        return app.subscribe('*', (newValue, oldValue, path) => {
            console.log(`๐Ÿ”„ State change at '${path}':`, {
                from: oldValue,
                to: newValue,
                timestamp: new Date().toISOString()
            });
        });
    };
    
    // State validation in development
    const validateStateStructure = (expectedStructure, currentPath = '') => {
        const current = currentPath ? app.getState(currentPath) : app.state;
        
        Object.entries(expectedStructure).forEach(([key, expectedType]) => {
            const fullPath = currentPath ? `${currentPath}.${key}` : key;
            const value = app.getState(fullPath);
            
            if (typeof expectedType === 'string') {
                if (typeof value !== expectedType) {
                    console.warn(`Type mismatch at '${fullPath}': expected ${expectedType}, got ${typeof value}`);
                }
            } else if (typeof expectedType === 'object') {
                validateStateStructure(expectedType, fullPath);
            }
        });
    };
    
    // Usage in development
    if (process.env.NODE_ENV === 'development') {
        const stateSchema = {
            user: {
                name: 'string',
                email: 'string',
                preferences: 'object'
            },
            todos: 'object',
            ui: {
                theme: 'string',
                isLoading: 'boolean'
            }
        };
        
        validateStateStructure(stateSchema);
        const unsubscribeLogger = logStateChanges(true);
    }

    // React-like useState hook for components
    const useCounter = (initialValue = 0) => {
        const [getCount, setCount] = app.useState('counter', initialValue);
        
        const increment = () => setCount(getCount() + 1);
        const decrement = () => setCount(getCount() - 1);
        const reset = () => setCount(initialValue);
        
        return { getCount, setCount, increment, decrement, reset };
    };
    
    // Component using useState pattern
    const CounterComponent = (props, context) => {
        const { getCount, increment, decrement, reset } = useCounter(props.initialValue);
        
        return {
            div: {
                children: () => [
                    { p: { text: () => `Count: ${getCount()}` } },
                    { button: { text: 'Increment', onClick: increment } },
                    { button: { text: 'Decrement', onClick: decrement } },
                    { button: { text: 'Reset', onClick: reset } }
                ]
            }
        };
    };

    // Powerful hooks with complex logic
    const useCompleteFeature = (config) => {
        const [getData, setData] = app.useState('feature.data', []);
        const [getLoading, setLoading] = app.useState('feature.loading', false);
        
        const processData = async () => {
            setLoading(true);
            // Can call external services during tracking!
            const result = await app.services.dataProcessor.analyze(getData());
            setData(result);
            setLoading(false);
        };
        
        return { getData, setData, getLoading, processData };
    };
</script>

Middleware

Transform state changes globally:

<script>
    const app = new Juris({
        middleware: [
            // Logging middleware
            ({ path, oldValue, newValue }) => {
                console.log(`${path}: ${oldValue} โ†’ ${newValue}`);
                return newValue;
            },
            
            // Validation middleware
            ({ path, newValue }) => {
                if (path === 'user.age' && newValue < 0) {
                    console.warn('Age cannot be negative');
                    return 0;
                }
                return newValue;
            },
            
            // Auto-save middleware
            ({ path, newValue }) => {
                if (path.startsWith('form.')) {
                    localStorage.setItem('formData', JSON.stringify(newValue));
                }
                return newValue;
            }
        ]
    });
</script>

External Subscriptions

Listen to state changes from outside components:

<script>
    // Subscribe to specific paths
    const unsubscribe = app.subscribe('user.isLoggedIn', (isLoggedIn) => {
        if (isLoggedIn) {
            initializeUserDashboard();
        } else {
            cleanupUserData();
        }
    });

    // Multiple subscriptions
    app.subscribe('todos', updateTodoCount);
    app.subscribe('filter', refreshTodoList);
    app.subscribe('user.preferences', savePreferences);

    // Cleanup
    unsubscribe();
</script>

๐Ÿงฉ Component System

Component Structure

<script>
    const ComponentName = (props, context) => {
        // props: passed from parent
        // context: { setState, getState, navigate, services }
        
        return {
            tagName: {
                // Attributes
                className: 'my-component',
                id: 'unique-id',
                
                // Event handlers
                onClick: (event, context) => {
                    // Handle click
                },
                
                // Content
                text: 'Hello World',
                
                // Children
                children: () => [
                    { span: { text: 'Child 1' } },
                    { span: { text: 'Child 2' } }
                ]
            }
        };
    };
</script>

Component Registration

<script>
    const app = new Juris({
        components: {
            // Simple component
            HelloWorld: () => ({
                h1: { text: 'Hello, World!' }
            }),
            
            // Component with props
            Greeting: (props) => ({
                p: { text: `Hello, ${props.name}!` }
            }),
            
            // Reactive component
            Counter: (props, { getState, setState }) => ({
                div: {
                    children: () => [{
                        span: { text: () => `Count: ${getState('counter', 0)}` }
                    }, {
                        button: {
                            text: 'Increment',
                            onClick: () => setState('counter', getState('counter', 0) + 1)
                        }
                    }]
                }
            })
        }
    });
</script>

๐Ÿ›ฃ๏ธ Routing System

Route Configuration

<script>
    const app = new Juris({
        router: {
            routes: {
                // Simple routes
                '/': 'HomePage',
                '/about': 'AboutPage',
                
                // Routes with guards
                '/dashboard': {
                    component: 'DashboardPage',
                    guards: ['authGuard']
                },
                
                // Parameterized routes
                '/user/:id': {
                    component: 'UserPage',
                    guards: ['authGuard'],
                    loadData: 'loadUserData'
                }
            },
            
            guards: {
                // Authentication guard
                authGuard: ({ getState, navigate }) => {
                    if (!getState('user.isAuthenticated')) {
                        navigate('/login');
                        return false;
                    }
                    return true;
                }
            }
        }
    });
</script>

Navigation

<script>
    // Programmatic navigation
    app.navigate('/dashboard');
    app.navigate('/user/123');

    // Navigation in components
    const Navigation = (props, { navigate, getState }) => ({
        nav: {
            children: () => [{
                button: {
                    text: 'Dashboard',
                    onClick: () => navigate('/dashboard'),
                    disabled: () => !getState('user.isAuthenticated')
                }
            }]
        }
    });
</script>

๐Ÿ” Route Guards

All 4 types of guards implemented in our demo:

1. Authentication Guard

Ensures user is logged in before accessing protected routes

2. Authorization Guard

Checks user permissions and roles for specific resources

3. Data Loading Guard

Loads required data before component renders

4. Unsaved Changes Guard

Prevents navigation when user has unsaved changes

Implementation Example

<script>
    ....
    router: {
        routes: {
            '/admin': {
                component: 'AdminPage',
                guards: ['authGuard', 'adminGuard'],
                loadData: 'loadAdminData'
            }
        },
        
        guards: {
            // Authentication Guard
            authGuard: ({ getState, navigate }) => {
                if (!getState('user.isAuthenticated')) {
                    navigate('/login');
                    return false;
                }
                return true;
            },
            
            // Authorization Guard  
            adminGuard: ({ getState, navigate }) => {
                if (!getState('user.isAdmin')) {
                    navigate('/unauthorized');
                    return false;
                }
                return true;
            },
            
            // Data Loading Guard (Async)
            loadAdminData: async ({ setState }) => {
                await new Promise(resolve => setTimeout(resolve, 1000));
                setState('adminData', { 
                    users: 150, 
                    todos: 2500,
                    lastUpdate: new Date().toISOString()
                });
            }
        }
    }
    ....
</script>

๐Ÿ’พ Synchronization

Our implementation includes a complete 3-tier synchronization system:

1. localStorage Sync

Automatic persistence and restoration on page reload

2. Cross-tab Sync

Real-time synchronization across browser tabs

3. Remote Server Sync

Bidirectional sync with backend API

localStorage Sync

<script>
    // Automatic persistence - no code needed!
    app.setState('todos', newTodos); // Automatically saved to localStorage
    app.setState('user.name', 'John'); // Nested paths supported

    // Skip persistence when needed
    app.setState('temp', data, { skipPersist: true });
</script>

Remote Sync Configuration

<script>
    // Enable remote sync
    app.services.remoteSyncService.config.baseUrl = 'https://your-api.com';
    app.services.remoteSyncService.setEnabled(true, {
        getState: app.getState.bind(app),
        setState: app.setState.bind(app)
    });

    // Manual sync operations
    await app.services.remoteSyncService.sync({ getState, setState });
    await app.services.remoteSyncService.pushToServer({ getState });
    await app.services.remoteSyncService.pullFromServer({ getState, setState });
</script>

๐Ÿ—๏ธ Backend Setup

Complete PHP backend implementation included!

Quick Setup

  1. Download our PHP backend files
  2. Create database:
    CREATE DATABASE juris_sync;
    </script>
  3. Configure database in config.php
  4. Upload files and run setup

API Endpoints

Endpoint Method Description
/api/push.php POST Push local state to server
/api/pull.php GET Pull remote state from server
/api/sync.php POST Bidirectional sync with conflict resolution
/api/admin.php GET Admin interface for data management

๐ŸŽฏ Design Patterns

Proven patterns and best practices for building applications with Juris across different use cases and architectural needs.

๐Ÿ”„ Enhancement Patterns

Progressive enhancement for existing websites and gradual adoption strategies

๐Ÿ—๏ธ Architecture Patterns

Component-first, layout-driven, and feature module organization patterns

๐Ÿ”ง State Patterns

Flat state, domain state, computed state, and event-driven patterns

โšก Performance Patterns

Surgical updates, batched state, and virtualization techniques

Pattern Selection Guide

Use Case Recommended Patterns Benefits
New SPA Component-First, Layout-Driven, Flat State Clean architecture, maintainable code
Existing Website Progressive Enhancement, Selective Enhancement No breaking changes, gradual adoption
E-commerce Domain State, Guard-Protected, Performance Scalable, secure, fast user experience
Enterprise Feature Modules, Security Patterns, Testing Team collaboration, maintainability

๐Ÿ”„ Enhancement Patterns

Progressive Enhancement Pattern

Start with working HTML, enhance with JavaScript. Perfect for existing websites and CMS integration.

<!-- HTML works without JavaScript -->
<div class="status-badge">Active</div>
            
<script>
    // Enhancement adds reactive behavior
    app.enhance('.status-badge', (props, { getState }) => ({
        textContent: () => getState('user.status', 'Active'),
        style: { 
            backgroundColor: () => getState('user.status') === 'Active' ? 'green' : 'red'
        }
    }));            
</script>

Selective Enhancement Pattern

Enhance specific elements while leaving others static - perfect for large websites where only certain sections need interactivity.

<script>
// Only enhance interactive elements
    app.enhance('.interactive-button', (props, { useState }) => {
        const [getClicks, setClicks] = useState('clicks', 0);
        
        return {
            onClick: () => setClicks(getClicks() + 1),
            style: { 
                backgroundColor: () => getClicks() > 5 ? 'gold' : 'blue'
            },
            textContent: () => `Clicked ${getClicks()} times`
        };
    });
            
    // Static content remains unchanged
    // <p>This paragraph stays static</p>
</script>

Layered Enhancement Pattern

Apply multiple enhancement layers to the same elements for complex interactions.

<script>
// Base enhancement for core functionality
    app.enhance('.interactive-card', (props, { useState }) => {
        const [getHovered, setHovered] = useState('isHovered', false);
        
        return {
            onMouseEnter: () => setHovered(true),
            onMouseLeave: () => setHovered(false),
            style: {
                transform: () => getHovered() ? 'scale(1.05)' : 'scale(1)',
                transition: 'transform 0.2s ease'
            }
        };
    });
    
    // Additional enhancement for advanced features
    app.enhance('.interactive-card', (props, { useState }) => {
        const [getClicked, setClicked] = useState('clickCount', 0);
        
        return {
            onClick: () => setClicked(getClicked() + 1),
            'data-clicks': () => getClicked()
        };
    });
</script>

๐Ÿ—๏ธ Architecture Patterns

Component-First Pattern

Build applications as collections of reusable components - ideal for new applications and design systems.

<script>
    const StatusCard = (props, { useState }) => {
        const [getMessage] = useState(`status.${props.id}.message`, 'No status');
        
        return {
            div: {
                className: 'status-card',
                style: { 
                    backgroundColor: () => props.type === 'error' ? '#fee' : '#efe',
                    borderLeft: () => `4px solid ${props.type === 'error' ? 'red' : 'green'}`
                },
                children: () => [
                    { h3: { text: props.title } },
                    { p: { text: () => getMessage() } },
                    { 
                        button: { 
                            text: 'Update Status',
                            onClick: () => {
                                const newMessage = prompt('Enter new status:');
                                if (newMessage) {
                                    props.onUpdate(props.id, newMessage);
                                }
                            }
                        }
                    }
                ]
            }
        };
    };
</script>

Feature Module Pattern

Organize code by features rather than technical layers - perfect for large applications and team collaboration.

<script>
// User feature module
    const UserModule = {
        components: {
            UserProfile: (props, { getState }) => ({
                div: {
                    className: 'user-profile',
                    children: () => [
                        { h2: { text: () => getState('user.name', 'Guest') } },
                        { p: { text: () => getState('user.email', 'No email') } }
                    ]
                }
            }),
            
            UserSettings: (props, { getState, setState }) => ({
                form: {
                    children: () => [
                        {
                            input: {
                                type: 'text',
                                value: () => getState('user.name', ''),
                                onInput: (e) => setState('user.name', e.target.value)
                            }
                        }
                    ]
                }
            })
        },
        
        services: {
            userService: {
                async loadUser(id) {
                    // API call to load user data
                    const response = await fetch(`/api/users/${id}`);
                    return response.json();
                }
            }
        }
    };
</script>

Micro-Frontend Pattern

Independent applications that compose together - ideal for large teams and legacy integration.

<script>
// Each team owns specific routes or features
    const ShoppingCartApp = new Juris({
        components: {
            ShoppingCart: (props, { getState, setState }) => ({
                div: {
                    className: 'shopping-cart',
                    children: () => getState('cart.items', []).map(item => ({
                        CartItem: { item }
                    }))
                }
            })
        },
        
        // Only manage cart-related state
        states: {
            'cart.items': [],
            'cart.total': 0
        }
    });
    
    // User profile managed by different team
    const UserProfileApp = new Juris({
        components: {
            UserProfile: (props, { getState }) => ({
                div: { /* user profile implementation */ }
            })
        },
        
        states: {
            'user.name': '',
            'user.preferences': {}
        }
    });
</script>

๐Ÿ”ง Middleware

Transform state changes globally with middleware functions that run on every state update.

Logging Middleware

<script>
    const loggingMiddleware = ({ path, oldValue, newValue }) => {
        console.log(`State change: ${path}`, {
            from: oldValue,
            to: newValue,
            timestamp: new Date().toISOString()
        });
        return newValue;
    };
</script>

Validation Middleware

<script>
    const validationMiddleware = ({ path, newValue }) => {
        // Validate user age
        if (path === 'user.age' && newValue < 0) {
            console.warn('Age cannot be negative, setting to 0');
            return 0;
        }
        
        // Validate email format
        if (path === 'user.email' && newValue && !newValue.includes('@')) {
            console.warn('Invalid email format');
            return oldValue; // Keep previous value
        }
        
        return newValue;
    };
</script>

Auto-save Middleware

<script>
    const autoSaveMiddleware = ({ path, newValue }) => {
        // Auto-save form data
        if (path.startsWith('form.')) {
            localStorage.setItem('formData', JSON.stringify(newValue));
        }
        
        // Auto-sync user preferences
        if (path.startsWith('user.preferences.')) {
            debounce(() => {
                fetch('/api/preferences', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(newValue)
                });
            }, 1000)();
        }
        
        return newValue;
    };
</script>

Performance Monitoring Middleware

<script>
    const performanceMiddleware = ({ path, oldValue, newValue }) => {
        const start = performance.now();
        
        // Process the change
        const result = newValue;
        
        const end = performance.now();
        const duration = end - start;
        
        // Log slow state changes
        if (duration > 10) {
            console.warn(`Slow state change detected: ${path} took ${duration}ms`);
        }
        
        return result;
    };
</script>

๐Ÿ”Œ Services

Services provide reusable functionality and can be injected into components through the context.

API Service

<script>
    const ApiService = {
            baseUrl: 'https://api.example.com',
            
            async get(endpoint) {
                const response = await fetch(`${this.baseUrl}${endpoint}`);
                if (!response.ok) throw new Error(`API Error: ${response.status}`);
                return response.json();
            },
            
            async post(endpoint, data) {
                const response = await fetch(`${this.baseUrl}${endpoint}`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(data)
                });
                if (!response.ok) throw new Error(`API Error: ${response.status}`);
                return response.json();
            }
        };

    // Use in components
    const UserList = (props, { services }) => ({
        div: {
            children: () => [
                {
                    button: {
                        text: 'Load Users',
                        onClick: async () => {
                            try {
                                const users = await services.api.get('/users');
                                setState('users', users);
                            } catch (error) {
                                setState('error', error.message);
                            }
                        }
                    }
                }
            ]
        }
    });
</script>

Theme Service

<script>
    const ThemeService = (props, { getState, setState, subscribe }) => ({
        onRegistered: () => {
            // Initialize theme from localStorage
            const savedTheme = localStorage.getItem('theme') || 'light';
            setState('ui.theme', savedTheme);
            
            // Apply theme to document
            const applyTheme = (theme) => {
                document.body.className = `theme-${theme}`;
                document.body.style.backgroundColor = theme === 'dark' ? '#1a1a1a' : '#ffffff';
                document.body.style.color = theme === 'dark' ? '#ffffff' : '#000000';
            };
            
            // Apply initial theme
            applyTheme(savedTheme);
            
            // Watch for theme changes
            const unsubscribe = subscribe('ui.theme', (newTheme) => {
                localStorage.setItem('theme', newTheme);
                applyTheme(newTheme);
            });
            
            return unsubscribe; // Cleanup function
        }
    });
</script>

Notification Service

<script>
    const NotificationService = {
        show(message, type = 'info', duration = 3000) {
            const notification = {
                id: Date.now(),
                message,
                type,
                timestamp: new Date()
            };
            
            // Add to notifications array
            const current = app.getState('ui.notifications', []);
            app.setState('ui.notifications', [...current, notification]);
            
            // Auto-remove after duration
            setTimeout(() => {
                this.remove(notification.id);
            }, duration);
            
            return notification.id;
        },
        
        remove(id) {
            const current = app.getState('ui.notifications', []);
            app.setState('ui.notifications', current.filter(n => n.id !== id));
        },
        
        clear() {
            app.setState('ui.notifications', []);
        }
    };
</script>

๐Ÿ“ก State Subscriptions

Listen to state changes from outside components for cross-cutting concerns and external integrations.

Basic Subscriptions

<script>
// Subscribe to authentication changes
    const authUnsubscribe = app.subscribe('user.isAuthenticated', (isLoggedIn) => {
        if (isLoggedIn) {
            console.log('User logged in, initializing dashboard');
            initializeUserDashboard();
        } else {
            console.log('User logged out, cleaning up');
            cleanupUserData();
        }
    });
    
    // Subscribe to theme changes
    const themeUnsubscribe = app.subscribe('ui.theme', (theme) => {
        document.body.className = `theme-${theme}`;
        localStorage.setItem('preferred-theme', theme);
    });
    
    // Cleanup subscriptions
    // authUnsubscribe();
    // themeUnsubscribe();
</script>

Complex Subscription Patterns

<script>
// Subscribe to multiple related paths
    const subscriptions = [];
    
    // Cart total calculation
    subscriptions.push(
        app.subscribe('cart.items', (items) => {
            const total = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
            app.setState('cart.total', total);
        })
    );
    
    // Save draft automatically
    let saveTimer;
    subscriptions.push(
        app.subscribe('editor.content', (content) => {
            clearTimeout(saveTimer);
            saveTimer = setTimeout(() => {
                localStorage.setItem('draft', content);
                app.setState('editor.lastSaved', new Date().toISOString());
            }, 2000); // Debounce saves
        })
    );
    
    // Analytics tracking
    subscriptions.push(
        app.subscribe('router.currentRoute', (route) => {
            // Track page views
            if (typeof gtag !== 'undefined') {
                gtag('config', 'GA_TRACKING_ID', {
                    page_path: route
                });
            }
        })
    );
    
    // Cleanup all subscriptions
    const cleanup = () => subscriptions.forEach(unsub => unsub());
</script>

Conditional Subscriptions

<script>
// Only subscribe when user is authenticated
    let analyticsSubscription = null;
    
    app.subscribe('user.isAuthenticated', (isAuthenticated) => {
        if (isAuthenticated && !analyticsSubscription) {
            // Start tracking when logged in
            analyticsSubscription = app.subscribe('user.actions', (action) => {
                trackUserAction(action);
            });
        } else if (!isAuthenticated && analyticsSubscription) {
            // Stop tracking when logged out
            analyticsSubscription();
            analyticsSubscription = null;
        }
    });
</script>

โšก Performance Optimization

Surgical Update Pattern

Juris's default behavior - only update specific elements that changed, not entire components.

<script>
// Traditional framework: Entire list re-renders when any item changes
    // Juris: Only the specific item that changed updates
    
    app.enhance('.todo-item', (props, { getState }) => {
        const id = props.element.dataset.id;
        
        return {
            className: () => {
                const todo = getState(`todos.${id}`);
                return `todo-item ${todo?.completed ? 'completed' : 'active'}`;
            },
            style: () => {
                const todo = getState(`todos.${id}`);
                return {
                    backgroundColor: todo?.priority === 'high' ? '#fee' : '#fff',
                    borderLeft: `4px solid ${todo?.completed ? 'green' : 'orange'}`
                };
            }
        };
    });
</script>

Batched State Updates

<script>
// Group multiple state changes together
    const updateUserProfile = (userData) => {
        // Collect all changes
        const updates = {
            'user.name': userData.name,
            'user.email': userData.email,
            'user.preferences.theme': userData.theme,
            'user.lastUpdated': new Date().toISOString()
        };
        
        // Apply as single batch
        Object.entries(updates).forEach(([path, value]) => {
            app.setState(path, value);
        });
    };
</script>

Lazy Loading Pattern

<script>
    const LazyComponent = (props, { getState, setState }) => {
        const loadComponent = async () => {
            if (!getState('lazyData.loaded')) {
                setState('lazyData.loading', true);
                
                try {
                    const data = await import('./heavy-component.js');
                    setState('lazyData.component', data.default);
                    setState('lazyData.loaded', true);
                } catch (error) {
                    setState('lazyData.error', error.message);
                } finally {
                    setState('lazyData.loading', false);
                }
            }
        };
        
        return {
            div: {
                children: () => {
                    if (getState('lazyData.loading')) {
                        return [{ div: { text: 'Loading...' } }];
                    }
                    
                    if (getState('lazyData.error')) {
                        return [{ div: { text: `Error: ${getState('lazyData.error')}` } }];
                    }
                    
                    const Component = getState('lazyData.component');
                    return Component ? [{ Component: props }] : [{
                        button: {
                            text: 'Load Component',
                            onClick: loadComponent
                        }
                    }];
                }
            }
        };
    };
</script>

Virtual Scrolling for Large Lists

<script>
    const VirtualList = (props, { getState, setState }) => {
        const itemHeight = 50;
        const visibleCount = Math.ceil(props.height / itemHeight);
        
        return {
            div: {
                style: {
                    height: `${props.height}px`,
                    overflow: 'auto'
                },
                onScroll: (e) => {
                    const scrollTop = e.target.scrollTop;
                    const startIndex = Math.floor(scrollTop / itemHeight);
                    setState('virtualList.startIndex', startIndex);
                },
                children: () => {
                    const items = getState('virtualList.items', []);
                    const startIndex = getState('virtualList.startIndex', 0);
                    const endIndex = Math.min(startIndex + visibleCount, items.length);
                    
                    const visibleItems = items.slice(startIndex, endIndex).map((item, i) => ({
                        div: {
                            key: startIndex + i,
                            style: {
                                height: `${itemHeight}px`,
                                transform: `translateY(${(startIndex + i) * itemHeight}px)`
                            },
                            text: item.name
                        }
                    }));
                    
                    return visibleItems;
                }
            }
        };
    };
</script>

๐Ÿ›ก๏ธ Security Patterns

XSS Prevention

Juris provides built-in protection against cross-site scripting attacks through safe content handling.

<script>
// Juris automatically sanitizes content
    const UserContent = (props, { getState }) => ({
        div: {
            // Safe: Juris escapes HTML automatically
            text: () => getState('user.input'), // <script>
becomes &lt;script&gt;
            
            // Dangerous: Only use innerHTML with trusted content
            innerHTML: () => {
                const content = getState('user.input');
                // Always sanitize user input before using innerHTML
                return sanitizeHTML(content);
            }
        }
    });
    
    // Simple HTML sanitizer
    const sanitizeHTML = (html) => {
        const div = document.createElement('div');
        div.textContent = html; // This escapes HTML
        return div.innerHTML;
    };
</script>

Input Validation Pattern

<script>
    const SecureForm = (props, { getState, setState }) => {
        const validateEmail = (email) => {
            const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            return re.test(email);
        };
        
        const validateInput = (field, value) => {
            const errors = getState('form.errors', {});
            
            switch (field) {
                case 'email':
                    if (!validateEmail(value)) {
                        errors.email = 'Please enter a valid email address';
                    } else {
                        delete errors.email;
                    }
                    break;
                    
                case 'password':
                    if (value.length < 8) {
                        errors.password = 'Password must be at least 8 characters';
                    } else {
                        delete errors.password;
                    }
                    break;
            }
            
            setState('form.errors', errors);
        };
        
        return {
            form: {
                onSubmit: (e) => {
                    e.preventDefault();
                    
                    // Validate all fields before submission
                    const email = getState('form.email');
                    const password = getState('form.password');
                    
                    validateInput('email', email);
                    validateInput('password', password);
                    
                    const errors = getState('form.errors', {});
                    if (Object.keys(errors).length === 0) {
                        // Submit form securely
                        submitForm({ email, password });
                    }
                },
                children: () => [
                    {
                        input: {
                            type: 'email',
                            value: () => getState('form.email', ''),
                            onInput: (e) => {
                                setState('form.email', e.target.value);
                                validateInput('email', e.target.value);
                            }
                        }
                    },
                    {
                        div: {
                            className: 'error',
                            text: () => getState('form.errors.email', ''),
                            style: {
                                display: () => getState('form.errors.email') ? 'block' : 'none'
                            }
                        }
                    }
                ]
            }
        };
    };
</script>

Authentication Pattern

<script>
    const AuthService = {
        async login(credentials) {
            try {
                const response = await fetch('/api/auth/login', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(credentials)
                });
                
                if (!response.ok) {
                    throw new Error('Login failed');
                }
                
                const data = await response.json();
                
                // Store token securely
                localStorage.setItem('auth_token', data.token);
                
                // Update application state
                app.setState('user.isAuthenticated', true);
                app.setState('user.data', data.user);
                
                return data;
            } catch (error) {
                app.setState('auth.error', error.message);
                throw error;
            }
        },
        
        logout() {
            localStorage.removeItem('auth_token');
            app.setState('user.isAuthenticated', false);
            app.setState('user.data', null);
            app.navigate('/');
        },
        
        getToken() {
            return localStorage.getItem('auth_token');
        },
        
        // Add token to API requests
        async authenticatedRequest(url, options = {}) {
            const token = this.getToken();
            
            return fetch(url, {
                ...options,
                headers: {
                    ...options.headers,
                    'Authorization': `Bearer ${token}`
                }
            });
        }
    };
</script>

๐Ÿงช Testing Patterns

Component Testing

<script>
// Test utility functions
    const createTestApp = (initialState = {}) => {
        return new Juris({
            states: initialState,
            components: {},
            services: {}
        });
    };
    
    const renderComponent = (Component, props = {}, context = {}) => {
        const mockContext = {
            getState: jest.fn().mockReturnValue(undefined),
            setState: jest.fn(),
            navigate: jest.fn(),
            services: {},
            ...context
        };
        
        return Component(props, mockContext);
    };
    
    // Example test
    describe('UserProfile Component', () => {
        test('renders user name from state', () => {
            const mockContext = {
                getState: jest.fn((path) => {
                    if (path === 'user.name') return 'John Doe';
                    return undefined;
                }),
                setState: jest.fn(),
                navigate: jest.fn(),
                services: {}
            };
            
            const component = UserProfile({}, mockContext);
            const result = component.div.children()[0];
            
            expect(result.h2.text()).toBe('John Doe');
        });
        
        test('handles missing user data gracefully', () => {
            const mockContext = {
                getState: jest.fn().mockReturnValue(undefined),
                setState: jest.fn(),
                navigate: jest.fn(),
                services: {}
            };
            
            const component = UserProfile({}, mockContext);
            const result = component.div.children()[0];
            
            expect(result.h2.text()).toBe('Guest');
        });
    });
</script>

Enhancement Testing

<script>
// Test enhancement behavior
    describe('Button Enhancement', () => {
        let app, button;
        
        beforeEach(() => {
            // Create test HTML
            document.body.innerHTML = `
                <button class="test-button" data-count="0">Click me</button>
            `;
            
            button = document.querySelector('.test-button');
            
            // Create test app
            app = createTestApp({ count: 0 });
            
            // Apply enhancement
            app.enhance('.test-button', (props, { getState, setState }) => ({
                textContent: () => `Clicked ${getState('count', 0)} times`,
                onClick: () => setState('count', getState('count', 0) + 1)
            }));
        });
        
        test('updates text content when state changes', () => {
            app.setState('count', 5);
            expect(button.textContent).toBe('Clicked 5 times');
        });
        
        test('handles click events', () => {
            button.click();
            expect(app.getState('count')).toBe(1);
            expect(button.textContent).toBe('Clicked 1 times');
        });
    });
</script>

Integration Testing

<script>
// Test complete application flows
    describe('Todo Application', () => {
        let app;
        
        beforeEach(() => {
            app = new Juris({
                states: { todos: [] },
                components: { TodoApp, TodoItem },
                router: {
                    routes: {
                        '/': 'TodoApp'
                    }
                }
            });
            
            app.render('#test-container');
        });
        
        test('complete todo workflow', async () => {
            // Add a todo
            const input = document.querySelector('.todo-input');
            const addButton = document.querySelector('.add-todo');
            
            input.value = 'Test todo';
            input.dispatchEvent(new Event('input'));
            addButton.click();
            
            // Verify todo was added
            expect(app.getState('todos')).toHaveLength(1);
            expect(app.getState('todos')[0].text).toBe('Test todo');
            
            // Toggle completion
            const toggleButton = document.querySelector('.todo-toggle');
            toggleButton.click();
            
            expect(app.getState('todos')[0].completed).toBe(true);
            
            // Delete todo
            const deleteButton = document.querySelector('.todo-delete');
            deleteButton.click();
            
            expect(app.getState('todos')).toHaveLength(0);
        });
    });
</script>

๐Ÿš€ Deployment

Static Deployment

Deploy Juris applications as static files with no server requirements.

๐Ÿ“ฆ Build Process

No build step required - deploy JavaScript files directly

๐ŸŒ CDN Distribution

Serve from CDNs for global performance

๐Ÿ’ฐ Cost Effective

Static hosting costs significantly less than server hosting

โšก High Availability

Static files provide better uptime and reliability

Deployment Checklist

  • Minify JavaScript files - Reduce file sizes for production
  • Configure HTTPS - Required for service workers and secure features
  • Set up CDN - Improve global performance
  • Configure caching headers - Optimize repeat visits
  • Test on target devices - Verify functionality across platforms
  • Monitor performance - Track real-world usage metrics

Production Configuration

<script>
// Production app configuration
    const app = new Juris({
        // Disable debug logging in production
        debug: false,
        
        // Production error handling
        errorHandler: (error) => {
            // Log to external service
            console.error('Production error:', error);
            
            // Show user-friendly message
            app.setState('ui.error', 'Something went wrong. Please try again.');
        },
        
        // Production middleware
        middleware: [
            // Only include essential middleware in production
            validationMiddleware,
            errorHandlingMiddleware
        ],
        
        // Production services
        services: {
            api: {
                baseUrl: 'https://api.yoursite.com',
                timeout: 10000
            }
        }
    });
</script>

Environment Configuration

<script>
// Environment-specific configuration
    const config = {
        development: {
            apiUrl: 'http://localhost:3000/api',
            debug: true,
            logLevel: 'verbose'
        },
        
        staging: {
            apiUrl: 'https://staging-api.yoursite.com',
            debug: true,
            logLevel: 'info'
        },
        
        production: {
            apiUrl: 'https://api.yoursite.com',
            debug: false,
            logLevel: 'error'
        }
    };
    
    // Detect environment
    const environment = window.location.hostname.includes('localhost') 
        ? 'development'
        : window.location.hostname.includes('staging')
        ? 'staging'
        : 'production';
    
    const currentConfig = config[environment];
</script>

Progressive Deployment

<script>
// Feature flags for gradual rollout
    const FeatureFlags = {
        newDashboard: window.location.search.includes('beta=true'),
        experimentalFeatures: localStorage.getItem('experimental') === 'true',
        
        // Server-controlled flags (loaded from API)
        async loadServerFlags() {
            try {
                const response = await fetch('/api/feature-flags');
                const flags = await response.json();
                Object.assign(this, flags);
            } catch (error) {
                console.warn('Failed to load feature flags:', error);
            }
        }
    };
    
    // Use feature flags in components
    const Dashboard = (props, { getState }) => ({
        div: {
            children: () => FeatureFlags.newDashboard 
                ? [{ NewDashboard: {} }]
                : [{ LegacyDashboard: {} }]
        }
    });
</script>

Monitoring and Analytics

<script>
// Production monitoring
    const MonitoringService = {
        // Performance monitoring
        trackPageLoad() {
            const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
            this.track('page_load_time', { duration: loadTime });
        },
        
        // Error tracking
        trackError(error) {
            this.track('error', {
                message: error.message,
                stack: error.stack,
                url: window.location.href,
                userAgent: navigator.userAgent
            });
        },
        
        // User interaction tracking
        trackUserAction(action, data = {}) {
            this.track('user_action', {
                action,
                ...data,
                timestamp: new Date().toISOString()
            });
        },
        
        // Generic tracking function
        track(event, data) {
            // Send to analytics service
            if (typeof gtag !== 'undefined') {
                gtag('event', event, data);
            }
            
            // Send to custom analytics
            fetch('/api/analytics', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ event, data })
            }).catch(console.error);
        }
    };
</script>

๐Ÿ“š API Reference

Juris Class

Constructor

<script>
    new Juris(config)
</script>

Config options:

  • states - Initial state object
  • components - Component definitions
  • services - Service definitions
  • middleware - State middleware functions
  • router - Router configuration
  • layout - Root layout component

Methods

State Management
<script>
    setState(path, value, context?)
    getState(path, defaultValue?)
    subscribe(path, callback)
</script>
Component Management
<script>
    registerComponent(name, componentFn)
    render(container?)
</script>
Navigation
<script>
    navigate(path)
    getCurrentRoute()
</script>

Component Context

Components receive a context object with:

<script>
    {
        setState: (path, value, context?) => void,
        getState: (path, defaultValue?) => any,
        navigate: (path) => void,
        services: object
    }
</script>

๐Ÿš€ Advanced Router

The Juris Router provides enterprise-grade routing capabilities including parameter validation, query string handling, route guards, lazy loading, transitions, and nested routing.

Routing Modes

๐Ÿ”— Hash Mode (Default)

Uses URL fragments for routing - works without server configuration

๐Ÿ“ History Mode

Clean URLs using HTML5 History API - requires server configuration

๐Ÿ’พ Memory Mode

In-memory routing for testing and server-side rendering

// Router mode configuration
            const app = new Juris({
                router: {
                    mode: 'history',        // 'hash' | 'history' | 'memory'
                    base: '/app',          // Base path for history mode
                    scrollBehavior: 'top', // 'top' | 'none' | 'maintain' | function
                    transitions: true      // Enable route transitions
                }
            });
            
            // Hash mode URLs: /#/users/123
            // History mode URLs: /app/users/123
            // Memory mode: No URL changes

Advanced Route Definition

routes: {
                // Simple routes
                '/': 'HomePage',
                '/about': 'AboutPage',
                
                // Advanced route configuration
                '/users/:id': {
                    component: 'UserPage',
                    meta: { title: 'User Profile', requiresAuth: true },
                    params: {
                        id: { 
                            type: 'number', 
                            required: true, 
                            min: 1,
                            max: 999999
                        }
                    },
                    query: {
                        tab: { type: 'string', enum: ['profile', 'settings', 'activity'] },
                        page: { type: 'number', default: 1, min: 1 }
                    },
                    guards: ['authGuard', 'userAccessGuard'],
                    loadData: 'loadUserData',
                    transitions: { enter: 'fadeIn', leave: 'fadeOut' }
                },
                
                // Pattern types
                '/products/:category?': 'ProductPage',     // Optional parameter
                '/admin/*': 'AdminPage',                   // Wildcard routes
                '/posts/:id/comments/:commentId': 'CommentPage', // Multiple parameters
                'RegExp:^/files/(.+)\\.(jpg|png|gif)$': 'ImageViewer' // Custom regex
            }

Parameter Validation & Types

Comprehensive parameter validation with multiple data types and constraints:

Number Parameters

params: {
                id: { 
                    type: 'number', 
                    required: true,
                    min: 1, 
                    max: 999999,
                    default: 1 
                },
                page: { 
                    type: 'number', 
                    min: 1, 
                    max: 1000, 
                    default: 1 
                }
            }

String Parameters

params: {
                slug: { 
                    type: 'string', 
                    pattern: /^[a-z0-9-]+$/, 
                    minLength: 3,
                    maxLength: 50,
                    required: true
                },
                category: {
                    type: 'string',
                    enum: ['electronics', 'books', 'clothing'],
                    optional: true
                }
            }

Boolean & Date Parameters

params: {
                published: { 
                    type: 'boolean', 
                    default: false 
                },
                date: { 
                    type: 'date', 
                    required: true,
                    min: new Date('2020-01-01'),
                    max: new Date('2030-12-31')
                }
            }

Query Parameters

Handle complex query strings with validation and type conversion:

'/search': {
                component: 'SearchPage',
                query: {
                    q: { type: 'string', required: true, minLength: 2 },
                    page: { type: 'number', default: 1, min: 1, max: 100 },
                    sort: { 
                        type: 'string', 
                        enum: ['date', 'name', 'relevance'], 
                        default: 'relevance' 
                    },
                    filters: { type: 'array' }, // For multiple values
                    published: { type: 'boolean', default: true }
                }
            }
            
            // Usage in components
            const SearchPage = (props, { getState }) => {
                const query = getState('router.query', {});
                
                return {
                    div: {
                        children: () => [
                            { h1: { text: `Search: "${query.q}"` } },
                            { p: { text: `Page ${query.page} - Sort by ${query.sort}` } },
                            { p: { text: `Filters: ${query.filters?.join(', ') || 'None'}` } }
                        ]
                    }
                };
            };

Navigation & Route Building

// Programmatic navigation with options
            context.navigate('/users/123', {
                replace: true,              // Replace current history entry
                query: { tab: 'profile' },  // Add query parameters
                state: { fromDashboard: true } // History state
            });
            
            // Build routes with parameters
            const userRoute = app.buildRoute('/users/:id', { id: 123 });
            // Result: "/users/123"
            
            // Build routes with query parameters
            const searchRoute = app.buildRoute('/search', {}, { 
                q: 'javascript', 
                page: 2,
                filters: ['recent', 'popular']
            });
            // Result: "/search?q=javascript&page=2&filters[]=recent&filters[]=popular"
            
            // Navigation with return URL
            context.navigate(`/login?returnUrl=${encodeURIComponent(currentRoute)}`);

Router Components

RouterLink Component

// Navigation links with automatic active state
            {
                RouterLink: {
                    to: '/users/123',                   // Target route
                    params: { id: 123 },               // Route parameters
                    query: { tab: 'profile' },         // Query parameters
                    exact: false,                      // Exact match for active state
                    activeClass: 'router-link-active', // CSS class for active state
                    replace: false,                    // Replace history entry
                    text: 'View User Profile',         // Link text
                    onClick: (e) => {                  // Custom click handler
                        // Additional logic before navigation
                    }
                }
            }

Router Component with Loading States

{
                Router: {
                    loadingComponent: 'LoadingPage',    // Component for loading state
                    errorComponent: 'ErrorPage',        // Component for errors
                    notFoundComponent: 'NotFoundPage'   // Component for 404s
                }
            }

Lazy Loading

Load components dynamically for better performance:

router: {
                lazy: {
                    AdminPage: {
                        loader: () => import('./components/AdminPage.js')
                    },
                    UserDashboard: {
                        loader: async () => {
                            const module = await import('./components/UserDashboard.js');
                            return module.UserDashboard;
                        }
                    },
                    HeavyComponent: {
                        loader: () => import('./components/HeavyComponent.js'),
                        placeholder: { div: { text: 'Loading heavy component...' } }
                    }
                }
            }
            
            // Route configuration with lazy loading
            routes: {
                '/admin': {
                    component: 'AdminPage', // Will be loaded lazily
                    guards: ['authGuard', 'adminGuard']
                }
            }

Route Data Loading

routes: {
                '/users/:id': {
                    component: 'UserPage',
                    loadData: async (context) => {
                        try {
                            context.setState('router.isLoading', true);
                            
                            // Load user data
                            const userId = context.params.id;
                            const userData = await fetchUser(userId);
                            context.setState('user.current', userData);
                            
                            // Load related data
                            const posts = await fetchUserPosts(userId);
                            context.setState('user.posts', posts);
                            
                        } catch (error) {
                            context.setState('router.error', 'Failed to load user data');
                        } finally {
                            context.setState('router.isLoading', false);
                        }
                    }
                }
            }

Route Transitions

Add smooth transitions between routes:

// Global transition configuration
            router: {
                transitions: true,
                transitionDuration: 300
            }
            
            // Per-route transitions
            routes: {
                '/dashboard': {
                    component: 'DashboardPage',
                    transitions: {
                        enter: 'slideInRight',
                        leave: 'slideOutLeft',
                        duration: 400
                    }
                },
                '/profile': {
                    component: 'ProfilePage',
                    transitions: {
                        enter: 'fadeIn',
                        leave: 'fadeOut',
                        duration: 250
                    }
                }
            }
            
            // CSS for transitions
            /*
            .slideInRight {
                animation: slideInRight 0.3s ease-out;
            }
            
            .slideOutLeft {
                animation: slideOutLeft 0.3s ease-out;
            }
            
            @keyframes slideInRight {
                from { transform: translateX(100%); }
                to { transform: translateX(0); }
            }
            
            @keyframes slideOutLeft {
                from { transform: translateX(0); }
                to { transform: translateX(-100%); }
            }
            */

Nested Routing

Create complex hierarchical routing structures:

routes: {
                '/admin': {
                    component: 'AdminLayout',
                    guards: ['authGuard', 'adminGuard'],
                    children: {
                        '': { component: 'AdminDashboard' },           // /admin
                        'users': { component: 'AdminUsers' },          // /admin/users
                        'users/:id': { 
                            component: 'AdminUserDetail',              // /admin/users/123
                            params: { id: { type: 'number', required: true } }
                        },
                        'settings': { 
                            component: 'AdminSettings',                // /admin/settings
                            children: {
                                'general': { component: 'GeneralSettings' },
                                'security': { component: 'SecuritySettings' }
                            }
                        }
                    }
                }
            }
            
            // AdminLayout component with RouterOutlet
            const AdminLayout = (props, context) => ({
                div: {
                    className: 'admin-layout',
                    children: () => [
                        { AdminSidebar: {} },
                        { 
                            div: {
                                className: 'admin-content',
                                children: [{ RouterOutlet: {} }] // Renders child routes
                            }
                        }
                    ]
                }
            });

Route Middleware

Apply logic to multiple routes with middleware:

router: {
                middleware: [
                    {
                        path: '/admin/*',
                        guard: async (context) => {
                            // Log admin access
                            console.log('Admin area accessed:', context.route, context.user);
                            
                            // Track analytics
                            if (typeof gtag !== 'undefined') {
                                gtag('event', 'admin_access', {
                                    page: context.route,
                                    user_id: context.getState('auth.user.id')
                                });
                            }
                            
                            return true;
                        }
                    },
                    {
                        path: '/api/*',
                        guard: async (context) => {
                            // Add API token to headers
                            const token = context.getState('auth.token');
                            if (token) {
                                context.headers = { Authorization: `Bearer ${token}` };
                            }
                            return true;
                        }
                    }
                ]
            }

Redirects and Aliases

routes: {
                // Redirects
                '/old-dashboard': {
                    redirectTo: '/dashboard'
                },
                '/legacy/:id': {
                    redirectTo: '/users/:id' // Parameter forwarding
                },
                
                // Aliases - multiple URLs for same component
                '/users/:id': {
                    component: 'UserPage',
                    alias: ['/profile/:id', '/member/:id', '/u/:id']
                },
                
                // Conditional redirects
                '/home': {
                    component: 'HomePage',
                    beforeEnter: (context) => {
                        if (!context.getState('auth.isAuthenticated')) {
                            context.navigate('/welcome');
                            return false;
                        }
                        return true;
                    }
                }
            }

Advanced Scroll Behavior

router: {
                scrollBehavior: (to, from, savedPosition) => {
                    // Return to saved position (browser back/forward)
                    if (savedPosition) {
                        return savedPosition;
                    }
                    
                    // Scroll to anchor
                    if (to.hash) {
                        const element = document.querySelector(to.hash);
                        if (element) {
                            return {
                                x: 0,
                                y: element.offsetTop - 80 // Account for fixed header
                            };
                        }
                    }
                    
                    // Scroll to top for new pages
                    if (to.path !== from.path) {
                        return { x: 0, y: 0 };
                    }
                    
                    // Maintain scroll position for same page
                    return false;
                }
            }

Router State Management

The router maintains comprehensive state information:

// Router state structure
            {
                router: {
                    currentRoute: '/users/123',          // Current route path
                    previousRoute: '/dashboard',         // Previous route
                    params: { id: 123 },                // Route parameters
                    query: { tab: 'profile', page: 1 }, // Query parameters
                    hash: 'section1',                   // URL hash
                    isLoading: false,                   // Loading state
                    error: null,                        // Error message
                    notFound: false,                    // 404 state
                    transition: {                       // Transition state
                        isTransitioning: false,
                        direction: 'forward'
                    }
                }
            }
            
            // Accessing router state in components
            const NavigationInfo = (props, { getState }) => ({
                div: {
                    children: () => [
                        { p: { text: () => `Current: ${getState('router.currentRoute')}` } },
                        { p: { text: () => `Previous: ${getState('router.previousRoute', 'None')}` } },
                        { 
                            p: { 
                                text: () => {
                                    const params = getState('router.params', {});
                                    return `Params: ${JSON.stringify(params)}`;
                                }
                            }
                        }
                    ]
                }
            });
            
            // Subscribe to router changes
            app.subscribe('router.currentRoute', (newRoute, oldRoute) => {
                console.log(`Route changed from ${oldRoute} to ${newRoute}`);
                
                // Update page title
                const routeConfig = app.getRouteConfig(newRoute);
                if (routeConfig?.meta?.title) {
                    document.title = `${routeConfig.meta.title} - My App`;
                }
            });

Complete E-commerce Example

const app = new Juris({
                router: {
                    mode: 'history',
                    base: '/shop',
                    
                    guards: {
                        authGuard: async (context) => {
                            if (!context.getState('auth.isAuthenticated')) {
                                const returnUrl = encodeURIComponent(context.route);
                                context.navigate(`/login?returnUrl=${returnUrl}`);
                                return false;
                            }
                            return true;
                        }
                    },
                    
                    routes: {
                        '/': 'HomePage',
                        
                        // Product catalog with filtering
                        '/products': {
                            component: 'ProductListPage',
                            query: {
                                category: { 
                                    type: 'string', 
                                    enum: ['electronics', 'books', 'clothing'],
                                    optional: true 
                                },
                                brand: { type: 'string', optional: true },
                                sort: { 
                                    type: 'string', 
                                    enum: ['price-asc', 'price-desc', 'name', 'rating'], 
                                    default: 'name' 
                                },
                                page: { type: 'number', min: 1, default: 1 },
                                limit: { type: 'number', min: 10, max: 100, default: 20 },
                                priceMin: { type: 'number', min: 0, optional: true },
                                priceMax: { type: 'number', min: 0, optional: true },
                                inStock: { type: 'boolean', default: true }
                            },
                            loadData: async (context) => {
                                const query = context.query;
                                const products = await fetchProducts(query);
                                context.setState('products.list', products);
                                context.setState('products.totalCount', products.total);
                            }
                        },
                        
                        // Product details
                        '/products/:id': {
                            component: 'ProductDetailPage',
                            params: {
                                id: { type: 'number', required: true, min: 1 }
                            },
                            loadData: async (context) => {
                                const product = await fetchProduct(context.params.id);
                                const reviews = await fetchProductReviews(context.params.id);
                                
                                context.setState('product.current', product);
                                context.setState('product.reviews', reviews);
                            }
                        },
                        
                        // Shopping cart
                        '/cart': {
                            component: 'CartPage',
                            loadData: async (context) => {
                                const cartItems = await fetchCartItems();
                                context.setState('cart.items', cartItems);
                            }
                        },
                        
                        // Checkout process
                        '/checkout': {
                            component: 'CheckoutLayout',
                            guards: ['authGuard'],
                            children: {
                                '': { redirectTo: '/checkout/shipping' },
                                'shipping': { component: 'ShippingStep' },
                                'payment': { 
                                    component: 'PaymentStep',
                                    guards: ['shippingCompleteGuard']
                                },
                                'review': { 
                                    component: 'ReviewStep',
                                    guards: ['paymentCompleteGuard']
                                },
                                'confirmation/:orderId': {
                                    component: 'ConfirmationStep',
                                    params: {
                                        orderId: { type: 'string', required: true }
                                    }
                                }
                            }
                        },
                        
                        // User account
                        '/account': {
                            component: 'AccountLayout',
                            guards: ['authGuard'],
                            children: {
                                '': { redirectTo: '/account/profile' },
                                'profile': { component: 'ProfilePage' },
                                'orders': { 
                                    component: 'OrdersPage',
                                    query: {
                                        status: { 
                                            type: 'string',
                                            enum: ['all', 'pending', 'shipped', 'delivered'],
                                            default: 'all'
                                        },
                                        page: { type: 'number', min: 1, default: 1 }
                                    }
                                },
                                'orders/:orderId': {
                                    component: 'OrderDetailPage',
                                    params: {
                                        orderId: { type: 'string', required: true }
                                    }
                                },
                                'addresses': { component: 'AddressesPage' },
                                'payment-methods': { component: 'PaymentMethodsPage' }
                            }
                        }
                    }
                }
            });

Router API Reference

๐Ÿ“ navigate(path, options)

Navigate to a route programmatically with options like replace, query, and state

๐Ÿ—๏ธ buildRoute(pattern, params, query)

Build route URLs with parameters and query strings

๐Ÿ” matchRoute(path)

Match a path against route patterns and return match details

๐Ÿ“ getCurrentRoute()

Get the current route path and state information

Best Practices

  • Parameter Validation - Always validate route parameters with appropriate types and constraints
  • Error Handling - Implement proper error handling in route guards and data loading
  • Loading States - Show loading indicators during async route operations
  • Nested Routes - Use nested routing for complex application structures
  • SEO Friendly - Use history mode and proper meta tags for better SEO
  • Performance - Implement lazy loading for heavy components

๐Ÿ’ก Examples

Complete Todo Application

Download and run our complete implementation with all features demonstrated:

โœ… Todo CRUD operations

Add, edit, delete todos with reactive UI

โœ… Advanced routing

Authentication and parameterized routes

โœ… Multi-tier synchronization

localStorage + cross-tab + remote server

โœ… User authentication

Role-based access control system

Testing Our Demo

Try these scenarios:

  1. Basic Todo Operations: Add, edit, delete todos; toggle completion status; filter by status
  2. Authentication Flow: Login with different user types (admin, user, newuser)
  3. Multi-tier Sync: Open multiple tabs and test cross-tab synchronization
  4. Route Guards: Test access controls and data loading states

Demo Accounts

admin

Has admin access to all routes

user

Regular user with completed profile

newuser

User without profile (blocked from profile routes)

๐ŸŽฏ Best Practices

State Management

  • Keep state flat - Avoid deep nesting when possible
  • Use descriptive paths - user.profile.name instead of user.data.n
  • Normalize data - Store arrays as objects with IDs as keys for easier updates
  • Use middleware - For cross-cutting concerns like logging and validation

Components

  • Keep components pure - Same props should always render the same output
  • Use reactive functions - For dynamic content that depends on state
  • Avoid side effects - Use services for API calls and complex logic
  • Compose components - Build complex UIs from simple, reusable components

Routing

  • Use descriptive routes - /user/:id/posts/:postId is better than /u/:i/p/:p
  • Implement proper guards - Always validate permissions and load required data
  • Handle loading states - Show loading indicators during async operations
  • Plan for errors - Implement error boundaries and fallback routes

Performance

  • Minimize re-renders - Use specific state paths in getState
  • Lazy load data - Load data in route guards or component lifecycle
  • Debounce inputs - For search and filter inputs
  • Use keys for lists - Help framework efficiently update list items

Security

  • Validate all inputs - Both client and server side
  • Implement proper authentication - Use secure tokens and validation
  • Use HTTPS - Always use secure connections for remote sync
  • Sanitize data - Prevent XSS attacks with proper data sanitization

๐Ÿค Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

# Clone the repository
    git clone https://github.com/jurisjs/juris.git
    cd juris-framework

    # Install dependencies (if any)
    npm install

    # Run tests
    npm test

    # Start development server
    npm run dev
</script>

Reporting Issues

Please use the GitHub Issues page to report bugs or request features.

Code Style

  • Use ES6+ features
  • Follow JSDoc conventions for documentation
  • Write tests for new features
  • Keep functions small and focused

๐Ÿ“„ License

MIT License - see the LICENSE file for details

๐Ÿ‘จโ€๐Ÿ’ป Author

Resti Guay - Version 0.0.1

๐Ÿ’ฌ Support

GitHub Issues and Discussions available

๐Ÿ™ Acknowledgments

Inspired by modern reactive frameworks, built with vanilla JavaScript