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