- Pequeña introducción a XUL
- Nuestro primer programa en XUL
- Un programa más complejo
- Comunicándonos con el usuario
- Otros widgets de XUL
- Creación de Menús
- RDF
- El registro chrome
- Modularidad en XUL
- Nuestra primera extensión
- XPI
Pequeña introducción a XUL
XUL es un lenguaje basado en XML para el desarrollo de interfaces de usuario multiplataforma del que se dice será la pesadilla de .NET de Microsoft. Su nombre deriva de las siglas de XML-based User-interface Language y fue creado por la gente del proyecto Mozilla apoyándose en su motor de renderizado, Gecko, que se encarga tanto del visualizado de webs como de la construcción de la interfaz de usuario.
Si quereis saber más sobre XUL, podeis encontrar una lista de sus principales ventajas en el documento 'The Joy of XUL' y algún tutorial en sitios como XUL-Planet.
Nuestro primer programa en XUL
Para que veais por encima la estructura de un programa en XUL vamos a escribir un programa de tipo ‘Hola Mundo’, aunque como todos los programadores que se creen originales y rebeldes en lugar de un ‘Hola Mundo’ nuestro programa mostrará alguna frase geek; por ejemplo ‘I know kung foo’.
Usando un editor de texto cualquiera (¿que tal vim?) escribimos lo siguiente, teniendo cuidado con las mayúsculas y las minúsculas, ya que XML es case sensitive, y lo guardamos como foobar.xul por ejemplo:
Código:
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<!--Esto es una ventana-->
<window id="ventana-principal" title="The Kung Foo Style" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<box>
<button label="Matrix has you" onclick="alert('I Know Kung Foo');" />
</box>
</window>
Si abrimos Mozilla o Firefox, nos vamos a Archivo→Abrir y seleccionamos nuestro fichero foobar.xul veremos algo similar a esto:
Ahora que ya habeis visto la sencillez de XUL vamos a ir línea por línea para ver que es cada cosa. Con la primera línea, <?xml version=”1.0”?> lo que hacemos es declarar este archivo como un archivo xml. Un documento XUL siempre tiene que empezar con esta línea, no tiene mas importancia.
La segunda línea nos permite indicar que hoja de estilo queremos utilizar para definir el aspecto de la aplicación. Estamos hablando de documentos css como los que se utilizan para definir el estilo de una página web, si; mientras XUL define que widgets vamos a utilizar en nuestra aplicación se usa css para definir el aspecto que tendrán estos. En este ejemplo en concreto estamos importando global.css referenciado por chrome://global/skin/ (chrome:// es un tipo especial de URL, podríamos haber utilizado una hoja de estilos creada por nosotros si quisieramos tal como si fuera un documento HTML).
En la tercera línea tenemos un comentario. Los comentarios siempre tienen que estar entre <!– y –> como define XML y es ignorado al interpretar el documento XUL.
Debajo, en la cuarta línea, estamos creando una ventana nueva con <window>, cuyos atributos son id para darle un nombre y poder referenciarla mas tarde, title que indica el texto a mostrar en la barra de título y xmlns que indica el espacio de nombres a utilizar. Un espacio de nombres indica los elementos que se reconocen como válidos a la hora de interpretar el fichero; en este caso en concreto solo se reconocerán los elementos definidos en XUL, eso es lo que hace la URL http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul (notar el there is only xul).
En la sexta línea tenemos una etiqueta box, por box model, el modelo de gestión del layout (como se colocan los objetos dentro de la ventana) que utiliza XUL. Probad a eliminar el <box> y </box>, vereis que la interfaz se descoloca.
Dentro del box utilizamos la etiqueta button para crear un botón que tendrá como texto a mostrar ‘Matrix has you’, definido por el atributo label. Ese onclick le sonará a la gente que utilice javascript; lo que hace es que al pulsar sobre nuestro botón (onclick) muestre un alert con el texto que le pasamos como argumento. Como habreis imaginado esto completa el cuadro de las aplicaciones XUL (por ahora): XUL define los widgets de la aplicación, con css definimos el aspecto de estos y con javascript su funcionamiento.
Por último cerramos la etiqueta window con </window>, tal como se hace con las etiquetas de HTML (XHTML es un subconjunto de XML, como lo es XUL, de ahí las semejanzas).
Un programa más complejo
Vamos a ver algún nuevo concepto sobre XUL. El programa que veremos esta vez es un poco mas complejo que el que vimos en la anterior entrega. Para empezar creamos una carpeta para el programa, por ejemplo c:\caffeine\ con una imagen de tipo gif con nombre aceptar y creamos nuestro fichero caffeine.xul dentro de ese directorio con el siguiente texto:
Código:
<?xml version="1.0"?>
<window id="ventana-principal" title="Ventana" width="400" height="200" screenX="20" screenY="20" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<box>
<button id="boton1" class="dialog" label="Aceptar" image="file://c:\caffeine\aceptar.gif" accesskey="a"/>
<button image="file://c:\caffeine\aceptar.gif" disabled="true" accesskey="c"/>
<button/>
</box>
</window>
Como veis tenemos la primera línea que indica que el documento es un documento XML. Al crear la nueva ventana en la siguiente línea vemos algún atributo que no conociamos aún: width que indica el ancho de la ventana en pixels, height que hace lo propio con el alto y screenX y screenY que nos da la posición inicial de la ventana en pixels.
Dentro de la ventana como vemos se defininen tres botones. En el primer botón que declaramos, boton1, tenemos un atributo class que indica la clase de botón que es boton1 (nos sirve para definir estilos distintos en la hoja de estilo, si no sabes css no te interesa); un atributo image, para mostrar una imagen en el botón y el atributo acceskey que asocia un acelerador con el botón.
El segundo botón que creamos tiene la peculiaridad de que no tiene texto (no tiene un atributo label), sino que simplemente tiene una imagen. Además esta deshabilitado con el atributo disabled y tiene como tecla de acceso directo ‘c’. Por último el tercer botón ilustra el que se puede crear objetos que no tengan atributos. En este caso dado que no definimos un texto a mostrar y tampoco una imagen el botón estará vacio.
Si hacemos doble click sobre el archivo caffeine.xul veremos algo parecido a esto:
Como último apunte, fijaros en que he comentado que tenemos que abrir el archivo haciendo doble click sobre el en lugar de hacerlo desde Archivo→Abrir. Esto es así por que si lo hicieramos de la segunda forma tendríamos una ventana dentro de la ventana de nuestro navegador, con lo cual no se vería el efecto de cambiar el tamaño de la ventana o cambiar el título. Además como apunte, si utilizais Mozilla Firefox y al hacer doble click sobre el archivo .xul os dice algo parecido a ‘c is not a registered protocol’ teneis que cambiar la asociacion para los archivos xul de firefox.exe -chrome “%1” que será seguramente lo que tengais a firefox.exe -chrome “file:%1”//.
Comunicándonos con el usuario
En este tercer ejemplo sobre XUL vamos a construir una versión mas elegante del Hola Mundo que nos permita introducir el texto que queremos que salga en el alert. El código del programa es el siguiente:
Código:
<?xml version="1.0"?>
<window id="ventana-principal" title="Caffeine" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<vbox>
<hbox>
<label value="Texto a mostrar"/>
<textbox id="miTexto" maxlength="10"/>
</hbox>
<button label="Mostrar" onclick="var texto=document.getElementById('miTexto');alert(texto.value);"/>
</vbox>
</window>
Ejecutad el programa y lo primero que vereis es que la ventana no ocupa todo el espacio del escritorio aún cuando no hemos especificado un ancho y un alto. Esto es así por el layout manager de XUL que se encarga de calcular la posición y el tamaño de los elementos de la interfaz de forma que no haya problemas cuando el usuario cambie el tamaño de las ventanas, por ejemplo. El layout manager de XUL se basa en dos etiquetas principalmente que son <vbox> y <hbox>. Con vbox lo que hacemos es indicar al gestor que posicione los elementos que van a estar dentro de <vbox></vbox> uno debajo de otro; con hbox, que es equivalente al <box> que hemos usado en ejemplos anteriores, le indicamos que los coloque de izquierda a derecha. Combinando estas dos etiquetas, espaciadores y los elementos flexibles podemos crear layouts complejas.
Si echamos un vistazo al código además de un vbox y un hbox de los que ya hemos hablado vemos dos cosas nuevas, el widget label y el widget textbox. Un label o etiqueta sirve para mostrar un texto en pantalla (un texto corto, para textos largos usariamos description) y el textbox o caja de texto para dar la posibilidad de escribir al usuario. El atributo value de label indica el texto que se mostrará. En el caso del textbox le damos un nombre con id, miTexto, ya que lo necesitaremos en el código javascript para obtener el texto que escribió el usuario en él. Además usamos el atributo maxlenght para indicarle que el máximo número de caractéres que pueden introducirse es 10.
Como último apunte un vistazo al onclick del botón ya que aquí es donde ocurre toda la magia. Como ya vimos en el ejemplo del ‘Hola Mundo’, onclick define una acción que se ejecutará cuando se haga click sobre ese elemento; en este caso cuando se presione el botón mostrar se ejecutará el código javascript que le pasamos. No es el objeto de estas entradas el explicar javascript pero basta con saber que se declara una variable llamada texto que apunta al elemento de la interfaz cuyo nombre es ‘miTexto’ y después se crea una ventana alert con el texto que contenga este.
Otros widgets de XUL
Esta vez vamos a terminar de ver los widgets básicos de XUL, junto con los botones, las cajas de texto o las etiquetas que ya vimos anteriormente. El código del programa ejemplo es el siguiente:
Código:
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<tabbox orient="vertical">
<tabs orient="horizontal">
<tab label="Radiobuttons"/>
<tab label="Checkboxes"/>
<tab label="Listbox"/>
<tab label="Imagenes"/>
</tabs>
<tabpanels>
<radiogroup orient="vertical">
<radio label="Cafe solo"/>
<radio label="Cafe con leche"/>
<radio label="Descafeinado"/>
</radiogroup>
<box>
<checkbox label="Donuts"/>
<checkbox label="Croisants"/>
<checkbox label="Caracolas"/>
</box>
<image src="file://C:\widgets\mozilla.png"/>
</tabpanels>
</tabbox>
</window>
Las pestañas se declaran mediante la etiqueta tabbox. orient nos dice si la orientación, en este caso de las pestañas, será horizontal o vertical. Cada una de las pestañas esta compuesta por dos componentes diferenciados, las pestañas en si, que vienen definidas por una serie de etiquetas tab dentro de un elemento tabs y el contenido de las pestañas que se define dentro de tabpanels. Dentro de tabpanels habrá tantos elementos como pestañas definamos en tabs (esto no es necesariamente cierto pero vamos a simplificar) y se seguirá una correspondecia por orden de declaración a la hora de asignar el contenido que corresponde a cada pestaña. En este caso, por ejemplo, en la pestaña 1 tenemos un elemento radiogroup compuesto por varios elementos radio, en la pestaña 2 tenemos una serie de checkboxes que agrupamos en un box para que sea un solo elemento y en la tercera pestaña tenemos una imagen.
Veamos ahora los otros widgets. En primer lugar, en la primera pestaña tenemos radiobuttons, que representan opciones excluyentes. Un grupo de radiobuttons se define con radiogroup y cada uno de los radiobuttons con radio, como podemos ver. Los checkboxes son opciones no excluyentes que se definen con la etiqueta checkbox. Por último las imágenes se definen con la etiqueta image que tiene como atributo la ruta a la imagen (src de source, fuente).
Creación de Menús
Antes de empezar con cosas mas serias vamos a ver como se implementan los menús en XUL.
Código:
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<menubar>
<menu label="Archivo">
<menupopup>
<menuitem label="Nuevo"/>
<menuseparator/>
<menuitem label="Abrir"/>
<menuitem label="Salvar"/>
<menuitem label="Cerrar"/>
</menupopup>
</menu>
<menu label="Editar">
<menupopup>
<menuitem label="Deshacer"/>
<menuseparator/>
<menuitem label="Copiar"/>
<menuitem label="Cortar"/>
<menuitem label="Pegar"/>
</menupopup>
</menu>
<menu label="Ver">
<menupopup>
<menuitem label="Regla"/>
<menu label="Fuente">
<menupopup>
<menuitem label="Arial"/>
<menuitem label="Verdana"/>
</menupopup>
</menu>
<menu label="Color">
<menupopup>
<radiogroup>
<radio label="Rojo"/>
<radio label="Azul"/>
<radio label="Negro"/>
<radio label="Verde"/>
</radiogroup>
</menupopup>
</menu>
<menuitem label="Pantalla Completa"/>
</menupopup>
</menu>
</menubar>
</window>
menubar define la barra de menús. Dentro de menubar tenemos un conjunto de etiquetas menu que son las que definen las entradas de menú en si. Si al pulsar sobre el menú se va a desplegar una lista de submenús, entonces utilizamos menupopup y menuitem para cada uno de los submenús. En el caso de que no hubieramos agrupado los submenús (menuitems) en un menupopup se habrían mezclado todas las entradas en una sola, que no es lo que queremos. Además tenemos otro elemento que es menuseparator, que introduce una línea para dividir el menú en partes. Notar también que los submenús también pueden ser menús con varias entradas como vemos en el caso de fuente en que definimos un nuevo menú y no un menuitem. Como último comentario decir que en lugar de menuitems o menús, como entradas dentro de los menús también podríamos tener otros elementos como checkboxes o radiobuttons como vemos en el caso del menú color.
RDF
Vamos a hacer un inciso para hablar sobre RDF. RDF es un estándar de la W3C cuyo nombre deriva de las siglas de ‘Resource Description Framework’ y que es un subconjunto de XML, al igual que XUL, que nos sirve para expresar hechos (cosas que son ciertas, la información en forma de predicados que tenemos sobre el mundo real). Sería algo parecido a los predicados de Prolog. Por ejemplo, un predicado del tipo ‘A Linus le gustan los pingüinos’ en RDF podría expresarse como:
Código:
<Description about="Linus">
<le-gustan>pingüinos</le-gustan>
</Description>
La etiqueta Description sirve para representar uno o mas hechos, con un atributo about para indicar sobre quien es ese hecho, es decir, el sujeto del hecho. Description puede contener una o varias etiquetas hijas siendo cada una de ellas un predicado.
Código:
<Description about="Linus">
<le-gustan>pingüinos</le-gustan>
<informatico>true</informatico>
<linuxero>true</linuxero>
<nacionalidad>finlandesa</nacionalidad>
</Description>
En este ejemplo definimos una serie de predicados sobre Linus, con una etiqueta definida por nosotros mismos para desribir ese predicado y con el valor entre las etiquetas.
Ahora, ¿como expresariamos el hecho de que a Linus le gustan además de los pingüinos los sombreros o los camaleones?. Podríamos usar varios predicados le-gustan pero RDF nos proporciona un método mejor. En RDF se definen tres etiquetas que sirven como contenedoras: <Bag>, que define un conjunto de items; <Seq>, que define un conjunto de items ordenados y <Alt>, que define un conjunto de elementos que son alternativos, es decir, que se consideran equivalentes. Los elementos de un contenedor se encierra entre <li> y </li> (li de list, lista, al igual que la etiqueta de XHTML). Veamos un ejemplo:
Código:
<Description about="Linus">
<Bag ID="Gustos">
<li>pingüinos</li>
<li>sombreros</li>
<li>camaleones</li>
</Bag>
<informatico>true</informatico>
<linuxero>true</linuxero>
<nacionalidad>finlandesa</nacionalidad>
</Description>
Ahora tenemos una lista de los gustos de Linus usando un Bag que sería equivalente a un predicado con varios valores. Para identificar el predicado que define este Bag usamos el atributo ID. Seq y Alt serían similares, tan solo cambia su significado.
Para finalizar esta pequeña introducción a RDF vamos a ver un ejemplo completo para explicar un par de cosas que quedan en el tintero.
Código:
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:BEBIDAS="http://www.caffeine.com/rdf#">
<RDF:Bag about="http://www.caffeine.com/tipos-de-bebida">
<RDF:li>
<RDF:Description about="http://www.caffeine.com/infusiones/te">
<BEBIDAS:nombre>Te</BEBIDAS:name>
<BEBIDAS:precio>1,12</BEBIDAS:precio>
</RDF:Description>
</RDF:li>
<RDF:li>
<RDF:Description about="http://www.caffeine.com/infusiones/tila">
<BEBIDAS:nombre>Tila</BEBIDAS:name>
<BEBIDAS:precio>0,9</BEBIDAS:precio>
</RDF:Description>
</RDF:li>
<RDF:li>
<RDF:Description about="http://www.caffeine.com/cafes/solo">
<BEBIDAS:nombre>Cafe solo</BEBIDAS:name>
<BEBIDAS:precio>1,2</BEBIDAS:precio>
</RDF:Description>
</RDF:li>
</RDF:Bag>
</RDF:RDF>
Lo primero que tenemos que comentar es la línea con la que comienza el RDF. Esta es similar al window de XUL en el que definimos que espacio de nombres vamos a utilizar. Usaremos RDF que está definida en tal URL y BEBIDAS que está definido en tal otra. Relacionado con esto, el RDF: que aparece antes de las palabras clave de RDF o el BEBIDAS: que aparece antes de los nombres de los predicados indica el espacio de nombres que estamos utilizando, es decir, RDF:li por ejemplo significa que nos estamos refiriendo a la etiqueta li que está definida dentro de RDF.
El registro chrome
Hasta ahora dividiamos un programa XUL en la interfaz en si, creada con XUL; el aspecto de los widgets, definido con CSS y la funcionalidad, programada en javascript. Sin embargo javascript no nos permite cosas como acceder al disco duro, de modo que si tuvieramos que limitar la funcionalidad de los programas basados en XUL a lo que podemos hacer con javascript, estos no tendrían demasiada utilidad.
La solución a este problema nos viene dada en forma de una libreria escrita en código dependiente de la plataforma llamada XPCOM y una capa intermedia entre javascript y esta librería llamada XPConnect, que traduce los objetos XPCOM en objetos javascript que puedan ser manipulados en nuestro programa.
El problema de esto es que la seguridad de Mozilla dejaría mucho que desear si cualquier script pudiera hacer cosas como borrar los archivos del disco duro. Aquí es donde vemos la utilidad de chrome.
Como ya se comentó la url chrome:// se refiere al directorio chrome dentro del directorio de instalación de Mozilla/Firefox. Solo los scripts que se encuentren en este directorio tienen permiso para utilizar XPConnect, lo cual implica que para usar los objetos de XPCOM necesitamos instalar nuestra aplicación en chrome. Sin embargo no basta simplemente con mover nuestros ficheros a ese directorio, si no que tenemos que añadir una serie de entradas a installed-chrome.txt para que nuestra aplicación sea añadida al ‘registro chrome’ de forma que Mozilla pueda resolver las urls chrome:// al directorio en que esta instalada nuestra aplicación.
Lo primero que tenemos que hacer es crear un archivo de tipo rdf llamado contents.rdf que proporcionará información a Mozilla sobre el nombre de la aplicación, el autor, la versión...
Código:
<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
<RDF:Seq about="urn:mozilla:package:root">
<RDF:li resource="urn:mozilla:package:caffeine"/>
</RDF:Seq>
<RDF:Description about="urn:mozilla:package:caffeine"
chrome:displayName="The Caffeine XPerience"
chrome:author="Zootropo"
chrome:name="caffeine"
chrome:extension="true"/>
</RDF:RDF>
En este ejemplo displayName es el nombre completo para la aplicación; author el autor de la aplicación, name el identificador de la aplicación (como se referirá Mozilla a él, tiene que ser igual que el valor después de urn:mozilla:package:) y extension indica si la aplicación es una extensión, de forma que se mostrará en la lista de extensiones de Mozilla en el caso de que sea true (cierto).
Una vez creado nuestro fichero contents.rdf lo copiamos a la carpeta de nuestra aplicación. Editemos ahora el fichero installed-chrome.txt que se encuentra dentro del directorio chrome, teniendo en cuenta cerrar antes el navegador, para registrar nuestra aplicación de forma que Mozilla sepa donde encontrarla. Al final del fichero añadimos por ejemplo para el caso de que el directorio de la aplicación sea caffeine/content/caffeine/ dentro del directorio chrome:
Código:
content,install,url,resource:/chrome/caffeine/content/caffeine/
Solo queda borrar el fichero chrome.rdf que contiene la información sobre las extensiones registradas en chrome para que vuelva a crearse cuando ejecutemos Mozilla/Firefox y tenga en cuenta nuestra aplicación. La información de cada una de las aplicaciones la recolectará basándose en installed-chrome.txt para encontrarla y en el contents.rdf que creamos para procesar el contenido de nuestra aplicación.
Ahora deberíamos ser capaces de poder ejecutar cualquier archivo xul de nuestra aplicación (y por añadidura referirnos a cualquier archivo de nuestra aplicación) usando una url chrome de la forma:
Código:
chrome://id-aplicacion/content/archivo.xul
Por ejemplo para abrir el archivo foo.xul de la aplicación con id caffeine podríamos simplemente escribir chrome://caffeine/content/foo.xul
Modularidad en XUL
A la hora de organizar los ficheros de nuestra aplicación se sigue un estándar de facto en el que tenemos tres subdirectorios dentro de la carpeta de nuestra aplicación: content, local y skin.
El directorio content incluye un subdirectorio con el nombre de nuestra aplicación que a su vez contiene todos los archivos xul de la aplicación, además de los archivos js con el código javascript de la aplicación y un fichero contents.rdf que nos dice entre otras cosas cual es el archivo .xul principal.
En el directorio locale se incluyen una serie de subdirectorios con el código del idioma correspondiente, por ejemplo es-ES para castellano o en-US para inglés de estados unidos. Dentro de cada uno de estos directorios tenemos un subdirectorio con el nombre de la aplicación que incluye a su vez los ficheros DTDque sirven para separar la aplicación de los mensajes de esta para facilitar el proceso de traducción y un fichero contents.rdf.
Por último el directorio skin contiene otro subdirectorio de nombre classic que incluye un directorio con el nombre de nuestra aplicación con los ficheros css que se utilizan para modificar el aspecto de la aplicación y un archivo contents.rdf.
Hasta ahora el código javascript que hemos utilizado estaba incrustado dentro de los ficheros xul, en el siguiente ejemplo veremos como importar el código de un fichero js separado del código xul. Como simple comentario, el código css que hemos utilizado hasta ahora estaba almacenado en archivos css aparte como dicta la norma para conseguir modularidad; sin embargo, al igual que el código javascript, el código css puede almacenarse en un fichero aparte o incrustado dentro del propio archivo xul.
Código:
<?xml version="1.0"?>
<!DOCTYPE window SYSTEM "chrome://caffeine/locale/caffeine.dtd">
<?xml-stylesheet href="chrome://caffeine/skin/caffeine.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:nc="http://home.netscape.com/NC-rdf#">
<script type="application/x-javascript"
src="chrome://caffeine/content/caffeine.js"/>
<button onclick="funcion();" label="&nombreBoton;" accesskey="&teclaAcceso"/>
</window>
La línea de script es la que se encarga de importar el código javascript desde el fichero javascript especificado de forma que podemos usar la función funcion() al hacer click en el botón aunque no la hallamos definido en este fichero. Podemos ver también la forma en que se importan los ficheros dtd con los mensajes de la aplicación en la segunda línea del código fuente. Hasta ahora no habíamos utilizado ficheros dtd, sino que incluíamos el texto de la aplicación imbuida dentro de esta; ahora con estos ficheros llevamos la modularidad de xul hasta el extremo, separando del código xul el funcionamiento, la apariencia y los mensajes.
El uso de los mensajes que se definen en los DTDs podemos verlo en el label y la accesskey del botón, un símbolo ampersand, el nombre que le dimos al mensaje en el dtd y punto y coma. El fichero DTD para este programa sería:
Código:
<!ENTITY nombreBoton "Mi Boton">
<!ENTITY teclaAcceso "M">
Para cada mensaje como vemos tenemos un !ENTITY, la segunda entrada de !ENTITY es el nombre del mensaje que se utilizará para referirse a él en el código XUL como ya vimos y el tercero el valor. En este DTD nombreBoton es “Mi Boton” pero podríamos tener otro DTD para el inglés en cuyo caso sería “My Button”, etc, de forma que la traducción de la aplicación es mas sencilla al separar el código y los mensajes.
Por último como ya dijimos cada uno de los directorios de la aplicación incluye un fichero contents.rdf para poder registrar ese directorio en el registro chrome. Veamos el caso del contents.rdf de los locales:
Código:
<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
<!-- Lista de todos los locales que incluimos, en este caso solo es-ES -->
<RDF:Seq about="urn:mozilla:locale:root">
<RDF:li resource="urn:mozilla:locale:es-ES"/>
</RDF:Seq>
<!-- Informacion sobre cada uno de los locales -->
<RDF:Description about="urn:mozilla:locale:es-ES"
chrome:displayName="Castellano (ES)"
chrome:name="es-ES">
<chrome:packages>
<RDF:Seq about="urn:mozilla:locale:es-ES:packages">
<RDF:li resource="urn:mozilla:locale:es-ES:caffeine"/>
</RDF:Seq>
</chrome:packages>
</RDF:Description>
</RDF:RDF>
Y el contents.rdf de los skins:
Código:
<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
<RDF:Seq about="urn:mozilla:skin:root">
<RDF:li resource="urn:mozilla:skin:classic/1.0" />
</RDF:Seq>
<RDF:Description about="urn:mozilla:skin:classic/1.0">
<chrome:packages>
<RDF:Seq about="urn:mozilla:skin:classic/1.0:packages">
</RDF:Seq>
</chrome:packages>
</RDF:Description>
</RDF:RDF>
Estos dos contents.rdf no necesitan comentarios. Son muy parecidos al contents.rdf del directorio contents. Tan solo queda registrar en el registro chrome contents, locals y skins. Si recordamos en el anterior post sobre XUL al registrar el contents añadíamos a installed-chrome.txt una línea similar a esta:
Código:
content,install,url,resource:/chrome/caffeine/content/caffeine/
Si echamos un vistazo a las demás entradas en installed-chrome veremos que algunas comienzan con skin, otras con locale y otras con contents. Esto es lo único que tenemos que cambiar a la hora de registrar skins y locales, lo demás es similar, por lo tanto para nuestra aplicación de ejemplo tendríamos que añadir las líneas:
Código:
skin,install,url,resource:/chrome/caffeine/content/caffeine/
locale,install,url,resource:/chrome/caffeine/locale/es-ES/
content,install,url,resource:/chrome/caffeine/skin/classic/caffeine/
Nuestra primera extensión
Ahora que hemos visto un poco por encima como funciona XUL vamos a ponernos un poco mas serios y vamos a crear una pequeña extensión para nuestro navegador. Por ahora algo sencillo como añadir una nueva entrada de menú al menú de herramientas de Firefox, que nos muestre un alert con la hora del sistema cada vez que se haga click sobre él. El proceso para Mozilla sería un proceso bastante parecido. Para ello tenemos que ver que son los overlays, cuya traducción al castellano sería algo asi como superposiciones.
Lo primero vamos a recordar que toda la interfaz de Mozilla y Firefox esta hecha con XUL, por lo tanto será razonable suponer que en algún lugar tiene que haber un fichero xul que lo define. Y acertariais al hacer esta suposición. La interfaz del navegador esta definida en un archivo llamado browser.xul en el subdirectorio chrome dentro de la carpeta en la que esta instalado nuestro navegador, pero si entrais en esa carpeta para echar un vistazo al código no lo encontrareis. browser.xul esta empaquetado dentro de uno de esos archivos .jar que veis.
Un archivo jar es simplemente un archivo .zip con la extensión renombrada. Podemos descomprimirlo con winzip u otro programa de compresión de archivos. Si descomprimimos browser.jar tendremos una carpeta browser que alberga una carpeta content en la que tenemos a su vez las carpetas browser y browser-region. Dentro de la primera se encuentra además del browser.xul al que nos referíamos otros archivos .xul, archivos js con código javascript y archivos css, hojas de estilo.
Si abrimos el archivo browser.xul tendremos una ventana del navegador de Firefox pero con la peculiaridad de que no veremos las barras de herramientas, los botones u otros widgets de las extensiones que tengamos instaladas. Esto es así por que cuando abrimos una ventana del navegador estamos abriendo browser.xul, pero también otros archivos .xul que se superponen. Sería algo así como coger varios dibujos en papel cebolla y ponerlos uno encima de otro, algo así como las capas de Photoshop. Esto es lo que vamos a ver ahora, el overlay.
Lo primero que tenemos que hacer es crear una carpeta para nuestra aplicación, por ejemplo ‘hora’ completando una estructura de directorios hora/content/hora. Dentro del directorio mas profundo de esta jerarquía creamos un archivo hora.xul con el siguiente código:
Código:
<?xml version="1.0"?>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:nc="http://home.netscape.com/NC-rdf#">
<popup id="menu_ToolsPopup">
<menuitem
oncommand="fecha=new Date(); hora=fecha.getHours(); minuto=fecha.getMinutes(); segundo=fecha.getSeconds(); alert(hora + ' : ' + minuto + ' : ' + segundo);" label="Mostrar Hora;"/>
</popup>
</overlay>
Lo que estamos haciendo aquí es crear un documento XUL que solo contiene una entrada de menú dentro de un popup que queremos superponer sobre el navegador. La única diferencia con los ejemplos que hemos visto hasta ahora es que en lugar de definir una ventana definimos un overlay pero aparte del nombre no hay mucha diferencia. Simplemente indicamos a Mozilla que este no es un documento xul completo, si no que vamos a combinarlo con otro/s documentos xul.
Ahora veamos como funcionaría el overlay con un caso sencillo antes de pasar a mayores. Supongamos un archivo XUL que importa el overlay que acabamos de definir; para ello usaría algo parecido a este código:
Código:
<?xul-overlay href="chrome://hora/content/hora.xul"?>
Lo que ocurriría entonces es que para cada elemento con un id definido en el archivo de overlay se buscaría el mismo elemento con el mismo id en el documento sobre el que queremos superponer. Antes de nada tendríamos que haber abierto el fichero browser.xul para ver donde se definen las entradas de menú del menú herramientas y cual es el ID de conjunto que las contiene. Ese trabajo ya esta hecho; como se puede ver en el código tenemos un elemento popup con id menu_ToolsPopup, aquí es donde está la clave.
En el caso de que no se encuentre una instancia de ese elemento con ese ID entonces este elemento no se tiene en cuenta al hacer el overlay. Si por el contrario si se encuentra, entonces el elemento y todo lo que se defina dentro de este en el overlay pasará a superponerse al documento raíz, lo cual quiere decir que el elemento del documento raíz adquirirá todos los atributos que se definan en el overlay además de todos sus subelementos.
En este ejemplo el documento raíz es el que se encargaba de añadir el overlay usando la etiqueta xul-overlay, sin embargo esto no nos es de utilidad en nuestro caso ya que no queremos tener que cambiar el código de browser.xul. La solución es hacer que sea el overlay el que diga a que documento se tiene que superponer. Esto podemos hacerlo añadiendo la información de los overlay a nuestro contents.rdf
Código:
<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:chrome="http://www.mozilla.org/rdf/chrome#">
<RDF:Seq about="urn:mozilla:package:root">
<RDF:li resource="urn:mozilla:package:hora"/>
</RDF:Seq>
<RDF:Description about="urn:mozilla:package:hora"
chrome:displayName="Mostrar Hora 0.1"
chrome:author="Zootropo"
chrome:authorURL="http://zootropo.blogspot.com"
chrome:name="hora"
chrome:extension="true"
chrome:description="Añade una opción al menú de herramientas para mostrar la hora actual.">
</RDF:Description>
<RDF:Seq about="urn:mozilla:overlays">
<RDF:li resource="chrome://browser/content/browser.xul"/>
</RDF:Seq>
<RDF:Seq about="chrome://browser/content/browser.xul">
<RDF:li>chrome://hora/content/hora.xul</RDF:li>
</RDF:Seq>
</RDF:RDF>
Vemos que tenemos un par de listas nuevas, una referida a urn:mozilla:overlays en el que los elementos de la secuencia son los documentos sobre los que se va a hacer el overlay (en este caso el browser.xul para superponer a nuestro navegador) y otra secuencia con el archivo xul sobre el que hacemos el overlay como sujeto y como entradas de la secuencia los archivos xul a superponer.
Borramos overlay.rdf como en el post anterior, añadimos una entrada nueva a installed-chrome.txt para el content de nuestra aplicación (no tendremos locale porque la única función javascript que utilizamos esta embebida en el archivo xul ni skin ya que no utilizamos ningún css) y reiniciamos nuestro navegador, tras lo cual tendremos una nueva entrada al final del menú de herramientas que nos permitirá ver la hora del sistema.
XPI
Ahora que ya hemos terminado nuestra pequeña extensión para Firefox vamos a ver como podemos crear un xpi para que se instale automáticamente. La extensión xpi viene de XPInstall donde XP se refiere a Cross (X) Platform o multiplataforma, es decir, que puede ser ejecutado en distintas plataformas (Windows, Linux, Mac OS,...).
Un xpi es un simple archivo zip con extensión renombrada que contiene un fichero de instalación y los ficheros que componen la extensión. En el caso de las versiones anteriores a la 0.9 se utilizaba install.js como script instalador, ahora en cambio se utiliza un fichero rdf, install.rdf, pero podemos incluir ambos si queremos que la extensión se pueda instalar en la versión 0.9 y también en las anteriores.
Un archivo install.rdf tendría un aspecto parecido a este:
Código:
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>{eb78c871-3d9d-433f-b49b-12468119be89}</em:id>
<em:name>Mostrar Hora</em:name>
<em:version>0.1</em:version>
<em:description>Muestra la hora del sistema en un alert al hacer click sobre la entrada de menu correspondiente.</em:description>
<em:creator>Zootropo</em:creator>
<em:homepageURL>http://zootropo.blogspot.com </em:homepageURL>
<em:file>
<Description about="urn:mozilla:extension:file:hora.jar">
<em:package>/content/hora/</em:package>
</Description>
</em:file>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>0.9</em:minVersion>
<em:maxVersion>0.9</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>
Tenemos primero una serie de predicados de información sobre nuestra aplicación que son los siguientes:
- em:id que se refiere a un id único que podemos generar con esta utilidad de Microsoft y no al identificador de la extensión
- em:name, el nombre de la extensión
- em:version, la versión de nuestra extensión.
- em:description, una descripción de la utilidad de nuestra aplicación.
- em:creator, nombre del creador de la aplicación.
- em:homepageURL, página web de la extensión.
- em:optionsURL, ventana que se mostrará al hacer click sobre las preferencias.
- em:aboutURL, ventana que se mostrará como About de la aplicación.
- em:iconURL, icono para identificar la extensión.
- em:updateURL, URL opcional para la autoactualización.
Si no quereis complicaros podeis utilizar para crear los xpi un script creado por Ted Mielczarek que podeis encontrar en su página web. También podeis consultar la traducción del documento de Ben Gooder sobre install.rdf.
Ahora que tenemos todos los archivos necesarios lo primero que tenemos que hacer es crear un archivo jar que incluya los archivos de nuestra extensión. Comprimimos en un mismo archivo zip las carpetas content, locale y skin de nuestra aplicación (o content solo si no hemos definido las otras dos). Cambiamos la extensión del archivo resultante a .jar de forma que nos quede un archivo hora.jar en este ejemplo y ya tenemos el archivo jar que empaqueta nuestra aplicación, nos queda empaquetar el .jar y el archivo de instalación en un xpi. Para eso creamos una carpeta chrome a la que movemos hora.jar y comprimimos en un .zip la carpeta chrome y el install.rdf (e install.js en el caso de que lo tengamos) y renombramos la extensión del archivo a .xpi.
El archivo xpi resultante para que comprobeis como se instala automáticamente como cualquier otra extensión de firefox es este.
Fuente Original: Mundo Geek
Autorizado por el autor original: zootropo
Licencia: http://creativecommons.org/licenses/by/2.0/