A lightweight, vanilla JavaScript framework for building modern User Interfaces with message-driven component-based architecture and flexible data management.
Note: This core library is extended by atom-websdk which is recommended for use in projects as it contains enhancements and many abstractions over this core library.
Element Framework (internally called "Muffin") is a minimalistic frontend framework that provides:
- Component-based architecture with Web Components
- Client-side routing for SPAs
- Real-time data synchronization via WebSockets
- Local storage integration with IndexedDB
- Template inclusion system for modular HTML
- Event-driven communication between components
Install in your project:
npm i --save github:FootLooseLabs/elementThen include the framework in your index.src.html:
<scriptsrc="./node_modules/muffin/dist/muffin.min.js"></script>Project structure:
my-project/
├── src/
│ ├── assets/
│ ├── components/
│ ├── pages/
│ ├── index.src.html
│ └── sw.js
├── gulpfile.js
├── package.json
├── .gitignore
└── .babelrc
Components are custom HTML elements that encapsulate data, markup, and behavior.
classContactCardextendsMuffin.DOMComponent{staticdomElName="contact-card"staticschema={"name": "","email": "","phone_no": ""}staticmarkupFunc=(data)=>{return` <h1>${data.name}</h1> <h3 class="contact">${data.email}<br/>${data.phone_no} </h3> <button on-click="handleEdit">Edit Contact</button> `}handleEdit(srcEl,event){console.log("Editing contact:",this.data.name);console.log("Source element:",srcEl);// Handle edit logic}}Key Properties:
domElName: HTML tag name for the componentschema: Default data structuremarkupFunc: Function that renders data into HTMLstateSpace: Component state managementinterfaces: External API definitions
DataSource handles data persistence, real-time updates, and local caching.
<contact-card><component-datasocket="websocket-name" label="contact-data">
{
"name": "John Doe",
"email": "john@example.com",
"phone_no": "+1234567890"
}
</component-data></contact-card>Features:
- Automatic IndexedDB persistence
- WebSocket real-time updates
- JSON fixture loading
- Data normalization and validation
Client-side routing for single-page applications with nested route support.
<divroute="home"><h1>Home Page</h1><buttononclick="Muffin._router.go('about')">About</button></div><divroute="about"><h1>About Page</h1><buttononclick="Muffin._router.go('home')">Home</button></div><script>Muffin._router=newMuffin.Router();document.addEventListener('DOMContentLoaded',()=>{Muffin._router.go('home');});</script>Router Features:
- Declarative route definitions with
routeattributes - Nested routing with
sub-route - URL parameter handling
- History API integration
- Programmatic navigation
Event-driven messaging system supporting WebSockets and local events.
// Create a WebSocket connectionvarsocket=PostOffice.addSocket(WebSocket,"api","ws://localhost:8080");// Listen for messagessocket.addListener("user-update",(data)=>{console.log("User updated:",data);});// Send messagessocket.sendMsg({lexemeName: "updateUser",msg: {id: 1,name: "Jane Doe"}});Modular HTML templates for better code organization.
<!-- index.src.html --><divroute="contact"><includesrc="pages/contact.html"></include></div><!-- pages/contact.html --><template><style>
.contact-container { padding:20px; }
</style><divclass="contact-container"><contact-card><component-datalabel="contact-info">
{"name": "Alice", "email": "alice@example.com"}
</component-data></contact-card></div></template>Components support on-<eventname> attributes in markup for direct event binding.
classInteractiveCardextendsMuffin.DOMComponent{staticdomElName="interactive-card"staticschema={title: "",count: 0}staticmarkupFunc=(data)=>{return` <div class="card"> <h2>${data.title}</h2> <p>Count: ${data.count}</p> <button on-click="increment">+</button> <button on-click="decrement">-</button> <input on-input="updateTitle" placeholder="Update title" /> <div on-mouseenter="highlight" on-mouseleave="unhighlight"> Hover me! </div> </div> `}increment(srcEl,event){this.data.count++;this.render();}decrement(srcEl,event){this.data.count--;this.render();}updateTitle(srcEl,event){this.data.title=event.target.value;}highlight(srcEl,event){srcEl.style.backgroundColor='#f0f0f0';}unhighlight(srcEl,event){srcEl.style.backgroundColor='';}}<include src="path/to/template.html">- Include external templates<component-data socket="name" label="key">- Component data binding
route="route-name"- Define routes for SPA navigationsub-route- Mark nested routeson-<eventname>="methodName"- Bind DOM events to component methods
.page- Default page styling._active- Active route indicator (customizable)
Components have built-in lifecycle methods:
classMyComponentextendsMuffin.DOMComponent{staticdomElName="my-component"connectedCallback(){// Called when component is added to DOMconsole.log("Component mounted");}onDomContentLoaded(){// Called when DOM is readythis.initializeComponent();}switchState(newState){// Handle state transitionsconsole.log("State changed to:",newState);}}Components can expose interfaces for external communication:
classAPIComponentextendsMuffin.DOMComponent{staticdomElName="api-component"staticadvertiseAs="UserAPI"staticLEXICON={getUser: {schema: {subscribe: true},inflect: (data)=>({action: "get_user", ...data})},updateUser: {schema: {},inflect: (data)=>({action: "update_user", ...data})}}getUser(request){// Handle get user requests}updateUser(request){// Handle update user requests}}Components can define state spaces and transitions:
classStatefulComponentextendsMuffin.DOMComponent{staticdomElName="stateful-component"staticstateSpace={"loading": {apriori: ["idle"]},"loaded": {apriori: ["loading"]},"error": {apriori: ["loading"]}}asyncloadData(){this.switchState("loading");try{constdata=awaitthis.fetchData();this.data=data;this.switchState("loaded");}catch(error){this.switchState("error");}}}Framework configuration in your application:
// Custom configurationwindow.Muffin={DEBUG_SCOPE: {_debugCmp: null},DB_NAME: "MyApp",DB_VERSION: 1.0};- Create components in
src/components/directory - Define pages in
src/pages/directory using templates - Set up routes in your main HTML file
- Configure data sources with WebSocket connections
- Build and test your application
- Modern browsers supporting Web Components
- ES6+ features required
- IndexedDB for local storage
- WebSocket API for real-time features
- Lightweight: Minimal framework overhead
- Modular: Component-based architecture
- Flexible: No rigid conventions, adaptable to needs
- Real-time: Built-in WebSocket support
- Offline-capable: Local storage integration
- SEO-friendly: Server-side rendering possible
The framework exposes the window.Muffin namespace containing:
window.Muffin.DOMComponent- Base component classwindow.Muffin.Router- Client-side routerwindow.Muffin.PostOffice- Messaging systemwindow.Muffin.DataSource- Data managementwindow.Muffin.Lexeme- Message structure systemwindow.Muffin.Introspector- Component introspection utilitieswindow.Muffin.DOMComponentRegistry- Component registration system
Legacy globals (for backward compatibility):
window.Router- Alias forwindow.Muffin.Routerwindow.PostOffice- Alias forwindow.Muffin.PostOfficewindow.DataSource- Alias forwindow.Muffin.DataSourcewindow.DOMComponent- Alias forwindow.Muffin.DOMComponent
Element Framework is designed to be implementation-flexible and lightweight. When contributing:
- Maintain minimal dependencies
- Preserve vanilla JavaScript approach
- Ensure backward compatibility
- Add comprehensive tests