marionettejs / backbone.marionette

The Backbone Framework
https://marionettejs.com
Other
7.06k stars 1.26k forks source link

Allow for LayoutView.prototype.render to be non-destructive #1263

Open jamesplease opened 10 years ago

jamesplease commented 10 years ago

Repeatedly calling LayoutView.prototype.render will destroy all of the views within the layout's regions, and those regions themselves, and completely re-set the layout's regions.

Yikes.

That's a lot. I'm not sure if we should do all of that on a re-render. This is to discuss the possibility of changing this behavior or maybe allowing an option as a parameter of render to change the behavior.

The original discussion can be found in #877.

algesten commented 10 years ago

Alright. Lets kick this off. What do we mean by LayoutView.render()

Or something else?

jamesplease commented 10 years ago

I think for the sake of the community not giving up on us we'll need to keep today's behavior the default...at least for now. But we can always add options to modify that behavior or new methods to implement new functionality.

But I think instead we should start with the problems we're trying to solve. What's wrong with today's behavior? What doesn't it allow one to do easily?

algesten commented 10 years ago

I think of render as something quite lightweight. In my views it ensures what's on screen reflects what's in my model.

Lightweight being the key here.

What I certainly don't expect is a call to render utterly destroying my child views and obliterating child view DOM.

One can also see it as separation of concerns.

I'd expect render to handle its own DOM and delegate render to the nested views. Nothing more.

jamesplease commented 10 years ago

@algesten I'm interested in hearing more, but I can't think of any alternative behavior that I prefer more than what we've got. Can you elaborate more on the behavior that you think is preferable?

pimlottc commented 10 years ago

This was a surprise me as well quite recently. I was trying to move from a CompositeView of CompositeViews to a CompositeView of LayoutViews in order to support multiple subcollections in my inner view (see fiddle). But LayoutView does not seem to be comfortable in this role.

It seems that the philosophy of LayoutView is quite different than that of CollectionView or CompositeView, which are perfectly at home nested deep within your view hierarchy where they are subject to being repeatedly re-rendered. They look after child views for you, recreating and rerendering them as necessarily as well as providing buffering to make it efficient. In contrast, LayoutView seems to be intended to live at the top of your view hierarchy, manually created and initialized with subviews and then never (or at least sparingly) re-rendered again, as doing so requires a manual reconstruction of all the child views.

I don't know if I'm "misusing" LayoutView and should be attempting a different tack (perhaps extending CompositeView), or if LayoutView should be made more flexible to support a more dynamic use deeper within the view.

fenduru commented 10 years ago

I'm also running into this issue. My use case is that I have a container div that has a Title (and a couple other pieces of information), and a table of data. The "title" is just text and doesn't warrant its own view, so I want it to be part of my layout's template, and give the layout a model. I want both the title and the table to be able to re-render themselves when their models change.

I think the biggest problem is that the layout closes the contained views... which now puts a lot of strain on my controller which has to listen to the layout's render event to ensure that new views get created and shown into the region (even though it has already done that before).

I think just getting rid of the _reinitializeRegions call could go a long way, and instead, after the re-render the layout could do a $('.myRegion').replaceWith(existingRegion.$el) Perhaps it would then make sense to call render on the view -- I'm not sure

jasonLaster commented 10 years ago

@andrewvaslas this issue has your name on it!

I'll go on record, this issue is legit. We should make it better.

jamesplease commented 10 years ago

Did you mention the right person, @jasonLaster? :)

jasonLaster commented 10 years ago

yep - andrew discovered this bug at work.

jamesplease commented 10 years ago

ahh ok. cool :)

algesten commented 10 years ago

I think I've said this in various other issues, but I try to condense it. I think LayoutView.render() should result in:

Now reg1 still believes it holds myWidget, when in fact, it's been moved. Consecutive redraw() will cause confusion.

samccone commented 10 years ago

Migrated to the v3 list https://docs.google.com/document/d/1Sx1YE2SJg-NGSGsd8mELf3wpywI-UyOvCkufbm6klOw/edit#

jamesplease commented 10 years ago

My thoughts:

I like the default behavior of render: that it re-renders the whole subtree by default. But I understand the reasoning for wanting finer control over this. I propose a new flag you can pass to render: deepRender. By default it's true, but you can set it to false to prevent rendering the whole tree.

The technical details of this will just involve picking up the regions (or the views that they contain), then putting them back in place after the render, which @jsteunou made a good prototype of in #1722. We can throw errors if the Regions no longer exist to prevent people from doing weird things. It wouldn't be too hard to do, I think; we'll just want to make sure we adequately test it.

In addition to render, we could add this option to show as well, so that you can prevent deeply rendered layouts when showing them inside a region.

What do y'all think?

jasonLaster commented 10 years ago

I could be persuaded that we keep the regions around and re-insert them, but I'm not sure we want to.

It would be simpler to re-show the regions after render.

render: ->
  // render the layout
  // trigger show on the layout

There are several reasons I like this better than the alternative:

If we trigger show at the end of render I'd propose adding the shouldShow flag to render (render({shouldShow: true})).

samccone commented 10 years ago

Since a layout is both a node and contains other child nodes (regions). Render is going to blow everything away, that is just a fact.

For a non destructive render (does not re-render child nodes) we would need to investigate a very different approach. Basically region contents would be DOM fragments that would be inserted instead of being rendered into regions, but then we would hit some weird detached node issues.

