Friday, July 3, 2009

Testing Extension Localization

Due to some latest feedback and offers for translation I started looking at localizing JumpStart. That is something that I have been putting off for a long time. I guess I needed some reminding that localization is not something nice to have, it is really a must.
All is well, it's not really hard to localize an extension - well at least not technically, finding right phrases for translation is another story. But how to test this now? I completely switched to Linux at home so this short tip is for Linux users.

If you want to start your Firefox under a different locale from the one set at the system level you have to use command that goes something like this:

LANGUAGE=en_US LANG=en_US.UTF-8 firefox -no-remote -P "your_profile_name"
(Firefox command line arguments)
If your locale is in fact en_US :) and you want to test Serbian for example you could change your language like this:
LANGUAGE=sr_RS LANG=sr_RS.UTF-8 firefox -no-remote -P "your_profile_name"
You could even use language that is not installed on your machine, for example I don't have German locales installed but I can still use de_DE to test my extension:
LANGUAGE=de_DE LANG=de_DE.UTF-8 firefox -no-remote -P "your_profile_name"
The Firefox obviously won't be shown in language you don't have installed on your machine but your extension will be showing it if you have it localized for the language.

I dug out this information from some old Ubuntu forum thread, hope you find it useful. If you have better way of doing this please let me know.

Thursday, April 23, 2009

JumpStart - version 0.5a3.4

New version of JumpStart is available. All feedback is very appreciated.

Most notable changes:
* Added options menu to the logo button

* Added info page to the preferences window so some basic compatibility information shown


* Fixed some issues with popup showing blank (most noticeable on Linux)
* Added experimental bookmarks page - suggestions welcome




Other changes:
Supports Firefox 3.5
Fixed preferences button in the add-ons dialog
Fixed toolbar button for smaller icons
Not showing toolbar button by default
Zoom removed
Removed options menu from the main menu
Redesigned preferences window

Monday, April 20, 2009

XUL Schema

I have published XUL Schema on CodePlex. It is fairly complete and it should work correctly for most scenarios. Get the latest source code and follow instructions from the home page. Here are some screenshots.
All comments are welcome. If something is not working correctly please report and I'll surely look in to it.
Again, consider this work in progress, it should work correctly for most of the scenarios but there are scenarios where it will break - for example templates with HTML contents. While I'm developing my extensions I keep updating the schema so from time to time check out the latest version of the code.

Monday, March 30, 2009

Filtering Bookmarks by Tags in Your Extension

It is not possible to filter by multiple tags in bookmarks window in Firefox. So I thought to try to see how it can be done. This exercise is interesting too to see how templates work and how to change the query for template in runtime. I prepared a XUL window that is just enough to demonstrate the point.
First of all get to know Places, the heart of the Firefox's bookmarks and history management system. The Places database is where Firefox keeps all it's records on your bookmarks and history, and it is simply SQLite db/file. Here's the schema and the database itself can be found at Firefox's profiles folder, the file is named places.sqlite. You can view it using SQLite Database Browser.
With all the links out of the way lets look at the example file:


<?xml version="1.0"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
title="Bookmarks - Test"
>

<script type="application/x-javascript">

var someSelectionQuery =
'select p.title as title ' +
'from moz_bookmarks_roots r ' +
'join moz_bookmarks tagsRoot on r.folder_id = tagsRoot.id ' +
'join moz_bookmarks tags on tagsRoot.id = tags.parent ' +
'join moz_bookmarks b on b.parent = tags.id ' +
'join moz_places p on b.fk = p.id ' +
"where r.root_name = 'tags' and tags.title in ({tags}) " +
'group by b.fk ' +
'having count(b.fk) = {count}';

function doOnSelect(tagList){
var items = tagList.selectedItems;
var count = tagList.selectedCount;
var query = document.getElementById('bookmarkslistQuery');

var tags = '';
var countStr = count + '';

for(var i=0; i &lt; count; i++){
if(i != 0) tags += ',';
tags += "'" + tagList.selectedItems[i].label + "'";
}

var realQuery = someSelectionQuery.replace('{tags}', tags);
realQuery = realQuery.replace('{count}', countStr);

query.textContent = realQuery;

var ml = document.getElementById('mainList');
ml.builder.rebuild();
}
</script>

