1 写在前面

最近使用 typescript 与 angular 编写 chrome 扩展, 对繁复的 contextMenus 创建步骤进行了提炼,并总结一个类

 

2 重构思路

2.1 一般方法

在编写 chrome 扩展中的 contextMenu 中,一般的思路是定义一个 JSON,并且遍历这个JSON数据并且以此创建 menu, 如:

 1 const menu = {
 2   "menus": [
 3     {"id": "main", "visible": true, "title": "main"},
 4     {"id": "sub1", "visible": true, "title": "sub1", "parentId": "main"},
 5     {"id": "sub11", "visible": true, "title": "sub11", "parentId": "sub1"},
 6     {"id": "sub12", "visible": true, "title": "sub12", "parentId": "sub1"},
 7     {"id": "sub2", "visible": true, "title": "sub2", "parentId": "main"}
 8   ]
 9 };
10 
11 const createMenu = () => {
12   menu.menus.forEach(value => {
13     chrome.contextMenus.create(value);
14   })
15 };
16 createMenu();

2.2 重构

在 menu 的个数很少的情况下,上述的传统方式可能不会构成问题,但在选项多的场景下(如编写工具类扩展),contextMenu 的可读性就变得极差

2.2.1 编写 Menu 类

因此我们将用 typescript 来构建一个层次结构清晰的类以组合编排 父-子 menu 的层次结构, 类的结构如下:

1 interface Menu {
2   createProperties: CreateProperties;
3   children?: Menu[];
4 }

这个类的结构很简单,但是已经足以代表需要创建 ContextMenu 的数据

现在上边使用过的 json 数据现在可以重写为:

 1 const defaultMenu: Menu = {
 2   createProperties: {
 3     id: 'main',
 4     visible: true,
 5     title: 'main'
 6   },
 7   children: [
 8     {
 9       createProperties: {
10         id: 'sub1',
11         visible: true,
12         title: 'sub1',
13 
14       },
15       children: [
16         {
17           createProperties: {
18             id: 'sub11',
19             visible: true,
20             title: 'sub11',
21             parentId: 'sub1'
22           }
23         },
24         {
25           createProperties: {
26             id: 'sub12',
27             visible: true,
28             title: 'sub12',
29             parentId: 'sub1'
30           }
31         }
32       ]
33     },
34     {
35       createProperties: {
36         id: 'sub2',
37         visible: true,
38         title: 'sub2',
39         parentId: 'main'
40       }
41     }
42   ]
43 };

结构似乎很清晰,但是不够完善,我们不想手动管理 menu 之间的父子关系,现在,删掉所有的 parentId ,我们将在接下来的节点来讲述如何动态维护关系

2.2.2 编写 collectMenuCreateProperties 方法

在上一节点中,我们创建了一个足以涵盖创建 contextMenu 的类 Menu,但是 chrome.contextMenus.create(property); 要求我们每次传入一个 CreateProperties 类,因此我们需要一个工具类从 Menu 类中抽取所有的 CreateProperties 信息,并且不要忘记了该方法必须能够动态维护 menu 间的父子关系(设置子menu 的 parentId 为上一层的 menu 的 id)

 1 function collectMenuCreateProperties(parent: Menu): CreateProperties[] {
 2   if (parent.createProperties.id === undefined) {
 3     throw  new Error('parent contextMenu must has id');
 4   }
 5   let result: CreateProperties[] = [];
 6   result.push(parent.createProperties);
 7   if (parent.children) {
 8     parent.children.forEach(child => {
 9       // 确保每一层的层级关系
10       child.createProperties.parentId = parent.createProperties.id;
11       result = result.concat(collectMenuCreateProperties(child));
12     });
13   }
14   return result;
15 }

2.2.3 编写 createMenu 方法

一切就绪,我们需要创建一个 contextMenu,这里有一些注意的点:

  • 如果在文件manifest.json 中对 background.js 设置了属性 "persistent": false ,可能会出现多次创建同一个menu
  • 我们的 menu 只需要创建一次,比如扩展初次安装的时候就很合适
  • 当调用者忘记传入构建源 Menu 时,需要确保运行不出错,这一点我们将采用ts中的默认值
 1 function createMenu(menu: Menu = defaultMenu): void {
 2   chrome.runtime.onInstalled.addListener(details => {
 3     const properties: CreateProperties[] = collectMenuCreateProperties(menu);
 4     // alert(JSON.stringify(properties));
 5     properties.forEach(property => {
 6       chrome.contextMenus.create(property);
 7     });
 8 
 9   });
10 }

