# Create a Component

This section introduces how to create a custom component through the capabilities provided by UIExtension, and uses the implementation of a Counter functionality as an example to explain the usage of UIExtension components.

# Basic Structure of a Component

A basic component typically consists of the following parts:

  1. Layout template: This defines the structure and layout of the component's view. The template syntax can be referred to the layout template.
  2. Styles: The component's styles can be added using the style attribute or the class attribute, similar to HTML. Styles added using the class attribute require a separate css file.
  3. Scripts: This contains the logic of the component. UIExtension components need to implement a new component class through integration to handle the behavior and interaction of the component.
  4. Module: If a component needs to be referenced by other components, it must be assigned a name and registered in a module. For more information about modularization, please refer to Modular.

Here is an example of a basic component structure:

/* my-component.css */
.my-counter {
    display: flex;
}
// my-component.js

const { SeniorComponentFactory } = UIExtension;
class MyComponent extends SeniorComponentFactory.createSuperClass({
    template: `
        <div class="my-counter" @var.my_counter="$component">
            <button class="my-btn" @on.click="my_counter.increment()">+</button>
            <div class="my-viewer">@{my_counter.count}</div>
            <button class="my-btn" @on.click="my_counter.decrement()">-</button>
        </div>
    `
}) {
    static getName() {}
    init() {
        super.init();
        this.count = 0;
    }
    increment() {
        this.count ++;
        this.digest(); // After data change, update must be triggered manually
    }
    decrement() {
        this.count --;
        this.digest(); // After data change, update must be triggered manually
    }
}
modular.module('custom', []).registerComponent(MyComponent);

# Create a Simple Component

Based on the above description of the basic structure of a component, let's now create a component that displays a clock. Click the run button below to start the example:

Running the example above, you will see a clock component that updates in real-time. This is just a simple component, but it demonstrates how to create and use a component, initialize and run a timer in the init and mounted lifecycle of the component, and how to reference component object properties in the component template. You can further extend and customize this example based on your needs.

# Event Triggering and Binding in Components

Components can not only display data, but also interact with users. Parent components can also interact with child components by listening to their events. With UIExtension, we can implement event triggering and listening to achieve interactive functionality.

# Event Triggering

To trigger an event in a component, we can use the trigger method. This method accepts an event name (required) and multiple data to be transmitted (optional). Here is an example:

class DidaComponent extends SeniorComponentFactory.createSuperClass({
    template: `
        <div></div>
    `
}) {
    static getName() {
        return 'dida'
    }
    mounted() {
        super.mounted();
        const execute = () => {
            if(this.isDestroyed) {
                return;
            }
            this.trigger('dida', performance.now());
            requestIdleCallback(execute);
        };
        requestIdleCallback(execute);
    }
}
modular.module('custom', []).registerComponent(DidaComponent);

In this example, the <dida></dida> component triggers a dida event and passes a timestamp whenever it is idle.

# Event Listening

There are two ways to listen to the events in a component. One is to use the @on.event-name directive, and the other is to use the Component#on interface. The following example will continue to use the dida component mentioned above to demonstrate these two usages:

class DidaBoxComponent extends SeniorComponentFactory.createSuperClass({
    template: `<div @var.box="$component">
        <custom:dida name="dida" @on.dida="box.onDidaDirectiveEvent($args[0])"></custom:dida>
    </div>
    `
}) {
    static getName() {
        return 'dida-box';
    }
    onDidaDirectiveEvent(time) {
        console.log('Event listened through directive was triggered', time)
    }
    mounted() {
        super.mounted();
        this.getComponentByName('dida').on('dida', time => {
            console.log('Event listened through "on" interface was triggered', time)
        })
    }
}

# Native DOM Event Listening

The @on directive can not only listen to custom events triggered by components, but also listen to native DOM events. For specific usage, please refer to the @on section.

# Component Lifecycle

The lifecycle of a UIExtension component is relatively simple, with three commonly used methods: init, mounted, and destroy. As shown in the examples above, init and mounted lifecycles can be implemented by overriding the methods of the parent class. The init method is called when the component is constructed, and is usually used to initialize some properties. The mounted method is called after the component is inserted into the DOM tree, and can be used to perform some DOM operations on itself or its child components. The destroy method requires adding tasks to be destroyed using addDestroyHook. These tasks usually involve removing side effects, such as unregistering events or clearing timers. You can refer to the ClockComponent mentioned in the section Create a Simple Component.

# Component Communication

We know that child components can communicate with parent components by triggering events, while parent components can communicate with child components by calling their methods. But how can components communicate with each other if they are not parent-child? The UIExtension framework also supports simple injection functionality, which allows singleton objects to be injected and enables communication between any components. The implementation of injection is very simple. Here is an example of a counter functionality:

  1. First, create a CounterService class. The role of CounterService is to keep track of a count property that can be shared by any component:

    class CounterService {
        constructor() {
            this.count = 0;
        }
    }
    
  2. Next, create two components: one for modifying the count and one for displaying the count. Both of these components inject the CounterService:

    class ModifyButtonComponent extends SeniorComponentFactory.createSuperClass({
        template: `<button @on.click="$component.onClick()"></button>`
    }) {
        static getName() {
            return 'modify';
        }
        static inject() {
            return {
                service: CounterService
            };
        }
        createDOMElement() {
            return document.createElement('button');
        }
        init() {
            this.step = 0;
        }
        onClick() {
            this.service.count += this.step;
            this.digest();
        }
        setStep(step) {
            this.step = step;
        }
    }
    class ShowCountComponent extends SeniorComponentFactory.createSuperClass({
        template: `<span style="border: 1px solid #ddd;padding: .5em 1em; display: inline-block;">@{$component.service.count}</span>`
    }) {
        static getName() {
            return 'show-count';
        }
        static inject() {
            return {
                service: CounterService
            };
        }
    }
    

Let's take a look at the final result:

In the above example, the CounterService class is injected into both the ModifyButtonComponent and ShowCountComponent components. This allows the two components to access and modify the count property of the CounterService instance. The ModifyButtonComponent component increments the count when clicked, while the ShowCountComponent component displays the current count value.

By injecting the CounterService into the two components, they share the same service instance, enabling communication between them. Any changes made to the count property in one component will also be reflected in the other component.

Dependency injection is a powerful feature that enables components to communicate and share data without being tightly coupled. It promotes modularity and reusability in applications.