Learning Ext JS(Fourth Edition)
上QQ阅读APP看书,第一时间看更新

The component life cycle

Before we move into the layout systems and widgets, you should know a few concepts about how components work.

Every component in the Ext JS framework extends from the Ext.Component class. This class extends from the Ext.Component, or by its alternate class name Ext.AbstractComponent class, which provides shared methods for components across the framework.

Note

To understand more about component hierarchies, see http://docs.sencha.com/extjs/5.1/core_concepts/components.html.

When we create components such as panels, windows, grids, trees, and any other, there's a process called "the component lifecycle" that you should understand.

It is important for us to know the things that occur during each of the phases in the lifecycle process. This will help us to create custom components or extend the existing ones.

Basically, there are three phases in the component's lifecycle: the initialization process, the rendering process, and the destruction process.

The initialization phase initializes our new instance and it gets registered on the component manager; then, the rendering phase will create all the required nodes on the DOM, and then the destruction phase will be executed when the component is destroyed, removing listeners and nodes from the DOM:

The component life cycle

The Ext.AbstractComponent/Ext.Component class directs the lifecycle process, and every class that extends from the Component class will participate in the lifecycle automatically. All the visual components (widgets) extend from these classes and if we're planning to create our own custom components, we should extend from those classes too.

In order to have a better understanding of all three phases, let's create a panel component and see what's going on in each phase step by step:

var panel = Ext.create("Ext.panel.Panel",{
  title: "My First panel",
  width: 400,
  height: 250,
  renderTo: Ext.getBody()
});
Note

When talking about the width and height of components, the unit measure is handled in pixels.

The initialization phase

The main purpose of this phase is to create the instance of the component according to the configurations that we defined. It also registers our new component in the component manager and a few other things. The following screenshot shows all the steps in this phase:

The initialization phase

Let's see all the steps in this phase in detail:

  1. The first step is to apply the configuration properties to the generated instance of the class that we are creating. In the previous code, the title, width, height, and renderTo properties will be copied to the instance of the panel, as well as to any other property that we decide to define.
  2. The second step is to define common events, such as enable, disable, or show. These are common events for every component.
  3. The next step is to assign an ID to the instance. If we define an ID in the configuration object, then the instance will use that ID. In our example, we didn't specify an ID. In this case, an autogenerated ID is assigned.
    Tip

    Assigning IDs to our components is considered bad practice. We need to avoid doing that because they should be unique. If we work on a big project with other developers, there's a big chance that we may repeat IDs. Duplicating IDs will drive us to unexpected behaviors, because the component's ID is used in the DOM elements when rendering the component, causing one component to maybe disappear.

  4. In the fourth step, the creation process verifies whether we have defined plugins in our configuration and tries to create all the required instances for those plugins. A plugin is an additional functionality for our instances. In our previous example, we didn't define any plugin, so this step is skipped.
  5. In the fifth step, the initComponent function is executed. We should override this method in our subclasses if we want to execute code when the instance is being created.
    Tip

    There are many more methods that are defined by the Component class. These template methods are intended to be overridden in the subclasses to add specific functionality in different phases of the lifecycle.

  6. In this step, the new instance is added to the Ext.ComponentManager object. This means that every component that we create will be stored in the component manager, allowing us to get any reference by using the Ext.getCmp method and passing the ID as a parameter:
    //getting a component by its ID
    var panel = Ext.getCmp("panel-1234");
    console.log(panel);
    Note

    The getCmp method is great for debugging applications. We can get the ID of any component by looking at the DOM elements. Then, we can get the instance and inspect the state of our object, but it's not encouraged to use this method in our code. Instead, we may use the Ext.ComponentQuery.query method as an example as follows:

    Ext.ComponentQuery.query('panel')

    This example will retrieve an array (of xtype panel or Ext.panel.Panel) that exists/is already created.

  7. The Component class contains two mixins, one for the event management and the other for the state of our components. In this step, the two mixins are initialized by calling their constructor.
  8. If we have defined plugins, they should be already instantiated in the previous step, and now they have to be initialized by calling the init() method of each plugin and by passing our component instance as a parameter. You will learn how plugins work and how to create one from scratch later in this book.

If the renderTo property has been defined in the configurations, the rendering phase starts in this step, which means that all the required nodes that visually represent our component will be inserted into the DOM. If we don't define this property, nothing happens, and we are responsible for rendering our instance whenever we need to:

var panel = Ext.create("Ext.panel.Panel",{
  title: "My First panel",
  width: 400,
  height: 250
});
panel.render(Ext.getBody());

If we want to render our component later, we can call the render method of our instance and pass the place where we want to add our new component as a parameter. In the previous code, we are rendering our panel on the body of our document, but we can also set the ID of the node where we want to place our component, for example:

panel.render("some-p-id");
Note

Note: if the component is inside another component or container then there is no need to call the panel.render method as this will be rendered when the container is created / rendered.

The rendering phase

The rendering phase only occurs if the component is not rendered already. In this phase, all the required nodes will be inserted to the DOM, the styles and listeners will be applied, and we will be able to see and interact with our new component. The following diagram shows the steps that are executed during this phase:

The rendering phase

Now, let's understand the preceding diagram in a step-by-step manner:

  1. In the first step, the beforeRender event is fired. If some of the listeners return false, then the rendering phase stops.
  2. In the second step, the process checks whether the component that is being rendered is a floating component, such as a menu or a window, to assign the correct z-index property. z-index is a CSS property that specifies the stack order of an element. The greater number assigned will be always in front of the other elements.
  3. The third step is to initialize the container by creating the container property, which refers to the DOM element, where the new component will be rendered. The container property is an Ext.dom.Element instance.
  4. In the fourth step, the onRender method is executed. The el property is created, which contains the main node element of the component. We can define a template for our components; if we do that, then the template will be created and appended to the main node in this step. We can override the onRender method in our subclasses to append specific nodes to the DOM.
  5. The next step is to set the visibility mode. There are three modes for hiding the component's element (display, visibility, or offset).
  6. If the overCls property is set, then a listener for the mouse over and mouse out is set to add or remove the css class for each state. We can set some CSS rules to these classes to modify the look of our components.
  7. In the seventh step, the render event is fired. The component's instance is passed as a parameter to the listeners.
  8. The eighth step is to initialize the content. There are three ways to set the content of the component:

    1. We can define an html property with tags and nodes that will be added to the content of our new component.
    2. We can define the contentEl property that should be the ID of an existing DOM element. This element will be placed as the component content.
    3. We can define a tpl property with a template to be appended to the content. Also, we should define a data property with an object containing the replacements in our template. We will talk about templates in future chapters.
  9. The following code shows the three ways to add HTML content to a component. We should use only one way at a time.
    //Using the HTML property
    Ext.create("Ext.Component",{
      width: 300,
      height: 150,
      renderTo: Ext.getBody(),
     html: "<h1>Hello!</h1><p>This is an <strong>example</strong> of content</p>"
      });
    
    //Using an existing DOM element with an ID content
    Ext.create("Ext.Component",{
      width: 300,
      height: 150,
      renderTo: Ext.getBody(),
     contentEl: "content"
    });
    
    //Using a template with data
    Ext.create("Ext.Component",{
      width: 300,
      height: 150,
      renderTo: Ext.getBody(),
     data: {name:"Veronica", lastName:"Sanchez"},
     tpl: ["<h1>Content</h1><p>Hello {name} {lastName}!</p>"]
    });
  10. Returning to the render phase, the next step is to execute the afterRender method. If the component contains children, these are rendered in this step too. We're going to talk about containers later.
  11. In the tenth step, the afterRender event is fired. We can listen to this event in our subclasses to perform some actions when all the required nodes are rendered in the DOM.
  12. In the eleventh step, all the listeners that depend on the new nodes are initialized.
  13. The last step is to hide the main component node if the hidden property is set to true in our configurations parameter. And also, if the disabled property is set to true, then the component executes the disable method, which adds some CSS classes to the main node to make the components appearance disabled and mark the disabled flag as true.

The following code shows an example of how the rendering phase works. We are starting the whole process by calling the render method:

var mycmp = Ext.create("Ext.Component",{
  width: 300,
  height: 150,
 data: {
 name:"Veronica",
 lastName:"Sanchez"
},
 tpl:["<h1>Content</h1><p>Hello {name} {lastName}!</p>"]
});

//The rendering phase starts for this component
mycmp.render(Ext.getBody());

By knowing the steps that are executed inside of the render phase, we will be able to overwrite the methods such as onRender, render, or afterRender in our own classes. This is very useful when creating new components or widgets.

The destruction phase

