Closed Caffeinix closed 7 years ago
Thank you for the idea! I took some time to think about this.
First of all just in case you missed it, Cell does have purely declarative ways of injecting elements into whichever position you want:
In this case what you should be doing is write a single cell object instead of multiple. For example, instead of:
var heading = {
$cell: true,
$type: "h1",
$text: "Header"
}
var paragraph = {
$cell: true,
$type: "p",
$text: "This is a paragraph"
}
You should be writing:
var body = {
$cell: true,
$type: "body",
$components: [{
$type: "h1",
$text: "Header"
}, {
$type: "p",
$text: "This is a paragraph"
}]
}
Or if you want to componentize each:
var heading = {
$type: "h1",
$text: "Header"
}
var paragraph = {
$type: "p",
$text: "This is a paragraph"
}
var body = {
$cell: true,
$type: "body",
$components: [heading, paragraph]
}
In this case you can use id
to auto-inject cell elements into anywhere. You just need to have an element with the same ID already on the HTML markup. Here's more on that: https://github.com/intercellular/tutorial#e-injecting-a-cell-into-an-exsiting-dom-tree
That said, coming back to the topic of exposing the $build
API I would love to hear your additional thoughts after having read my comments above.
I do think it would be helpful to document this, but at the same time I think it would be the best if everything could be taken care of in purely declarative manner.
I think the methods above should cover all the cases that we don't need an explicit instantiation but please let me know if I'm missing something, or if there are other useful cases for explicit instantiation. I would love to improve it somehow. Thank you!
Thanks for your quick reply! You are absolutely right, I hadn't noticed the ability to inject into an existing DOM tree. I agree that fixes the vast majority of the problem (and it's arguably more elegant than a $parent
attribute, too). Given that, it makes sense to prefer that approach to the imperative $build
API in most cases.
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. The $build
API does solve that problem, but I can imagine other solutions too, such as eschewing a variable declaration entirely and having a Cell
function instead:
Cell({
$type: 'h1',
$text: 'Hello, world!'
});
Alternatively, Cell
could return the genotype which could then be assigned to a lexically scoped variable:
const heading = Cell({
$type: 'h1',
$text: 'Hello, world!'
});
That still looks reasonably declarative to me. :)
@Caffeinix you are totally right that let
and const
variables don't get detected in the global scope.
My first instinct to your comment was to ask "when would we need lexical scoping to declare cell variables though?". It's probably because in most cases I would only use a single Cell variable in the global scope to build an entire DOM tree, in which case there's no need for lexical scoping to begin with.
But I do want to understand if there are cases where using let or const makes sense. Could you share some scenarios where using let
or const
instead of var
makes more sense? Would appreciate your input. Thank you!
My concern is mainly about namespace collisions. For example:
var Element = { // Oops! I just overwrote the constructor for the built-in Element type!
$cell: true,
$type: 'h1'
}
Bugs like that are subtle, and will tend to come back to bite you long after you've forgotten what you named that variable.
As you say, there should only ever be one global variable in the app, representing the root element, so maybe this isn't the most horrible thing. But it does feel like a spooky side effect in an otherwise very functional approach. (I also wonder about performance -- do you go through every single property of window
looking for a $cell
attribute?)
I think a Cell()
function or similar would be a safer and more explicit way of achieving the same effect, and if you decide not to implement it I'll probably use the $build
API out of an abundance of paranoia, but I wouldn't call it an existential design issue. :)
BTW, imagine site X is built with Cell.js (and has their own $cell root), site Y is built without Cell.js. Suppose I make a self-contained widget (e.g. showing tweets) using Cell.js which both sites X and Y want to embed/inject on their page. Is it advised to pack the widget in an iframe for this case? can there be any conflicts with site X?
One thing to consider is the current injection approach only works if the full DOM is parsed at load time. Having an explicit mount/$build function would be useful for starting an app within the context of a legacy application. For example I'm thinking cell would be good for something I'm doing inside of a legacy Angular application but obviously rewrite isn't in the cards. But of course the page I would want to mount on doesn't exist yet.
This is one of the big value propositions that Elm has, and potentially for Cell, is that you can introduce it incrementally as you go. I see a real value in being able to have something like cell which is more low level with some good abstractions which skirt the complexities of dealing with runtime DOM binding in frameworks like React/Angular. For me it's needing to write something more complex than what angular's template dsl gives me or adding charting libraries.
Random question to tack on. I was stepping through the $node.Dirty and diff stuff in the chrome debugger and it appears that when it comes down to it you're serializing and diffing strings. Maybe I'm reading this wrong because I see you have the LCS implementation being used though I'm not too familiar with it. I would think this wouldn't work with unordered collections like Objects, Maps, Sets.
Tangentially, I wonder what performance gains could be had using immutable datastructures with fast equality checks.
Hey guys, I have a draft for addressing this issue. Please check it out and would appreciate feedback https://github.com/intercellular/cell/issues/125
Resolving this with this release: https://github.com/intercellular/cell/releases/tag/v1.1.0 👍
According to the official documentation, the canonical way to instantiate a cell is like this:
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 aniframe
.Of course, as you are probably about to point out, there is already an undocumented solution to this problem:
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: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:However, I hope you keep the imperative version as well, because it allows developers to avoid polluting the global namespace. For example: