Coder Social home page Coder Social logo

cell's People

Contributors

devsnek avatar fprijate avatar gliechtenstein avatar kittygiraudel avatar mrdougwright avatar norchow avatar sehegit avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cell's Issues

Genotype snapshot feature

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.

Offline support

How is the state regarding reading pages generated using celljs in tools providing offline mode i. e. pocket ?

Cell Search Engine

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):

  1. Completely separate library: Built on top of cell utilizing already exposed APIs (If you look closely we store a lot of metadata on the genotype so this may be possible) but not touching Cell itself. Since it's completely decoupled from Cell, may even be reusable in scenarios outside of Cell as well.
  2. Extension: built on top of Cell, but maybe Cell can expose more APIs to make this easier
  3. Built into Cell: Built into cell itself as an optional structure.

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.

Some ideas

Just sharing some of my ideas so far:

1. HTMLElement.prototype.$find / HTMLElement.prototype.$where

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);

2. Filter function for $find and $where

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();

Why use the prototype methods? (Comparison with jQuery)

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.

Conclusion

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)

Tabbed navigation demo

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?

Cell.js Long Term Plan and Design Decisions

What is Cell, really?

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:

  • microservices
  • artificial neural networks
  • decentralized applications
  • blockchain
  • autonomous applications that evolve on their own without human intervention, even after we die
  • etc.

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.

How does this idea translate to the current implementation?

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.

Attribute Naming Conventions

1. $type

I went through multiple iterations for this attribute before deciding on $type. Here are some I tried: $tag, $node, $element, $el, $e, $c.

  • The reason $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.
  • Shorter names like $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.

2. $components

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.

3. $cell

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.

3.1. Why not merge $cell and $type?

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.

3.2. Why not just use a factory method to explicitly create cells, and get rid of the $cell attribute?

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):

  1. The app checks the geolocation of a user
  2. Depending on the location, it queries some 3rd party service to see what kind of apps are available
  3. Depending on the response it receives, it queries another 3rd party service which responds with an adequate app template.
  4. The app also takes into account locally stored data that is private to only the user.
  5. All of these are composed together to construct a completely unique app. For example, if you're in your neighborhood the app may turn into a restaurant review app, but if you're away from home traveling, then it could turn into a travel app.
  6. The key to all this is that each of these modules is completely independent and has no knowledge of each other. These data/application are automatically resolved, delivered, and constructed from multiple different parties, in realtime.

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.

Variables

Does Cell modify the DOM directly?

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.

Why is the "no prefix" option used for DOM attributes?

Cell has 3 classes of variables:

  • $ prefix variables: special purpose reserved variables
  • _ prefix variables: user defined local variables
  • no prefix variables: maps 1:1 to DOM attributes natively

Let 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.

What's up with all the cell biology reference in the code?

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:

  1. Language shapes how we think
  2. If you are reading the source, I presume you are reading it because you want to understand it better.
  3. In that case, thinking about it from a biological perspective will help you understand it much better.
  4. Cell's long term goal is to evolve into a more general purpose engine, so it doesn't make sense to use concepts constrained to frontend web development.

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:

  1. building a web app framework that acts like a cell
  2. or a cell that can act like a web app framework

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.

List third-party components

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)?

something about template

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>

performance?

Hi @gliechtenstein

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

Some little doubt about the cell

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:

  • I have tried some demos about cell, I find if I want to change the data of DOM, I just assigned value to $text or something else. It's easy to understand, but if I change the value many times, can cell merge these changes to one just like React or vue?
  • When I used jQuery a few years ago, I selected DOM and change the value; When I used React one year ago, someone tell me that don't operate DOM directly and just change the state. I find the code in cell is mixed these two thought, how contributors think about?
  • Because I am a React lover, I like JSX and I think it may be more intuitional than a JS-Object style component, so is supporting JSX possible in the future?

Last but not least, thanks all contributors again!

Uncaught TypeError: Cannot read property '$type' of undefined (in Membrane.build)

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 }
     }

Add a TodoMVC example

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.

Question about instantiation

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?

HTML5 Drag and Droppable Cells

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

  • Cell lets you express an app as data (JSON format)
  • each Cell encapsulates all the app logic on itself
  • there's no dependency
  • designed to be injectable

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

The second cell will remove after I start typing

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

forEach polyfil

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

Manual Instantiation of Cells : Draft version 1.

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:

1. Namespace

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.

2. Integration into legacy frameworks

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.

3. Doesn't work in dynamically loaded contexts

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:

  1. Wait for window.onload event
  2. Find all global variables that have a $cell attribute
  3. Automatically build cells from these variables

This 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.

One possible approach (and why it's not optimal)

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.

Goals

The main goals:

  1. The Creator function does not pollute the global namespace but belongs to each HTML element, including the body.
  2. Any regular HTML node should have the ability to: Evolve into a cell.
  3. Any regular HTML node should have the ability to: Create a new cell without directly adding it to the DOM.

Solution

Here's the solution:

  1. Add a $cell() function to window.Element.prototype.
  2. The $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.

1. Evolving into a cell

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.

2. Create a new cell without directly adding it to the DOM.

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:

  1. There is no $cell attribute on the gene object.
  2. You manually add them to the DOM with document.body.appendChild(cell)

Conclusion

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.

Let me know what you think

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!

Awesome Cell

Related to #120

Set up a github page for sharing example code, tutorials, ideas, simple gists, or whatever cell related that's awesome

Dynamically loading a remote Cell script from an existing Cell app and injecting into the existing Cell app.

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.

Empty tag generate option

Current:
$components: [{
$type: "img",
src: "http://localhost/img.jpg"
},
->

I want to generate empty tag with xml style (add '/' before '>').
->

Could you add option to select it?

New $html special attribute.

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);           
}

[Bug?] this.class cannot set class

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
    }
}

Analogies are hard to parse

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!

Probably not a cell issue..

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.

website stuff

*.js.org

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

Proper CDN with versioning

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

Impossible to clear $components

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>

Suggestions

Some suggestions:

  1. Rename $type into $tag, it's more appropriate and less confusing, otherwise: $type: "input", type: "text"

  2. Rename $components into $kids. Again, less confusing, more appropriate and much shorter!

  3. 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.

  4. Remove $cell, detect elements by $tag, allow $tag == ''.

window.webkitStorageInfo is deprecated on Chrome.

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

JSX support?

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.

Possition of the $cell

It would be nice if I could specify that $cell :

  1. replaces existing element
  2. is inserted as first child of existing element
  3. is inserted as a last child of existing element
  4. existing element is inserted into it as a first child.
  5. existing element is inserted into it as a last child

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.

$update do not gets executed automatically in window.setTimeout()

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">
<script src="https://www.celljs.org/cell.js"></script>
<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>

Destructor

Motivation

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?

Nested Structures

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>

Style attribute and the element's style property are conflicted in Cell elements

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!

Example using client-side Routing and Graphql?

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.

How to render a cell to a specific place in a page

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!

Allow explicit instantiation of cells

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);
});

Styling the html element

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>

deprecated message

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.