LocalStore -> injectscript

Forums » Dev.Opera » Opera Extensions Development Discussions

You need to be logged in to post in the forums. If you do not have an account, please sign up first.

Go to last post

30. November 2010, 21:59:54

stefanvd

Posts: 69

LocalStore -> injectscript

Hi,

I create a option page (with localStore HTML5) and this work OK.
But now i want to get the settings inside my inject script. I try this in my injectscript.js. But it doesn't work.

if(!localStorage['test'])localStorage['test'] = 'true'; // found nothing then it is true
var test = localStorage[test];

if(test== 'true'){opera.postError('TEST: OK');}

What do i wrong, any idea?

Kind Regards,
Stefan

1. December 2010, 04:32:21

eternal1

Posts: 103

Originally posted by stefanvd:

var test = localStorage[test];



If you copy & pasted this then the problem would be that you forgot to surround test with single (or double) quotes.

1. December 2010, 17:03:11

stefanvd

Posts: 69

Hi,

I have now this, but it doesn't work. It not show the postError (when my option is true).

inside the inject.js file:

if(!localStorage['test'])localStorage['test'] = 'true'; // found nothing then it is true
var test = localStorage['test']; // add the ' quotes

if(test== 'true'){opera.postError('TEST: OK');}

Kind Regards,
Stefan

1. December 2010, 18:43:03

eternal1

Posts: 103

I tried it for the first time, and I got uncaught exception (undefined variable localStorage).

properly using window.localStorage solved the issue for me.

1. December 2010, 19:30:15

stefanvd

Posts: 69

YES!!
it work smile
Thanks!!

1. December 2010, 20:05:37

stefanvd

Posts: 69

Hi,

it get my localstore information from "test". But it doesn't update when i change it in my option page.
Must i place inside my option page also the word "window" ?

Current got this for save and reading of my localstore files.

//options.html
// my save button
function save_options(){
if($('test').checked)localStorage['test'] = 'true';
else localStorage['test'] = 'false';
}

//read by open windows onload
function read_options(){
if(localStorage['test'] == 'true')$('test').checked = true;
}

Default previous post, the "test" is true. But when i change the setting in my option page to false (check off the box). Click save button.
It still show the old value.

2. December 2010, 02:05:41

eternal1

Posts: 103

Since the absense of "window." breaks the earlier test you should be using "window.".

