This is a continuation effort of the amazing web framework Cell.js. The original can still be found at its abandoned project repository: intercellular/cell, but use with caution as most of the links/images/instructions there will no longer work
A self-constructing web app framework powered by a self-driving DOM.
Cell has one and only one design goal: Easy.
- Easy to learn: There is NO API to learn. You just need to remember 3 rules.
- Easy to use: You just need a single HTML file with a single
<script src>
line. - Easy to read: Write an entire app as a piece of JSON-like, human-readable data structure.
- Easy to integrate: Integrating into an existing website is as simple as copy and pasting a Youtube embed code.
- Easy to reuse: Everything is powered by stateless functions instead of es and objects, making it extremely modular.
- Easy to maintain: "Development workflow" doesn't exist. No NPM, No Webpack, No Babel, just vanilla Javascript and 100% based on web standards.
- Via CDN (preferred)
- source (about 30kb)
<script src="https://cdn.jsdelivr.net/gh/lesichkovm/[email protected]/cell.js"></script>
- minified (about 11kb)
<script src="https://cdn.jsdelivr.net/gh/lesichkovm/[email protected]/dist/cell.min.js"></script>
- Manual
Download the file cell.js and add to your project
Try downloading to your local machine and open it in your browser.
Seriously, there is no additional code or dependency, no environment to set up. What you see is what you get.
<html>
<script src="https://cdn.jsdelivr.net/gh/lesichkovm/[email protected]/cell.js"></script>
<script>
var el = {
$cell: true,
style: "font-family: Helvetica; font-size: 14px;",
$components: [
{
$type: "input",
type: "text",
placeholder: "Type something and press enter",
style: "width: 100%; outline:none; padding: 5px;",
$init: function(e) { this.focus() },
onkeyup: function(e) {
if (e.keyCode === 13) {
document.querySelector("#list")._add(this.value);
this.value = "";
}
}
},
{
$type: "ol",
id: "list",
_items: [],
$components: [],
_add: function(val) { this._items.push(val) },
$update: function() {
this.$components = this._items.map(function(item) {
return { $type: "li", $text: item }
})
}
}
]
}
</script>
</html>
Here's the generated DOM tree, as viewed in Chrome inspector:
A couple of things to note from the code:
- There are no framework classes to inherit and extend from.
- There are no API method calls.
- There are no HTML body tags.
- All we have is a single JSON-like variable.
- The DOM just builds itself without you running any function.
Cell has no API. 100% of your code will be vanilla Javascript, and there is no framework method or class to implement.
To use Cell, you simply define a variable that describes the DOM content and behavior.
When you follow the 3 rules below, Cell turns it into HTML.
When you define a Javascript object, its attributes map 1:1 to DOM attributes. So,
var node = {
id: "container",
class: "red"
}
maps to:
<div id="container" class="red"></div>
Key | Description |
---|---|
$cell | Required. Tells Cell to create a cell element using this object as a root |
$type | The type of element to create. (div , form , textarea , etc.) |
$components | Array of nested child nodes |
$text | Text content inside the element (for simple nodes with no $components) |
$html | Unescaped html content inside the element |
$init | A function that auto-executes when the element gets created |
$update | A function that auto-executes when any data stored inside the element changes |
For example,
var el = {
$cell: true,
$type: "div",
$components: [
{ $type: "span", $text: "Javascript" },
{ $type: "span", $text: "objective-c" },
{ $type: "span", $text: "ruby" },
{ $type: "span", $text: "java" },
{ $type: "span", $text: "lisp" }
]
}
becomes:
<div>
<span>Javascript</span>
<span>objective-c</span>
<span>ruby</span>
<span>java</span>
<span>lisp</span>
</div>
Cell lets you store data and application logic directly on HTML elements.
To define a variable on an element's context, simply prepend your attribute name with "_". Cell will treat it as data and make sure it doesn't affect the view.
el = {
$cell: true,
$type: "button",
type: "button",
$text: "Get next item",
onclick: function(e) { this._next() },
_next: function() {
this._index++;
this.$text = this._items[this._index];
},
_index: 0,
_items: ["javascript", "objective-c", "ruby", "java", "lisp"]
}
Here we use _items
to store an array, _index
to store an integer counter, and _next
to store a function that will run this element by incrementing _index
and iterating through _items
.
When Cell loads, it first looks for all Javascript variables that have a $cell
key.
When it finds one, it takes that blueprint object (called a "Genotype"
in Cell) and creates a DOM tree ("Phenotype"
) from it.
So far this is just a static DOM tree. To make it dynamic, you need to write a program that "remote controls" these HTML elements.
Normally Javascript frameworks maintain a separate centralized data structure and application context (Model-View-Controller or some variation) that synchronizes with and controls HTML elements dynamically.
Cell takes a decentralized approach. It creates a DOM tree where each element is self-aware (It can contain an entire Model-View-Controller environment of its own) and can therefore "drive" itself autonomously (Internally called "Nucleus"
).
Instead of having a central master application control the DOM, Cell directly injects application context into each relevant HTML element so they can run on their own, independent from the outside world.
Learn more about the underlying architecture here.
Cell has no overarching framework that powers each and every corner of your app.
Normally web app frameworks maintain a central "Model-View-Controller" architecture (or similar) which takes care of everything throughout the app's lifecycle.
Cell works differently. It just creates the DOM and then goes away, because each HTML element it creates can self-drive itself with its own model-view-controller. Instead of controlling the DOM remotely with a framework's API, with Cell you control it directly and natively.
Comparison | Frameworks before Cell | Cell |
---|---|---|
Control | Centralized | Decentralized |
Structure | A master Model-View-Controller program that controls all the HTML elements | Each html element as the container of its own Model-View-Controller logic |
Your App | Full of framework API syntax | Just a vanilla Javascript. No framework code. |
Job | Manages everything throughout the entire app lifecycle | Runs exactly once at the beginning to create an autonomous DOM tree, and then goes away. |
Nowadays, just to make a simple web app you need to learn all kinds of middlemen technologies.
These tools were born out of necessity as web apps became more complex. But if you take a fundamentally different approach, you may not need them at all.
Here are some of the reasons why these middlemen have been necessary, and why Cell doesn't need them.
Normally web app frameworks let you use their API by extending or inheriting from their class. Cell has no class and no API method.
Most web app frameworks depend on other complex libraries (Don't forget to
npm install
before doing anything!) Cell doesn't depend on any library.
Just by choosing to use a framework you have already lost the war against dependency. From then on, you need to use
npm install
for every frontend Javascript library you need to use. Cell frees you from this loop and lets you use frontend Javascript libraries with simple<script src>
.
Cell stays away from inventing any framework-specific markup such as HTML templates. There's no template to compile.
Cell is built with ES5, which works in ALL browsers (including IE). There's no need to transpile your code to use Cell, it just works right away.
Cell is based on the same idea behind Jasonette, a simple way to build cross-platform iOS/Android native apps with nothing but JSON markup.
Just like Jasonette, Cell lets you express application logic as a piece of flat data. This allows you to not only transform and manipulate data, but also the application logic itself.
Let's say we have this view:
var El = {
$cell: true,
class: "container",
$components: [
{ $type: "span", $text: "Four Barrel", class: "row" },
{ $type: "span", $text: "Philz", class: "row" },
{ $type: "span", $text: "Blue Bottle", class: "row" },
{ $type: "span", $text: "Stumptown", class: "row" },
{ $type: "span", $text: "Counter Culture", class: "row" }
]
}
We see many repeating span
lines, so let's extract them out into a function:
Coffee = ["Four Barrel", "Philz", "Blue Bottle", "Stumptown", "Counter Culture"]
Item = function(brand) {
return { $type: "span", $text: brand, class: "row" }
}
var El = {
$cell: true,
class: "container",
$components: Coffee.map(Item)
}
Notice how the Item
is simply a stateless function. We run a map
on it with the Coffee
array and end up with the same structure as before.
Normally web app frameworks implement reusable components with classes. You need to extend the framework's class and then create components from its instance.
A "component" on Cell is nothing more than a stateless function. This is extremely liberating because functions have zero overhead compared to classes.
Because of this functional programming approach:
- You can split out your app into as many modules as you want.
- Being able to break down your app into such granular pieces makes it extremely reusable, even in other apps.
- "Components" are not just for views anymore. Because your app logic fits into a JSON-like object that can be easily transformed, filtered, and manipulated, components can encapsulate the entire Model-View-Controller.
Normally when you use a web app framework, you write code that heavily depends on the framework API.
So if you ever want to use a new framework, you have to rewrite the entire app, taking a huge amount of time to make it do exactly the same thing it used to do.
With Cell, your can write code that never becomes useless, simply because:
- Cell doesn't have an API, so there's nothing to "depend" on.
- With the functional programming approach, you can write infinitely modular code.
Being able to containerize your app's logic and data inside its HTML elements and then "ship" it to the DOM enables a lot of cool things.
Looks like a DOM, Acts like a DOM, it actually IS a DOM
Cell creates an "Actual DOM". There's nothing virtual or magical about it. It really IS just a pure HTML element.
This means we can apply any 3rd party Javascript or CSS libraries (like jQuery, Bootstrap, Foundation, Select2, CodeMirror, etc.) the same way we would use it on vanilla HTML.
Normally, using a web app framework is an all or nothing deal, because the framework takes over your entire frontend.
Cell completely encapsulates your app's logic into discrete HTML elements, so integrating it into an existing web app or website is as simple as copy and paste.
-
An example TODO app built with cell.js https://github.com/brettdewoody/celljs-todo-demo
-
Another TODO app https://github.com/SilverSoldier/todoMVC-celljs
By default Cell.js will use the window.onload event to initialize any $cell element present. However in some events you may not want it to do this, but do this manually.
Here is an example, which will call an API endpoint and render the JSON result. If the JSON is passed as string, it will be parsed with a special function, which supports functions (see: http://www.eslinstructor.net/jsonfn/) so that you can use $init, $update, etc.
fetch("https://auto.sinevia.com:1881/test.js").then(response => {
return response.text();
}).then(response => {
window.dispatchEvent(new CustomEvent('cell-render', {
detail: response,
}));
});
Dispatched after the cell.js is downloaded, and the window "onload" event is triggered
window.addEventListener("cell-loaded", function (e) {
console.log("Cell.js is loaded");
});
Dispatched after a custom rendering is triggered with "cell-render"
window.addEventListener("cell-rendered", function (e) {
console.log("Cell.js custom render complete");
});
if (window.celljs == false) {
var script = document.createElement('script');
script.src = "https://cdnjs.cloudflare.com/ajax/libs/cell/1.2.0/cell.min.js";
document.head.appendChild(script);
}
By default the library will autorender when the page is loaded. To disable this behaviour use the "data-autoload" attribute with a value of "no"
<script src="cell.js" data-autoload="no"></script>
2021.02.13 - Added attribute "data-autoload" to disable autoloading 2021.02.13 - Added custom "cell-render", "cell-loaded", "cell-rendered" events 2021.02.13 - Added global variable celljs to notify the library is present on the page 2020.11.20 - Fixed deprecation messages in Firefox