intercellular / cell Goto Github PK
View Code? Open in Web Editor NEWA self-driving web app framework
Home Page: https://www.celljs.org
License: MIT License
A self-driving web app framework
Home Page: https://www.celljs.org
License: MIT License
So full disclosure I've been tinkering a lot with kind of a overly-minimalist embedded-DOM adaptation of some of these ideas:
https://github.com/guscost/protozoa
And it turns out that this kind of JSON format can definitely be generated from JSX source. I understand a lot of the benefit of the "compact" or "process-less" development workflow, but writing JSX can be really nice and I'm missing it. It turns out it is possible to just add that syntax by itself if you want it! I was able to extend TypeScript to support generating JS object literals from JSX rather than calls to React, and I have a working implementation here:
https://github.com/guscost/protozoa-tsx
It would be pretty easy to adapt to generate objects for any object-spec type of API.
like https://github.com/DiscoverMeteor @gliechtenstein, i will take the "zh-CN"
This library is very interesting. What would be the shortcomings and the ways to use this lib in a progressive web app (PWA) with Graphql and how to do routing?
Such demo example would be great.
AFAIK, currently both clients (Apollo & Relay) which help with cache and other optimizations require React.
Document the new manual instantiation feature https://github.com/intercellular/cell/releases/tag/v1.1.0
I'm curious why setting $text to "" is ignored (https://github.com/intercellular/cell/blob/develop/cell.js#L139)
This means you can't clear the contents of a cell, no? This breaks things, like your "Super simple synchronized input" example. Type a bunch of stuff in the text field, hit Ctrl+A, backspace. The old text still remains because setting $text to "" was ignored.
the people at js.org provide free subdomains for your projects. I thought it might be cool to grab cell.js.org
https://github.com/js-org/dns.js.org
i think it would be best to serve cell from a versioned cdn cdn.celljs.org/1.0.0/cell.min.js
perhaps using cdnjs as our main cdn
Some suggestions:
Rename $type into $tag, it's more appropriate and less confusing, otherwise: $type: "input", type: "text"
Rename $components into $kids. Again, less confusing, more appropriate and much shorter!
Reverse the naming convention: attributes should start with $, everything else is regular names. Since there are just a few attributes, it makes sense to mangle their names instead of prefixing all the variables with '_', which makes it harder to read. You only use a few reserved keywords (like $tag and $kids), so you can distinguish them from the attributes.
Remove $cell, detect elements by $tag, allow $tag == ''.
It would be nice if I could specify that $cell :
Maybe by extending $cell values:
var bla = {
$cell: v,
$type: "div",
id: "div1",
. . .
}
where v can be
v=true // backward compatible
v="R" // replaces div1 element
v="CF" // is inserted as first child of div1 element
v="CL" // is inserted as a last child of div1 element
v="PF" // div1 element is inserted into it as a first child.
v="PL" // div1 element is inserted into it as a last child
It would be very useful when inserting some new content
into existing static html pages.
Components currently support the $init
callback that fires when the element is generated for the first time. This enables construction logic such as enabling a specific behaviour, issuing an API call or, as outlined here, subscribing to an observable.
However, the lifetime of an element might come to an end, typically due to a user action (e.g., closing a widget which removes a bunch of elements). In this case, it becomes necessary to terminate all ongoing transactions created by this element, such as cancelling API calls or unsubscribing from an observable. Such logic typically resides in a destructor callback, which could be called $deinit
.
What about adding such callback? Or what would be the alternatives?
I think Cell is uniquely positioned to implement something like this.
It's not easy to do this using traditional approaches or other frameworks, but since
it should be possible to make completely drag and droppable components.
This will probably not be a feature of the core library but built on top of Cell. Just an idea stage for now
First of all, it's a brilliant celljs is a brilliant concept. I love it!
At the moment I face a little issue. I've created the following page with two cells:
<script>
editTodo = {
$cell:true, $type: 'body', style: 'padding:30px',
$components: [
{
$type: 'div', $text: 'Enter todo:'
},
{
$type: 'input',
onkeyup: function(e) {
if (e.keyCode === 13) {
document.getElementById('#list')._add("{ title: '" + (this.value) + "'}");
this.value = "";
}
}
}
]
}
listTodo = {
$cell:true, $type: 'ul', id: 'list', _todos: [],
$components: [],
_add: function (todo) {
this._todos.push(todo);
},
$update: function() {
this.$components = this._todos.map(function(todo){
return { $type: "li", $text: todo.title }
})
}
}
</script>
However, the second cell (listTodo) will be removed if I start typing in the textbox. Can you help me in solving this issue.
BTW, I'm working with Chrome.
Best regards,
Martin Zevenbergen
cell
architecture is great and very simple to use, but maybe it's a bit low level. It makes easy to be modular, so what about adding a list of already known cell-based components like the traditional ScroolView and similar ones, so others can go and use them directly of the selves instead of need to craft them from scratch using HTML? Or maybe propose that cell-based components use the cell-*
prefix on npm, or maybe do both things (list and prefix)?
With the following code I cannot set the class of the button:
{
$type: 'button',
$text: 'Send message',
$init: function() {
this.class = "button-primary"
}
}
However, other attributes do work like this, for e.g.:
{
$type: 'button',
$text: 'Send message',
$init: function() {
this.disabled = true
}
}
On all the examples I've found so far, a JSON-like object is assigned to a variable at the global context, being only identified by having a $cell: true
attribute. Does it get "automagically" fetch and used? How? Shouldn't it be better to be set explicitly passing it as a parameter to a function call? Why was it done this way?
I noticed you don't have any benchmarks to compare with other frameworks and don't mention anything about cell's performance. Would you be interested in submitting an implementation of js-framework-benchmark? I'm curious where it would land in the table [1] and even more interested if the actual implementation would be as simple as the docs claim.
BTW, not all dom libs require build tools or extending framework classes ;) [2] [3]
cheers!
[1] https://rawgit.com/krausest/js-framework-benchmark/master/webdriver-ts/table.html
[2] https://github.com/leeoniya/domvm
[3] https://github.com/thysultan/dio.js
Something causes Gene.diff to create a diff with an undefined item, which causes errors when building a new Membrane because reading $type
of undefined.
The fix I specify below is probably not the right thing to do because of the way the error is triggered (see below). The addition of the $update
function and the root cell being body
are what specifically cause this error to happen.
Code to repro:
window.app = {
$cell: true,
$type: 'body',
$components: [
{ $type: 'div', $update: function() {} },
],
};
My current fix:
diff --git a/cell.js b/cell.js
index b4d1e3b..ee02462 100644
--- a/cell.js
+++ b/cell.js
@@ -105,7 +105,7 @@
var plus = _new.map(function(item, index) {
return { item: item, index: index }
}).filter(function(item, index) {
- return new_common.indexOf(index) === -1
+ return item.item && new_common.indexOf(index) === -1
})
return { "-" : minus, "+": plus }
}
Hello, one thing I can't really find out from the docs is how to render a cell to a specific place in a page that contains also other stuff. From what I can understand, the cell will be rendered exactly where the component with $cell: true will be found in the DOM. However, I prefer to put my JS scripts at the bottom of the page and not mix'em with the DOM. So I'd like to define a placeholder-div for the cell and instruct it to be rendered there (like how React.render works). Is this possible right now in cell.js?
TIA!
Current implementation processes $text value as innerHTML:
Here is a code:
if(key === "$text"){
if(typeof val === "function") val = Phenotype.multiline(val);
if(val.toString().length > 0) {
$node.innerHTML = val;
}
}
My proposal is to use new $html attribute for this purpose:
if(key === "$html"){
if(typeof val === "function") val = Phenotype.multiline(val);
if(val.toString().length > 0) {
$node.innerHTML = val;
}
}
and in case of $text, we append text content to node.:
if(key === "$text"){
. . .
var textnode=document.createTextNode(val);
$node.appendChild(textnode);
}
Cell attaches a data structure called Genotype
to each node to allow each node to manage its own state in a decentralized manner.
So if you take any node created by Cell, and query its Genotype
you'll get the "blueprint" of the node. For example you create a node like this:
c = {
$cell: true,
id: "item",
$text: "Hello world",
_update(message) {
this.$text = message;
}
}
and query it like this:
console.log(document.querySelector("#item").Genotype)
This is pretty useful for debugging since the Genotype
object updates in realtime to keep the state of each node.
However, there's a limitation to the current implementation. Currently Cell wraps all the functions with Nucleus.bind()
to make sure each time a function is called it gets added to the Nucleus._queue and the diff logic is triggered when the call stack becomes empty.
This means when I do something like
console.log(document.querySelector("#item").Genotype._update)
it will print the wrapper function that's returned by Nucleus.bind()
, not the original function.
Need to find a way to retrieve each function's original form instead of the bound function so it's completely stateless and reusable.
Here's a gist which includes deleting todos by clicking on them:
https://gist.github.com/guscost/97f014c6b609246b25a0d11253d90b23
A lot of people have been asking for an explicit cell instantiation method.
Currently, Cell automatically figures out what elements to turn into Cells by searching from global variables with a $cell
key. This works in most cases, but there are many other cases where it makes sense to manually create cells.
Some of the problems brought up were:
From #12 (comment)
"The one thing I would still like to find a solution for is the fact that declarative cells must not be lexically scoped -- only var or a global declaration will do, and neither const nor let will work."
Like @Caffeinix pointed out in above comment, Cell won't be able to automatically detect const
and let
variables since they are not global variables.
The power of Cell comes from its pluggable nature. You can use it with any existing framework and any web library with zero hassle because it creates an actual, completely encapsulated HTML node instead of a virtual DOM.
@kingoftheknoll mentioned integrating with angular #12 (comment) and I think this argument generally applies to all other frameworks/libraries out there.
It would be great to be able to create cells that can be injected into other frameworks. Cell is well positioned for that since it doesn't create any extra data structure like virtual DOM but instead creates native HTML elements that contain everything they will ever need to work when plugged into any context.
This is related to #2 above, but nowadays many web apps use dynamic module loading, and web browsers themselves will natively support them in the future via ES6.
To be clear, I think most of these modular approaches on the frontend web context are convoluted and mostly unnecessary (which was the main reason I wrote Cell), but I also see no reason not to support these approaches since allowing manual instantiation has very clear and powerful benefits.
Currently it's not easy to integrate Cell into existing web apps that dynamically load modules. Maybe you're using angular, maybe you're using Backbone.js, or anything else really. This is because the main Cell creation logic creates cells this way:
window.onload
event$cell
attributeThis means, by default there's only one opportunity to create cells--at the very beginning.
Liberating Cell from this specific lifecycle will allow dynamic creation of cells, making Cell truly injectable into all kinds of context.
A lot of people suggested an option to add something like this:
const $node = Cell({
$type: "p",
$text: "I will be created by a global Cell variable"
})
Well the thing is, it would be best if we could avoid the global variable Cell
altogether. To understand the rationale behind this, please read #111
Basically, we already have a decentralized DOM, why not utilize it instead of creating a centralized constructor? This would be in line with the philosophy of the library after all.
The main goals:
Here's the solution:
$cell()
function to window.Element.prototype
.$cell()
function can be used in two different ways, depending on the existence of the $cell
key on the JSON argument.With this addition, we should be able to achieve the 3 goals mentioned above.
Let's say we already have an HTML page, and we want to morph parts of it into Cells.
This is already somehow possible in a 100% declarative manner by using the "id" attribute But that approach assumes you're using Cell as standalone, because it depends on the window.onload
event.
Anyway, sometimes you may want to use Cell along inside an existing web app that dynamically loads modules, which means using window.onload
event won't work. We want to be able to instantiate these Cells inside each module after they're loaded.
The new Element.prototype.$cell()
function allows you to take ANY regular HTML element, pass the blueprint object (gene) as a parameter, and morph into a cell. Here's an example: https://jsfiddle.net/euzrzk8n/1/
<html>
<script src="https://rawgit.com/intercellular/cell/plan/cell.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>
<div id="viz"></div>
<script>
document.querySelector("#viz").$cell({
_sampleSVG: null,
$cell: true,
_r: 40,
_cx: 50,
_cy: 50,
_width: 100,
_height: 100,
_color: 'red',
$init: function(){
var self = this;
this._sampleSVG = d3.select(this)
.append("svg")
.attr("width", this._width)
.attr("height", this._height);
this._sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", this._r)
.attr("cx", this._cx)
.attr("cy", this._cy)
.on("mouseover", function(){d3.select(this).style("fill", self._color);})
.on("mouseout", function(){d3.select(this).style("fill", "white");});
}
})
</script>
</html>
Here's another example: https://jsfiddle.net/3v1ttcga/2/
I took an example template from bootstrap website and just converted static components into Cells.
Above method should cover a lot of the use cases where you want to manually create cells.
But since we're at this, let's go deeper.
The previous approach assumes that we already have an HTML node we want to transform. What if we just want to create a cell from scratch, without adding it to the DOM directly?
In this case, you can just get rid of the $cell
attribute from the argument when calling the $cell()
function, and it will only create the cells in memory.
Here's an example of creating a <p>
element from <body>
without automatically appending : https://jsfiddle.net/vj5bwjxb/
let gene = {
$type: "p",
$text: "I will be created by 'body' but you'll have to manually add me to the DOM"
};
for(let index=0; index<10; index++){
let cell = document.body.$cell(gene);
document.body.appendChild(cell);
}
Notice:
$cell
attribute on the gene
object.document.body.appendChild(cell)
By adding the additional $cell()
method on window.Element.prototype
we were able to avoid creating a global variable that creates cells.
This is also in line with the core philosophy since it is not some centralized entity that creates cells, but any node can create a cell, which means no compromise was made.
Here's the branch for this feature: https://github.com/intercellular/cell/tree/plan
And you can try this version in action by including:
<script src="https://rawgit.com/intercellular/cell/plan/cell.js"></script>
I think these two features powered by a single addition of the $cell()
function should address most of the problems brought up so far, while preserving the decentralized nature of Cell.
But I want to make sure I'm not missing anything, and also would love to hear your thoughts. Let me know. Thanks!
I think binding event listener should use addEventListener rather than set property directly....
Great work, I love simplicity of the interaction model and programmer friendliness. You've managed to create what amounts to an AST for front end development that's easy to work with.
Ironically the metaphors used in describing cell are the most complicated part of understanding the whole thing. If you want people to be able to truly pick it up and understand it quickly I suggest doing away with the cell metaphor all together. I find the code in cell.js much easier to read and parse than GENESIS.md and I suspect many other people will have this same problem. Words like Genotype and Phenotype have no meaning to me and are hard to distinguish as I'm not a biologist and having to keep the biology metaphor active while I try to understand what's going on is just too much cognitive load to be useful. The name is great though search might not be the best: https://encrypted.google.com/search?hl=en&q=cell%20js .
Anyway, that's my 2c. Again, great work on the library!
This library has an unconventional architecture. And the code initially had near 0 annotation. This made it difficult to understand what's going on.
So I have started adding comments https://github.com/intercellular/cell/blob/develop/cell.js
I would love to improve it, so please let me know if there are parts that are unclear and I'll update or add more comments.
According to the official documentation, the canonical way to instantiate a cell is like this:
var x = {
$cell: true,
$type: 'div', // or some other element type
// ...
};
This automatically appends the cell to the end of the body tag (unless $type
is 'body'
, in which case it gets injected instead). But this is very limiting: the claim that you can reuse cells by simply pasting them into existing apps is weakened by the fact that you have no real control over where those cells end up in your document without using an iframe
.
Of course, as you are probably about to point out, there is already an undocumented solution to this problem:
var x = {
$type: 'div',
// ...
};
window.addEventListener('load', function() {
document.querySelector('#myContainerElement').$build(x);
});
This works well, although it's a little unfortunate that we have to wait for the load
event because the $build
function hasn't been added to the existing DOM elements yet. This feature is powerful and useful, and I think it should be (at the very least) officially documented and supported.
It would be even nicer if we didn't have to wait for the load
event. Since we've already loaded the script by the time we get here, perhaps it would be better to put the $build
function on some existing object so we can access it immediately:
document.$build(document.querySelector('#myContainerElement'), x);
It might also be nice to add a $parent
key on the genotype that could be used to specify a (static) parent for the element:
<body>
<div id="myContainerElement"></div>
<script src="https://www.celljs.org/cell.js"></script>
<script>
var x = {
$cell: true,
$type: 'div',
$parent: document.querySelector('#myContainerElement'),
// ...
};
</script>
</body>
However, I hope you keep the imperative version as well, because it allows developers to avoid polluting the global namespace. For example:
// This does not work:
const x = {
$cell: true,
$type: 'div',
// ...
};
// But this does:
const y = {
$type: 'div',
// ...
};
window.addEventListener('load', () => {
document.querySelector('#myContainerElement').$build(y);
});
cell.js:356 [Deprecation] 'window.webkitStorageInfo' is deprecated. Please use 'navigator.webkitTemporaryStorage' or 'navigator.webkitPersistentStorage' instead.
(anonymous) @ cell.js:356
detect @ cell.js:354
create @ cell.js:379
(anonymous) @ cell.js:404
While it might feel dated, it actually illustrates the basic interplay between a lot of moving parts (persistence, aggregate values, view filter, etc), and is simple enough to fit in one file.
How is the state regarding reading pages generated using celljs in tools providing offline mode i. e. pocket ?
I was trying to implement an app that consists of just an input field initially, which when submitted will dynamically load a remote script and do something on onload
event, using Cell.
More specifically, I was trying to find a way to load a remote cell app from an existing cell app, and inject it into the existing cell app.
But I ran into a problem. When loading a remote script dynamically, it is not possible to know what variables have loaded as a result of that script. The new manual instantiation feature introduced in #125 doesn't help because we have no knowledge about the newly introduced variables in the root context.
Anyway here's the half-finished code. The only problematic part is the Fill in the blank here
part inside onload()
. Once that's figured out, everything should just work.
{
$cell: true,
_load: function(url) {
this.querySelector("#script_slot").$components.push({
$type: "script",
src: url,
onload: function() {
/*
* Fill in the blank here
* Need to turn the loaded gene objects into cells
* but we do not know what variables have loaded.
*/
}
});
},
$components: [
{
id: "script_slot",
$components: []
},
{
$type: "input",
placeholder: "Enter a javascript url and press enter",
onkeyup: function(e) {
// when the enter key is pressed, find the nearest ancestor
// that contains _load() and call that function.
if(e.keyCode === 13) this._load(this.value);
}
}
]
}
Trying to figure out a nice and clean way to tackle this.
it could be done through a modification of the API, or maybe there's a way to do this without modifying the API. Maybe there's a clean way.
Related to #120
Set up a github page for sharing example code, tutorials, ideas, simple gists, or whatever cell related that's awesome
First of all, thanks all contributors of cell! I like this framework!
I think it's succinct and elegant, likes a cute kitten~
But I still have some questions about this project:
Last but not least, thanks all contributors again!
Any tips or elegant way to handle a nested structure like this?
<div class="mdc-form-field">
<div class="mdc-textfield" data-mdc-auto-init="MDCTextfield">
<input id="firstname" type="text" class="mdc-textfield__input">
<label for="firstname" class="mdc-textfield__label">
First Name
</label>
</div>
</div>
I'm playing with cell.js on Chrome and I get this error message in the Developer Console:
[Deprecation] 'window.webkitStorageInfo' is deprecated. Please use 'navigator.webkitTemporaryStorage' or 'navigator.webkitPersistentStorage' instead.
(anonymous) @ cell.js:348
detect @ cell.js:346
create @ cell.js:371
(anonymous) @ cell.js:395
Inspired by the discussion started by @kingoftheknoll on this thread I started thinking about a "cell search engine" (just a rough concept for now).
Basically, I think Cell should be kept decentralized, but it would be nice to have some sort of a centralized index or registry that makes communication easier and more efficient. By "search engine" I just mean keeping a reverse index of the cell nodes and their genes, but I guess potentially this can be expanded to create a universal "cell search engine" (but that's more like a sci-fi story at this point, so let's not go there yet 😄)
Anyway, this could be implemented in either of the following format (In the order of desirability):
I think a good approach is to try the first approach (completely separate library) and then think about other options if there's a roadblock.
Just sharing some of my ideas so far:
These used to exist in the early days of development, but I took them out right before releasing. But I think it makes sense to share it in this context. Basically these are convenience methods you can use instead of querySelector
or querySelectorAll
.
Select multiple items
this.$where(".items").forEach(function(item) { item._refresh(); });
Select one item
this.$find("#counter")._update(this.value);
I took out these methods because the utility (basically just a convenience wrapper around querySelector and querySelectorAll) didn't justify having to pollute the node's attribute namespace.
But it may make sense if there's some additional functionalities for these methods. Which is where the "cell search engine" comes in.
In addition to doing something like:
this.$where(".items").forEach(...)
What if we could do the following:
this.$where(function(item){
return item._done === true;
})._remove();
One nifty side effect to this approach is it's kind of like a cell-native way of doing extensions. In case of jQuery they implement extensions by letting you attach functions to $.fn
.
Cell is actually very similar to jQuery except that it's similar in an exactly opposite way. Instead of wrapping an jquery object on a node, we're wrapping a gene objects inside a node.
So in this context, these $find
and $where
functions could be thought of as sort of "cell extensions", which means maybe we can expand on this concept to create a nice extension system.
This is just a rough idea I just randomly came up this morning, so just thought I would share. It should be possible to index the entire DOM tree since each node contains a full Genotype. (You can try by querying $node.Genotype
and you'll see the gene from which the node was constructed)
In this demo https://play.celljs.org/items/O4Mf6y/edit
If you Input something then delete all text in input field
You will see that the display out of sync
How do you style the html element at the top of your file in the celljs context? In example below, I added a background-color class to the the top html tag.
Thanks.
<!DOCTYPE html>
<html class="background-color: black" lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<title> </title>
<meta name="author" content="">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://www.celljs.org/cell.js"></script>
<link rel="stylesheet" href="https:/unpkg.com/tachyons/css/tachyons.min.css">
</head>
disregard. Great project!
Hello,
While playing around with cell I created the following demo: https://codepen.io/mdings/pen/awEOKJ.
The result is not that perfect because the content div flashes while the visibility of the other divs is being updated. So my question is whether the approach I've taken to create the navigation is the right one? Or is there a better way to come to the same result?
Currently, specifying a style for an element within Cell is usually done like this:
el = {
style: "background-color: red;",
$text: "Hello, world!"
}
While it is convenient to define the element's style attribute within Cell, it overwrites the element's style property with a string (that you defined for the attribute). However, when I access a non-Cell HTMLElement's style property (or if the property was not overwritten by Cell) I will get a CSSStyleDeclaration object as described here: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style
It works fine to apply styles to the element, but when you need to change it within the script, you must change the string (theoretically, I have not tested that) instead of changing the CSSStyleDeclaration as you would with a non-Cell element. This leads to unpredictable behavior when dealing with the styling of Cell elements in JavaScript:
el = {
style: "background-color: red;",
$text: "Hello, world!",
onhover() {
// What you should do with an HTMLElement:
this.style.backgroundColor = "blue"; // this.style is a string, so that doesn't do anything!
}
}
The current solution is to not define a style attribute, and instead use the given CSSStyleDeclaration in the element's $init function to add styling (or just use a CSS file, which can be inconvenient in some situations).
To retain the convenience and flexibility of styling within the element's Cell definition, I think a $style special keyword should be added, or at least the style attribute should be handled properly and not overwrite the style property of the element. If there were a $style keyword, its value could be an object that has keys corresponding to the CSSStyleDeclaration properties you want to change:
el = {
$style: { backgroundColor: "red" },
$text: "Hello, world!",
onhover() {
this.style.backgroundColor = "blue"; // It works!
// (this effect is for demonstrating Cell; the :hover pseudo selector should be used in CSS)
}
}
If a styling is not important enough to be its own special keyword, developers should still be informed to set their styles within the $init function of an element if they want to use a proper style property in their scripts, or resolve the conflict between Cell's style attribute and JavaScript's style CSSStyleDeclaration.
By the way, Cell is the reason I enjoy web dev again after months of being burned out. Thanks for making this awesome script and sharing it to the world!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <html> <meta name= "viewport" content= "width=device-width, initial-scale=1" > <link href= "https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel= "stylesheet" > <script> var GithubApp = { $cell: true , _items: [{tag: 'bbbb' }], $init: function (){ var self = this ; window.setTimeout( function () { self._items = [{tag: 'aaaaa' }]; //$update do not gets executed automatically },2000); }, _template: function (item){ return { $components: [{ $text: item.tag }] }; }, $update: function (){ this .$components = this ._items.map( this ._template); } } </script> </html> |
-> GitHub
I think is not convenient just use object to create elements!
this is your example:
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 }
})
}
}
]
}
In your example, I think that is very confused. you can use JSX loader to modify it. just like:
<div>
<input />
<div>
...some html
</div>
</div>
Can you list the differences with (almost) native Browser WebComponents?
Seems to me (at the moment) you created a very lightweight Polymer.
Hi @gliechtenstein. Any idea why this graphql query result is not coming back into celljs. The demo I did before didnt have search in it. I added search and somehow I cant get the return to come back. It looks like its an async issue maybe. Can you take a look at these gists and see if anything pops out to you? This is the index.html gist.
this is the index.js gist.
Server is in cloud so you should be able to run it. You will notice in the console that the queries come back both for load and search. Any additional, random programming advice you feel like you can give, go right ahead.
Thanks.
Once the $components
property is set to a non-empty array it will not again be updated to reflect an empty array.
Example: pressing Empty button after any other will not clear the list.
<html>
<head>
<script type="text/javascript" src="https://www.celljs.org/cell.js"></script>
</head>
<body>
<script>
var app = {
$cell: true
, $type: 'div'
, _items: []
, $update: function() { this.querySelector(".list")._refresh(); }
, $components: [
{
$type: 'ul'
, class: 'list'
, _refresh: function() {
this.$components = this._items.map(function(i){ return { $type: 'li', $text: i} });
}
}
, {
$type: 'button'
, $text: 'get 1,2,3,4'
, onclick: function() { this._items = ['1', '2', '3', '4']; }
}
, {
$type: 'button'
, $text: 'get primes'
, onclick: function() { this._items = ['2', '3', '5', '7']; }
}
, {
$type: 'button'
, $text: 'get empty list'
, onclick: function() { this._items = []; }
}
]
};
</script>
</body>
</html>
NodeList behaves as array so:
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
It's shorter and throws some exceptions.
Array.prototype.forEach
Here's what Cell is, in one sentence:
Cell is a function that constructs a decentralized structure of autonomous HTML nodes from a blueprint.
But this post is about the future of Cell, so just for the sake of discussion let me switch out the "HTML nodes" part with a more generic term "objects":
Cell is a function that constructs a decentralized structure of autonomous objects from a blueprint.
Let's step back and think about this for a moment. An HTML node is nothing more than a special purpose object that has its own distinct properties. And all Cell does is create autonomous versions of these objects using the Web API.
If we dive further into this line of thinking, theoretically we should be able to do the same for any kind of object. So.. does this mean we can apply Cell.js on any other types of objects today?
Not today. But I think it should be possible. We just need to successfully decouple the DOM part out from the core. And when that becomes possible, we should be able to build not just frontend web apps, but all kinds of other applications using the cell approach, such as:
The point is, Cell potentially can evolve into a general purpose "autonomous engine" that can be injected into any object natively, just like what it does today with the DOM.
The reason I mention all this is because every aspect of the library implementation and design was influenced by this potential factor. Let me explain.
I went through multiple iterations for this attribute before deciding on $type
. Here are some I tried: $tag
, $node
, $element
, $el
, $e
, $c
.
$tag
didn't work was because it’s too specific to the HTML context. We can't, for example, use it to describe a custom class for building microservices.$node
and $element
are less explicit than $tag
, but they still have a relatively concrete meaning.$el
, $e
and $c
may be shorter, but they fail because they're not readable. Ideally anyone with no prior experience with Cell should be able to take a quick glance at a Cell app code and kind of understand what’s going on.In the end I chose $type
because it is an abstract term. It can be applied to pretty much any context, and intuitive enough that anyone would get it with no prior knowledge.
The only case I'm not fully satisfied is when using it to describe <input type='text'>
because it looks like {$type: "input", type: "text"}
(two "type"s), but really this is the only case and I think the benefit outweighs the cost.
I went through multiple options before deciding on $components
. The strongest contender for this attribute was $children
.
The reason I dropped $children
was because the term implies that there’s a top-down tree-like hierarchy.
This is true for the DOM tree, but it may not be the case for other types of potential usage. For example a different implementation in a parallel universe may have a “previous/next”
relationship instead of a “parent/child”
relationship. It could also be a many-to-many
relationship instead of a tree structure
.
$components
on the other hand is more abstract. The term can also imply an ownership relationship but is not as explicit. It's used to describe an encapsulated environment and doesn't necessarily imply a parent/child relationship.
This is why I picked $components
.
This attribute is critical for keeping cells completely autonomous.
The “$cell” attribute is used to indicate that its parent object should turn into an HTML node.
Some people raise the question: "why not just merge $cell
and $type
to describe the type of a node? That way we get rid of one attribute and it's cleaner".
Well it's a bit complicated. The $cell
attribute is used as an explicit marker for cell construction. For example let's say we have
Li = {
$type: "li",
$text: "Item"
}
OrderedList = {
$cell: true,
$type: "ol",
$components: [Li, Li, Li]
}
Because the OrderedList
is the only object that contains the $cell
attribute, that's the only one that turns into an HTML node.
The end result looks like this:
<ol>
<li>Item</li>
<li>Item</li>
<li>Item</li>
</ol>
Granted, Li does eventually get referenced inside $components
so they do get incorporated into the DOM as one of OrderedList
's components, but that's exactly the point.
By NOT including the $cell
attribute, we are able to define all these "cell candidates" such as Li
without having them automatically added to the DOM. The $cell
attribute lets us dynamically introduce cells when we need them.
To make the point clear, let's see what happens if we did have the $cell
attribute on the Li
variable, like this:
Li = {
$cell: true,
$type: "li",
$text: "Item"
}
OrderedList = {
$cell: true,
$type: "ol",
$components: [Li, Li, Li]
}
The end result would have been:
<li>Item</li>
<ol>
<li>Item</li>
<li>Item</li>
<li>Item</li>
</ol>
which is not what we are expecting.
This is why we need an explicit marker ($cell
) to indicate whether an object needs to be turned into an HTML element or not. In many cases we don't want them to be immediately added but dynamically added later, sort of like how we use HTML templates.
If you have a better idea to get around all these issues simultaneously, please feel free to share. It's always best to have less attributes than more.
This feature used to exist but I took it out right before I released the library publicly. It used to look something like this:
Cell.$build({
$type: "div",
$text: "hi"
})
It's not that I think having a factory method is a bad idea. In fact we can add it if we really need to.
The real reason I took it out was because I believe the main way to interact with the library should be the declarative approach. If something doesn't work with the declarative approach, we should make it work instead of trying to solve that problem with a factory method, because that's not solving the problem fundamentally.
Even if we find out that it's impossible to have a 100% coverage with the declarative approach, this doesn't mean we should completely switch to the factory method approach and get rid of the $cell
attribute.
The primary approach to using Cell should always be the declarative approach (and maybe the factory method can be used in edge cases where this is really not possible)
Let me explain why with an example web app:
<html>
<script src="https://www.celljs.org/cell.js"></script>
<script src="https://walk.com/gene.js"></script>
<script src="https://eat.com/gene.js"></script>
<script src="https://socialize.com/gene.js"></script>
<script src="https://appearance.com/gene.js"></script>
<script src="https://inheritance.com/gene.js"></script>
<script src="https://think.com/gene.js"></script>
</html>
Each script is loaded from a different source and each has its own distinct feature. One may describe a certain behavior, one may describe the data, one may function as a catalyst that composes two other objects to create a new object with a completely different behavior. The point is, none of them explicitly depend on another.
And when they all come together, they somehow emerge into an app.
The important part is that these scripts have no knowldege of one another.
Why is this important? Let's say we're trying to build an emergent app that shapeshifts itself depending on context. Here's how it could work (Please use your imagination because these types of apps don't exist yet, which is what Cell was built for):
In cases like this, you can't use a factory method because factory method only works when you know what your app will look like when you're writing your app. In this particular case you don't know until everything is resolved and constructed.
This type of app may sound like a sci-fi story but it is indeed possible with Cell, and is what Cell was designed for. And to be able to write apps like this, we need a completely decentralized way of writing autonomous modules.
This is why I think we should be almost paranoid about getting the declarative approach to work, which is why the factory method is not the solution.
p.s. I'm not saying Cell is only for these weird cases. It's a perfectly fine framework for building all the normal web apps we use every day. My point is we can do that AND much more if this framework was completely declarative.
One of the strengths of Cell is the syntax is its intuitive syntax. To set a state using some framework API method like setState()
, you simply assign it directly to the this
context, like this:
button = {
$type: "button",
$text: "Click me",
onclick: function(e){
this.$text = "Clicked";
}
}
A lot of people look at this usage and immediately think they're directly storing everything on the HTML element. But rest assured, that's not how it works. Cell keeps an internal proxy object that makes it look as though you're directly accessing the DOM but in fact you're only touching that proxy object.
Cell has 3 classes of variables:
$ prefix
variables: special purpose reserved variables_ prefix
variables: user defined local variablesno prefix
variables: maps 1:1 to DOM attributes nativelyLet me explain how this system came to be:
The reason Cell plays so well with any other web technology is because it does not mess with DOM attributes.
Many things can go wrong if we don't do it this way because we have no control over these custom objects (in this case HTML elements) Cell attaches to. For example, if we decide to use the $
prefix to refer to the dom attributes, we can never be sure what this will result in in the future when the web standard changes or browser implementations change.
This can especially become a very serious issue when we consider the fact that someday Cell may evolve into a general purpose engine for constructing any kind of autonomous object. We need to minimize the complexity as much as possible if we want to achieve flexibility and extensibility.
Anyway, that's why the native DOM attributes are treated as first citizens and no prefix is used to refer to them. Native attributes should be left untouched to reduce complexity.
If you don't understand what this question means or if you haven't read through the source code, you can ignore this. This is for those of you who have read through the code and thinking about tweaking the code (and hopefully contributing!)
First thing you'll notice when you look at the source code is there’s a lot of biology stuff going on (genotype
, phenotype
, gene
, nucleus
, etc.) instead of the typical programming keywords like model, view, controller, state, props, etc. we are familiar with.
Here's why:
But here's the real reason: I named them this way out of 100% practical reasons. During development I got stuck multiple times. I found it very difficult to structure and visualize the codebase because cell's architecture doesn't really fit into any existing "web app framework" paradigm. For example, during the early days I used terms like "Model", "View", and "Controller", and these terms got me almost halfway there, but after that they did more harm than good because a lot of the critical concepts in Cell were hard to categorize using web development concepts, and these web development way of thinking limited my way of thinking.
One day I decided to try to organize the code purely based on cell analogy, because after all, that's where I got the inspiration from. And once I did that everything became crystal clear and I could finish the library.
Lastly, here's a question: If you could pick from:
What would you build?
I think it's much more powerful to try to build a simulated cell that can act like a web app framework (and more), because it can be applied to many different contexts. And to achieve this I think we need to think like a biologist instead of a computer scientist.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.