Creating a Popover in extension bar for safari - javascript

I am creating a Safari Extension Bar and was wanting to have multiple Links in it and by clicking on the link have a popover specific to that link appear.
So far I have found these:
https://developer.apple.com/library/archive/documentation/Tools/Conceptual/SafariExtensionGuide/AddingPopovers/AddingPopovers.html
https://developer.apple.com/documentation/safariextensions/safariextension/1635377-popovers
However everything seems to be referring to using them with toolbar items, not extension bars.
I was wondering if it is even possible to make popovers work with links in an extension bar and if so if someone could point me in the right direction with this.

Sure, why not? Here's some sample code to get you started.
Say your extension bar has a couple of links like this:
Open Popover 0
Open Popover 1
(Not the most elegant way to run some JavaScript when you click a link, but whatever.)
Furthermore, say you have a single toolbarItem (toolbar button) and you want a different popover to pop up under it depending on which link on the bar you click. The openPopover function can be as simple as this:
function openPopover(pid) {
var tbItem = safari.extension.toolbarItems[0];
var thisPop = safari.extension.popovers.filter(function (p) {
return p.identifier == pid;
})[0];
tbItem.popover = thisPop;
tbItem.showPopover();
}
Since the extension bar has access to the global safari object of your extension, it can directly manipulate toolbarItems and open popovers, without having to pass messages to the global page. Indeed, your extension may not need a global page at all.

Related

Add menu item in nw.js doesn't show up on Windows

Trying to add a new menu item to a submenu in NW.js (Node WebKit.) Doing that with
if (this.menu.createMacBuiltin) {
this.menu.createMacBuiltin('Menu');
this.menuItem = this.menu.items[0];
isMac=true;
} else {
this.menuItem = new gui.MenuItem({label: 'Menu'});
this.menuItem.submenu = new gui.Menu();
this.menu.append(this.menuItem);
}
this.menuItemSubmenu = this.menuItem.submenu;
However, adding a menuItem dynamically like so
this.newMenuItem = new gui.MenuItem({label:'New'});
this.menuItmeSubmenu.insert(this.newMenuItem,0);
does not work for Windows, but works perfectly for Mac. When I restart the Windows app, the menu item does show up.
Why does Windows not automatically update the menu? How can I fix it?
It's a bit hard to tell since you posted only small portion of your code, and didn't mention if you use node-main in the config file.
This might be caused due to race conditions - maybe the first code runs after the second. Try to print out this.newMenuItem.items just before doing the dynamic insertion. Do you see the existing tray menu?
Try to change insert to append - did the tray menu item appear?
If both seems ok, try this workaround: instead of adding a new item, rebuild the list. First, empty it using :
for (var i = 0; i < this.newMenuItem.items.length; i++){
this.newMenuItem.removeAt(0);
}
Then use append to re-add all items. Did it work?
UPDATE
The reason I asked if you are using node-main is that when you use it, on the main script that you set as node-main, while this script is running window is not yet defined:
window: defined as a property of 'global', points to the DOM window
global object. Note that it would be updated upon page navigation.
This symbol is not available at the time the script is loaded, because
the script is executed before the DOM window load (source)
To overcome this you need to run any code that requires the window object under your main script as configured in the package.json file.
I found a way to fix it.
if (windows) {
this.refreshMenuBar();
}
refreshMenuBar = function() {
this.menu.remove(this.menuItem);
this.menu.append(this.menuItem);
win.menu = this.menu;
};
Basically, removing then adding the main menu item, then re-assigning win.menu. For some reason this updates the menu when it doesn't register a change in Windows. After removing and re-adding the entire menu, changes are displayed.

How to add a right click on my Firefox extension's icon?

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);

JQwidgets scroll doesn't work on chrome when i close opened window

