zoukankan      html  css  js  c++  java
  • 编写 Chrome 扩展——contextMenus 的快捷创建

    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 }

     

  • 相关阅读:
    html+css实现简易下拉菜单
    Win10 设置外网多用户远程桌面连接
    ubuntu 14.04 下svn + apache2 配置
    JavaScript 学习笔记(一)
    生成Log文件的写法
    运行执行sql文件脚本的例子
    css实现文本框和下拉框结合的案例
    angularjs 1 开发简单案例(包含common.js,service.js,controller.js,page)
    将字符串转成只有首字母是大写
    java之springboot的spring-boot-starter-aop的切面编程的使用(四)
  • 原文地址:https://www.cnblogs.com/siweipancc/p/11971561.html
Copyright © 2011-2022 走看看