剪贴板自定义类型跨浏览器支持

原创 2017年01月03日 14:11:22

引子

在编辑器开发中, 遇到了需要设置内容到剪贴板和获取并剪贴板内容的情况. 有关以下问题

  • 跨浏览器获取Clipboard
  • 从剪贴板中获取不同类型数据

可以参考 THE DEFINITIVE GUIDE TO COPYING AND PASTING IN JAVASCRIPT

下面分享以下在 THE DEFINITIVE GUIDE TO COPYING AND PASTING IN JAVASCRIPT 基础上如何实现跨浏览器支持自定义类型.

自定类型浏览器支持情况

THE DEFINITIVE GUIDE TO COPYING AND PASTING IN JAVASCRIPT 一文我们可以了解
clipboardData.setData(type, value) API 中 type 在不同浏览器中的支持情况:

  • Chrome and Safari: They support any content type on the clipboardData, including custom types. So, we can call clipboardData.setData('application/lucidObjects', serializedObjects) for pasting, and then call var serialized = clipboardData.getData('application/lucidObjects')
  • Firefox: It currently only allows access to the data types described above. You can set custom types on copy, but when pasting, only the white-listed types are passed through.
  • Internet Explorer: In true IE fashion, it supports just two data types: Text and URL. Oh, and if you set one, you can’t set the other (it gets nulled out). There is a hack, however, that also allows us to indirectly get and set HTML.

Windows Edge 浏览器的情况与 FireFox 类似 ( Windows Edge 浏览器2016年才发布, THE DEFINITIVE GUIDE TO COPYING AND PASTING IN JAVASCRIPT 一文写于 2014 年, 故没有提到 Windows Edge ).

在编辑器(仅支持 Webkit 内核浏览器和 Windows Edge )中, 除了设置text/plain, text/html 到剪贴板外, 还会用到挺多自定义类型的数据 (如 text/yne-table-json, text/yne-json). 自定义类型数据的主要使用场景是:

  • 同一个端不同笔记间(同一个编辑器, 不同实例)相互拷贝粘贴
  • 不同端的编辑器(不同编辑器, 不同实例)相互拷贝粘贴, 如从PC端中的编辑器拷贝, 粘贴到Web浏览器的编辑器中

但 Windows Edge 浏览器的剪贴板又不支持自定义类型数据, 于是琢磨着如何应对 Windows Edge 浏览器.

  1. 在 Windows Edge 下 try ... catch ... 设置内容到剪贴板, 如果不能设置到剪贴板, 则不设置.
  2. 无论在 Webkit 内核浏览器还是 Windows Edge 中, 都能统一处理设置到剪贴板.

第一种解决方案肯定不太好, 而第二种方案又该如何实现呢?

思路

Clipboard API and events - Mandatory data types 可以发现, 剪贴板支持读取的数据类型:

  • text/plain
  • text/uri-list
  • text/csv
  • text/css
  • text/html
  • application/xhtml+xml
  • image/png
  • image/jpg, image/jpeg
  • image/gif
  • image/svg+xml
  • application/xml, text/xml
  • application/javascript
  • application/json
  • application/octet-stream

剪贴板支持写入的数据类型

  • text/plain
  • text/uri-list
  • text/csv
  • text/html
  • image/svg+xml
  • application/xml, text/xml
  • application/json

无论读取还是写入剪贴板, 都支持写入类型.

能否将自定义类型数据 JSON 序列化后写入标准写入类型中的一种呢? 要读取时,也可以
从剪贴板中读取该标准写入类型, JSON 处理后再提取出自定义类型.

参考代码

后来实验成功, 以下是参考代码:

DataTransfer.js


/**
 *
 * @see https://www.lucidchart.com/techblog/2014/12/02/definitive-guide-copying-pasting-javascript
 * @see https://w3c.github.io/clipboard-apis/#mandatory-data-types
 *
 * @author fudesign2008
 * @date  2016-12-29
 */
