I've finished developing the main functionality of a Chrome Extension, but I'm struggling to retain the user preferences in the popup.html file. So, I have a button in the popup.html file which is supposed to be a user preference, but I am not able to retain the user preference upon reloading the page and clicking on the extension again.
I tried using the Chrome Storage API, but I was unable to achieve what I wanted.
Here is a picture of popup.html:
I want to be able to save a user's preference if they choose to toggle between on and off. But as of what I have now, when the user slides it on (as indicated in the picture), upon clicking away from the popup and clicking the icon again, the button always defaults to the 'off' position.
I tried using a combination of localStorage and the Chrome Storage API to load the user preferences, but I was unable to do so.
Here is my popup.js file:
function saveChanges() {
// Get a value.
if ($('#myonoffswitch').is(':checked')) {
localStorage.mydata = 'y';
} else {
localStorage.mydata = 'n';
}
// Save it using the Chrome extension storage API.
chrome.storage.sync.set({
'value': localStorage.mydata
}, function () {
});
}
$(document).ready(function () {
if (localStorage.getItem('mydata')) {
if (localStorage.mydata == 'n') {
$('myonoffswitch').attr('checked', false);
} else {
$('myonoffswitch').attr('checked', true);
}
} else {
if ($('#myonoffswitch').is(':checked')) {
localStorage.setItem('mydata', 'y');
} else {
localStorage.setItem('mydata', 'n');
}
}
$('#myonoffswitch').click(function () {
saveChanges();
});
});
Here is my popup.html file:
<html>
<head>
<title>Replace some text</title>
<link rel="stylesheet" type="text/css" href="popup.css">
<script type='text/javascript' src='jquery-1.12.3.js'></script>
<script type="text/javascript" src="options.js"></script>
</head>
<body>
<div>
<div class='logo'>
</div>
<div class='main-text'>
</div>
<div class='buttons-area'>
<div class="onoffswitch">
<input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox" id="myonoffswitch">
<label class="onoffswitch-label" for="myonoffswitch"></label>
</div>
<div class='block-spoilers-message'>
On Off Button
</div>
</div>
</div>
</body>
</html>
And finally, here is a relevant part of my content script:
$(document).ready(function () {
var on_off_pref = true;
chrome.storage.onChanged.addListener(function (changes, namespace) {
for (key in changes) {
var storageChange = changes[key];
on_off_pref = storageChange.newValue;
console.log('Storage key "%s" in namespace "%s" changed. ' +
'Old value was "%s", new value is "%s".',
key,
namespace,
storageChange.oldValue,
storageChange.newValue);
}
});
if (on_off_pref === 'y') {
//Execute main functionality here only if the button in popup.html is on.
}
});
So, in short, I need to preserve the settings on popup.html and use those settings in the content script to determine whether or not to run the main part.
I have looked at the other StackOverflow solutions, but I have not been able to get any of them to work.
Any help on resolving this issue would be thoroughly appreciated!
see what #Xan said
You just forgot # in $('myonoffswitch').attr('checked', true);
your webextension needs the storage permission. See https://sites.google.com/site/getsnippet/browser/chrome/extensions/storage
Related
In the Firefox extension, I want to implement a simple toggle switch that will enable/disable an extension. A basic idea is that the change of state will be saved as a boolean into browser (sync) storage. The state should be read every time, so an extension will know if should work or not.
But - my Javascript knowledge is so poor that I came into trouble.
Here is simple HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="styles.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.1.2/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<form id="form" class="ps-3 mt-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="flexSwitch">
<label class="form-check-label" for="flexSwitch">Plugin ON/OFF</label>
</div>
</form>
<label id="test"></label>
<br>
<script src="options.js"></script>
</body>
</html>
And here is a simple JS file:
function CheckAndSave()
{
var state = document.getElementById("flexSwitch");
if(state.checked)
{
document.getElementById('test').innerHTML = 'ON';
browser.storage.sync.set({ delovanje: 1 });
}
else
{
document.getElementById('test').innerHTML = 'OFF';
browser.storage.sync.set({ delovanje: 0 });
}
restoreState();
}
function restoreState()
{
//browser.storage.sync.get("delovanje", function(items) { console.log(items)});
let getting4 = browser.storage.sync.get("delovanje");
getting4.then(setCurrentChoice, onError);
function onError(error) {
console.log(`Error: ${error}`);
}
function setCurrentChoice()
{
var toggle = document.getElementsByName("flexSwitch");
if (result.delovanje === 1)
toggle.checked = true;
else
toggle.checked = false;
}
}
document.addEventListener("DOMContentLoaded", restoreState);
document.getElementById("flexSwitch").addEventListener('change', CheckAndSave);
What is wrong with my code? Is my way of saving Boolean ok?
I tried to write to the console for "debugging", but I don't know how to do it - this is a pop-up after a user press an icon, and nothing is shown in the console.
Most of all you did a mistake here:
function setCurrentChoice(result)
{
var toggle = document.getElementsByName("flexSwitch");
if (result.delovanje === 1)
toggle.checked = true;
else
toggle.checked = false;
}
In this case, toggle will be array like object, but not the element you expect.
You should use document.getElementById("flexSwitch") as previously.
Another issue that you missed an argument in the setCurrentChoice function. It should take settings like this:
function setCurrentChoice(result){...}
I would also suggest to hide the logic of getting element behind the scene by either wrapping it to the function:
const getToggle = () => document.getElementById("flexSwitch")
Or even move it to the separate class and encapsulate all logic there:
class Toggle {
constructor() {
this._el = document.getElementById("flexSwitch");
}
setCheck(value) {
this._el.checked = value;
}
}
Here is the working sample:
function CheckAndSave()
{
var state = document.getElementById("flexSwitch");
if(state.checked)
{
document.getElementById('test').innerHTML = 'ON';
chrome.storage.sync.set({ delovanje: 1 });
}
else
{
document.getElementById('test').innerHTML = 'OFF';
chrome.storage.sync.set({ delovanje: 0 });
}
}
function restoreState()
{
chrome.storage.sync.get("delovanje",setCurrentChoice );
function setCurrentChoice(result)
{
var toggle = document.getElementById("flexSwitch");
if (result.delovanje === 1)
toggle.checked = true;
else
toggle.checked = false;
}
}
document.addEventListener("DOMContentLoaded", restoreState);
document.getElementById("flexSwitch").addEventListener('change', CheckAndSave);
This approach will help you reduce the code and accidental mistakes.
P.S. Here is how I worked with the storage
The code seems okay, while there are some things I would change (for refactoring purposes to match my flavour) I think it should be working without much issue.
In any case verify the following.
The browser.storage.sync API is only available from extensions, so check that the HTML and JS that you are posting are actually part of the extension that you are using.
The manifest.json is what tells the browser what resources can your extension access, verify that you did add the "storage" permission on there here you can read more about it for chrome, though it will be the same for other browsers
For debugging purposes always remember that the browser lets you have great tools. Read more about developer tools, but as a starter I would tell you to open them and put a debugger statement there where you feel like there's something that isn't working as expected. And then with the console start looking for the properties that you are not finding.
To log items to the console use console.log('XXX') and it should show what you want
I think the problem is that the change event is not fired when setting toggle.checked with JavaScript. Just call CheckAndSave(); from the end of setCurrentChoice.
The brief page below does not work. Specifically, "window.speechSynthesis.speak(msg)" does not work until the button has been pressed. If the button has been pressed then the "Hello" message works. If it has not then any calls to "window.speechSynthesis.speak(msg)" do not produce any audible output.
Suspecting that it has something to do with initialization of speechSynthesis - some things have been tried below to ensure that it is initialized when "Hello" is called. None have worked. Although it seems like it should have. It seems like it is getting properly initialized only if it is called from the button press.
The setup of the SpeechSynthesisUtterance itself is the same whether called from the button or the timeout. That setup works when called by the button. But nowhere else until it has been called by the button.
What is wrong here?
<!DOCTYPE html>
<html>
<head>
<title>Voice Test 3</title>
</head>
<body>
<div id="header">User Interface Terminal</div>
<input type="text" id="control_box"></input><br>
<button id="startButton" onclick="voicemessage('Button');">start</button><br>
<script>
function voicemessage(ttstext) {
var msg = new SpeechSynthesisUtterance(ttstext);
msg.volume = 1;
msg.rate = 0.7;
msg.pitch = 1.3;
window.speechSynthesis.speak(msg);
document.getElementById('control_box').value = ttstext;
}
window.speechSynthesis.onvoiceschanged = function() {
document.getElementById('control_box').value = "tts voices recognized";
window.setTimeout(function() {
voicemessage("Hello");
}, 5000);
};
window.addEventListener('load', function () {
var voices = window.speechSynthesis.getVoices();
})
</script>
</body>
</html>
This may be due to the browser itself...
Recent updates in some browsers (Firefox and Chrome) have policies to prevent audio from being accessed unless some user interaction triggers it (like a button click)...
I am trying to copy 'window.location.href' e.g. the URL of the current page to clipboard from my extension.
My issue is that when I copy the URL to clipboard, it is the extensions URL that is copied and not the page I am visiting.
Extensionbar:
<!DOCTYPE HTML>
<html>
<head>
<button onclick="copyFunction();">Copy</button>
<script type="text/javascript">
function copyFunction() {
var inputDump = document.createElement('input'),
hrefText = window.location.href;
document.body.appendChild(inputDump);
inputDump.value = hrefText;
inputDump.select();
document.execCommand('copy');
document.body.removeChild(inputDump);
}
</script>
</head>
</html>
From my understanding the solution should be this, but I fear I am too clueless how to proceed: https://developer.apple.com/documentation/safariservices/safari_app_extensions/passing_messages_between_safari_app_extensions_and_injected_scripts
This is how I (tried to) proceed, by creating a global.html page and an injected script.
Global page:
<!DOCTYPE HTML>
<script>
safari.application.addEventListener("command", copyFunction, false);
function copyFunctionEvent(event) {
if (event.command == "CopyToClipboard") {
safari.application.activeBrowserWindow.activeTab.page.dispatchMessage("CopyToClipboard", "all");
}
}
</script>
Injected script:
function myextension_openAll(event){
if (event.name == 'CopyToClipboard'){
function copyFunction() {
var inputDump = document.createElement('input'),
hrefText = window.location.href;
document.body.appendChild(inputDump);
inputDump.value = hrefText;
inputDump.select();
document.execCommand('copy');
document.body.removeChild(inputDump);
}
}
safari.self.addEventListener("message", myextension_openAll, true);
Actual:
safari-extension://com.myextension-0000000000/abc123/extensionbar.html
Expected:
http://www.google.com (e.g. if current tab)
From your code above (Extensionbar html), you seem to write legacy Safari extension (.safariextz), and it has been deprecated. See What’s New in Safari and WebKit" session on WWDC18
I recommend you rewrite your code into Safari App Extension by following process, which can be written in Swift. I'm not sure why wrong URL is copied to clipboard in your code, but rewriting your code would solve the problem as a result.
Creating App Extension project
Create App Extension by following [File] -> [New] -> [Project...] then choose [Safari Extension App] on Xcode. Project template contains example of menubar implementation.
Copying location.href by clicking menu bar button
Following code would add functionality to copy location.href when you click menu bar button.
Just paste this into SafariExtensionHandler.swift.
class SafariExtensionHandler: SFSafariExtensionHandler {
override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
// WHen injected script calls safari.extension.dispatchMessage, the message will come here
guard let href = userInfo?["href"] as? String else { return }
// Save href to clipboard
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(href, forType: .string)
}
override func toolbarItemClicked(in window: SFSafariWindow) {
// Request injected script a message to send location.href
window.getActiveTab { currentTab in
currentTab!.getActivePage { currentPage in
currentPage!.dispatchMessageToScript(withName: "getHref", userInfo: nil)
}
}
}
}
And injected script (script.js) as follows.
safari.self.addEventListener("message", function(event) {
console.log("event received");
safari.extension.dispatchMessage("sendHref", { "href": location.href });
});
Working Example
Complete working code here, This may help your work. Good luck :)
https://github.com/horimislime/safari-extension-menubar-example
I'm starting with javascript, websockets and jQuery developing a simple example. In the html page I only have a button, that, when pressed, has to send its state (ON/OFF for instance). In index html, I have the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"></meta>
<title>First App</title>
<link rel="stylesheet" href="css/style.css">
<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/APP.js"></script>
</head>
<body>
<div id='hello_message'>
Connecting...
</div>
<button id='state'>Turn on</button>
<div id='off'>OFF</div>
<div id='on'>ON</div>
</body>
</html>
My intention is to open a websocket between the client and the server when the page is loaded, and keep it open for any information to be sent between both of them. To this end, I have the following file containing the js code (APP.js):
window.onload = APPStart;
// Page onload event handler
function APPStart() {
state = false;
if ("WebSocket" in window)
{
var ws = new WebSocket("ws://10.30.0.142:8020");
ws.onopen = function()
{
alert ("Connected");
$('#hello_message').text("Connected");
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
};
ws.onclose = function()
{
alert("Connection is closed...");
};
window.onbeforeunload = function(event) {
socket.close();
};
}
else
{
// The browser doesn't support WebSocket
alert("WebSocket NOT supported by your Browser!");
}}
Now, every time someone clicks on button, I would like to execute the following code:
// program checks if led_state button was clicked
$('#state').click(function() {
alert ("click");
// changes local led state
if (led_state == true){
$('#on').hide();
$('#off').show();
state = false;
ws.send("ON");
}
else{
$('#off').hide();
$('#on').show();
state = true;
ws.send("OFF");
}
});
I've tried to put this part of the code inside the function APPStart, but it doesn't work. I also suspect that jQuery is not working either since messages are not updated. Any suggestion to make it work?
Thanks for the comments. The code works, the problem was in the cache of the browser. Once I noticed it, I cleaned the cache and everything started to work. Silly me.
How can you check to see if a pop-up is already open strictly by the original pop-up's name, and not URL, etc.
The pop-up is opened via window.open().
Keep the handle to the window:
var popup = window.open( URL, name, features )
So later you can check whether it's closed by using it's "closed" property.
if (popup.closed) {
// closed
}
else {
// still open
}
You can see it working here: http://www.javascripter.net/faq/windowclosed.htm
EDIT
You should be able to do just what Cheery said, but if you would like more detail, I tested this, and it works:
<html>
<head>
<script type="text/javascript">
var popup;
function openPopup() {
popup = window.open("http://www.stackoverflow.com", "so", "location=1,status=1,scrollbars=1,width=300,height=300");
}
</script>
</head>
<body>
<button onclick="openPopup()">open popup</button>
<button onclick="checkIfPopupIsOpen()">check for popup</button>
<script type="text/javascript">
function checkIfPopupIsOpen() {
if (popup.closed) {
alert("it's closed");
}
else {
alert("it's still open");
}
}
</script>
</body>
</html>