1# Migrating Web Components Between Different Windows
2
3The **Web** component can be attached to and detached from the component trees in different windows, which enables the same **Web** component to be migrated between different windows. For example, you can drag a tab page to an independent window or drag it to another window.
4
5Web components are migrated between different windows based on the [Custom Node](../ui/arkts-user-defined-node.md) capability. You can use [BuilderNode](../ui/arkts-user-defined-arktsNode-builderNode.md) to create offline nodes for **Web** components and use [Custom Placeholder Node](../ui/arkts-user-defined-place-hoder.md) to attach and detach web nodes. The **Web** component is migrated between windows by detaching the web node from one window and attaching it to another window.
6
7In the following example, a **Web** component is created using a command when the main window Ability is started. You can use the functions and classes provided in **common.ets** to attach and detach the **Web** component. In addition, **Index.ets** provides an implementation method for attaching and detaching **Web** components. In this way, you can attach and detach **Web** components in different windows, in other words, migrate **Web** components between different windows. The following figure shows the migration process.
8
9![Example of Migrating Web Components](./figures/web-component-migrate_en.png)
10
11> **NOTE**
12>
13> Do not attach a **Web** component under two parent nodes at the same time. Otherwise, unexpected behavior occurs.
14
15```ts
16// Main window ability.
17// EntryAbility.ets
18import { createNWeb, defaultUrl } from '../pages/common'
19
20// ...
21
22  onWindowStageCreate(windowStage: window.WindowStage): void {
23    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
24
25    windowStage.loadContent('pages/Index', (err) => {
26      if (err.code) {
27        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
28        return;
29      }
30      // Create a dynamic Web component, in which UIContext should be passed. (The component can be created at any time after loadContent() is called, and only one Web component is created for the application.)
31      createNWeb(defaultUrl, windowStage.getMainWindowSync().getUIContext());
32      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
33    });
34  }
35
36// ...
37```
38
39```ts
40// Provide the capability for attaching Web components dynamically.
41// pages/common.ets
42import { UIContext, NodeController, BuilderNode, FrameNode } from '@kit.ArkUI';
43import { webview } from '@kit.ArkWeb';
44import { hilog } from '@kit.PerformanceAnalysisKit';
45
46export const defaultUrl : string = 'https://www.example.com';
47
48// Data is an input parameter of encapsulation class.
49class Data{
50  url: string = '';
51  webController: webview.WebviewController | null = null;
52
53  constructor(url: string, webController: webview.WebviewController) {
54    this.url = url;
55    this.webController = webController;
56  }
57}
58
59// @Builder contains the specific information of the dynamic component.
60@Builder
61function WebBuilder(data:Data) {
62  Web({ src: data.url, controller: data.webController })
63    .width("100%")
64    .height("100%")
65    .borderStyle(BorderStyle.Dashed)
66    .borderWidth(2)
67}
68
69let wrap = wrapBuilder<[Data]>(WebBuilder);
70
71// Used to control and report the behavior of the node in NodeContainer. This function must be used together with NodeContainer.
72export class MyNodeController extends NodeController {
73  private builderNode: BuilderNode<[Data]> | null | undefined = null;
74  private webController : webview.WebviewController | null | undefined = null;
75  private rootNode : FrameNode | null = null;
76
77  constructor(builderNode : BuilderNode<[Data]> | undefined, webController : webview.WebviewController | undefined) {
78    super();
79    this.builderNode = builderNode;
80    this.webController = webController;
81  }
82
83  // This function must be overridden, which is used to construct the number of nodes, return the nodes and attach them to NodeContainer.
84  // Call it or rebuild() to refresh when NodeContainer is created.
85  makeNode(uiContext: UIContext): FrameNode | null {
86    // This node will be attached to the parent node of NodeContainer.
87    return this.rootNode;
88  }
89
90  // Attach the Webview.
91  attachWeb() : void {
92    if (this.builderNode) {
93      let frameNode : FrameNode | null = this.builderNode.getFrameNode();
94      if (frameNode?.getParent() != null) {
95        // Check whether the custom node is attached before.
96        hilog.error(0x0000, 'testTag', '%{public}s', 'The frameNode is already attached');
97        return;
98      }
99      this.rootNode = this.builderNode.getFrameNode();
100    }
101  }
102
103  // Detach the Webview.
104  detachWeb() : void {
105    this.rootNode = null;
106  }
107
108  getWebController() : webview.WebviewController | null | undefined {
109    return this.webController;
110  }
111}
112
113// Create a BuilderNode required for saving Map.
114let builderNodeMap : Map<string, BuilderNode<[Data]> | undefined> = new Map();
115// Create a webview.WebviewController required for saving Map.
116let webControllerMap : Map<string, webview.WebviewController | undefined> = new Map();
117
118// Use getUIContext() of the window or custom component to obtain the UIContext object required for initialization.
119export const createNWeb = (url: string, uiContext: UIContext) => {
120  // Create a WebviewController.
121  let webController = new webview.WebviewController() ;
122  // Create a BuilderNode.
123  let builderNode : BuilderNode<[Data]> = new BuilderNode(uiContext);
124  // Create a dynamic Web component.
125  builderNode.build(wrap, new Data(url, webController));
126
127  // Save the BuilderNode.
128  builderNodeMap.set(url, builderNode);
129  // Save the WebviewController.
130  webControllerMap.set(url, webController);
131}
132
133// Customize the API for obtaining BuilderNode.
134export const getBuilderNode = (url : string) : BuilderNode<[Data]> | undefined => {
135  return builderNodeMap.get(url);
136}
137// Customize the API for obtaining WebviewController.
138export const getWebviewController = (url : string) : webview.WebviewController | undefined => {
139  return webControllerMap.get(url);
140}
141
142```
143
144```ts
145// Use the pages of NodeController.
146// pages/Index.ets
147import { getBuilderNode, MyNodeController, defaultUrl, getWebviewController } from "./common"
148
149@Entry
150@Component
151struct Index {
152  private nodeController : MyNodeController =
153    new MyNodeController(getBuilderNode(defaultUrl), getWebviewController(defaultUrl));
154
155  build() {
156    Row() {
157      Column() {
158        Button("Attach Webview")
159          .onClick(() => {
160            // Do not attach the same node to different pages at the same time.
161            this.nodeController.attachWeb();
162            this.nodeController.rebuild();
163          })
164        Button("Detach Webview")
165          .onClick(() => {
166            this.nodeController.detachWeb();
167            this.nodeController.rebuild();
168          })
169        // NodeContainer is used to bind to the NodeController node. Calling rebuild() triggers makeNode().
170        // The Page is bound to NodeController() through NodeContainer(). As a result, the dynamic component page is successfully displayed.
171        NodeContainer(this.nodeController)
172          .height("80%")
173          .width("80%")
174      }
175      .width('100%')
176    }
177    .height('100%')
178  }
179}
180
181```
182