I use JQwidgets ,, I use to print data onclick print-button
as code :
$("#print").click(function () {
var gridContent = $("#jqxgrid").jqxGrid('exportdata', 'html');
var newWindow = window.open('', '', 'width=800, height=500'),
document = newWindow.document.open(),
pageContent =
'<!DOCTYPE html>\n' +
'<html>\n' +
'<head>\n' +
'<meta charset="utf-8" />\n' +
'<title>jQWidgets Grid</title>\n' +
'</head>\n' +
'<body>\n' + gridContent + '\n</body>\n</html>';
document.write(pageContent);
document.close();
newWindow.print();
});
When I close printing-widow(not continue printing), I can't use the grid-scroll (on chrome)..
google-chrome Version 34.0.1847.131 m
This worked fine on Firefox and IE..
How to fix the scroll after closing printing-window on chrome
Fiddle-Demo
It looks like you're not the only one with this issue.
I understand that your code is already setup and you want to run with what you have, but unless someone comes up with a hack or Google decided to fix what is clearly a bug, I think you need to re-think how you are approaching this issue.
If chromeless windows were an option, or if the print dialogue were a modal then you could pull this off with the current strategy, but neither of those options are possible in Chrome. Even if you were able to get around this scrolling issue somehow you're still left with a less than desirable UX problem in that if the user hits "cancel" in the print dialogue then they are left with a still open blank window.
Here is a JS fiddle to demonstrate that you need to change your approach: DEMO
You can see from this demonstration that even if we run a completely separate script from within the new window by passing it as plain text in the content object, it still causes the same issue. This means to me that this is a parent/child type of a relationship that is not easily circumvented with JS.
I recommend 2 alternative possible solutions:
Option1:
<input type="button" value="Print" onclick="window.print(); return false;" />
This triggers a full screen print dialogue that can't be closed from the "Windows Close Button." That way you can avoid the issue all together. Then you can use a combination of JS and Print Styles to target and isolate the information you want to print. I know it's more work but I think may be the better cross-platform solution.
This option is more brute force and simplistic in nature (and you have already commented that you know this but I'm leaving it up because it's still an option).
DEMO
Option2:
User clicks on a link/button that opens a new tab/window
In the same function the data from your table gets loaded into a JSON Object
The JSON object is loaded into a print template in the new tab/window
the template initiates the print function
By taking these actions, I think you will have disassociated the JS instance enough that the new tab will not affect the initiating script.
This is a browser bug - you'd have to find some sort of hack to fix it.
Doesn't sound like you want to put the print dialog code elsewhere thus not affecting your scroll bar. That is the obvious solution but it sounds like you can't do that.
Here's what I would do: Wait until someone has triggered the problematic condition, then put an event listener on the scroll event. when it happens... go ahead and reload the page.
Simple, easy, fun.
var needToReload = false;
$("#print").click(function () {
... as you have
needToReload = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
}
$('#contentjqxgrid').scroll(function () {
if (needToReload) {
window.location.reload();
}
});
$("#jqxscrollbar").jqxScrollBar({
width: 5,
height:180,
theme:'energyblue',
vertical:true
});
$("#jqxscrollbar1").jqxScrollBar({
width: 300,
height:5,
theme:'energyblue'
});
Look at jsfiddle: http://jsfiddle.net/8PtUX/6/

Javascript and CSS onclick Div Swap

I am new to both Javascript and designing for Facebook. I am using Shortstack to create custom tabs and have created a 3 panel sub-tab application using the service. In the 3rd panel, I have 19 div's holding information. By default, I use CSS to hide these DIVs (display:none;) and have a series of links at the top of the panel that change the visibility of each DIV onclick. Only the active onclick content is visible at any time.
The tab functions properly in Firefox, Chrome, and even Safari on the Mac, but fails in all browsers on the PC, and fails differently. In IE, immediately after the swap happens an error message pops up which mentions the publisher not allowing the action in an iFrame. In Firefox the tab just goes blank with no error message.
My script is below. As I stated, I am new to coding for Facebook and working with Javascript as I am a designer and not a programmer, but am eager to learn.
Thank you in advance for your thoughts and ideas.
function showhide(layer_ref) {
var thisDiv;
// check to see if any DIVs are currently showing
var divlist = ["div1","div2","div3","div4","div5","div6","div7","div8","div9","div10","div11","div12","div13","div14","div15","div16","div17","div18","div19"];
// loop through the list of DIVs in "divlist"
for (x = 0; x < divlist.length; x++) {
thisDiv = document.getElementById(divlist[x]);
// if the DIV is showing, hide it
if (thisDiv.style.display == "block") {
thisDiv.style.display = "none";
}
}
// show the appropriate DIV
thisDiv = document.getElementById(layer_ref);
thisDiv.style.display = "block";
}
If you try to change the things in iframe that could be a problem if the iframe is loaded from different domain. It is basically security rule - you don't want to allow rogue code to change/read/write things on the page other than it's own.
To answer your question better we need to know where is changing javascript is located and what it tries to change (are those two things loaded from the same domain or not).
The script itself looks ok to me.

Google Chrome opens window.open(someurl) just fine...but page/window with clicked link also opens someurl.com

As the title says "Google Chrome opens window.open(someurl) just fine...but page/window with clicked link also opens someurl.com.
When I click the "Click here" link with the onclick="shpop..." call attached, my pop up opens /facebook_login.php' correctly...BUT...at the same time, the original window opens /facebook_login.php too!
This happens in Chrome and IE, but FF is fine and doing just what i want..
I have this link:
Click here
I know I could remove the href="/facebook_login.php" and replace with href="#" .. but I need the link to work if js is disabled.
I have this js code imported in my tag:
function shpop(u,t,w,v)
{
var text = encodeURI(t);
var uri = encodeURI(u);
var h = document.location.href;
h = encodeURI(h);
var wwidth='600'; /*popup window width*/
var wheight='300'; /*popup window height*/
if(v=='' || undefined==v)v=document.domain; /*popup name/title */
switch(w){
case 'loginfb':
var url = '/facebook_login.php';
wwidth='980';
wheight='600';
break;
}
window.open(url,v,'width='+wwidth+',height='+wheight);
return false
}
Any ideas?
what is with returning false, and having false in the onclick?
This
onclick="shpop('','','loginfb','');return false"
Just needs to be
onclick="return shpop('','','loginfb','');"
If the onclick returns any error, the link will still open up. Do you see any errors in the JavaScript console? I wonder if the browsers are freaking out about any . in the window name from using document.domain. Try giving it a name.
onclick="return shpop('','','loginfb','foobar');"
According to the latest browser statistics - well last time it was measured anyway (2008) only 5% of users had Javascript disabled. Nowadays it's likely to be less. Consider that all browsers have it enabled by default. Therefore it's generally only advanced users that for whatever reason choose to disable javascript, and will therefore understand that there's a good chance any website they visit won't work as expected - Facebook, Google, Amazon - everyone uses javascript these days. It's perfectly acceptable to assume the user is using it, with one overall <noscript> version at the start of your page for those users if you really really want to cover all your bases :)
Here is the simplest solution:
<a href="/facebook_login.php"
target="FBpopup"
onclick="window.open('about:blank','FBpopup','width=980,height=600')">
Click here
</a>
You don't need return false because you actually want the link to execute.
The trick is to use the same window name in both the window.open and in the link target.
window.open will create the popup, then your login page will run in that popup.
If popups are blocked or Javascript is disabled, your login page will run in a new tab.

Categories