define(function (require) {
    var _ = require('underscore'),
        Class = require('jtk/core/Class'),
        L = require('jtk/core/L'),
        USER_AGENT = require('../util/userAgent'),
        ONLY_SUPPORT_STANDARD_TYPES = USER_AGENT.isEdge(),
        REGISTERED_TYPES = {
            /**
             * @type {Boolean} is standard type or not
             * @see https://w3c.github.io/clipboard-apis/#mandatory-data-types
             */
            'text/html': true,
            'text/plain': true,
            'text/uri-list': true,
            'text/yne-image-json': false,
            'text/yne-json': false,
            'text/yne-note-id': false,
            'text/yne-table-json': false,
        },
        ALT_STANDARD_TYPE = 'application/json',
        DataTransfer;

    DataTransfer = Class.extend({

        /**
         * @param {Event} options.event
         */
        initialize: function (options) {
            var that = this,
                event = options.event,
                rawEvent = event.originalEvent || event;

            that._dataTransfer = rawEvent.dataTransfer;

        },

        _getCustomData: function (type) {
            var that = this,
                dataStr,
                dataObj;

            if (!that._altData) {
                dataStr = that._dataTransfer.getData(ALT_STANDARD_TYPE);
                try {
                    dataObj = JSON.parse(dataStr);
                    that._altData = dataObj;
                } catch (ex) {
                    L.error(ex);
                    that._altData = {};
                }
            }

            return that._altData[type];
        },

        /**
         * @param {String} type
         * @return {Any}
         */
        getData: function (type) {
            var that = this,
                dataTransfer = that._dataTransfer,
                isStardard,
                value;

            if (ONLY_SUPPORT_STANDARD_TYPES) {
                isStardard = REGISTERED_TYPES[type];
                if (isStardard === true) {
                    value = dataTransfer.getData(type);
                    L.log('get standard type', type, value);
                } else if (isStardard === false) {
                    value = that._getCustomData(type);
                    L.log('get custom type', type, value);
                } else {
                    L.error('type should be registered!', type);
                }
                return value;
            } else {
                return dataTransfer.getData(type);
            }
        },

        /**
         * @param {Object} data
         */
        setDataMap: function (dataMap) {
            var dataTransfer = this._dataTransfer,
                setData = function (value, type) {
                    L.log('set data to data transfer', type, value);
                    try {
                        dataTransfer.setData(type, value);
                    } catch (ex) {
                        L.error(ex);
                    }
                },
                customData = {},
                customCounter = 0,
                str;

            if (ONLY_SUPPORT_STANDARD_TYPES) {
                _.each(dataMap, function (value, type) {
                    var isStardard = REGISTERED_TYPES[type];

                    if (isStardard === true) {
                        setData(value, type);
                    } else if (isStardard === false){
                        L.log('set custom type', type);
                        customData[type] = value;
                        customCounter++;
                    } else {
                        L.error('type should be registered!', type);
                    }
                });
                if (customCounter > 0) {
                    try {
                        str = JSON.stringify(customData);
                        setData(str, ALT_STANDARD_TYPE);
                    } catch (ex) {
                        L.error(ex);
                    }
                }
            } else {
                _.each(dataMap, setData);
            }
        }

    });

    return DataTransfer;
});


ClipboardData.js


/**
 *
 * @see https://www.lucidchart.com/techblog/2014/12/02/definitive-guide-copying-pasting-javascript
 * @see https://w3c.github.io/clipboard-apis/#mandatory-data-types
 *
 * @author fudesign2008
 * @date  2016-12-29
 */
define(function (require) {

    var DataTransfer = require('./DataTransfer'),
        ClipboardData = DataTransfer.extend({
            /**
             * @param {Event} options.event
             * @override
             */
            initialize: function (options) {
                var that = this,
                    event = options.event,
                    rawEvent = event.originalEvent || event;

                that._dataTransfer = rawEvent.clipboardData;
            }
        });

    return ClipboardData;
});

无论在 Webkit 内核浏览器和 Windows Edge 浏览器都能如此使用:


var ClipboardData = require('./ClipboardData');