The main idea of this phase is to clean the DOM, remove the listeners, and clear the used memory by deleting objects and arrays. It's very important to destroy all of our components when we don't want them anymore. The destroy phase will be executed when the user finishes the task with our component, for example, if we create a window and this window's property closeAction is set to destroy (this value is set by default), the destroy phase will be invoked when the user closes the window.

The following diagram shows the steps that are executed in this phase:

The destruction phase

  1. The destruction phase starts by firing the beforeDestroy event. If any listener returns false, then the destruction is stopped; otherwise, if the destruction continues and the component is floating, then this is unregistered from the floating manager.
  2. The second step executes the beforeDestroy method. Some subclasses use this method to remove their children or to clear memory.
  3. In the third step, if the component that is being destroyed is a child of another component, then the parent reference to this component is removed.
  4. In the fourth step, the onDestroy method is executed. We should extend this method in order to destroy our component properly, and also make sure that child components being added are destroyed and that the custom listeners we create are cleaned up.
  5. The fifth step tries to destroy all the plugins, if there are any, and also state that mixins are being destroyed.
  6. If the component is rendered, then in the sixth step, all the nodes from the DOM are purged (listeners) and are removed from the document.
  7. In the next step, the destroy event is fired. We can listen for this event and perform some actions if needed.
  8. The last step is to unregister the instance of the component from the component manager and clear all the events.

One important thing to keep in mind is that we should always remove and clear the memory that we're using in our components, as well as the nodes in the DOM that we have added before. We should override the appropriate methods in order to destroy our components correctly.

If we want to eliminate a component, we can execute the destroy method of the component. This method will trigger the destroy phase and all the previous steps will be executed:

//The destroy phase starts for this component
cmp.destroy();

The lifecycle in action

Now that we know the process of the creation of a component, we can create our own component, taking advantage of the lifecycle to customize our component. The following example shows the methods that we can override to add the functionality that we need in any of the available steps of the lifecycle:

Ext.define('Myapp.sample.CustomComponent',{
  extend: 'Ext.Component',
  initComponent: function(){
    var me = this;
    me.width = 200;
    me.height = 100;
    me.html = {
    tag: 'p',
    html: 'X',
    style: { // this can be replaced by a CSS rule
      'float': 'right',
      'padding': '10px',
      'background-color': '#e00',
      'color': '#fff',
      'font-weight': 'bold',
      'cursor': 'pointer'
    }
  };
    me.myOwnProperty = [1,2,3,4];
    me.callParent();
    console.log('Step 1. initComponent');
    },
  beforeRender: function(){
    console.log('Step 2. beforeRender');
    this.callParent(arguments);
    },
  onRender: function(){
    console.log('Step 3. onRender');
    this.callParent(arguments);
    this.el.setStyle('background-color','#ccc');
    },
  afterRender : function(){
    console.log('4. afterRender');
    this.el.down('p').on('click',this.myCallback,this);
    this.callParent(arguments);
    },
  beforeDestroy : function(){
    console.log('5. beforeDestroy');
    this.callParent(arguments);
    },
  onDestroy : function(){
    console.log('6. onDestroy');
    delete this.myOwnProperty;
    this.el.down('p').un('click',this.myCallback);
    this.callParent(arguments);
    },
  myCallback : function(){
    var me = this;
    Ext.Msg.confirm('Confirmation','Are you sure you want to close this panel?',function(btn){
       if(btn === 'yes'){
      me.destroy();
            }
        });
    }
});

The previous class overrides the template methods. This term is used for the methods that are automatically executed during the lifecycle. From the previous code, we can see how to add content using the html property, how to add listeners to the elements that we create, and more importantly, how to destroy and clear our events and custom objects.

In order to test our class, we need to create a HTML file called lifecycle_03.html file, include the Ext JS library, and our class, and then we need to create the instance of our class as follows:

Ext.onReady(function(){
    Ext.create('Myapp.sample.CustomComponent',{
        renderTo : Ext.getBody()
    });
});

As a result, we will see something like the following screenshot in our browser:

The lifecycle in action

As we can see, there are four messages in the JavaScript console. These messages were sent by each of the methods that we have overridden. We can also see the order of the execution based on the lifecycle. Now, if we want to destroy this component, we need to click the red button at the top-right. This action will call the destroy method that is responsible for clearing the nodes from the DOM, events, and objects from memory.

The lifecycle in action

Understanding the lifecycle of the components in Ext JS is essential in order to add custom events/listeners so we can provide proper functionality and custom code in our application.