Thinking about this over the past few days I don't think there is a "clean" layout based solution. Instead I see this as being something very different and perhaps even a misuse of what a layout is intended to be/do.

jasonLaster commented 10 years ago

One revision to the proposed api

Layout.extend({
  shouldShowOnRender: true
});

I like the class propety better because it's something that a developer would configure on the class level.

As a side-note, implementing non-destructive renders w/ the persistent region strategy should not be a difficult solution for developers either on a one-off basis with some onBeforeRender onRender logic.

algesten commented 10 years ago

@jmeas I don't care much for deep or shallow rendering, as I see it, that is not the crux of the problem. There is one single line in the code that is my pet peeve.

https://github.com/marionettejs/backbone.marionette/blob/master/src/marionette.layoutview.js#L38

This single row that it impossible to repeatedly call render() without utterly destroying the views shown in the regions.

jasonLaster commented 10 years ago

@algesten - i bet. I'm in favor of adding a flag so that we can work around it if we want to.

samccone commented 10 years ago

@algesten can you explain why you need to rerender the entire layout?

JSteunou commented 10 years ago

@samccone I dont know for him, but for me it's marionette which re-render an entire layout when this one is in a region itself. Otherwise I do not re-render entire layout manually.

algesten commented 10 years ago

@JSteunou nailed it. Nested layouts triggers renders on every .show()

https://github.com/marionettejs/backbone.marionette/blob/master/src/marionette.region.js#L166

I have a main app layout, and in those regions I swap in/out other layouts. My nested views get utterly destroyed.

algesten commented 10 years ago

The main problem is that Layout.render() is destructive. Not the recursiveness or shallowness of it.

fenduru commented 10 years ago

@samccone LayoutView extends from ItemView. In my LayoutView's template I should be able to put things like <%= someProperty %> and drive the LayoutView with a model. Now when someProperty changes I want that to be reflected on screen, but right now my options are:

While I also agree with @JSteunou 's issue where calling region.show(layoutWithChildren) will destroy the children, it is not the only issue here.

fenduru commented 10 years ago

This Backbone plugin uses a detach/reattach approach:

https://github.com/rotundasoftware/backbone.subviews

jasonLaster commented 10 years ago

Re-opening so that I can try and get a non-breaking fix in before v3.

jamesplease commented 10 years ago

I've been thinking about this more and more, and I am all for detaching the regions and putting them back into place after things have been re-rendered as the default behavior.

I can't wait for this to land!

jasonLaster commented 10 years ago

Hmm - my money is on re-calling show as things could have changed.

jamesplease commented 10 years ago

Can you elaborate?

jamesplease commented 10 years ago

@jasonLaster, do you still think you'll have time to work on a non-breaking fix for this?

jasonLaster commented 10 years ago

Yep. November should be good for me.

fenduru commented 9 years ago

Not to be a pest, but any update here @jasonLaster? Very excited for this change

jasonLaster commented 9 years ago

Hey @fenduru! Stop by sometime https://gitter.im/marionettejs/backbone.marionette.

This issue is definitely on my hitlist. I'm finishing up the main Inspector push. Will hopefully circle back soon though.

samccone commented 9 years ago

http://npmjs.org/virtual-dom

This very well could be exactly what we have been looking for :)

jamesplease commented 9 years ago

Minionette has a pretty clever region that supports this. Ref. I can see us implementing something similar-ish.

megawac commented 9 years ago

/cc @jridgewell

jridgewell commented 9 years ago

Minionette takes this to an extreme: I never render a subview, it's up to you to do that. Combine that with each region's placeholder view, it allows Minionette to detach and reattach the region when rendering the parent view.

It's very similar to rotundasoftware/backbone.subviews, though I don't make you insert the placeholder yourself. You can either use <%= view("nav") %> directly in the template, or provide a selector for some DOM element in the view's template. The Region docs explain the idea pretty well.

The only thing to look out for is the region's placeholder will reference a DOM element that isn't really in the parent view after calling $el.html(). You'll need call #setElement on the placeholder with a DOM element in the parent view's tree.

ianmstew commented 9 years ago

Leaving child view re-rendering up to the developer as an option makes sense, although I haven't had this problem myself because I write my templates/views in such a way that I never need to re-render a layout, unless the layout is trivial.

rbu commented 7 years ago

Marionette 3.1 now comes with a "detachChildView" function that allows you to re-attach a child view later on, which can be used to build this:

http://marionettejs.com/docs/v3.1.0/marionette.view.html#detaching-a-child-view

Thanks a bunch!

paulfalgout commented 7 years ago

Agreed.

There's a little bit of complexity here dealing with how to either reset or rebuild the regions.. and what to do it.. for instance you re-render the parent view, and the child view detaches.. but then the DOM element for the region renders out of the parent view.. I suppose you then destroy the child view, or do you keep it around for later? then what if you render it again and the region renders back in? what are the strategies for knowing if child views need to be re-rendered or if they're merely detached

rafde commented 7 years ago

moved to https://gitter.im/marionettejs/topics/topic/57fb29d884f1db0614985140/allow-for-layoutviewprototyperender-to-be-non-destructive

paulfalgout commented 6 years ago

As we start to look at this in 4.x I think there are 3 scenarios.

  1. marionette managed destructive regions on render (what it is now)
  2. marionette managed non-destructive regions (ie: marionette detaches/reattaches on render)
  3. marionette non-managed regions (ie: the renderer must know to render around the regions content on render) I think we need to setup a way to allow for this to be configurable per view, but not per region.