最后,别忘了在顶层导出

1 export {Menu, defaultMenu};
2 export {createMenu};
3 export {collectMenuCreateProperties};

附:

 完整的代码

  1 import CreateProperties = chrome.contextMenus.CreateProperties;
  2 
  3 export {Menu, defaultMenu};
  4 export {createMenu};
  5 export {collectMenuCreateProperties};
  6 
  7 
  8 /**
  9  *  一系列创建 {@link contextMenus} 需要的数据
 10  *  @see defaultMenu
 11  */
 12 interface Menu {
 13   createProperties: CreateProperties;
 14   children?: Menu[];
 15 }
 16 
 17 /**
 18  *  默认的的数据  :
 19  *  [{"id":"main","visible":true,"title":"main"},
 20  *  {"id":"sub1","visible":true,"title":"sub1","parentId":"main"},
 21  *  {"id":"sub11","visible":true,"title":"sub11","parentId":"sub1"},
 22  *  {"id":"sub12","visible":true,"title":"sub12","parentId":"sub1"},
 23  *  {"id":"sub2","visible":true,"title":"sub2","parentId":"main"}]
 24  */
 25 const defaultMenu: Menu = {
 26   createProperties: {
 27     id: 'main',
 28     visible: true,
 29     title: 'main'
 30   },
 31   children: [
 32     {
 33       createProperties: {
 34         id: 'sub1',
 35         visible: true,
 36         title: 'sub1',
 37 
 38       },
 39       children: [
 40         {
 41           createProperties: {
 42             id: 'sub11',
 43             visible: true,
 44             title: 'sub11',
 45           }
 46         },
 47         {
 48           createProperties: {
 49             id: 'sub12',
 50             visible: true,
 51             title: 'sub12',
 52           }
 53         }
 54       ]
 55     },
 56     {
 57       createProperties: {
 58         id: 'sub2',
 59         visible: true,
 60         title: 'sub2',
 61       }
 62     }
 63   ]
 64 };
 65 
 66 
 67 /**
 68  * 主要方法
 69  * 创建 contextMenus
 70  * 1 监听 {@link  chrome.runtime}事件,事件的实体可能是:("install", "update", "chrome_update", or "shared_module_update")
 71  * 2 移除之前创建的所有 {@link  chrome.contextMenus}
 72  * 3 执行创建
 73  *
 74  * @param menu 执行创建的上下文信息
 75  * @see Menu
 76  * @see defaultMenu
 77  */
 78 function createMenu(menu: Menu = defaultMenu): void {
 79   chrome.runtime.onInstalled.addListener(details => {
 80     const properties: CreateProperties[] = collectMenuCreateProperties(menu);
 81     // alert(JSON.stringify(properties));
 82     properties.forEach(property => {
 83       chrome.contextMenus.create(property);
 84     });
 85 
 86   });
 87 }
 88 
 89 /**
 90  * 递归方式返回 parent 中包含的{@link CreateProperties} 对象,
 91  * 每一层的 {@link CreateProperties.id} 必须不为空
 92  * 并以编码的方式保证: 第二层开始, {@link CreateProperties.parentId} 被正确设置
 93  *
 94  * @param parent 顶层
 95  */
 96 function collectMenuCreateProperties(parent: Menu): CreateProperties[] {
 97   if (parent.createProperties.id === undefined) {
 98     throw  new Error('parent contextMenu must has id');
 99   }
100   let result: CreateProperties[] = [];
101   result.push(parent.createProperties);
102   if (parent.children) {
103     parent.children.forEach(child => {
104       // 确保每一层的层级关系
105       child.createProperties.parentId = parent.createProperties.id;
106       result = result.concat(collectMenuCreateProperties(child));
107     });
108   }
109   return result;
110 }

用例:

 1 import {Component, OnInit} from '@angular/core';
 2 import {createMenu} from './contextMenus';
 3 
 4 @Component({
 5   selector: 'app-event-page',
 6   templateUrl: './event-page.component.html',
 7   styleUrls: ['./event-page.component.css']
 8 })
 9 
10 /**
11  * @author siweipancc
12  * @version 1.0.0
13  */
14 export class EventPageComponent implements OnInit {
15   ngOnInit() {
16     createMenu();
17   }
18 
19 }

 

 

 

 

 

 

posted on 2019-12-02 16:40  四维胖次  阅读(3785)  评论(0编辑  收藏  举报