I'm wanting to link to a certain tab (Portfolio Tab) on a page from the main menu of a website, so when clicked it goes to that page with that portfolio tab open.
So far I've come up with this (using jQuery Tabslet) which works when not on the same page, but doesn't work if the user happens to be on the same page as the tabs, and so does nothing.
The link I use in the main menu is /about/#tab-3 which is doing the job of going to the about page with the portfolio tab open.
I thought I may need to trigger a page refresh when on the same page? And perhaps remove the #tab-3 from the url too.
Not being a jQuery expert, I unfortunately just don't know.
Here is the code so far
Thanks in advance.
jQuery(document).ready(function($){
$('.tabs').tabslet({
active :1,
animation : true,
container: '.tabs-container'
});
var hash = $.trim( window.location.hash );
var anchor = $('a[href$="'+hash+'"]');
if (anchor.length > 0){
anchor.click();
}
window.onload = function () {
if (location.hash) {
window.scrollTo(0, 0);
}
};
});
Advise: Always mention a reference to the plugin you use. I assume here you talk about this one.
This plugin acts on tab click only.
So when using a window hash in a link from another page like you do, you have to "simulate" a click on the tab.
So you will use an attribute selector to find the anchor having a href corresponding to the hash...
And click it.
window.onload = function () {
if (location.hash) {
window.scrollTo(0, 0);
$("a[href='"+location.hash+"']").click(); // Add this!
}
};
Good Day!
I've searched and searched again and I didn't find any help for this problem...
Context :
I've developed a Google Chrome extension that is very simple: send email to somebody with one click. To configure this, there is an option page on this extension to set the email address to which to send. My Google Chrome extension is available here (English translation, just the text, not the install).
Users have asked me to make this extension for Firefox so I'm working on it!
I've read tutorials on cfx and it's OK. But I need to have my extension respond to a right-click on my extension's icon in the toolbar. Not on the page, on my icon.
I'm using ActionButton (or ToggleButton) to add my icon to this the toolbar but I can't find a way to add a menu on the right click (there's already the default Firefox context menu but I want to add "Options".)
If somebody has the solution it would be great!
(I'm not familiar with XUL. So, if possible, a JavaScript only solution, please ^^ )
PS : I'm French so please excuse me for my bad English
EDIT : I've found how to set preferences in my "package.json" file but I want my own window.
And if we could "bind" the button "Options" in Add-on Manager to my own window it would be perfect!
EDIT 2 : as it's not clear for everyone I would detail here what I want for my extension :
- simple click (left click) on the icon get the current URL and send it to a mail address (OK for this)
- simple click ONLY DO THIS. This extension aims to be very very simple !
- right click on the icon shows Firefox's context-menu and I want to add "Options" here to show my options page
- Addon Manager could have a "Options" button near "Deactivate" and "Debug" and I want this option button to show my options page.
=> 2 way to see my option page : by the right click or by the addon manager and this is why I need your help !
General UI comments
Using right-click to directly activate your function is contrary to the general UI that is used system wide. Right-click is, system-wide (on some systems), used to open the context menu. In Firefox in the toolbar this is used to bring up the context menu for the toolbar area. This is what your users are generally going to expect to happen when they use right-click. You are probably better off either using something like shift-left-click, or letting the user define what combination is to be used to activate your function. If it is that you are attempting to add an option to the context menu, then that would normally be accessed via right-click.
Alternatives used in other add-ons:
A second section to your button with a down arrow. Clicking on the down arrow opens an expanded action or options menu.
Use the tooltip to display an action or options menu when the mouse is hovered over your button. This is done by creating a custom tooltip by enclosing the popup within a <tooltip id="myTooltip"></tooltip> element and referencing it in the button with <button tooltip="myTooltip"/> (tooltip property, tooltip attribute).
Using right-click
The problem appears to be that the Add-on SDK ActionButton has abstracted away your ability to have listeners for arbitrary events. It also does not give you access to the actual event object which is normally passed to event handlers (listeners). Further, its click event appears to actually be a command event, not a click event. One of the significant differences between a click and a command event is that the command event does not normally fire on a right-click.
You are going to need to gain access to the button outside of the ActionButton interface and add a listener for click events and then in your click event handler, you can make a choice to perform your action based on the state of event.button and event.shiftKey.
Adapting some code based on what I posted as an answer for a different question, you are going to want something like (not tested with modifications):
function loadUi(buttonId) {
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
forEachCustomizableUiById(buttonId, loadIntoButton, window);
}
function forEachCustomizableUiById(buttonId ,func, myWindow) {
let groupWidgetWrap = myWindow.CustomizableUI.getWidget(buttonId);
groupWidgetWrap.instances.forEach(function(windowUiWidget) {
//For each button do the load task.
func(windowUiWidget.node);
});
}
function loadIntoButton(buttonElement) {
//Make whatever changes to the button you want to here.
//You may need to save some information about the original state
// of the button.
buttonElement.addEventListener("click",handleClickEvent,true);
}
function unloadUi(buttonId) {
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
forEachCustomizableUiById(buttonId, unloadFromButton, window);
}
function unloadFromButton(buttonElement) {
//Return the button to its original state
buttonElement.removeEventListener("click",handleClickEvent,true);
}
function handleClickEvent(event) {
If( (event.button & 2) == 2 && event.shiftKey){
event.preventDefault();
event.stopPropagation();
doMyThing();
}
}
function doMyThing() {
//Whatever it is that you are going to do.
}
As should be implied by the above code, you will need to make sure to remove your listener when uninstalling/disabling your add-on. You will also want to make sure that loadUi() gets called when a new window opens so that your handler is added to the new button.
Adding to the context menu
There is no direct way to change the context menu just for your icon. The ID for the context menu is toolbar-context-menu. What you can do is add items to the context menu which are normally hidden="true". When you get the event that the right-click has happened on your icon you can change the state of hidden for those items you added. Then in an event handler that is called on the the popuphidden event for the context menu (<menupopup id="toolbar-context-menu">) you can set the state of hidden="true" for the <menuitem> element(s) which you have added to the <menupopup id="toolbar-context-menu"> in each browser window.
Something along the lines of:
function loadIntoContextMenu(win){
let doc = win.ownerDocument;
let contextPopupEl = doc.getElementById("toolbar-context-menu");
contextPopupEl.insertAdjacentHTML("beforeend",
'<menuitem label="My Item A" id="myExtensionPrefix-context-itemA"'
+ ' oncommand="doMyThingA();" hidden="true" />'
+ '<menuitem label="My Item B" id="myExtensionPrefix-context-itemB"'
+ ' oncommand="doMyThingB();" hidden="true" />');
contextPopupEl.addEventListener("popuphidden",hideMyContextMenuItems,true);
}
function unloadFromContextMenu(win){
let doc = win.ownerDocument;
let contextPopupEl = doc.getElementById("toolbar-context-menu");
let itemA = doc.getElementById("myExtensionPrefix-context-itemA");
let itemB = doc.getElementById("myExtensionPrefix-context-itemB");
contextPopupEl.removeChild(itemA);
contextPopupEl.removeChild(itemB);
contextPopupEl.removeEventListener("popuphidden",hideContextMenuItems,true);
}
function setHiddenMyContextMenuItems(element,text){
//The element is the context menu.
//text is what you want the "hidden" attribute to be set to.
let child = element.firstChild;
while(child !== null){
if(/myExtensionPrefix-context-item[AB]/.test(child.id)){
child.setAttribute("hidden",text);
}
child = child.nextSibling;
}
}
function showContextMenuItems(event){
//The target of this event is the button for which you want to change the
// context menu. We need to find the context menu element.
let contextmenuEl = event.target.ownerDocument
.getElementById("toolbar-context-menu");
setHiddenMyContextMenuItems(contextmenuEl,"false");
}
function hideContextMenuItems(event){
//This is called for the popuphidden event of the context menu.
setHiddenMyContextMenuItems(event.target,"true");
}
//Change the handleClickEvent function in the code within the earlier section:
function handleClickEvent(event) {
If( (event.button & 2) == 2){
//don't prevent propagation, nor the default as the context menu
// showing is desired.
showContextMenuItems(event);
}
}
Again, I have not tested this. It should demonstrate one way to accomplish what you desire.
However, given that we are talking about the context-menu, it is probably better to use the contextmenu event rather than the click event and testing for a right-click. In which case, we would change some of the functions above to be the following:
function loadIntoButton(buttonElement) {
//Make whatever changes to the button you want to here.
buttonElement.addEventListener("contextmenu",handleContextmenuEvent,true);
}
function handleContextmenuEvent(event) {
showContextMenuItems(event);
}
You can obtain each open primary browser window through the use of nsIWindowMediator. The following function, from MDN, will run the function you pass to it once for each open window:
Components.utils.import("resource://gre/modules/Services.jsm");
function forEachOpenWindow(todo) // Apply a function to all open browser windows
{
var windows = Services.wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements()) {
todo(windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow));
}
}
In the Add-on SDK:
function forEachOpenWindow(todo) // Apply a function to all open browser windows
var windows = require("sdk/windows");
for (let window of windows.browserWindows) {
todo(windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow));
}
}
You can add a listener which calls loadIntoContextMenu for new windows with the following code (also from MDN):
Components.utils.import("resource://gre/modules/Services.jsm");
Services.wm.addListener(WindowListener);
var WindowListener =
{
onOpenWindow: function(xulWindow)
{
var window = xulWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
function onWindowLoad()
{
window.removeEventListener("load",onWindowLoad);
if (window.document.documentElement.getAttribute("windowtype") == "navigator:browser"){
loadIntoContextMenu(window);
//It would be better to only do this for the current window, but
// it does not hurt to do it to all of them again.
loadUi(buttonId);
}
}
window.addEventListener("load",onWindowLoad);
},
onCloseWindow: function(xulWindow) { },
onWindowTitleChange: function(xulWindow, newTitle) { }
};
I have implemented a menu-button that has a primary and secondary action. Although it isn't right/left click, the button has two sides:
This allows you to associate two different actions with your button without altering the usual context menu flow of Firefox. Download the files on GitHub and store them in your lib folder.
Usage is similar to other button types. Include the following code in main.js (or any js file in the lib directory)
const { MenuButton } = require('./menu-button');
var btn = MenuButton({
id: 'my-menu-button',
label: 'My menu-button',
icon: {
"16": "./firefox-16.png",
"32": "./firefox-32.png"
},
onClick: click
});
The click function will be passed the same state object as the toggle and action buttons, and will be passed an additional boolean argument: isMenu. It should be used like so
function click(state, isMenu) {
if (isMenu) {
//menu-button clicked
} else {
//icon clicked
}
}
I tried your extension on Chrome after answering this question and see that my answer probably isn't what you're looking for, so I'll make a different suggestion (leaving the other answer up because I think it is useful for people looking for multiple actions on a single button).
One thing I would say is that (some) Chrome users know that the Options menu item refers to the extension and not browser options. Those users know that the menu item is there, and use it to change their extension settings. Firefox users wouldn't expect that to be the case, because the context menu actions all affect the browser, not the extension. In the same way, (some) Firefox users know that to change their extension settings, they must navigate to about:addons (or Tools/Addons) and click the Preferences button next to the extension. This is the expected route to changing your preferences. So I would argue that adding a context-menu option is very complicated and not a good solution.
Instead, if your users haven't yet set their preferences, I think you should do what you already do in Chrome: create a Panel, associate it with your button (by using position: button in the panel constructor), and tell your users that they need to set their preferences by navigating to Tools/Addons. If you use the simple prefs module, a Preferences button will appear next to your extension and the options that you set in your package.json will be editable there.
Unfortunately, this is a very basic page, and won't look like the nice HTML options page you made.
Bonne chance.
Besides all the reservations in the other answers and without reusing the existing toolbar contextmenu, here is how:
const utils = require('sdk/window/utils');
const window = utils.getMostRecentBrowserWindow();
const doc = window.document;
const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
const { getNodeView } = require("sdk/view/core");
let ButContext = doc.createElementNS(XUL_NS,'menupopup');
ButContext.setAttribute("id","customIDString");
menuitem = doc.createElementNS(XUL_NS,"menuitem");
menuitem.setAttribute("label", "customLabelString");
ButContext.appendChild(item);
var myBut = require("sdk/ui/button/action").ActionButton({
id: "myButton",
label: "Right click me!"
// And other options
});
//Here is the context magic
getNodeView(myBut).setAttribute('context', "customIDString");
//either ; gets removed together with the button
getNodeView(myBut).appendChild(ButContext);
//or ; the correct place but needs to be removed manually
doc.getElementById("mainPopupSet").appendChild(ButContext);
It's possible to made popup like this?
Not contents in it. The style and position of popuping is interesting for me.
Under toolbutton, with arrow up to center of button.
The panel widget didn't do the same, it just popup in center of window.
How I can do this popup with Add-on SDK?
On Firefox 30 and above, you can attach a Panel to a ToggleButton like this:
var panel = require("sdk/panel").Panel({
...
onHide: function(){
button.state('window', {checked: false});
}
});
var button = require("sdk/ui").ToggleButton({
...
onClick: function(state) {
if (state.checked) {
panel.show({
position: button
});
}
}
});
Specify where to attach the panel via position parameter in call to Panel.show; In this case, we're attaching it to the ToggleButton.
more information: https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/panel#Attaching_panels_to_buttons
The panel API lets you create basic panels using HTML5 (+ Javascript and CSS, of course).
var panel = require("sdk/panel").Panel({
contentURL: require("sdk/self").data.url("myFile.html")
});
panel.show();
Then there are Australis "View" panels, which are XUL-based and integrate better with the Australis customization API, e.g. it looks more Firefox-"native" and the "Views" are properly integrated when a ancestor toolbar button in the "hamburger menu". There is no SDK abstraction available ATM.
I have created an extension successfully and I am using an alert in extension.js to show the final message to the user when he clicks on the context menu but I want a customized popup window dialog box created using js and html instead of javascript alert.
Please help how can I do that. My extension works for the links on the webpages . when right button is clicked it shows the context menu and when clicked it throws the alert but I want to show a nice popup for the message.
The key is understanding the scopes in which your code is running. The context menu runs in the background scope and does not have direct access to the DOM of the active page. Hence, to implement a solution you must pass information from the background scope to the extension's page scope, and in that scope create the custom popup OR use Crossrider's notification plugin.
The following example demonstrates the implementation using the notification plugin:
extension.js:
appAPI.ready(function($) {
// Handler to receive messages
appAPI.message.addListener(function(msg) {
if (msg.type === 'show-notification')
showNotification(msg.notification);
});
function showNotification(data) {
// You can replace the notifier with your own custom code
appAPI.notifier.show({
'name':'my-notification-name',
'title':data.title,
'body':data.body,
'link':'http://example.com',
'theme':'default',
'position':'top-right',
'close':true,
'sticky':false,
'width':'400px',
'closeWhenClicked':true
});
}
});
Important: Do not forget to add the notification plugin to the extension.
background.js:
appAPI.ready(function($) {
appAPI.contextMenu.add(
"CustomMI",
"Custom menu item",
function (data) {
// Send message to active tab when context menu item selected
appAPI.message.toActiveTab({
type: 'show-notification',
notification: {
title: 'Context Menu Item',
body: '<b>Veiwing page</b>: '+data.pageUrl
}
});
}, ["all"]
);
});
[Disclosure: I am a Crossrider employee]
How do I disable a context menu item created by my own chrome extension within my own site? For content scripts in a chrome extension, there is the excludes_matches option but context menus don't appear to have an equivalent.
I just don't want the context menu item I'm adding to show up when the user is on my own site.
UPDATE
People seem to be confused about what I'm doing so here's the code. In my "background_page" I add an item to the context menu like so:
chrome.contextMenus.create(
{
"title": "My Context Menu Item",
"contexts": ["link"],
"onclick": myContextItemClicked
});
The item shows up when a user right clicks on any <a> tag as expected. What I want to do is prevent it from showing up when a user is within my own site.
I solved similar problem by triggering menu creation on mousedown event in a content script injected to every page. Something like:
content script:
document.addEventListener("mousedown", function(event){
if(event.button == 2) {
chrome.extension.sendRequest({"createMenu"});
}
}, true);
background page:
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if(request === "createMenu") {
chrome.contextMenus.removeAll(function() {
if(sender.tab.url != "http://mysite.com") {
chrome.contextMenus.create({...});
}
});
}
});
Doesn't look very effective but there is no other way around afaik. It works pretty smooth though so users won't be able to see that you are recreating the menu before displaying it.