Reducing Repositioning in Marionette for Better Performance

I'm grappling with a web application that uses Marionette to render complex nested views. The challenge I face is minimizing reflows by ensuring child elements are rendered and placed in parent containers before being inserted into the DOM. How can I achieve this seamless rendering of child elements?

Currently, I render all children views in the onRender function. While they seem to appear simultaneously with the parent view, there's no clear confirmation if this is the intended approach. I've also explored using onBeforeShow, which triggers before insertion into the DOM, but it complicates rerendering as straightforwardly calling render becomes impossible.

To provide more context, here's a snippet of code illustrating my predicament:

var Layout = LayoutView.extend({
    //...

    initialize: function() {
        listenTo(this.model, "some event indicating data change", this.render);
    },

    onRender: function() {
        //Will the new view render in time to reduce page reflows effectively?
        this.someRegion.show(new view(new model()));
    }
});

Alternatively, employing onBeforeShow allows passing a reference to the container region

var Layout = LayoutView.extend({
    //...

    initialize: function() {
        listenTo(this.model, "some event indicating data change", this.callback);
    },

    callback: function() {
        this.container.show(this, {forceShow: true});
    },

    onBeforeShow: function() {
        //While preventing page reflows, this method isn't ideal.
        this.someRegion.show(new View(new Model()));
    }
});

var LayoutContainer = LayoutView.extend({
    //...

    onRender: function() {
        var model = new LayoutModel();
        var layout = new Layout({
            model: model,
            container: this.containerRegion
        });
        this.containerRegion.show(layout);
    }
});

Another option involves a parent object overseeing when to instruct the view to render

var Layout = LayoutView.extend({
    //...

    onBeforeShow: function() {
        //While preventing page reflows, this method isn't ideal.
        this.someRegion.show(new View(new Model()));
    }
});

var LayoutContainer = LayoutView.extend({
    //...

    onRender: function() {
        var model = new LayoutModel();
        this.layout = new Layout({ model: model });
        listenTo(model, "some event indicating data change", this.rerenderSubview);
        this.containerRegion.show(this.layout);
    },

    rerenderSubview: function() {
        this.containerRegion.show(this.layout, {forceShow: true});
    }
});

Answer №1

Pay attention to your model's instructions before taking action

Once a views el is connected to the DOM, rendering will immediately attach the View to the DOM, resulting in the top level view being attached right away. This means that if callbacks are placed in onRender, they will be attached sequentially leading to reflow.

An alternative is to place subview shows in onBeforeShow, creating a tree of Views that will be displayed at once, assuming the top View has been shown using show. The question then arises as to where to call the top level show. Two options are possible:

The straightforward option

The simplest approach is to have the top level LayoutView display its children in onRender, with subsequent child views showing their children in onBeforeShow. This may result in a few paints (one for the top level and one for each region it displays), but not an excessive cascade and likely no performance concerns (especially if there are no visible performance issues yet).

The slightly more complex option

If a single repaint on model change is desired, a wrapping View can be created as indicated in the question. Instead of having the child view directly reference it, ensure the wrapping View holds a reference to the model and triggers a layout refresh when the model changes. In this case, all callbacks can be onBeforeShow, and changes will lead to only one paint. There will be no dependencies between Views, ensuring consistent display through the same callback.

var LayoutContainer = LayoutView.extend({
    initialize: function() {
        this.listenTo(this.model,'change',this.reshowLayout);
    },

    reshowLayout: function() {
        var layout = new Layout({
            model: this.model,
            container: this.containerRegion
        });
        this.containerRegion.show(layout);
    }
});

Answer №2

Marionette ensures proper DOM insertion

Marionette follows a specific process for attaching children of a Collection/CompositeView to the DOM only after everything has been rendered, including parent and child nodes. The sequence of actions can be observed as follows:

During the execution of Region.show():

show: function () {
  // ...
  if (_shouldShowView) {
    view.render();
    // ...
    this.attachHtml(view);
  }      
  // ...
}

Upon calling region.show(), view.render() is triggered, followed by CompositeView.render(),

render: function() {
  //...
  this._renderRoot();
  this._renderChildren();
  //...
  return this;
}

The functions view_renderRoot() and view._renderChildren() are invoked in sequence within this method. It's important to note that these functions do not directly attach the views to the DOM; they simply prepare the templates and store the HTML nodes in memory.

Final Step - DOM Insertion

Subsequently, in Regions.show(), the actual insertion of the views into the DOM occurs through region.attachHtml():

attachHtml: function(view) {
  // empty the node and append new view
  this.el.innerHTML='';
  this.el.appendChild(view.el);
},

If you opt not to use the region manager and manually call .render() on your view, remember to insert the view into the DOM similar to how region.attachHtml() does it.

All Children Linked to Parent Prior to DOM Inclusion

In a CompositeView, the rendered children are attached to the parent view either through CompositeView.attachBuffer() (for initial collection render or upon collection.reset()) or via CompositeView._insertBefore/_insertAfter (sortable/unsortable children).

All these methods ultimately involve

collectionView.$el.append(childView.el)
.

In cases where the parent view is not yet part of the DOM, all attachments are made to a collectionView.$el existing solely in memory.

Simplified Event Handling

The design of Marionette simplifies the rendering and injection of children views by removing the need to manually handle when each child should be rendered. Proper nesting setup from the start ensures a recursive process where all children are linked before being appended to the DOM. If you require guidance on setting up nested views effectively, share your relevant code for further assistance.

Answer №3

Revamp LayoutView.render()

After carefully considering the feedback and clarifying the issue, I have come up with a potential solution:

Instead of simply calling ItemView.render() within LayoutView.render(), I recommend overriding the former in your parent LayoutView. Here is how you can do it:

render: function() {
  this._ensureViewIsIntact();

  if (this._firstRender) {
    // Exclude resetting regions on first render
    this._firstRender = false;
  } else {
    // On subsequent renders, re-initialize `el` for each region
    this._reInitializeRegions();
  }

  var layout = Marionette.ItemView.prototype.render.apply(this, arguments);

  // Render and attach the child after parent rendering
  this.someRegion.show(new view(new model()));

  return layout;

},

I must admit that I haven't tested this approach before, but sometimes thinking outside the box is what leads to successful solutions.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transform your standard HTML buttons into a collective of radio buttons

Looking for a way to make some ordinary buttons function as radio buttons without adding another library to the project. Any suggestions on how this can be achieved using just HTML and JavaScript/jQuery? Appreciate any help, thank you. ...

Get binary information without relying on the use of arraybuffer

I have a specific resource that I am utilizing: function _arrayBufferToBase64(buffer) { var binary = ''; var bytes = new Uint8Array(buffer); var len = bytes.byteLength; for (var i = 0; i < len; i++) { binary += String. ...

Exploring the capabilities of CasperJS through double clicking

I'm currently working on creating a bot using CasperJS. The main goal is for the bot to send trade offers by offering an item, but I'm facing difficulties in figuring out how to click on the item. I attempted to use Resurrectio, however, it' ...

When working with the Google Sheets API, an error occurred: "this.http.put(...).map is not a valid

Having difficulty with a straightforward request to the Google Sheets API using the PUT method. I followed the syntax for http.put, but an error keeps popping up: this.http.put(...).map is not a function. Here's my code snippet: return this.http ...

Which JavaScript objects contain the addEventListener method within their prototype?

I am aware of the following: Element.prototype.addEventListener Window.prototype.addEventListener Document.prototype.addEventListener Are there any more? ...

Unusual sequence of JQuery Ajax calls

Within my website, there is a project div that is displayed using EJS. The data for the projects in EJS are rendered using a forEach loop, resulting in multiple similar divs appearing on the page. Each project div is assigned an id for identification pur ...

I'm looking to generate a semicircle progress bar using jQuery, any suggestions on how

Hi there! I'm looking to create a unique half circle design similar to the one showcased in this fiddle. Additionally, I want the progress bar to be displayed in a vibrant green color. I've recently started learning about Jquery and would apprec ...

What is the best way to update my JSX filter and add another condition to it?

I have a function that I use to filter and sort HTML items in a list. Sometimes, the filter results in zero items passing the check. In such cases, it would be ideal for a specific HTML element to appear: <span> no items pass the filter </span> ...

Is there a way to manipulate the checkbox using the filter?

I'm struggling to create a controllable checkbox that will be checked if the post id matches an id from another array. I want the checkbox to add the post id to a bookmark array when clicked, and I need it to be controllable. The Redux store provides ...

Information is being received, but unfortunately, it cannot be displayed

Currently, I am experimenting with using the axios http request to showcase some data. My focus is on exploring how to exhibit api data on the client side with react. If you are interested in seeing my progress so far, feel free to check out the link belo ...

typescript handling a supposedly optional property that is not truly optional

As I embark on my journey with TypeScript, please bear with me if this is not the conventional way of doing things. I have a few objectives in transitioning this JavaScript code to TypeScript. Item = {} Item.buy = function (id) {} Item.sell = function (i ...

What is the most efficient way to transfer a large volume of documents from mongoDB using http?

I'm dealing with a large mongoDB database that contains millions of documents and I need to fetch them all at once without crashing or encountering cursor errors. My goal is to send this data over http using express in nodeJS. The collection contains ...

Navigating to a particular div using a click event

I am trying to achieve a scrolling effect on my webpage by clicking a button that will target a specific div with the class "second". Currently, I have implemented this functionality using jQuery but I am curious about how to accomplish the same task using ...

Setting up Mongoose with Admin JS in NestJS: A Step-By-Step Guide

After successfully configuring adminJS in my Nest JS application, it now runs smoothly on localhost:5000/admin. @Module({ imports: [ import('@adminjs/nestjs').then(({ AdminModule }) => AdminModule.createAdminAsync({ ...

Struggling to make the JavaScript addition operator function properly

I have a button that I want to increase the data attribute by 5 every time it is clicked. However, I am struggling to achieve this and have tried multiple approaches without success. var i = 5; $(this).attr('data-count', ++i); Unfortunately, th ...

Tips for effectively adjusting lighting in a customized shader material

Presenting a demonstration showcasing the use of a height map in three.js coupled with custom shader(s). Everything appears to be functioning smoothly now. The working demo can be found on this link. Below are the shader scripts: <script id="vertexShad ...

How can I merge my two custom filters into a single, more efficient custom filter?

I have developed two custom filters that are quite similar. The only distinction between these filters is the array they utilize. Therefore, I am considering creating a single custom filter and passing an array as a parameter to it. The arrays I intend to ...

Navigating through cors in next.js

Currently, I have set up my front end using Netlify and my backend using Heroku with Next.js For the fetch request on the front end, here is an example: fetch(`https://backendname.herokuapp.com/data`, { method: 'POST', headers: { & ...

Using Vue.js to display svg content inside the <svg> element

One of my Vue.js components is responsible for dynamically building the content of an svg element. Let's simplify things and say that the content consists of a <circle cx="50" cy="50" r="60" /> This component achieves this by manipulating a dat ...

How can I restrict the selection of only one checkbox within an iframe window?

Here is my JavaScript snippet: var iframe = document.getElementById('pltc'); iframe.contentWindow.document.open('text/htmlreplace'); iframe.contentWindow.document.write('<input type="checkbox" name="tc0">Yes<input type="c ...