Even though you can use localStorage you should preferably use widget.preferences though. From the option.html article (http://dev.opera.com/articles/view/opera-extensions-options-page/):

The widget.preferences interface is exactly the same as that of the localStorage object, except that it doesn't have the same size limitations as localStorage, and widget.preferences default values can be declared in the <preference> elements of your config.xml file, as seen in The preference Element and its Attributes section of the Widget Packaging and Configuration specification. Therefore, when the script in our options.html template sets a preference on the widget.preferences object, a storage event is fired on the active documents of the extension (especially the background process). By listening to the storage events, the background process of your extension can react immediately to any change in the widget.preferences.



But regardless of (window.)localStorage or widget.preferences, you must add a "storage" event handler in your index.html/background.js:

window.addEventListener('storage', onStorage, false);
...
...
// then either: var storage = widget.preferences;
// or: var storage = window.localStorage;

function onStorage(e){
    if (e.storageArea !== storage) return;

    if (e.key == 'test'){
        // 'test' has been updated, so update variables and/or call the functions/methods you need to.
        opera.postError('Preference "test" updated: ' + storage['test']);
    }
}


Maybe you already have that code, but since I'm not sure I added it in case you don't.

However, you can start using the template given to us by Opera themselves: http://dev.opera.com/articles/view/opera-extensions-options-page/nonsense.oex

Modify it for your needs, no need to touch the javascript in options.html, just modify the html to reflect your preferences (options). The javascript will intelligently take care of setting the preferences that result in "storage" events being sent to the extension background (index.html/background.js/whatever_name), which is handled by the above test code.

2. December 2010, 05:44:30 (edited)

spadija

Posts: 1636

widget.preferences isn't available to an injected script, and inside an injected script, localStorage refers to the localStorage object for the domain the script was injected into, not the extension's localStorage. To get my extension's settings into an injected script, I use something like this:

// BACKGROUND SCRIPT

// Singleton object that can store and retrieve arbitrary objects from widget.preferences
var storage = function() {
	// This is a list of the names of all settings
	this.settings = [
		'foo',
		'bar',
	];
	
	// Gets an object from widget.preferences
	this.get = function(name) {
		var data = widget.preferences[name];
		if (typeof data === 'undefined')
			return null;
		return JSON.parse(data);
	}
	
	// Sets an object in widget.preferences
	this.set = function(name, value) {
		widget.preferences[name] = JSON.stringify(value);
	}
	
	this.isSetting = function(name) {
		return settings.indexOf(name) != -1;
	}
	
	// Gets all objects listed as settings and returns them as an object
	// i.e. var settings = storage.getSettings();
	//      var foo = settings.foo;   OR   var foo = settings['foo'];
	this.getSettings = function() {
		var data = {};
		for (var i = 0; i < this.settings.length; i++) {
			data[this.settings[i]] = this.get(this.settings[i]);
		}
		return data;
	}
}

// When a script connects, send it the settings object
opera.extension.addEventListener('connect', function(e) {
	e.source.postMessage({
		action: 'settings',
		settings: storage.getSettings(),
	});
}, false);

// OPTIONAL: When a setting changes, update all the injected scripts with the new setting
window.addEventListener('storage', function(e) {
	// If the update item is not a setting, don't send an update
	if (!storage.isSetting(e.key))
		return;

	opera.extension.broadcastMessage({
		action: 'update setting',
		name: e.key;
		value: storage.get(e.key),
	});
}, false);


// INJECTED SCRIPT

// This is where we'll store settings in the injected script
var settings = null;

// Listen to messages from the background script 
opera.extension.addEventListener('message', function(e) {
	switch (e.data.action) {
		// Update the entire settings object
		case 'settings':
			settings = e.data.settings;
			break;
		// Update one setting
		case 'update setting':
			settings[e.data.name] = e.data.value;
			break;
	}
}, false);

// You can now do stuff with the settings object as long as you check that it's been initialized first.
// i.e. if (settings) {
//      	do stuff
//      }

2. December 2010, 18:17:22

stefanvd

Posts: 69

Hi,

use @eternal1
I add the "storage" event handler in my index.html page (script inside).
But it doesn't change the value.

2. December 2010, 21:20:07 (edited)

eternal1

Posts: 103

@stefanvd
You have to provide more information, like fragments from your code, or even the extension itself. It's hard to say what is causing problems without any knowledge of details in your code. For example, I don't even know how you are actually implementing the options page; 1) inside an injected tab, 2) inside the popup, or 3) in options.html. I now, again, think it might be 1 (inside an injected script, somewhat like Lex1's NoAds, more or less) even though I thought it was 3 before (inside options.html, given the fact that you mentioned "options page"). But I'm not sure, so please provide this information:

1. Where does the code that writes to window.localStorage (or preferably, widget.preferences) reside: injected script, popup, or options.html ?
2. Copy and paste the piece of code that writes to the aforementioned storage/preferences object.

2. December 2010, 21:31:39

stefanvd

Posts: 69

Originally posted by eternal1:

@stefanvd
You have to provide more information, like fragments from your code, or even the extension itself. It's hard to say what is causing problems without any knowledge of details in your code. For example, I don't even know how you are actually implementing the options page; 1) inside an injected tab, 2) inside the popup, or 3) in options.html. I now, again, think it might be 1 (inside an injected script, somewhat like Lex1's NoAds, more or less) even though I thought it was 3 before (inside options.html, given the fact that you mentioned "options page"). But I'm not sure, so please provide this information:

1. Where does the code that writes to window.localStorage (or preferably, widget.preferences) reside: injected script, popup, or options.html ?
2. Copy and paste the piece of code that writes to the aforementioned storage/preferences object.



Here the extension (with the above change i make)
TOTL http://turnoffthelights.googlecode.com/files/Turn%20Off%20the%20Lights.oex

Only the autoplay option i am working. When the option is on -> it inject a green div (20px x 20px) on the current webpage. If the option is Off -> a red div.

Kind Regards,
Stefan

2. December 2010, 23:40:52

spadija

Posts: 1636

Just in case you glossed over my last post because of the giant block of code, the reason your injected script is not getting your settings is because localStorage in your index/options page doesn't refer to the same thing as localStorage in your injected script. Each extension has its own localStorage, which is what your index/options pages see. Every web domain also gets its own localStorage, which is what your injected script sees.

In order to get settings from your extension's localStorage (or widget.preferences), you need to pass them from the background script to the injected script in a message. The giant block of code in my last post is the method I use to do that. When an injected script loads, it fires the "connect" event in the background script. The background script now has a way of messaging the injected script, so it collects all the settings it needs to send to the injected script into one object and passes it to the injected script with postMessage. The injected script then gets the message and saves all the settings it was sent.

It looks like you will have to alter your injected script's message handling code so that it can understand different types of messages. postMessage can send JSON objects, so I send an object with the "action" field to say what kind of message it is.

3. December 2010, 02:29:35 (edited)

eternal1

Posts: 103

@spadija
lol, I'm in hurry so I'm myself gonna gloss over your latest post for now bigsmile

@stefanvd
You have a save button that re-writes the preferences in one go, instead of immediate update to each individual preference (note the singular). Nothing wrong with this of course. The problems however are enumerated below:

1. You accidentally had prefixed window.localStorage in your save_options() function, with yet another "window.", making: window.window.localStorage. Fix that.

2. You took my sample "storage" listener/handler and check for the key (preference) "test". Thing is you do not set any "test" on the localStorage object from inside options.html. Therefore the check for key "test" inside "storage" listener in index.html will never be true. So for verifying that it works check for "autoplay" instead of "test" in the "storage" listener:

    if (e.key == 'autoplay'){
        // 'autoplay' has been updated, so update variables and/or call the functions/methods you need to.
        opera.postError('Preference "autoplay" updated: ' + storage['autoplay']);
    }


3. Since you write all preferences to localStorage object in one go, it's not particularly nice to listen for indicidual storage events. You can of course listen and check for the key matching the storage variable that you set last in options.html. But better is, after you set the last storage variable in options.html then simply send a message to the background script.

3.1. Before "window.close();" in your save_options() in options.html add:
opera.extension.postMessage({action: 'options-updated'});


3.2. In index.html, replace my previous sample "storage" listener with:
function onMessage(e){
    switch (e.data.action){
        case 'options-updated':
            // options have been updated, so update variables and/or call the functions/methods you need to.
            opera.postError('options updated !');
            break;
    }
}


3.3. And lastly, in index.html, change:

window.addEventListener('storage', onStorage, false);


to:

opera.extension.onmessage = onMessage;

3. December 2010, 14:26:18

stefanvd

Posts: 69

@eternal1
sorry, i still got the same problem, (or do i forgot something else?)
see here the new version:
http://turnoffthelights.googlecode.com/files/Turn%20Off%20the%20Lights%20alpha2.oex

Kind Regards,
Stefan

3. December 2010, 17:24:38 (edited)

eternal1

Posts: 103

@stefanvd

I assume you are not confident in English. Therefore translations of the dev articles to German and other languages would be welcomed by all. Any volunteers reading this ? smile

You have embedded "function onMessage(e){...}" inside "function onStorage(e){...}" so now it looks like this:

function onStorage(e){
    if (e.storageArea !== storage) return;

function onMessage(e){
    switch (e.data.action){
        case 'options-updated':
            // options have been updated, so update variables and/or call the functions/methods you need to.
            opera.postError('options updated !');
            break;
    }
}

}


Remove above code and put this instead:

function onMessage(e){
    switch (e.data.action){
        case 'options-updated':
            // options have been updated, so:
            // * Call any function(s)/method(s) that rely on the options.
            // * Post message(s) to injected scripts giving the options that injected scripts rely on.
            opera.postError('options updated !');
            break;
    }
}

3. December 2010, 17:53:46

spadija

Posts: 1636

Originally posted by eternal1:

I know you are not confident in English, so translations of the dev articles to German and other languages would be welcomed by all. Any volunteers reading this ?


I'm not confident enough in my German to translate something like that, but I can translate my previous posts if you want.

We're starting to get on the right track here. My first post was a giant block of generic code, so here it is again, but simplified and more specific to your extension. The ". . ."'s are where some of your original code goes.
// INDEX.HTML
window.addEventListener('load', function(){
var theButton, ToolbarUIItemProperties = . . .
//You can use this for your button's click event
theButton.addEventListener("click", function(){
	// Send the message as a JSON object instead of a string. Normally, I would suggest using focused.postMessage, but there's a bug with iframes that makes that fail sometimes.
	var focused = opera.extension.tabs.getFocused();
	if (focused)
		opera.broadcastMessage({ 
			action: 'lightsoff',
			url: focused.url,
		});
}, false);
opera.contexts.toolbar.addItem(theButton);

// show welcome page
. . .

// When a script loads, it calls the "connect" event here Then, we send the new script all the extension's settings. Use JSON.parse because a boolean value gets saved as a string 
opera.extension.addEventListener('connect', function(e) {
	e.source.postMessage({
		action: 'settings',
		settings: {
			autoplay: JSON.parse(localStorage['autoplay'] || 'false')
		}
	});
}, false);

// When a setting changes, update all the injected scripts with the new setting
window.addEventListener('storage', function(e) {
	if (e.storageArea !== localStorage)
		return;
	// Send a message to all scripts saying which setting changed and what its new value is
	opera.extension.broadcastMessage({
		action: 'update setting',
		name: e.key;
		value: localStorage[e.key],
	});
}, false);

}, false);


// LIGHT.JS
// In my experience, opera.extension.tabs.getFocused().url strips everything after the #, so I do the same here.
var url = window.location.href.replace(/#.*$/, '');
// This is where we'll store settings in the injected script. localStorage in light.js and localStorage in index.html are NOT the same object.
var settings = null;

// Listen to messages from the background script 
opera.extension.onmessage = function(e) {
	switch (e.data.action) {
		// Update the entire settings object
		case 'settings':
			settings = e.data.settings;
			break;
		// Update one setting
		case 'update setting':
			settings[e.data.name] = e.data.value;
			break;
		case 'lightsoff'
			if (!settings || if (url == e.data.url) {)
				return;
			var autoplay = settings['autoplay'];
			if(autoplay == 'true'){
				. . .
			} else {
				. . .
			}
		break;
	}
}
. . .

3. December 2010, 20:33:38

stefanvd

Posts: 69

@eternal1
where i can start to translate the dev article for the Dutch users.

@spadija
I try to you code, look easy now smile
but... now i see not my lamp button.

see here new version:
http://turnoffthelights.googlecode.com/files/Turn%20Off%20the%20Lights%20alpha3.oex

3. December 2010, 21:01:08 (edited)

eternal1

Posts: 103

Originally posted by spadija:

I'm not confident enough in my German to translate something like that, but I can translate my previous posts if you want.



Not for me. I thought stefanvd was German so that was the reason I asked anyone reading if he/she wants to volunteer for translation of extension related articles. There are always developers who cannot follow articles (proprely) due to them not being confident with English.

Originally posted by stefanvd:

@eternal1
where i can start to translate the dev article for the Dutch users.



I'm not sure what you mean, and though I'm presuming you already know where the articles are, here's the link just in case: http://dev.opera.com/articles/extensions/

If I misunderstood you let me know.

Edit:
Oh I think you meant to translate and upload to dev.opera.com. In that case I have no idea. Opera Software can enlighten us if it is possible and in that case how. But regardless, there's always the possiblity to translate and post in your my-opera blog, and add links in your signature. :|

3. December 2010, 21:09:34

stefanvd

Posts: 69

Originally posted by eternal1:


I'm not sure what you mean, and though I'm presuming you already know where the articles are, here's the link just in case: http://dev.opera.com/articles/extensions/

If I misunderstood you let me know.

Edit:
Oh I think you meant to translate and upload to dev.opera.com. In that case I have no idea. Opera Software can enlighten us if it is possible and in that case how. But regardless, there's always the possiblity to translate and post in your my-opera blog, and add links in your signature. :|



I want to translate few english article (http://dev.opera.com/articles/extensions/) to the language -> Dutch.
Thanks!! i found the link how to start translate the extension article.

@spadija
waiting...

3. December 2010, 21:17:12

eternal1

Posts: 103

Originally posted by stefanvd:

Thanks!! i found the link how to start translate the extension article.



It's here for anyone else interested: http://dev.opera.com/articles/info/

3. December 2010, 22:04:07

spadija

Posts: 1636

OK. I've properly merged my code with yours and it looks like it's working. http://files.myopera.com/spadija/extensions/Turn%20Off%20the%20Lights%20alpha3.oex

There were a few syntax errors keeping things from working (like that semicolon after e.key that I put instead of a comma). I also moved the code that happens when the script gets a "lightsoff" message into another function to make the code easier to read.

3. December 2010, 22:33:02

stefanvd

Posts: 69

Hi,

Thanks! It work great on the current open tab page.

But if i refresh the page it use back the default value (show red box).
Possible the option 'autoplay' for all website the same? and use the last save store.

Kind Regards,
Stefan

3. December 2010, 23:00:27

spadija

Posts: 1636

Ah. Right. onconnect apparently doesn't get fired after the tab's URL changes. I'm not sure what the best way to deal with this is, but this might work:

Try replacing the current "connect" handler with this.
function postSettings(listener) {
	listener.postMessage({
		action: 'settings',
		settings: {
			autoplay: ...
		}
	});
}

opera.extension.addEventListener('connect', function(e) {
	postSettings(e.source);
}, false);

opera.extension.tabs.addEventListener('focus', function(e) {
	var focused = opera.extension.tabs.getFocused();
	if (focused)
		postSettings(focused);
}, false);

4. December 2010, 10:26:01

stefanvd

Posts: 69

problem still here, show always the red box (when i change the setting 'autoplay' to 'true' in my option.html). sad

Kind Regards,
Stefan

4. December 2010, 14:31:48 (edited)

eternal1

Posts: 103

@stefanvd

This doesn't necessarily relate to your problem. Anyway, since I remember it I thought mentioning: in your options.html (in the html markup) I remember that when you called save_options, you immediately also called _gaq(){} (some google function I think ?):

<SOME_ELEMENT_I_DONT_REMEMBER_WITCH onclick="save_options();_gaq(...);">


There exists however no _gaq(){} in the context of options.html.

Anyhow, if you always get the red rectangle, then something is wrong in your injeced script, just like there were wrongs in your options.html and index.html. In my humble opinion you really need to have a basic understanding of:

1. Messaging.
2. localStorage / widget.preferences.

When you know what you are doing then you won't have all these problems.

4. December 2010, 15:33:02

stefanvd

Posts: 69

Originally posted by eternal1:

@stefanvd

<SOME_ELEMENT_I_DONT_REMEMBER_WITCH onclick="save_options();_gaq(...);">



that is the Google analytic code.

Opera support localstore, so i use my Chrome option page (that use localstore for save the settings).
It work on Chrome so normal no change must make on the options.html page, only the problem is that it use back the default value when i refresh the page.

4. December 2010, 19:27:38

spadija

Posts: 1636

Okay. I found the problem.

In light.js, you have "if(autoplay == 'true')", and the code I gave you for index.html used "autoplay: JSON.parse(localStorage['autoplay'] || 'false')". JSON.parse('true') returns a boolean true instead of the string 'true', and true != 'true'. Either change light.js to use "if(autoplay == true)", or change index.html to "autoplay: localStorage['autoplay'] || 'false'".

There's also a bug in messaging at the moment where opera.extension.tabs.getFocused().postMessage() will post to an iframe in a page instead of the main page itself. I forgot about that with that last bit of code. If you use this instead, it will work around the bug, but it's less efficient.

function getSettings() {
	return {
		autoplay: JSON.parse(localStorage['autoplay'] || 'false'),
	};
}

opera.extension.addEventListener('connect', function(e) {
	e.source.postMessage({
		action: 'settings',
		settings: getSettings()
	});
}, false);

opera.extension.tabs.addEventListener('focus', function(e) {
	// Avoid the messaging bug by updating all scripts' settings
	opera.extension.broadcastMessage({
		action: 'settings',
		settings: getSettings()
	});
}, false);


Now that I think about it, you could pass the settings with the "click" message to avoid updating every script's settings all the time, but I don't have time right now to test that.

4. December 2010, 20:24:41

stefanvd

Posts: 69

THANK YOU!!!
SOLVED! (->end post)

It work now great smile
appreciate your help!!!

Kind Regards,
Stefan

14. February 2012, 06:37:24

santex79

Posts: 3

Thanks a million! It works fine! The problem is fixed! Appreciate your help!

Forums » Dev.Opera » Opera Extensions Development Discussions