<listbox datasources="profile:places.sqlite" ref="*" querytype="storage" seltype="multiple" id="tagsList" onselect="doOnSelect(this);">
<template>
<query>
select tags.title
from moz_bookmarks_roots r
join moz_bookmarks tagsRoot on r.folder_id = tagsRoot.id
join moz_bookmarks tags on tagsRoot.id = tags.parent
where r.root_name = 'tags'
</query>
<action>
<listitem uri="?" label="?title"/>
</action>
</template>
</listbox>

<listbox datasources="profile:places.sqlite" ref="*" querytype="storage" id="mainList">
<template>
<query id="bookmarkslistQuery">
select b.title as title
from moz_bookmarks_roots r
join moz_bookmarks tagsRoot on r.folder_id = tagsRoot.id
join moz_bookmarks tags on tagsRoot.id = tags.parent
join moz_bookmarks b on b.parent = tags.id
</query>
<action id="blaction">
<listitem uri="?" label="?title"/>
</action>
</template>
</listbox>

</window>



OK, lets quickly go through this. Lets skip the JavaScript part at the beginning, at the end there are two listbox elements that have templates. The first listbox is showing a list of your tags. It's very simple SQLite template. Datasource attribute defines the datasource on which the query will be executed. In this case the file is profile:places.sqlite, and that is Places database, other required attribute is querytype which is marking that our datasource is a SQLite file. We have very simple template that has only query and action elements. Query is just a SQLite query, simple, once you figure out what is where in the database. And template is using tag titles as labels in listitem. What it will do is just render a list of tags, and by selecting tags bookmarks will be filtered.
The second listbox is rendering all tagged bookmarks, that's just to show something until a tag is selected. The label is name of bookmarked page. It is pretty much the same thing as with tags listbox. It is going to be a bit different though, since the query is going to be changed dynamically.
So finally the JavaScript. doOnSelect function is doing all the work (well there's no other function there). On selection of tag, multiple can be selected, the function is just making a list of tag names separated by commas, updating the someSelectionQuery to include filter on tags. There is a little trick on group by and having - we're just making sure that particular place has the exact number of occurences as the number of tags selected. Basically all selected tags have to be parents of the bookmarked page, so that is what the query is checking. Oh, I forgot to mention Places SQL queries best practices.
At the end of the function we need to call builder.rebuild() on the templated element to update the content since the query is changed, it won't be updated by itself.
Phew, lots of work for such an ugly screen.

Sunday, March 8, 2009

Setup Your Virtual Folder During Deployment

After manually setting http handlers for a particular folder in IIS 5 and 6 and then seeing how it can be done programmatically we are ready to use it in our deployment.
It is quite simple actually. Here are the things we need to do:

1. Create web setup project
2. Create custom installer action (it's in VB though, but it is simple enough)
3. Override Commit method of the created installer class
4. Add your action to be executed in the commit phase of the setup

Steps 1 and 2 should be straight forward, however steps 3 and 4 require some more explanation. First of all the web setup project exposes two useful properties through Installation Address User Interface Dialog Box (that's just where you select your site and virtual folder): TARGETSITE and TARGETVDIR. These are as their names suggest: site (its metabase value) and virtual folder where the deployment will occur. That simplifies our life in a way that we can get a reference to our virtual folder's metabase entry like this:


webSiteId = Context.Parameters["TARGETSITE"];
webSiteId = webSiteId.Substring(webSiteId.LastIndexOf('/') + 1);

virtualDirectory = Context.Parameters["TARGETVDIR"];

var myVirtualDir = new System.DirectoryServices.DirectoryEntry(
"IIS://localhost/W3SVC/" + webSiteId +"/ROOT/" + virtualDirectory);

Piece of cake right?
Now, what have we done is get TARGETSITE parameter, and we took just the site's id (because the format doesn't exactly fit), we used TARGETVDIR and composed the path we can use in DirectoryEntry constructor. After getting the directory entry you just do your configuration magic.
There is one piece of the puzzle missing - how do we get those context parameters into our custom installer action? As the documentation says the parameters are available, however we still need to pass them into our action as part of the custom action data. Nothing easier:



Or in plain text: /TARGETVDIR="[TARGETVDIR]" /TARGETSITE="[TARGETSITE]"
And at the end, the answer to Why do we do this in the commit phase? Because the web setup will overwrite anything you change before the commit phase. It is creating the virtual folder, or setting default configuration to it if it already exists, in the commit phase. This means that not before the commit phase that you have the correct starting point for your changes.

So to conclude: nothing ground breaking, just lots of simple little steps that you cannot really quickly figure out from confusing MSDN; unless, of course you are making your living out of deployment projects and you knew all this already.