Closed GetContented closed 9 years ago
Hi Julian, yes this is probably something that will be useful, but I'm not planning to do much "promotion" until 0.2.0 is released - hopefully this summer.
Briefly, the pros and cons I would list are:
For freactive.dom
:
Pros:
Cons:
For freactive.core
:
Pros:
Not sure what the cons are...
Nice work. Should pop this on the front page IMHO.
Would freactive allow one to do iframe content-diffing between changing srcDoc text? (in other words, when my ratom that contains the srcDoc of my iFrame changes, it currently will rerender the entire thing, causing masses of img reloads... but a DOM-diffing (freactive.dom ?) would allow this not to be the case.)
Another question - does freactive do atom-change updates automatically? Reagent is pretty fantastic at this, but it can get bogged down sometimes.
Regarding srcDoc diffing, are you talking about diffing HTML text?
@aaronc Kind of, but not really. I'm talking about loading an iFrame's srcdoc initially with HTML source text, the browser obviously then renders that into the actual DOM, but then subsequent updates being done via comparing the iFrame's DOM with the modified to-be-rendered VDOM (VDOM diffing). Does that make any sense to you?
The reason I bring this up is that React and therefore Om & Reagent all have a problem where everything has to be wrapped... and this precludes doing things like this, amongst other things like "components" that return just text nodes, but if we use the actual DOM in freactive, it's possible to have a VDOM in an iFrame and do partial updates using "ordinary HTML" (such as my current project just happens to need).
Okay, I think I understand... mostly. One of the long-term goals is to support server-side rendering in pure Clojure (no js-engine required), by interpreting freactive's syntax as a dialect of hiccup. Now there are some difficulties here.
First of all, if we show the user the pre-rendered HTML, no events will be wired up. One pretty straight-forward solution is to overlay the UI with a translucent loading indicator until the virtual DOM gets wired up.
To re-use the pre-rendered elements when freactive mounts is a little more difficult. We could diff against actual DOM elements via tagName
, namespaceURI
and attributes
but IE support for attributes
is somewhat flaky. For events, getEventListeners
is not supported cross-browser. So, I'm thinking the way to support this would be via some "unsafe-reuse-nodes" flag which indicates that you have created those elements and even if they can't be diffed fully, as long tagName
and namespaceURI
match you "know" that it's safe.
Does that make sense? Would this address your issue?
What do you mean by "do atom-change updates automatically"? freactive will automatically bind to anything that implements IDeref
and IWatchable
- i.e. an atom, cursor, rx, etc...
@aaronc Cool - that's what I thought (freactive will automatically...) great :) just wasn't sure, because I was reading some issue yesterday that made tme think freactive had different design goals in mind (made little sense, but all good). This was the issue: https://github.com/aaronc/freactive/issues/16
Yeah, right now it just doesn't support [my-component-fn ...]
which imho is sort of syntax sugar. You need to explicitly write (rx ...)
to create a "component". But there are reasons for this - freactive is agnostic to the data type you're binding with - you could create a "component" with javelin's defc=
for all I care. Also freactive encourages you do bind more locally - directly to attributes if you like - why diff a whole node if all you want to change is one attribute. But like I said, I'll probably support reagent's syntax to make it easy for reagent users to transition...
re: "One of the long-term goals"
Sorry I think I've led you up the garden path a bit. I just got excited with the per-element DOM updating idea, but I don't think this is really something beneficial to either of us. The below is what I was going to say, but I don't think it'll work. I'm being far too ambitious. :)
...
Actually I have a different thing that I need to do in mind... my app is an HTML editor of sorts (editing happens in fragments of HTML), and as users make their changes, there's also a preview of those changes: an iFrame that is driven by a render function, which simply glues the correct model state together and is bound to the iFrame's srcDoc attr. On every keypress, the entire iFrame is re-rendered, which means all images are reloaded. I've experimented with doing delayed updates using a kind of slow debouncing algorithm, and even experimented with creating my own custom iFrame component that interpreted the HTML as React, in order to use the diffing. Trouble is, the HTML source (being edited) isn't under my program's direct control, it's under the control of a different user of the app. I hope this makes sense.
My thought was because freagent is able to wrap ANY HTML element, it would be able to do efficient diffing within the iFrame... only changing the (usually text) portions that a user is editing, but, yes, if they were editing portions of javascript or CSS, that would cause some pretty serious issues... especially if the thing they were editing was itself a full MVC JS application. Not really sure how to get around this. Perhaps I'll just look to more efficient caching as a better way to do this.
If you stick with browsers that support Element.attributes and getEventListeners you could probably do the diffing correctly.
On Mon, Jun 1, 2015 at 12:48 PM, JulianLeviston notifications@github.com wrote:
re: "One of the long-term goals"
Sorry I think I've led you up the garden path a bit. I just got excited with the per-element DOM updating idea, but I don't think this is really something beneficial to either of us. The below is what I was going to say, but I don't think it'll work. I'm being far too ambitious. :)
...
Actually I have a different thing that I need to do in mind... my app is an HTML editor of sorts (editing happens in fragments of HTML), and as users make their changes, there's also a preview of those changes: an iFrame that is driven by a render function, which simply glues the correct model state together and is bound to the iFrame's srcDoc attr. On every keypress, the entire iFrame is re-rendered, which means all images are reloaded. I've experimented with doing delayed updates using a kind of slow debouncing algorithm, and even experimented with creating my own custom iFrame component that interpreted the HTML as React, in order to use the diffing. Trouble is, the HTML source (being edited) isn't under my program's direct control, it's under the control of a different user of the app. I hope this makes sense.
My thought was because freagent is able to wrap ANY HTML element, it would be able to do efficient diffing within the iFrame... only changing the (usually text) portions that a user is editing, but, yes, if they were editing portions of javascript or CSS, that would cause some pretty serious issues... especially if the thing they were editing was itself a full MVC JS application. Not really sure how to get around this. Perhaps I'll just look to more efficient caching as a better way to do this.
— Reply to this email directly or view it on GitHub https://github.com/aaronc/freactive/issues/44#issuecomment-107633519.
re: "Yeah, right now it just doesn't" - excellent. This project is great! Love where it's going. Interestingly, this project (and reagent) consistently highlight some of the issues there seem to be with Clojure(script) in terms of them being not lazy enough, or data-driven enough, and also with the way the browser functions right now. (live rewriting of an application on the fly from wtihin itself anyone? figwheel FTW, but compiler needs to be within the browser). Anyway I'm rambling. Sorry!
As to my other issue, perhaps hipo can me help with its "live DOM node reconciliation" https://github.com/jeluard/hipo/ (ie VDOM diffing). Obviously, if the DOM node that is being manipulated is a script tag or somesuch, the entire page/frame would need to be reloaded. Ah I'll see how I go :)
I'll close this now. Thanks very much for your time. This stuff is great.
Is there any advantage to having DOM diffing at all? Unwanted re-renders can be avoided by minimizing the scope of rx
-expressions or using non-reactively
, right? You currently have to do it by hand, but it doesn't seem to far-fetched that a macro could interrogate state derefs in the view function and figure out how to wrap things in these two macros.
The biggest advantage we've found with freactive is that bindings maintain the references to DOM nodes. This opens up the possibilities in working with 3rd-party libs without worrying about them playing nice with virtual DOM. We had a lot of headaches getting some Polymer elements to work correctly within virtual DOM. paper-dialog moves elements in the DOM to get "modal" overlay behavior, and the diff got confused. And we never did get the ACE editor to work correctly with vdom, but it was straightforward with freactive.
In freactive's design diffing has always seemed to be the least important piece to me - it's what happens when all else fails to prevent unnecessary changes. I'm not sure there's no advantage to at all, but in an otherwise well-designed system you're right that diffing really wouldn't do that much. If a diffing algorithm is maintained, there would be a way to turn it off and I definitely wouldn't want it to interfere with using Polymer. In terms of features, I see management of keyed sequences of items being a much higher priority which would also eliminate a lot of use cases for diffing. Is this the virtual DOM lib that you're referring to: https://github.com/Matt-Esch/virtual-dom?
I'm curious, where are the cases that you need to use non-reactively
frequently? Yes, ideally this would not need to be done by hand.
I guess there might be cases where diff could be useful, but I have yet to run into them. I can contrive some examples, but the our common use-cases so far would be pretty well-served by some basic static analysis - just doing this by hand now. Part of the reason diff seems marginal might be that we're using HTML custom elements, and have them wired up so that they manage their own state and DOM subtree internally. The more likely case where we would encounter large pieces of DOM with small differences is going to be keyed sequences.
We were using that https://github.com/Matt-Esch/virtual-dom, via our first attempt at web components: https://github.com/allgress/web-components. That turned out to be kind of a mess, trying to add features to deal with aspects of working with other "uncooperative" libraries, including a half-baked version of Elm-style FRP. Some of the issues we saw might have been handled using virtual-dom's "widgets" and/or other methods, but it felt like we had too many different fiddly knobs to turn already. Our second attempt is https://github.com/allgress/cereus, leveraging freactive. The library itself is considerably smaller, and the consensus on our team is that the resulting code is much cleaner.
Most of our use of non-reactively
was to prevent parent rx
-expressions from registering dependencies of child rx
-expressions. The latest changes to freactive have fixed that, so that use-case is gone. I also use non-reactively
to avoid redundant state updates or possible race conditions on elements handling user input. For example, to make a validating text input we need to read the current text on each keypress, but I don't want to update the value
attribute every time a key is pressed, as it's redundant at best. Finally, I think Cereus should probably wrap the custom-element lifecycle functions in non-reactively
, to ensure that any internal deref's are not picked up by parent element rx
-expressions. Haven't actually run into problems with this yet, but that may be due to the current aggressive use of non-reactively
from the previous parent/child dependency issue.
And using freactive and Polymer 1.0, I can't reproduce the race condition issue I saw before with text inputs, so that use of non-reactively
is gone as well.
Cool, glad that use of non-reactively
is not needed as much. Like I said, ideally you would only need to use it in very special cases... its need in ordinary use cases I would probably treat as a bug.
Btw, I just pushed a new DOM implementation to freactive's develop
branch (and clojars). It builds on what was there but is overall significantly different and in my mind simpler... in a good way. Probably the biggest new thing is support for managing keyed sequences of elements (with any level of nesting)! There is no diffing support although it could be added - but again low priority. Overall, hopefully everything should work as before. I wonder if you could possibly test this against your Polymer code to make sure there are no glitches?
Immediately hit this:
Uncaught #ExceptionInfo{:message "Can't safely do manual DOM manipulation within the managed element tree. Please do manual DOM manipulation only on top-level managed elements.", :data {:managed-element #<[object HTMLElement]>}}
I'll put together an isolated repro soon, but I think what's happening is I've got a parent custom element creating a child custom element. That occurs in the context of dom/mount!
. The child also calls dom/mount!
to render it's subtree, guessing somehow these nested calls are an issue.
Yes, previously it would have been possible to have nested sub-trees that could be manipulated manually, but in this implementation it really isn't. The reason for this is that element sub-sequences are now allowed and this requires element states to know where things are. Manual manipulation would break that. However... a method could be added to DOMElement to do append/insert safely. But I'm wondering what is the use case for nested mounts?
On Wed, Jun 3, 2015 at 6:00 PM, Dave Dixon notifications@github.com wrote:
Immediately hit this:
Uncaught #ExceptionInfo{:message "Can't safely do manual DOM manipulation within the managed element tree. Please do manual DOM manipulation only on top-level managed elements.", :data {:managed-element #<[object HTMLElement]>}}
I'll put together an isolated repro soon, but I think what's happening is I've got a parent custom element creating a child custom element. That occurs in the context of dom/mount!. The child also calls dom/mount! to render it's subtree, guessing somehow these nested calls are an issue.
— Reply to this email directly or view it on GitHub https://github.com/aaronc/freactive/issues/44#issuecomment-108627609.
I'm not sure it really is necessary to nest dom/mount!. The first implementation of Cereus used it to mount to the custom element shadow DOM. But I've not made shadow DOM optional - by default the view mounts to the custom element itself. I would still like Cereus to support the use-case of using custom elements directly from HTML. So I think I need to mount in this case, but if I can get some info about the context where the element is being attached, maybe I can conditionally call mount! when the parent is not managed by freactive. Is there a way to do that?
The other thing I'll try now is to just defer the call to dom/mount! with js/setTimeout.
I just added a managed? fn to freactive.dom. Nested manipulation may be possible but it wouldn't use mount! and is somewhat complex (due to element seqs) - ideally I'd prefer to avoid it.
On Wed, Jun 3, 2015 at 6:32 PM, Dave Dixon notifications@github.com wrote:
I'm not sure it really is necessary to nest dom/mount!. The first implementation of Cereus used it to mount to the custom element shadow DOM. But I've not made shadow DOM optional - by default the view mounts to the custom element itself. I would still like Cereus to support the use-case of using custom elements directly from HTML. So I think I need to mount in this case, but if I can get some info about the context where the element is being attached, maybe I can conditionally call mount! when the parent is not managed by freactive. Is there a way to do that?
The other thing I'll try now is to just defer the call to dom/mount! with js/setTimeout.
— Reply to this email directly or view it on GitHub https://github.com/aaronc/freactive/issues/44#issuecomment-108632933.
It isn't really "nested" manipulation per se. Rather I want to treat the sub-tree of a custom element in isolation, updated by the state atom owned by that element. The state in turn is updated via event-handlers from internal elements, or attribute changes from the outside world.
My current solution when attaching a custom element is to create a "root" via document.createDocumentFragment()
, call dom/mount!
on that to render the view, and then stuff the document fragment into the custom element instance. And all of that happens in setTimeout, with the thought of avoiding cross-talk between dom/mount!
calls. Took a quick look at the code, and didn't see any obvious issues with this approach, but would be interested in your thoughts. So far seems to work fine.
Well there's nothing to prevent you from using different atoms in different sub-trees like in om. I still don't understand what the issue with doing a single mount! would be.
By nesting mounts you risk that the child mount won't get disposed of properly when its parent gets disposed. That said, if you pass an unmanaged node to mount! in the current impl, that node is wrapped with the proper on-dispose callback. For the use case where someone may want another renderer as a child of freactive, I added wrap-unmanaged-node which takes an on-dispose callback.
On Wed, Jun 3, 2015 at 9:23 PM, Dave Dixon notifications@github.com wrote:
It isn't really "nested" manipulation per se. Rather I want to treat the sub-tree of a custom element in isolation, updated by the state atom owned by that element. The state in turn is updated via event-handlers from internal elements, or attribute changes from the outside world.
My current solution when attaching a custom element is to create a "root" via document.createDocumentFragment(), call dom/mount! on that to render the view, and then stuff the document fragment into the custom element instance. And all of that happens in setTimeout, with the thought of avoiding cross-talk between dom/mount! calls. Took a quick look at the code, and didn't see any obvious issues with this approach, but would be interested in your thoughts. So far seems to work fine.
— Reply to this email directly or view it on GitHub https://github.com/aaronc/freactive/issues/44#issuecomment-108671158.
That's good info, thank you. I believe you're correct, and my approach would not have allowed the mount to be properly disposed. Web components have a standard set of lifecycle methods, so I think the right approach is to wire up the disposedCallback
function to handle this.
I don't know how you would handle shadow-DOM with a single mount. In full implementation of the (draft) web components standard like Chrome has, the shadow DOM is essentially hidden from the rest of the DOM, and is where you do the actual rendering for the component. The component can have "light DOM", which is what the user specifies as content, but the light DOM is really input for rendering. Example:
<paper-button>
<span>Button</span>
</paper-button>
The <span>
as shown isn't rendered. Instead it gets moved into the shadow DOM and stuffed inside <paper-material>
to give it a particular look (Polymer 1.0 has an option to efficiently emulate some aspects of the shadow DOM for browsers without a native implementation, hence the need for the funky DOM API's). So each shadow DOM needs to be a separate mount.
So there's nothing wrong with doing multiple mounts on a single page - nesting is the only issue and can probably be avoided with good planning. But if you need to nest you can do it - just make sure you mount!
on a node that you created with document.createElement
, etc. - not one created and managed by freactive. The main reason for the restriction is really that the mount point should have no other children so that freactive can fully manage its children.
Got this working fine.
Cool... There's a possibility I may loosen some restrictions later down the road, but glad you have this working how it is.
Okay, but that's definitely in the category of "nice to have". For browsers supporting shadow DOM, I'll just mount to the shadow root (haven't tested this yet, but can't see why it wouldn't work). Everybody else just gets an unmanaged container
Related question: what will happen when Polymer moves managed nodes around in the DOM? Cereus doesn't really support this yet (no use-cases in our current application), but at some point probably will need to do the same thing.
How will Polymer be moving managed nodes around the DOM? freactive provides specific interfaces for moving nodes (haven't documented this yet but the recommended way is to use IProjectionTarget's which can manage all sorts of reorderings including reordering of nested sequences). The tradeoff for this support is that using standard DOM API's to move nodes will definitely break things.
On Fri, Jun 5, 2015 at 2:21 PM, Dave Dixon notifications@github.com wrote:
Okay, but that's definitely in the category of "nice to have". For browsers supporting shadow DOM, I'll just mount to the shadow root (haven't tested this yet, but can't see why it wouldn't work). Everybody else just gets an unmanaged container as a child of the managed custom element. That serves as the mount point. The only very minor downside is that there's an extra element in the rendered DOM tree of the custom element, not a big deal.
Related question: what will happen when Polymer moves managed nodes around in the DOM? Cereus doesn't really support this yet (no use-cases in our current application), but at some point probably will need to do the same thing.
— Reply to this email directly or view it on GitHub https://github.com/aaronc/freactive/issues/44#issuecomment-109387067.
Polymer 1.0 seems to have done away with one scenario, where <paper-dialog>
would move itself in the DOM to give modal behavior. But other Polymer elements or custom element libraries could conceivably do this, and freactive's proper handling of this was one of the main attractions for us.
For Polymer 1.0, when using "shady DOM", they move elements to emulate the light/shadow/composed DOM behavior of the web components spec. So markup like this:
<paper-button>
<span>Submit</span>
</paper-button>
winds up as this in the DOM:
<paper-button role="button" tabindex="0" aria-disabled="false" class="x-scope paper-button-0" pressed="">
<paper-ripple class="style-scope paper-button" animating="">
<div id="background" class="style-scope paper-ripple" style="opacity: 0; background-color: rgb(0, 0, 0);"></div>
<div id="waves" class="style-scope paper-ripple"><div class="wave-container style-scope paper-ripple" style="top: -20.4296875px; left: 0px; width: 82.234375px; height: 82.234375px; -webkit-transform: translate(0.2578125px, 0.3125px); transform: translate3d(0.2578125px, 0.3125px, 0px);"><div class="wave style-scope paper-ripple" style="opacity: 0.25; -webkit-transform: scale(2.58437067390754, 2.58437067390754); transform: scale3d(2.58437067390754, 2.58437067390754, 1); background-color: rgb(0, 0, 0);"></div></div></div>
</paper-ripple>
<paper-material animated="" class="content style-scope paper-button x-scope paper-material-0" elevation="0">
<span>Submit</span>
</paper-material>
</paper-button>
And Polymer uses either the Polymer.dom APIs (discussed in Issue #45) or the raw DOM APIs to actually manipulate the DOM.
Let's continue this discussion in issue #45.
More people will choose Freactive if they understand the tradeoffs of design choices and advantages / disadvantages of each compared to reagent, Om, etc.