Closed gliechtenstein closed 7 years ago
Nice turnaround! I think you're taking the right approach here. I was describing Cell.js to a coworker today and I described it as 'an extension to the DOM api' which I think this aligns with. It's why my attention was caught by Cell.js in the first place. Cell kind of reminds me of a Clojurescript framework call Hoplon which takes a similar approach of extending the element's api. There might be some ideas to glean from there about further abstractions.
Again, nice work!
lgtm
$cell()
method seems to me like a good addition and a novel approach, but it's missing one of the (main?) concerns, at least mine: the magic behing the declarative style of create elements. That's why I think we still needs a Cell()
namespace, but not as a creator returning Cell objects, but as an appender to the body
element. It's said, as an alternative to the current "main Cell creation logic":
- Wait for
window.onload
event- Find all global variables that have a $cell attribute
- Automatically build cells from these variables
Probably the window.onload
event is the main concern, since user would need to be conscious about the page structure and events, removing abstraction. That's why I wanted that logic to be hidden inside the Cell()
function. This could be as simple as:
// Sort-of Promise on the `DOMContentLoaded` event
let DOMContentLoaded = false
window.addEventListener('DOMContentLoaded', function()
{
DOMContentLoaded = true
})
function Cell(gene)
{
const body = document.body
if(DOMContentLoaded) return body.$cell(gene)
window.addEventListener('DOMContentLoaded', body.$cell.bind(body, gene))
}
that by way, would be done as an independent module (cell-primordialsoup
? :-P) and help to remove references to the window.onload
event and others from the cell.js
module and make it more oriented as a pure DOM nodes expansion. How do you see it?
i definitely agree that DOMContentLoaded
should be used over load
although i'm still not sure about a global Cell
namespace
That's why I said that maybe makes sense the global Cell namespace being hosted on an independent module :-)
I'm thinking... in case of the global namespace, better window.$cell()
so people don't confuse it as a class, isn't it?
better make it cElL
just to be sure
@piranna I don't think I quite understand the use case you have in mind for this, could you share a specific example where this is necessary? That would be helpful. Thanks!
@kingoftheknoll thanks! taking a look at hoplon.
Don't need to polute the global context with the genes, and instead of need to wait to window.onload so cell.js can query the window object for the genes to add them to the body, be able to add them at any time, before or after the page DOM has fully loaded.
El 27/06/2017 10:29, "gliechtenstein" notifications@github.com escribió:
@piranna https://github.com/piranna I don't think I quite understand the use case you have in mind for this, could you share a specific example where this is necessary? That would be helpful. Thanks!
— You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub https://github.com/intercellular/cell/issues/125#issuecomment-311289935, or mute the thread https://github.com/notifications/unsubscribe-auth/AAgfviKG2E4UDJtKLRmZmpCVGjGexuyoks5sIL1agaJpZM4OF8QA .
Assuming that you're purely using Cell and not integrating into other libraries or frameworks (since you mention DOMContentLoaded
) There's no need to wait for the load
event, nor any pollution of the global context (The Cell() function approach on the other hand would pollute the global namespace with the Cell
variable itself). All you need to do is just place the scripts at the bottom of the HTML.
Morphs the body itself into a cell https://jsfiddle.net/3spkvjdj/
<html>
<script src="https://rawgit.com/intercellular/cell/plan/cell.js"></script>
<script>
document.body.$cell({
$cell: true,
$type: "body",
$components: [{
$type: "h1",
$text: "Title"
}, {
$type: "p",
$text: "Paragraph"
}]
});
</script>
</html>
Creates a cell and appends it to body https://jsfiddle.net/bnadvaku/
<html>
<script src="https://rawgit.com/intercellular/cell/plan/cell.js"></script>
<body>
<div>blah blah</div>
<div>halb halb</div>
<script>
document.body.appendChild(document.body.$cell({
$type: "h1",
$text: "Title"
});
document.body.appendChild(document.body.$cell({
$type: "p",
$text: "Paragraph"
});
</script>
</body>
</html>
Note that neither of the two options uses the automatic cell creation (There is no global variable that contains the $cell
attribute).
Just to make some clarifications on the change, I added a new function called plan, which makes it possible to decouple the prototype overrides from actual Cell creation.
Notice that the plan
function gets called immediately without waiting for any event. Hope this explains things better.
Let me know if you have a specific example where neither of these approaches would work. Thanks!
I know I can set <script>
tags at the end, after the DOM body
is available, point is about setting them on head
the same way as the examples you have on the Reame.md file, where you combine several cells using script tags that add a gene to the global context. What I ask for, is that these scripts don't modify the global context and instead enable the gene directly instead of wating for window.onload
. In fact, doing so would allow to remove it and almost fully remove God. It's said, I'm asking for a cleaner alternative to document.body.$cell()
, that I find too much verbose and repetitive, the polution of global context, and to be forced to set the gene scripts after the DOM body.
By the way, I like the split for plan()
, I think it's the way to go :-)
Maybe the compromise is to have the module export a Cell constructor which which would take the element to bind to as it's first argument and proxy the invocation to that element's prototype.$cell
function. This means you can import or require the Cell
function which will be scoped to your module at hand and not conflict with an global namespace. I'm a big fan of this because while super simple ES5 should be the base option, for other reason's like linting, css-modules and some of the niceties of ES2015+ I'm still going to be using a build tool anyway. So having a second pattern of invocation is not mutually exclusive. It rather respects the two worlds at which people write JS applications.
I have merged this in https://github.com/intercellular/cell/commit/b4751fc15f3442707b3b442b9d0ce0fc16aed7f3 since all the issues brought up so far are something that can be discussed independent from this particular commit, and probably even something that can be built on top of the current approach if we decide to.
That said, let's continue the discussion.
Re: using Cell as a module,
@kingoftheknoll you can already use the exported variables if you want https://github.com/intercellular/cell/blob/develop/cell.js#L379 but this is a separate issue from whether to support it from the pure front-end context IMO since require
s don't pollute global context but here we are talking about introducing a global variable named Cell
to the browser global context.
Furthermore, you don't even need to use the exported variables with the latest update, since the plan()
function gets called by default when you require the module, which means instead of calling some creator function, you can directly instantiate cells from your element. Example:
require('./cell.js')
.....
.... somewhere in your module
... $el.$cell( /* The gene object goes here */ )
....
Re: adding the "window.Cell" global variable on the browser context,
@piranna I'm having hard time understanding what you are suggesting. Especially the following paragraph leaves me confused because I'm not sure how a global Cell
variable can get rid of the God
object.
What I ask for, is that these scripts don't modify the global context and instead enable the gene directly instead of wating for window.onload. In fact, doing so would allow to remove it and almost fully remove God
I'm also confused because you mention polluting the global scope as a problem of the current approach, but the document.body.$cell()
approach doesn't involve any global variables. Rather, adding the Cell
global variable is obviously polluting the global scope.
What would really help me understand your argument is if you could share an actual example you are envisioning, in code. I would appreciate it if you could provide an example of
document.body.$cell()
or the original declarative approach cannot achieveI am all for getting rid of God if it's ever possible, I'm all listening. Thank you!
p.s.
BTW, just to be clear, I do understand the overall spirit of your suggestion, but I keep asking for an example because I think the document.body.$cell()
and the {$cell: true}
declarative approaches pretty much cover all the cases when combined, that even if we introduce a Cell
variable I don't know where that will be useful other than the fact that it's shorter than document.body.$cell()
. Which is why it would be better to have a discussion based on a tangible example.
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 https://github.com/intercellular/cell/issues/12#issuecomment-308876874
Like @Caffeinix pointed out in above comment, Cell won't be able to automatically detect
const
andlet
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 https://github.com/intercellular/cell/issues/12#issuecomment-310935220 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:
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.
One possible approach (and why it's not optimal)
A lot of people suggested an option to add something like this:
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 https://github.com/intercellular/cell/issues/111Basically, 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:
Solution
Here's the solution:
$cell()
function towindow.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.
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/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/Notice:
$cell
attribute on thegene
object.document.body.appendChild(cell)
Conclusion
By adding the additional
$cell()
method onwindow.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:
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!