$(el).on('copy', function (event) {
    var clipboardData = new ClipboardData({
            event: event
        });

    clipboardData.setDataMap({
        'text/plain': 'plain xxxx',
        'text/html': 'html xxxx',
        'yne-json': 'xxxx',
        'yne-table-json': 'yyyy'
    });
}).on('paste', function (event) {
    var clipboardData = new ClipboardData({
            event: event
        }),
        html = clipboardData.get('text/html'),
        yneTableJSON = clipboardData.get('yne-table-json');

    console.log('clipboard data text/html', html);
    console.log('clipboard data yne-table-json', yneTableJSON);

});

以上方案能够解决以下场景

  • 同一个端不同笔记间(同一个编辑器, 不同实例)相互拷贝粘贴

无法解决以下场景

  • 不同端的编辑器(不同编辑器, 不同实例)相互拷贝粘贴, 如从PC端中的编辑器拷贝, 粘贴到Web浏览器的编辑器中

参考

Android自定义组合控件

【课程介绍】 开发工具:Android Studo ;如何自定义组合控件
  • 2016年11月13日 21:15

nw.js node-webkit系列(10)Native UI API Clipboard的使用

Clipboard是对操作系统剪贴板的一个抽象,目前只支持获取和设置纯文本内容。 (一)例子 // Load native UI library var gui = require('nw.gui...
  • zeping891103
  • zeping891103
  • 2016-02-29 13:55:45
  • 1545

Android 剪贴板详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 微博:厉圣杰 源码:AndroidDemo/Clipboard 如本文有助于你理解 Android 剪贴板,不妨给我一个 Star。对于码农而言,...
  • I_wait_for_you
  • I_wait_for_you
  • 2017-04-19 14:29:44
  • 446

剪切板clipboard

工作中遇到的一个问题,在项目系统中复制一段内容,或点击一个按钮,然后将这段内容添加到使用者当前电脑系统的剪切板中.刚开始的时候觉得很迷惑不知如何实现,后来前端开发人员说,她们可以做,但后来效果不好,操...
  • dqchouyang
  • dqchouyang
  • 2016-08-03 00:41:50
  • 434

C++操作剪贴板

剪贴板内置在windows中,并且使用系统的内部资源RAM,或虚拟内存来临时保存剪切和复制的信息,可以存放的信息种类是多种多样的。剪切或复制时保存在剪贴板上的信息,只有再剪贴或复制另外的信息 ,或停...
  • glt3953
  • glt3953
  • 2013-04-16 12:48:15
  • 7080

使用 NW.js 跨平台开发

转自:https://zhuanlan.zhihu.com/p/20070166 越来越多的应用开始借助于 Web 技术。比如,Brackets、Peppermint 和Pinegrow 都...
  • u013220054
  • u013220054
  • 2017-07-11 11:46:48
  • 324

闲来无事,说说Windows剪贴板 -- 剪贴板概述部分

首先,讨论一下剪贴板是什么   Windows的帮助文件中对剪贴板的描述是这样的:剪贴板是从一个地方复制或移动并打算在其他地方使用的信息的临时存储区域。可以选择文本或图形,然后使用“剪切”或“复制”...
  • shuimuniao
  • shuimuniao
  • 2012-08-23 08:07:04
  • 3853

Visual C++剪贴板操作

文章摘要:  1、文本内容的操作  2、WMF数据的操作  3、位图的操作  4、设置使用自定义格式  5、感知剪贴板内容的改变  6、自动将数据粘贴到另一应用程序窗口 一、如何将文本内...
  • glt3953
  • glt3953
  • 2013-04-16 12:53:38
  • 1537

ZeroClipboard 复制到剪切板 的一些事件

ZeroClipboard.config({ moviePath: "http://zeroclipboard.org/javascripts/zc/ZeroClipboard_1.3.1.s...
  • hanshileiai
  • hanshileiai
  • 2014-12-27 13:02:52
  • 2801

Visual C#的剪切板編程(转)

Visual C#是微软.Net框架中的一个重要的程序开发语言,虽然在.Net框架中还有其它的程序开发语言,但微软似乎对Visual C#更喜爱有加。这同时也就决定了Visual C#在.Net框架中...
  • ycl111
  • ycl111
  • 2005-10-27 15:49:00
  • 1622
收藏助手
不良信息举报
您举报文章:剪贴板自定义类型跨浏览器支持
举报原因:
原因补充:

(最多只允许输入30个字)