1e41f4b71Sopenharmony_ci# AttributeModifier
2e41f4b71Sopenharmony_ci
3e41f4b71Sopenharmony_ci## Overview
4e41f4b71Sopenharmony_ciThe introduction of the @Styles and @Extend decorators in declarative syntax helps address some reuse issues, but there are limitations in certain scenarios:
5e41f4b71Sopenharmony_ci- Both @Styles and @Extend are processed at compile time and do not support cross-file exports for reuse.
6e41f4b71Sopenharmony_ci- @Styles only supports universal attributes and events, not component-specific attributes.
7e41f4b71Sopenharmony_ci- Although @Styles supports the use of polymorphic styles, it does not support parameter passing, which means it cannot expose certain properties externally.
8e41f4b71Sopenharmony_ci- @Extend supports private attributes and events of specific components, but it does not support cross-file exports for reuse either.
9e41f4b71Sopenharmony_ci- Neither @Styles nor @Extend supports service logic for dynamically determining whether to set certain attributes. They only allow setting all possible attributes using ternary expressions, which is inefficient when dealing with a large number of attributes.
10e41f4b71Sopenharmony_ci
11e41f4b71Sopenharmony_ci
12e41f4b71Sopenharmony_ciTo address the above issues, ArkUI introduces the **AttributeModifier** mechanism, which allows for dynamic modification of attributes through **Modifier** objects. The table below is a comparison of the capabilities between the **AttributeModifier** mechanism and the @Styles and @Extend decorators.
13e41f4b71Sopenharmony_ci|  Capability |  @Styles  |  @Extend  |  AttributeModifier  |
14e41f4b71Sopenharmony_ci| :-----: | :-----: | :-----: | :-----: |
15e41f4b71Sopenharmony_ci|  Cross-file export |  Not supported |  Not supported |  Supported |
16e41f4b71Sopenharmony_ci|  Universal attribute setting |  Supported |  Supported |  Supported |
17e41f4b71Sopenharmony_ci|  Universal event setting |  Supported |  Supported |  Partially supported |
18e41f4b71Sopenharmony_ci|  Component-specific attribute setting	 |  Not supported |  Supported |  Partially supported |
19e41f4b71Sopenharmony_ci|  Component-specific event setting |  Not supported |  Supported |  Partially supported |
20e41f4b71Sopenharmony_ci|  Parameter passing |  Not supported |  Supported |  Supported |
21e41f4b71Sopenharmony_ci|  Polymorphic styles |  Supported |  Not supported |  Supported |
22e41f4b71Sopenharmony_ci|  Service logic |  Not supported |  Not supported |  Supported |
23e41f4b71Sopenharmony_ci
24e41f4b71Sopenharmony_ciIt is evident that **AttributeModifier** offers enhanced capabilities and flexibility compared to @Styles and @Extend. Currently, efforts are underway to expand its functionality to cover all aspects of attribute and event settings. Looking ahead, **AttributeModifier** is expected to offer most, if not all, of the functionalities provided by @Styles and @Extend. In light of its superior adaptability and the ongoing development to support a comprehensive range of features, it is recommended that you use **AttributeModifier** for the aforementioned scenarios.
25e41f4b71Sopenharmony_ci
26e41f4b71Sopenharmony_ci## API
27e41f4b71Sopenharmony_ci
28e41f4b71Sopenharmony_ci```ts
29e41f4b71Sopenharmony_cideclare interface AttributeModifier<T> {
30e41f4b71Sopenharmony_ci
31e41f4b71Sopenharmony_ci  applyNormalAttribute?(instance: T): void;
32e41f4b71Sopenharmony_ci  
33e41f4b71Sopenharmony_ci  applyPressedAttribute?(instance: T): void;
34e41f4b71Sopenharmony_ci  
35e41f4b71Sopenharmony_ci  applyFocusedAttribute?(instance: T): void;
36e41f4b71Sopenharmony_ci  
37e41f4b71Sopenharmony_ci  applyDisabledAttribute?(instance: T): void;
38e41f4b71Sopenharmony_ci  
39e41f4b71Sopenharmony_ci  applySelectedAttribute?(instance: T): void;
40e41f4b71Sopenharmony_ci
41e41f4b71Sopenharmony_ci}
42e41f4b71Sopenharmony_ci```
43e41f4b71Sopenharmony_ci
44e41f4b71Sopenharmony_ci**AttributeModifier** is an API that requires you to implement methods in the form of ApplyXxxAttribute. *Xxx* signifies various states of polymorphism, including the default state, pressed state, focused state, disabled state, and selected state. **T** represents the attribute type of the component. Within the callback, you can access the attribute object and use it to set the attributes.
45e41f4b71Sopenharmony_ci
46e41f4b71Sopenharmony_ci```ts
47e41f4b71Sopenharmony_cideclare class CommonMethod<T> {
48e41f4b71Sopenharmony_ci  attributeModifier(modifier: AttributeModifier<T>): T;
49e41f4b71Sopenharmony_ci}
50e41f4b71Sopenharmony_ci```
51e41f4b71Sopenharmony_ci
52e41f4b71Sopenharmony_ci**attributeModifier** is a universal component method that allows you to pass in a custom modifier. Since the type **T** is explicitly defined when a component is instantiated, the type **T** passed to the method must be the corresponding attribute type for that component, or it must be **CommonAttribute**.
53e41f4b71Sopenharmony_ci
54e41f4b71Sopenharmony_ci## Behavior Specifications
55e41f4b71Sopenharmony_ci
56e41f4b71Sopenharmony_ci- The **attributeModifier** method accepts an instance that implements the **AttributeModifier\<T>** API. Here, **T** must be the specific attribute type corresponding to the component, or it must be **CommonAttribute**.
57e41f4b71Sopenharmony_ci- When a component is initialized for the first time or when its associated state variable changes, if the passed instance implements the corresponding API, the **applyNormalAttribute** callback will be invoked.
58e41f4b71Sopenharmony_ci- When the **applyNormalAttribute** callback is invoked, a component attribute object is passed in. Through this object, you can set the attributes and events of the current component. 
59e41f4b71Sopenharmony_ci- If an attempt is made to execute attributes or events that are not yet supported, an exception will be thrown during execution.
60e41f4b71Sopenharmony_ci- When an attribute change triggers the **Apply*Xxx*Attribute** API, any attributes that were previously set on the component but not included in the current change will revert to their default values.
61e41f4b71Sopenharmony_ci- The API can be used to leverage polymorphic styling capabilities. For example, if you need to set certain attributes when the component enters a pressed state, you can implement the **applyPressedAttribute** method to achieve this.
62e41f4b71Sopenharmony_ci- If the same attribute is set on a component using both attribute methods and **applyNormalAttribute**, the principle of property override is followed, which means that the last set attributes take effect.
63e41f4b71Sopenharmony_ci- A single **Modifier** instance object can be used across multiple components.
64e41f4b71Sopenharmony_ci- If **applyNormalAttribute** is used multiple times on a single component with different **Modifier** instances, each time the state variables are updated, the attribute settings of these instances will be executed in the order they were applied, which also follows the principle of property override.
65e41f4b71Sopenharmony_ci
66e41f4b71Sopenharmony_ci## Attribute Setting and Modification 
67e41f4b71Sopenharmony_ci
68e41f4b71Sopenharmony_ci**AttributeModifier** provides a powerful mechanism to separate the UI from styling. It enables the dynamic customization of component attributes with support for parameter passing and service logic writing, and triggers updates through state variables.
69e41f4b71Sopenharmony_ci
70e41f4b71Sopenharmony_ci  ```ts
71e41f4b71Sopenharmony_ci  // button_modifier.ets
72e41f4b71Sopenharmony_ci  export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
73e41f4b71Sopenharmony_ci    // A private member variable that can be dynamically modified externally
74e41f4b71Sopenharmony_ci    isDark: boolean = false
75e41f4b71Sopenharmony_ci
76e41f4b71Sopenharmony_ci    // The constructor allows for parameter passing when creating an instance.
77e41f4b71Sopenharmony_ci    constructor(dark?: boolean) {
78e41f4b71Sopenharmony_ci      this.isDark = dark ? dark : false
79e41f4b71Sopenharmony_ci    }
80e41f4b71Sopenharmony_ci
81e41f4b71Sopenharmony_ci    applyNormalAttribute(instance: ButtonAttribute): void {
82e41f4b71Sopenharmony_ci      // instance is the attribute object for the Button, which can be modified here.
83e41f4b71Sopenharmony_ci      if (this.isDark) {// Service logic can be written here.
84e41f4b71Sopenharmony_ci        // After attribute changes trigger the apply function, attributes that were set before but not included in the change will revert to their default values.
85e41f4b71Sopenharmony_ci        instance.backgroundColor(Color.Black)
86e41f4b71Sopenharmony_ci      } else {
87e41f4b71Sopenharmony_ci        // Chaining of attribute methods is supported.
88e41f4b71Sopenharmony_ci        instance.backgroundColor(Color.Red)
89e41f4b71Sopenharmony_ci          .borderColor(Color.Black)
90e41f4b71Sopenharmony_ci          .borderWidth(2)
91e41f4b71Sopenharmony_ci      }
92e41f4b71Sopenharmony_ci    }
93e41f4b71Sopenharmony_ci  }
94e41f4b71Sopenharmony_ci  ```
95e41f4b71Sopenharmony_ci  ```ts
96e41f4b71Sopenharmony_ci  // demo.ets
97e41f4b71Sopenharmony_ci  import { MyButtonModifier } from './button_modifier'
98e41f4b71Sopenharmony_ci
99e41f4b71Sopenharmony_ci  @Entry
100e41f4b71Sopenharmony_ci  @Component
101e41f4b71Sopenharmony_ci  struct attributeDemo {
102e41f4b71Sopenharmony_ci    // The modifier is decorated with @State, with behavior consistent with that of a regular object.
103e41f4b71Sopenharmony_ci    @State modifier: MyButtonModifier = new MyButtonModifier(true);
104e41f4b71Sopenharmony_ci
105e41f4b71Sopenharmony_ci    build() {
106e41f4b71Sopenharmony_ci      Row() {
107e41f4b71Sopenharmony_ci        Column() {
108e41f4b71Sopenharmony_ci          Button("Button")
109e41f4b71Sopenharmony_ci            .attributeModifier(this.modifier)
110e41f4b71Sopenharmony_ci            .onClick(() => {
111e41f4b71Sopenharmony_ci              // When the level-1 attribute of the modifier is changed, a UI update is triggered, causing applyNormalAttribute to be executed again.
112e41f4b71Sopenharmony_ci              this.modifier.isDark = !this.modifier.isDark
113e41f4b71Sopenharmony_ci            })
114e41f4b71Sopenharmony_ci        }
115e41f4b71Sopenharmony_ci        .width('100%')
116e41f4b71Sopenharmony_ci      }
117e41f4b71Sopenharmony_ci      .height('100%')
118e41f4b71Sopenharmony_ci    }
119e41f4b71Sopenharmony_ci  }
120e41f4b71Sopenharmony_ci  ```
121e41f4b71Sopenharmony_ci  ![AttributeModifier](figures/AttributeModifier01.gif)
122e41f4b71Sopenharmony_ci
123e41f4b71Sopenharmony_ciIf the same attribute is set on a component using both attribute methods and **applyNormalAttribute**, the principle of property override is followed, which means that the last set attributes take effect.
124e41f4b71Sopenharmony_ci
125e41f4b71Sopenharmony_ci  ```ts
126e41f4b71Sopenharmony_ci  // button_modifier.ets
127e41f4b71Sopenharmony_ci  export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
128e41f4b71Sopenharmony_ci    isDark: boolean = false
129e41f4b71Sopenharmony_ci
130e41f4b71Sopenharmony_ci    constructor(dark?: boolean) {
131e41f4b71Sopenharmony_ci      this.isDark = dark ? dark : false
132e41f4b71Sopenharmony_ci    }
133e41f4b71Sopenharmony_ci
134e41f4b71Sopenharmony_ci    applyNormalAttribute(instance: ButtonAttribute): void {
135e41f4b71Sopenharmony_ci      if (this.isDark) {
136e41f4b71Sopenharmony_ci        instance.backgroundColor(Color.Black)
137e41f4b71Sopenharmony_ci      } else {
138e41f4b71Sopenharmony_ci        instance.backgroundColor(Color.Red)
139e41f4b71Sopenharmony_ci          .borderColor(Color.Black)
140e41f4b71Sopenharmony_ci          .borderWidth(2)
141e41f4b71Sopenharmony_ci      }
142e41f4b71Sopenharmony_ci    }
143e41f4b71Sopenharmony_ci  }
144e41f4b71Sopenharmony_ci  ```
145e41f4b71Sopenharmony_ci  ```ts
146e41f4b71Sopenharmony_ci  // demo.ets
147e41f4b71Sopenharmony_ci  import { MyButtonModifier } from './button_modifier';
148e41f4b71Sopenharmony_ci
149e41f4b71Sopenharmony_ci  @Entry
150e41f4b71Sopenharmony_ci  @Component
151e41f4b71Sopenharmony_ci  struct attributeDemo {
152e41f4b71Sopenharmony_ci    @State modifier: MyButtonModifier = new MyButtonModifier(true);
153e41f4b71Sopenharmony_ci
154e41f4b71Sopenharmony_ci    build() {
155e41f4b71Sopenharmony_ci      Row() {
156e41f4b71Sopenharmony_ci        Column() {
157e41f4b71Sopenharmony_ci          // As the attribute is set before the modifier, the button's color changes in accordance with the value of the modifier.
158e41f4b71Sopenharmony_ci          Button("Button")
159e41f4b71Sopenharmony_ci            .backgroundColor(Color.Blue)
160e41f4b71Sopenharmony_ci            .attributeModifier(this.modifier)
161e41f4b71Sopenharmony_ci            .onClick(() => {
162e41f4b71Sopenharmony_ci              this.modifier.isDark = !this.modifier.isDark
163e41f4b71Sopenharmony_ci            })
164e41f4b71Sopenharmony_ci        }
165e41f4b71Sopenharmony_ci        .width('100%')
166e41f4b71Sopenharmony_ci      }
167e41f4b71Sopenharmony_ci      .height('100%')
168e41f4b71Sopenharmony_ci    }
169e41f4b71Sopenharmony_ci  }
170e41f4b71Sopenharmony_ci  ```
171e41f4b71Sopenharmony_ci  ![AttributeModifier](figures/AttributeModifier03.gif) 
172e41f4b71Sopenharmony_ci
173e41f4b71Sopenharmony_ciIf **applyNormalAttribute** is used multiple times on a single component with different **Modifier** instances, each time the state variables are updated, the attribute settings of these instances will be executed in the order they were applied, which also follows the principle of property override.
174e41f4b71Sopenharmony_ci
175e41f4b71Sopenharmony_ci  ```ts
176e41f4b71Sopenharmony_ci  // button_modifier.ets
177e41f4b71Sopenharmony_ci  export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
178e41f4b71Sopenharmony_ci    isDark: boolean = false
179e41f4b71Sopenharmony_ci
180e41f4b71Sopenharmony_ci    constructor(dark?: boolean) {
181e41f4b71Sopenharmony_ci      this.isDark = dark ? dark : false
182e41f4b71Sopenharmony_ci    }
183e41f4b71Sopenharmony_ci
184e41f4b71Sopenharmony_ci    applyNormalAttribute(instance: ButtonAttribute): void {
185e41f4b71Sopenharmony_ci      if (this.isDark) {
186e41f4b71Sopenharmony_ci        instance.backgroundColor(Color.Black)
187e41f4b71Sopenharmony_ci          .width(200)
188e41f4b71Sopenharmony_ci      } else {
189e41f4b71Sopenharmony_ci        instance.backgroundColor(Color.Red)
190e41f4b71Sopenharmony_ci          .width(100)
191e41f4b71Sopenharmony_ci      }
192e41f4b71Sopenharmony_ci    }
193e41f4b71Sopenharmony_ci  }
194e41f4b71Sopenharmony_ci  ```
195e41f4b71Sopenharmony_ci  ```ts
196e41f4b71Sopenharmony_ci  // button_modifier2.ets
197e41f4b71Sopenharmony_ci  export class MyButtonModifier2 implements AttributeModifier<ButtonAttribute> {
198e41f4b71Sopenharmony_ci    isDark2: boolean = false
199e41f4b71Sopenharmony_ci
200e41f4b71Sopenharmony_ci    constructor(dark?: boolean) {
201e41f4b71Sopenharmony_ci      this.isDark2 = dark ? dark : false
202e41f4b71Sopenharmony_ci    }
203e41f4b71Sopenharmony_ci
204e41f4b71Sopenharmony_ci    applyNormalAttribute(instance: ButtonAttribute): void {
205e41f4b71Sopenharmony_ci      if (this.isDark2) {
206e41f4b71Sopenharmony_ci        instance.backgroundColor('#2787D9')
207e41f4b71Sopenharmony_ci      } else {
208e41f4b71Sopenharmony_ci        instance.backgroundColor('#707070')
209e41f4b71Sopenharmony_ci      }
210e41f4b71Sopenharmony_ci    }
211e41f4b71Sopenharmony_ci  }
212e41f4b71Sopenharmony_ci  ```
213e41f4b71Sopenharmony_ci  ```ts
214e41f4b71Sopenharmony_ci  // demo.ets
215e41f4b71Sopenharmony_ci  import { MyButtonModifier } from './button_modifier';
216e41f4b71Sopenharmony_ci  import { MyButtonModifier2 } from './button_modifier2';
217e41f4b71Sopenharmony_ci
218e41f4b71Sopenharmony_ci  @Entry
219e41f4b71Sopenharmony_ci  @Component
220e41f4b71Sopenharmony_ci  struct attributeDemo {
221e41f4b71Sopenharmony_ci    @State modifier: MyButtonModifier = new MyButtonModifier(true);
222e41f4b71Sopenharmony_ci    @State modifier2: MyButtonModifier2 = new MyButtonModifier2(true);
223e41f4b71Sopenharmony_ci
224e41f4b71Sopenharmony_ci    build() {
225e41f4b71Sopenharmony_ci      Row() {
226e41f4b71Sopenharmony_ci        Column() {
227e41f4b71Sopenharmony_ci          Button("Button")
228e41f4b71Sopenharmony_ci            .attributeModifier(this.modifier)
229e41f4b71Sopenharmony_ci            .attributeModifier(this.modifier2)
230e41f4b71Sopenharmony_ci            .onClick(() => {
231e41f4b71Sopenharmony_ci              this.modifier.isDark = !this.modifier.isDark
232e41f4b71Sopenharmony_ci              this.modifier2.isDark2 = !this.modifier2.isDark2
233e41f4b71Sopenharmony_ci            })
234e41f4b71Sopenharmony_ci        }
235e41f4b71Sopenharmony_ci        .width('100%')
236e41f4b71Sopenharmony_ci      }
237e41f4b71Sopenharmony_ci      .height('100%')
238e41f4b71Sopenharmony_ci    }
239e41f4b71Sopenharmony_ci  }
240e41f4b71Sopenharmony_ci  ```
241e41f4b71Sopenharmony_ci  ![AttributeModifier](figures/AttributeModifier04.gif) 
242e41f4b71Sopenharmony_ci
243e41f4b71Sopenharmony_ci## Polymorphic Style and Event Setting
244e41f4b71Sopenharmony_ci
245e41f4b71Sopenharmony_ciYou can use **AttributeModifier** to set polymorphic styles and events, which enables the reuse of event logic and supports various states such as default, pressed, focused, disabled, and selected. For example, if you need to set certain attributes when the component enters a pressed state, you can implement the **applyPressedAttribute** method to achieve this.
246e41f4b71Sopenharmony_ci
247e41f4b71Sopenharmony_ci  ```ts
248e41f4b71Sopenharmony_ci  // button_modifier.ets
249e41f4b71Sopenharmony_ci  export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
250e41f4b71Sopenharmony_ci    applyNormalAttribute(instance: ButtonAttribute): void {
251e41f4b71Sopenharmony_ci      // instance is the attribute object for the Button, used to set attributes for the normal state.
252e41f4b71Sopenharmony_ci      instance.backgroundColor(Color.Red)
253e41f4b71Sopenharmony_ci        .borderColor(Color.Black)
254e41f4b71Sopenharmony_ci        .borderWidth(2)
255e41f4b71Sopenharmony_ci    }
256e41f4b71Sopenharmony_ci
257e41f4b71Sopenharmony_ci    applyPressedAttribute(instance: ButtonAttribute): void {
258e41f4b71Sopenharmony_ci      // instance is the attribute object for the Button, used to set attributes for the pressed state.
259e41f4b71Sopenharmony_ci      instance.backgroundColor(Color.Green)
260e41f4b71Sopenharmony_ci        .borderColor(Color.Orange)
261e41f4b71Sopenharmony_ci        .borderWidth(5)
262e41f4b71Sopenharmony_ci    }
263e41f4b71Sopenharmony_ci  }
264e41f4b71Sopenharmony_ci  ```
265e41f4b71Sopenharmony_ci  ```ts
266e41f4b71Sopenharmony_ci  // demo.ets
267e41f4b71Sopenharmony_ci  import { MyButtonModifier } from './button_modifier'
268e41f4b71Sopenharmony_ci
269e41f4b71Sopenharmony_ci  @Entry
270e41f4b71Sopenharmony_ci  @Component
271e41f4b71Sopenharmony_ci  struct attributeDemo {
272e41f4b71Sopenharmony_ci    @State modifier: MyButtonModifier = new MyButtonModifier();
273e41f4b71Sopenharmony_ci
274e41f4b71Sopenharmony_ci    build() {
275e41f4b71Sopenharmony_ci      Row() {
276e41f4b71Sopenharmony_ci        Column() {
277e41f4b71Sopenharmony_ci          Button("Button")
278e41f4b71Sopenharmony_ci            .attributeModifier(this.modifier)
279e41f4b71Sopenharmony_ci        }
280e41f4b71Sopenharmony_ci        .width('100%')
281e41f4b71Sopenharmony_ci      }
282e41f4b71Sopenharmony_ci      .height('100%')
283e41f4b71Sopenharmony_ci    }
284e41f4b71Sopenharmony_ci  }
285e41f4b71Sopenharmony_ci
286e41f4b71Sopenharmony_ci  ```
287e41f4b71Sopenharmony_ci  ![AttributeModifier](figures/AttributeModifier02.gif) 
288