Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
218 views
in Technique[技术] by (71.8m points)

javascript - Showing context menu buttons only when right-clicked on classes that start with "Story"

Is there a way to show context menu actions, only when the user right-clicks on classes that start with "story".

For example: if the user right-clicks on an object in the page of class "story ....", the context menu buttons should appear, otherwise nothing should happen.

Here is my code (though it does not work):

var divs = document.querySelectorAll("[class^=story]"); //get all classes that start with "Story"

    window.oncontextmenu = function() {

        for(var i=0; i < divs.length; i++)
        {
            divs[i].onclick = function() { 
            chrome.contextMenus.create
            (
                {"id": "butto1", "title": "1", "contexts":["all"], "onclick": genericOnClick}
            );
            chrome.contextMenus.create
            (
                {"id": "button2", "title": "2", "contexts":["all"], "onclick": genericOnClick}
            );
            chrome.contextMenus.create
            (
                {"id": "button3", "title": "3", "contexts":["all"], "onclick": genericOnClick}
            );

            };
        }

        return true; 
    };


function genericOnClick(info, tab) {
  //do some crap here
  chrome.contextMenus.removeAll();
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

In this related answer, I explained that context menu items cannot be created on the fly, because the time between a contextmenu event and the appearance of the context menu item is not sufficient to get a chrome.contextMenus.create call in between.

The other answer explains how to make sure that the context menu entry shows the selected text. This was done by listening to the selectionchange event. For your purpose, we want to use an event which has the desired timing.

I'm going to use the mouseover and mouseout events. By depending on mouse events, the context menu will not work when you use the keyboard, e.g. by focusing an element using JavaScript or the Tab key, followed by pressing the context menu key. I did not implement it in the solution below.

The demo consists of three files (plus an HTML page to test it). I put all files in a zip file, available at https://robwu.nl/contextmenu-dom.zip.

manifest.json

Every Chrome extension requires this file in order to work. For this application, a background page and content script is used. In addition, the contextMenus permission is required.

{
    "name": "Contextmenu based on activated element",
    "description": "Demo for https://stackoverflow.com/q/14829677",
    "version": "1",
    "manifest_version": 2,
    "background": {
        "scripts": ["background.js"]
    },
    "content_scripts": [{
        "run_at": "document_idle",
        "js": ["contentscript.js"],
        "matches": ["<all_urls>"]
    }],
    "permissions": [
        "contextMenus"
    ]
}

background.js

The background page will create the context menus on behalf of the content script. This is achieved by binding an event listener to chrome.runtime.onConnect, which offers a simple interface to replace context menu entries.

chrome.contextMenus.create is called whenever a message is received over this port (from the content script). All properties, except for onclick are JSON-serializable, so only the "onclick" handler needs a special treatment. I've used a string to identify a pre-defined function in a dictionary (var clickHandlers).

var lastTabId;
// Remove context menus for a given tab, if needed
function removeContextMenus(tabId) {
    if (lastTabId === tabId) chrome.contextMenus.removeAll();
}
// chrome.contextMenus onclick handlers:
var clickHandlers = {
    'example': function(info, tab) {
        // This event handler receives two arguments, as defined at
        // https://developer.chrome.com/extensions/contextMenus#property-onClicked-callback

        // Example: Notify the tab's content script of something
        // chrome.tabs.sendMessage(tab.id, ...some JSON-serializable data... );

        // Example: Remove contextmenus for context
        removeContextMenus(tab.id);
    }
};

chrome.runtime.onConnect.addListener(function(port) {
    if (!port.sender.tab || port.name != 'contextMenus') {
        // Unexpected / unknown port, do not interfere with it
        return;
    }
    var tabId = port.sender.tab.id;
    port.onDisconnect.addListener(function() {
        removeContextMenus(tabId);
    });
    // Whenever a message is posted, expect that it's identical to type
    // createProperties of chrome.contextMenus.create, except for onclick.
    // "onclick" should be a string which maps to a predefined function
    port.onMessage.addListener(function(newEntries) {
        chrome.contextMenus.removeAll(function() {
            for (var i=0; i<newEntries.length; i++) {
                var createProperties = newEntries[i];
                createProperties.onclick = clickHandlers[createProperties.onclick];
                chrome.contextMenus.create(createProperties);
            }
        });
    });
});

// When a tab is removed, check if it added any context menu entries. If so, remove it
chrome.tabs.onRemoved.addListener(removeContextMenus);

contentscript.js

The first part of this script creates simple methods for creating context menus.
In the last 5 lines, the context menu entries are defined and bound to all current and future elements which match the given selector. Like I said in the previous section, the argument type is identical to the createProperties argument of chrome.contextMenus.create except for "onclick", which is a string which maps to a function in the background page.

// Port management
var _port;
var getPort = function() {
    if (_port) return _port;
    _port = chrome.runtime.connect({name: 'contextMenus'});
    _port.onDisconnect.addListener(function() {
        _port = null;
    });
    return _port;   
}

// listOfCreateProperties is an array of createProperties, which is defined at
// https://developer.chrome.com/extensions/contextMenus#method-create
// with a single exception: "onclick" is a string which corresponds to a function
// at the background page. (Functions are not JSON-serializable, hence this approach)
function addContextMenuTo(selector, listOfCreateProperties) {
    // Selector used to match an element. Match if an element, or its child is hovered
    selector = selector + ', ' + selector + ' *';
    var matches;
    ['matches', 'webkitMatchesSelector', 'webkitMatches', 'matchesSelector'].some(function(m) {
        if (m in document.documentElement) {
            matches = m;
            return true;
        }
    });
    // Bind a single mouseover+mouseout event to catch hovers over all current and future elements.
    var isHovering = false;
    document.addEventListener('mouseover', function(event) {
        if (event.target && event.target[matches](selector)) {
            getPort().postMessage(listOfCreateProperties);
            isHovering = true;
        } else if(isHovering) {
            getPort().postMessage([]);
            isHovering = false;
        }
    });
    document.addEventListener('mouseout', function(event) {
        if (isHovering && (!event.target || !event.target[matches](selector))) {
            getPort().postMessage([]);
            isHovering = false;
        }
    });
}

// Example: Bind the context menus to the elements which contain a class attribute starts with "story"
addContextMenuTo('[class^=story]', [
    {"id": "butto1", "title": "1", "contexts":["all"], "onclick": 'example'},
    {"id": "button2", "title": "2", "contexts":["all"], "onclick": 'example'},
    {"id": "button3", "title": "3", "contexts":["all"], "onclick": 'example'}
]);

The previous code assumes that all context menu clicks are handled by the background page. If you want to handle the logic in the content script instead, you need to bind message events in the content script. I've shown an (commented) instance of chrome.tabs.sendMessage in the background.js example, to show where this event should be triggered.

If you need to identify which element triggered the event, don't use a predefined function (in a dictionary) as shown in my example, but an inline function or a factory function. To identify the element, a message needs to be paired with an unique identifier. I'll leave the task of creating this implementation to the reader (it's not difficult).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...