As of altseven 3.2.x, I have made some changes to the way view components listen for changes and register for rendering with the render queue. The first changes come in the config() function inside the View component. The bubbling of mustRender events from child to parent has been removed. The render queue now tracks what components are being rendered and ensures that parent views are rendered before their children.

As a result, the setState() function in the View component now fires the mustRender event for all views, and the mustRender handler add the view to the queue.

setState: function( args ){
    this.state = args;
    // setting state requires a re-render
    this.fireEvent( 'mustRender' );
},

and the mustRender event handler, which now queues views for rendering:

this.on( "mustRender", function(){
    a7.ui.enqueueForRender( this.props.id );
}.bind( this ));

 

Essentially, the complexity of managing the rendering queue has largely been moved from the View component to the a7.ui component. Let's look at the enqueueForRender method. As with other methods in components in altseven, enqueueForRender references an internal component method, _enqueueForRender:

_enqueueForRender = function( id ){
  if( ! _stateTransition ){
    a7.log.info( 'enqueue: ' + id );
    if( ! _queue.length ){
      a7.log.trace( 'add first view to queue: ' + id );
      _queue.push( id );
      // wait for other possible updates and then process the queue
      setTimeout( _processRenderQueue, 18 );
    }else{
      let childIds = _getChildViewIds( id );
      if( _views[ id ].props.parentID === undefined ){
        // if the view is a root view, it should be pushed to the front of the stack
        a7.log.trace( 'add to front of queue: ' + id );
        _queue.unshift( id );
      }else{
        let parentIds = _getParentViewIds( id );

        let highParent = undefined;
        if( parentIds.length ){
          highParent = parentIds.find( function( parentId ){
            return _queue.indexOf( parentId ) >= 0;
          });
        }

        // only add if there is no parent in the queue, since parents will render children
        if( highParent === undefined ){

          a7.log.trace( 'add to end of queue: ' + id );
          _queue.push( id );
        }
      }

      // remove child views from the queue, they will be rendered by the parents
      childIds.forEach( function( childId ){
        if( _queue.indexOf( childId ) >= 0 ){
          a7.log.trace( 'remove child from queue: ' + childId );
          _queue.splice( _queue.indexOf( childId ), 1 );
        }
      });
    }
  }else{
    _deferred.push( id );
  }
},

First, let's see what variables we're using, and what they are doing. We have _stateTransition, which is a boolean value that tracks whether the current queue is rendering. If the queue is not rendering, the process will look to add the requested view to the queue, which is held in an array of string IDs in a variable called _queue. If the current queue is rendering, the view ID is instead added to another Array called _deferred. Once the current queue is rendered, _queue will be emptied and the IDs in _deferred will be added to _queue.

Next we have childIds, which holds an array of IDs of all child views of the current view being processed. The function uses this array to remove any child views of the current view from the rendering queue. Since parents render their own children by default using the rendered event handler, we don't want the children in the render queue or they will be rendered more than once.

We also have parentIds, which as you might expect holds an array of view IDs for the parents of the current view being processed, and highParent, which is the highest parent view in the chain of parentIds. We use these values in conjunction to verify that we do not add views to the queue if the view has a parent already in the queue. As with childIds, we only want to add the highest level view in a given subtree to the queue; we'll leave child view rendering to the rendered event handler.

_getChildViewIds and _getParentViewIds are functions that the enqueueForRender function calls to return these child and parent view ID arrays. Like much of the newest functionality in altseven, these functions do their jobs, but they are not optimized or expected to be particularly efficient. I don't expect deep nesting of view components in altseven apps, so I don't see optimization as a particular priority in this case.

As you can see at the beginning of the function, if the _queue.length property is 0, the function adds the view ID as the first element of _queue and then schedules the queued to be processed using setTimeout(). It leaves just enough time ( 18 ms ) for related views to register themselves for rendering as needed.

Processing the Rendering Queue

Let's look at _processRenderQueue.

    _processRenderQueue = function(){
      a7.log.trace( 'processing the queue' );
      _stateTransition = true;

      _queue.forEach( function( id ){
          _views[ id ].render();
      });
      _queue = [];
      _stateTransition = false;
      _deferred.forEach( function( id ){
        _enqueueForRender( id );
      });
      _deferred = [];
    },

Once the setTimeout function fires _processRenderQueue sets _stateTransition to true, which signals to _enqueueForRender that it should queue requested updates in the _deferred queue. It then iterates through _queue and calls the render() function for the given view ID in _views. What is significant here is that the array of views in _views is the original array that was registered by a7.ui.register(). Altseven works by registering all the views in the application, which places the views in this _views array for the duration of the application. Views are always referenced by this variable, which can be called outside the a7.ui component using the property a7.ui.views. Unlike early iterations of the framework, altseven 3.2.x does not pass views around or attempt to copy them. Once they are in a7.ui.views, that's where they stay until they are destroyed.