Get CKEditor textarea content using javascript [SOLVED]

There is this useful getData() function to retrieve the contents of a ckEDITOR text area element:

If you insert a ckEDITOR called ‘my_editor‘ then you can read the value that the user typed inside the editor’s textarea using this JS

       var editorText = CKEDITOR.instances.my_editor.getData();

Hope it helps! 🙂

jQuery en 10 minutos | guia para impacientes

¿Quieres aprender jQuery rápido, gratis y fácilmente? Si sabes ya algo de javascript, esto va a ser coser y cantar.

Incluir la librería

Siempre es recomendable delegar el hosting de la librería a google. ¿Por qué? lee esto.

<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>

Acceder a un elemento del DOM

Hay varias formas de acceder a los elementos del DOM. Las más comunes son por id, por clase o por elemento.

Si quieres, puedes consultar el listado completo de selectores en la web oficial.

Si eres impaciente, empieza viendo un ejemplo tonto de acceso de las tres formas.

Acceso por id

Asi se accede al elemento del DOM identificado por id=identificador

$("#identificador").click(function(){
  alert("hola");
});

Acceso por clase

Asi se accede a los elementos del DOM que sean de clase=’miclase’

$(".miClase").click(function(){
  alert("hola");
});

Acceso por nombre elemento

Asi se accede al PRIMER elemento del dom que sea un <a>

$("a").click(function(){
  alert("hola");
});

Acceso por otros métodos: name

Y una forma algo más elaborada de acceder a otros elementos. Por ejemplo, acceder al PRIMER input con name=first_name dentro del primer form que se encuentre:

$("input[name=first_name]");

El método de arriba es un poco lento porque hay que recorrer tooooodos los (posibles) forms del DOM. Es mejor atacar directamente al form en cuestión. Supongamos que el form id=myform.

$("#myform input[name=first_name]")

Funciones que se ejecutarán tan pronto como el DOM esté listo

Imagina que deseas que se cargue el dom completo antes de ejecutar algunas funciones. Lo harás asi:

<script type="text/javascript">
$(document).ready(function(){
                        // aqui defino lo que quiero que se ejecute cuando el DOM esté listo
 
			// Cambio el comportamiento del div con id=jeje ante el evento 'click'
			$("#jeje").click(function(){
    				alert('Has hecho click en la zona identificada por id=jeje');
			});
 
                        // también puedo llamar a funciones definidas en otro js
                        mifuncion();
 
		});
</script>

Prevenir la reacción estándar del navegador ante un evento

Los navegadores tienen un comportamiento predefinido frente a algunos eventos. Con jQuery podermos anularlo asi:

$("a").click(function(event){
    event.preventDefault();
    // mas codigo
});

Callbacks

Funciones que se pasan como parámetro a otra función y se ejecutan cuando la función padre termina.

Callback sin parámetros

Ejemplo:

$.get('myhtmlpage',myCallback);

Callback con parámetros

Ojo, hazlo asi:

$.get('myhtmlpage',function(){
  myCallBack(param1,param2)
});

Y no asi

$.get('myhtmlpage',myCallBack(param1,param2));

Si haces lo segundo, se pasará como nombre de función callback el resultado de ejecutar myCallBack(param1,param2)… lo que NO es correcto!

Además de pasar parámeotros por GET ($.get), también se admiten peticiones por POST ($.post) y otras. Consulta la lista de shorthand-methods completa en la web de jQuery

AJAX en jQuery

jQuery admite las peticiones asíncronas con la función jQuery.ajax. Por ejemplo este trozo de código llama a la url especificada y cuando se termina la ejecución del código de la misma, se ejecuta la función success. En data tendremos el resultado de la ejecución de la url.

$.ajax({
  url: 'ajax/test.html',
  success: function(data) {
    $('.result').html(data);
    alert('Load was performed.');
  }
});

Un ejemplo algo más completo de esto mismo (con su HTML y tal)

    <html>
    <head>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
        <script type="text/javascript">
            $(document).ready(function(){
                // Inicializo controlador del div con id=jeje
                $("#jeje").click(function(){
                    $.ajax({
                    url: 'callback.php',
                    success: function(data) {
                        $('#botones').html(data);
                        //alert('Load was performed. Data = '+data+'.');
                    }
                    });
                });
            });
        </script>
    </head>
    <body>
        <div id="jeje" style="background-color: #ff0000; width: 150px; height: 150px; border:1px solid;">
            Haz click por aqui dentro...
        </div>
        <div id="botones">
            Al hacer click arriba en esta zona aparecerán los botones
      </div>       
    </body>
</html>

Y el código php al que llamamos (callback.php) podría ser tan simple como:

<?php
  echo '<input type="button" id="aficha" value="Añadir Ficha" />';
  return 0;
?>

Eventos en jQuery

Si habéis leido hasta aqui, veréis que he usado varias veces el evento .click(). Hay muchos más eventos (consulta el listado completo de eventos). Su invocación y uso es similar al .click()
Es importante notar que no todos se pueden aplicar a todo tipo de elementos. Por ejemplo, el .select() solo es aplicable a elementos input-text y textareas de un form.

Manipulación del DOM

El siguiente grupo de elementos son los que hacen referencia a la manipulación del DOM.
El manual completo con todas las funciones se puede ver aqui

Por ejemplo, podemos añadir clase a un determinado elemento:

$("a").addClass("miclase");

pasando de:

<a href="mipagina.html">mipagina</a>

a

<a class="miclase" href="mipagina.html">mipagina</a>

De forma similar, podemos eliminar una clase:

$("a").removeClass("miclase");

Se pueden combinar ambas para obtener interesantes efectos:

$("a").removeClass("inactive").addClass("active");

Incluso poner clases múltiples:

$("a").addClass("miclase activo");

Otro de los manipuladores interesantes es .attr(), con el que podemos obtener y cambiar el valor de un atributo.

var eltitulo = $("div").attr("title") + "_modificado";
$("div").attr("title", eltitulo);
/* o lo que es lo mismo, $("div").attr("title") = eltitulo; */

Incluso podemos cambiar varios atributos con una sola llamada:

$("div").attr({
  title: 'mititulo',
  alt: 'texto alternativo'
});

Podemos hacer también un loop por los atributos usando .each():

function(target) {
    $.each($(target.attributes), function(index) {
      alert("Attribute name: " + target.attributes[index].name + " Attribute value: " + target.attributes[index].value);
});

Efectos

El último grupo importante que nos falta por ver. Por ejemplo, si queremos que un div se oculte, podemos usar .hide(). Incluso nos permite ajustar la velocidad con que queremos que se produzca este “escondido”.

Otros efectos básicos son .show() y .toggle().

Pero hay muchos más efectos (algunos bastante complejos). Podéis consultar el listado completo de efectos en la web de jQuery.

SMF 2.0 RC3: Flickr HTML code to BBCode mod [working]

We are developing a photographic website (with an SMF 2.0 RC3 forum) and most of our users have their photos hosted in Flickr.

(Note: I have installed WYSIWYG Quick Reply v2 RC3 package)

So I’d like to make their lifes easier when sharing their photos by adding a custom button (here in the post wysiwyg editor) which converts this html structure (the code snippet flickr shows to the user):

<a href="URL1" title="this_is_the_title"><img src="IMGURL" width="342" height="500" alt="alt_text" /></a>

To something like:

[url=URL1][img]IMGURL[/img][/url]

How to do this?

* Edit your Themes/default/GenericControls.template.php
* Locate:

function template_control_richedit($editor_id, $smileyContainer = null, $bbcContainer = null)
{
	global $context, $settings, $options, $txt, $modSettings, $scripturl;
 
	$editor_context = &$context['controls']['richedit'][$editor_id];

* Add after:

?>
	<script language="javascript">
	    function convierteCode(){
	    	// dado un codigo tipo flickr lo convierte a BBCode (def en GenericControls.template.php)
                        var flickr = document.getElementById("flickrcodeinput").value;
			flickr = flickr.replace('<a href="','[url=');
			flickr = flickr.replace('" /></a>','[/img][/url]');
			flickr = flickr.replace(/"(.)+title(.)*><img(.)+src="/,'][img]');
			flickr = flickr.replace(/"(.)+width(.)*\[/,'[');
			flickr = flickr.replace('[/url]','[/img][/url]');											 
			document.getElementById("message").value += flickr;
			window.frames[0].document.write(flickr);
	    }
	</script>
<?php

*Locate:

echo '
		<div>
			<div>
				<textarea class="editor"

* Replace with:

echo '
		<div>
			<div>
			    <!-- flickr input and button-->
                               <input type="text" id="flickrcodeinput" width="100px"></input>&nbsp;
                               <input type="button" value="Inserta IMG flickr" onClick="convierteCode();"></input>
                               <br /><br />
                            <!--flickr input and button end -->
				<textarea class="editor"

* Upload the new GenericControls.template.php

(Note for curious people: I also tried this by adding a custom button which would produce a new bbcode like [flickr][/flickr] and blahblahblah, but I could not make it work. More details here)

Javascript – sort tables (by column values) [easy and opensource]

If you see a table in a client-side application, you’ll expect to be able to click on the headers and have the table sort, would you not? I know it always annoys me when you can’t. A fair few web applications do allow this; most of them, which are pulling this data by submitting a SQL query to a relational database (an environment eminently suited to tabular data) implement this by resubmitting the whole page with something like ordercolumn=4 in the URL, and then adding an ORDER BY clause to their SQL query to return the data from the DB ordered by the specified column.

Resubmit the page? Just to sort data we already have? I’m sure we can do better than that: just using javascript!

Take a look at sorrtable javascript library and follow the simple steps to have a sortable table.

1. Download the Javascript library
2. Include the Javascript library, by putting a link to it in the HEAD of your page, like so:

      <script src="sorttable.js"></script>

3. Mark your table as a sortable one by giving it a class of “sortable”:

         <table class="sortable">

Note that the library’s JavaScript file is called sorttable (two Ts), but the class you add to the table is sortable (one T).

Easy, isn’t it? 😉

W3C validator issues with ampersand (&) in javascript location.href

When I tried to validate the code for http://www.hyips.es (one of my freelance projects) the validator kept complaining:

cannot generate system identifier for general entity langpair.

This error is caused by the validator parsing the code inside of a <script> tag. Below are the lines which were producing the error (related to a function which translates the page from spanish to english using google translator):

<script type="text/javascript"> 
function es2eng(){
  location.href = "http://translate.google.com/translate?u=" + this.location.href + "&langpair=es|en&h1=es&ie=UTF-8"; 
}
</script>
<a href="#" onclick="es2eng();"><img style="border: medium none; margin:1px;" src="/images/banderas/england-flag.gif" alt="translate to english" height="20" width="30" /></a>

After reading a lot of confussing comments (like Try to change ‘&’ to ‘&amp;’ or to ‘%26’), and just when I was about to define the function in an external js file (not a big fan of this, because the function was only one line long…), I came to this revealing post in webmasterworld.com.

The problem can be solved as easy as this: change

<script type="text/javascript">
your script is here
</script>

To:

<script type="text/javascript">
// <![CDATA[
your script is here
// ]]>
</script>

The change stops W3C’s validator from parsing the code inside the script tags, therefore the error disappears! 🙂

Browser detection with javascript

In a perfect world, all the major browsers would co-exist happily together, supporting the same set of objects and features. But then again, in that world, I’d be on an island enjoying the sun.

Before we reach that place, this tutorial looks at how to detect support for a particular JavaScript property or method before attempting to invoke it. It serves as an alternate, in many cases, superior way to strict browser detection for creating cross browser friendly scripts.

Here is an small example of how to detect the browser & version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<body>
 
<script type="text/javascript">
var browser=navigator.appName;
var b_version=navigator.appVersion;
var version=parseFloat(b_version);
 
document.write("Browser name: "+ browser);
document.write("<br />");
document.write("Browser version: "+ version);
</script>
 
</body>
</html>

Another (more practical) way to discriminate the browser can be done like this:

1
2
3
4
5
6
7
  var browser=navigator.appName;
  if (browser=="Netscape"){
       // mozilla, netscape, chrome, ...
  }
  else if (browser=="Microsoft Internet Explorer"){
       // Internet Explorer 4 or Opera with IE user agent
  }

Have fun 😉

DeepZoom & SEADragon (IV)

IMHO SEADragon should include some controls to go to next/previous image and not restrict the navigation between images to clicking the thumnails. So I decided to build these new custom buttons.

Documentation

Before you continue reading these post you should be familiar with SEADragon, DeepZoom and jCarousel. If you are not, please read the following posts:
[1] DeepZoom & SEADragon part one (build basic html page with the viewer)
[2] DeepZoom & SEADragon part two (use jCarousel to display thumbs)
[3] DeepZoom & SEADragon part three (remove SEADragon logo).

Adding custom controls to the viewer: some “easy” example

Before changing my code I decided to ask google if someone had added custom buttons to the viewer, and google, as usual, answered my question. In ajaxdaddy.com I found this -great- example of how to do it. It’s quite well explained there. Please read it carefully.

Adding custom controls to the viewer: integration with jCarousel

The example shown above is great. But I had a slightly diferent problem: I had to integrate the next/previous button behaviour with my thumbs carousel, so it moved forwards and backwards when necessary.

We well part from the previous example code to explain all the modifications.

  1. Change doOpen function: this function had two parameters, the first one was the item to show. I changed this parameter so it was the index to read, and not the item itself.
    I also defined a new global variable (I know, it is not the best choice, but it works) called selectedIndex to know what is the index corresponding to the shown image at any time. I did all this in order to make easier the next steps 😉

    This was my code *before* the modifications:

    function doOpen(item, anchor)
    {
         if(selectedItem)
            selectedItem.button.src = thumbsPath + selectedItem.thumb + '_rest.png';
     
         if(item){
            viewer.openDzi(item.dzi, item.xml);
            titleDiv.innerHTML = item.title;
            descDiv.innerHTML = item.desc;
     
            item.button.src = thumbsPath + item.thumb + '_selected.png';
            selectedItem = item;
     
            if (anchor) {
                   window.location.hash = "#" + item.thumb;
            }
        }
    }

    *After* the mods, my code is:

    var selectedIndex = null;
    function doOpen(index, anchor){
        item = data[index];
        if(selectedItem)
               selectedItem.button.src = thumbsPath + selectedItem.thumb + '_rest.png';
     
        if(item){
               viewer.openDzi(item.dzi, item.xml);
               titleDiv.innerHTML = item.title;
               descDiv.innerHTML = item.desc;
     
               item.button.src = thumbsPath + item.thumb + '_selected.png';
               selectedItem = item;
    	   selectedIndex = index;
     
               if (anchor) {
                    window.location.hash = "#" + item.thumb;
               }
        }
    }

    Of course the way of calling these functions also changes. Again, a before&after:
    *before*:

    doOpen(dataMap[hash] || data[0], false);


    *after*:

    doOpen(hash || 0, false);

  2. Download two images for the buttons. For instance, you can take this two:
    Next
    Previous

    Place them in ./img   folder and name them load_next_image.png and load_prev_image.png.

  3. Now, we have to add the new buttons and their behaviour, which is done by the following code (i show only the part for the “next” button, since the “prev” is pretty similar):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    	var nextControl = makeControlNEXT();
    	nextControl.style.marginLeft="4px";
    	nextControl.style.marginBottom="4px";
    	viewer.addControl(nextControl, Seadragon.ControlAnchor.TOP_RIGHT);				
     
    	/*
    	* Creates the new "next" button which does:
    	* (1) Show -if available- the next image in the SeaDragon viewer
    	* (2) Moves forwards -if necessary- the JCarousel so the
    	*/
            function makeControlNEXT() {
                var controlNEXT = document.createElement("a");
                var controlImg = document.createElement('img');
    	    controlImg.src="./img/load_next_image.png";
    	    controlImg.className="thumb";
    	    controlImg.title="next";
                controlNEXT.href = "#"; // so browser shows it as link
    	    controlNEXT.id = "controlNEXT2";  // this is QUITE important for the next part!! (jCarousel)
                controlNEXT.className = "controlNEXT";
                controlNEXT.appendChild(controlImg);
     
                Seadragon.Utils.addEvent(controlNEXT, "click", doOpenNEXT);
                return controlNEXT;
            }
     
            function doOpenNEXT(event) {
                 Seadragon.Utils.cancelEvent(event);    // so link isn't processed
     
                 if (!viewer.isOpen()) {
                    return;
                 }
     
                 if ( (selectedIndex+1) < count){ // if there is a next image to show...show it!
    		 doOpen(selectedIndex+1,true);
    	     }
         }

    Note the controlNEXT.id = "controlNEXT2"; in line 18. We will use this in next section to “connect” this to the function that must be executed in jCarousel.

  4. At this point you should have both buttons working with SEADragon. This is, when you click next button the viewer will show the next image. But there still remains something: notice the jCarousel in the bottom, it does not move forwards nor backwards when the displayed image changes! This is not a problem if you only have three images, but I’ll assume you’ll have more than three. So lets get to work.
  5. Modify the way jCarousel is built. Again, a before&after is shown:
    *before:

    <script type="text/javascript"> 
        jQuery(document).ready(function() {
             jQuery('#mycarousel').jcarousel();
        });
    </script>

    *after:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    <script type="text/javascript"> 
       function mycarousel_initCallback(carousel) {
                jQuery('.jcarousel-control a').bind('click', function() {
                       carousel.scroll(jQuery.jcarousel.intval(jQuery(this).text()));
                       return false;
                });
     
                 jQuery('#controlNEXT2').bind('click', function() {
    		    $.balance = selectedIndex% 3;
    		    $.bloquear = selectedIndex;
    		    if ($.balance == 0 && $.bloquear!=0){ // must the carousel be moved?
      			carousel.next();
    		    }
    		    return false;
    	      });
     
      	      jQuery('#controlPREV2').bind('click', function() {
    		    $.balance = selectedIndex% 3;
    		    $.bloquear = selectedIndex;
    		    if ($.balance == 2 && $.bloquear!=0){
      		        carousel.prev();
    		    }
    		    return false;
    	      });
       };
     
       jQuery(document).ready(function() {
            if ($.balance === undefined) $.balance = -1;
               jQuery('#mycarousel').jcarousel(
    		{
    			scroll: 3,
                            initCallback: mycarousel_initCallback
    		}
    	    );
             });
    </script>

    Notice the controNEXT2 in line 17? It is the same id that we had in section 3, line 18.

So, the behaviour when clicking “next” or “previous” buttons in the top of the viewer is:
– First, call doOpenNEXT or doOpenPREV which makes SEADragon viewer to show the next / previous image.
– Second, call the function defined in section 5, line 18 which makes jCarousel to move forwards or backwards if necesary.
– We use selectedIndex variable to decide if the jCarousel must or mustn’t move.

You can see a FULL EXAMPLE HERE.

Solving the browser issues: IE8 vs Mozilla

If you run the example above in a Mozilla/Netscape browser it will work fine. But if you try to do it in a Internet Explorer browser, the jCarousel will not move when it has to.

This is related with the order of execution of functions:
– Mozilla: it executes first doOpenNEXT / doOpenPREV and then the function next of jCarousel.
– IE8: the order of execution is first the function of jCarousel and then the others.

So selectedIndex is not updated when it has to be in IE8 and it fails to move the jCarousel when it has to.

Please read my browser detection with javascript post before doing anything. Then modify the jCarousel functions as in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* browser detection! */
var browser=navigator.appName;
var b_version=navigator.appVersion;
var version=parseFloat(b_version);
jQuery('#controlNEXT2').bind('click', function() {
	if (browser=="Netscape"){
          // mozilla, netscape, ...
	  $.balance = selectedIndex% 3;
	  $.bloquear = selectedIndex;
	}
	else if (browser=="Microsoft Internet Explorer"){
          // Internet Explorer 4 or Opera with IE user agent
          $.balance = (selectedIndex+1)% 3;
	  $.bloquear = (selectedIndex+1);						
	}
	if ($.balance == 0 && $.bloquear!=0){
  	     carousel.next();
	}
	return false;
});
 
jQuery('#controlPREV2').bind('click', function() {
   if (browser=="Netscape"){
        $.balance = selectedIndex% 3;
	$.bloquear = selectedIndex;
  }
  else if (browser=="Microsoft Internet Explorer"){
        $.balance = (selectedIndex-1)% 3;
        $.bloquear = (selectedIndex-1);
  }
  if ($.balance == 2 && $.bloquear!=0){
       carousel.prev();
  }
  return false;
});

Now it works fine!
Visit the FULL FINAL EXAMPLE!

DeepZoom & SEADragon (II)

In my previous DeepZoom & SeaDragon post I explained how to build a web gallery using SEADragon.

Show thumbs in JCarousel

I dont like the thumbs to be displayed all at once, so I decided to use a jCarousel instead. jCarousel is a jQuery plugin for controlling a list of items in horizontal or vertical order. The items, which can be static HTML content or loaded with (or without) AJAX, can be scrolled back and forth (with or without animation).

jCarousel is quite simple to use. In order to have a carousel you include the JCarousel libraries (available for download in ) and build a code like the following.

1
2
3
4
5
6
7
8
<ul id="mycarousel" class="jcarousel-skin-tango">
    <li><img src="http://static.flickr.com/66/199481236_dc98b5abb3_s.jpg" width="75" height="75" alt="" /></li>
    <li><img src="http://static.flickr.com/75/199481072_b4a0d09597_s.jpg" width="75" height="75" alt="" /></li>
    <li><img src="http://static.flickr.com/57/199481087_33ae73a8de_s.jpg" width="75" height="75" alt="" /></li>
    <li><img src="http://static.flickr.com/77/199481108_4359e6b971_s.jpg" width="75" height="75" alt="" /></li>
    <li><img src="http://static.flickr.com/58/199481143_3c148d9dd3_s.jpg" width="75" height="75" alt="" /></li>
    <li><img src="http://static.flickr.com/72/199481203_ad4cdcf109_s.jpg" width="75" height="75" alt="" /></li>
  </ul>

Its very simple: an <il> for each <img>, everything wrapped in an <ul> called, for instance, mycarousel.

You can see a super-simple example of JCarousel HERE.

JCarousel & SEADragon

Before you continue reading you should understand the code shown in
my first DeepZoom & SeaDragon post. You can see it working HERE.

The modifications to show the thumbs in a JCarousel are quite simple (I will only show the relevant part of the new code):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<script type="text/javascript" src="ajax/0.8/seadragon-branded.js"></script>
<script type="text/javascript" src="data2.js"></script>
<script type="text/javascript">
                    var viewerDiv = document.getElementById('viewer');  
                    var thumbsDiv = document.getElementById('thumbs');
                    var titleDiv = document.getElementById('title');
                    var descDiv = document.getElementById('desc');
		    var botonesDiv = document.getElementById('botones');
                    viewerDiv.innerHTML = "";       // for the CMS
                    var viewer = new Seadragon.BrandedViewer(viewerDiv);   
                    var selectedItem = null;
 
    		    var thumbsPath = 'http://155.210.85.31/dzi/miguel/mini/'; 
 
                    function doOpen(item, anchor)
                    {
                        if(selectedItem)
                            selectedItem.button.src = thumbsPath + selectedItem.thumb + '_rest.png';
 
                        if(item)
                        {
                            viewer.openDzi(item.dzi, item.xml);
                            titleDiv.innerHTML = item.title;
                            descDiv.innerHTML = item.desc;
 
                            item.button.src = thumbsPath + item.thumb + '_selected.png';
                            selectedItem = item;
 
                            if (anchor) {
                                window.location.hash = "#" + item.thumb;
                            }
                        }
                    }
 
 
		    /* here begins the IMPORTANT PART... */
		    var ul = document.createElement('ul');
                    ul.id    = "mycarousel";
		    ul.className = "jcarousel-skin-tango";
		    thumbsDiv.appendChild(ul);
 
                    var count = data.length;
                    var dataMap = {};
                    var a;
 
 
		     for (a = 0; a < count; a++)
                    {
                        var item = data[a];
                        var image = document.createElement('img');
 
                        // index item by its thumb name
                        dataMap[item.thumb] = item;
 
                        image.src = thumbsPath + item.thumb + '_rest.png';
                        image.className = "thumb";
 
                        image.title = item.title;
                        image.id = a;
			image.name = a;
 
                        Seadragon.Utils.addEvent(image, "click",
                                Seadragon.Utils.createCallback(null, doOpen, item, true));
 
                        thumbsDiv.appendChild(image);
                        item.button = image;
 
			var li = document.createElement('li');
			li.appendChild(image);
 
			ul.appendChild(li);
                    }
 
                    // if the page's hash is set, open image with that hash
                    // if there is one. otherwise, just open the first image.
                    // note that if there's a hash, it'll begin with #.
                    var hash = (window.location.hash || " ").substr(1);
                    doOpen(dataMap[hash] || data[0], false);    // don't anchor
 
                </script>
                <script type="text/javascript"> 
                     jQuery(document).ready(function() {
                            jQuery('#mycarousel').jcarousel();
                     });
				</script>
</div>

DeepZoom & SEADragon (I)

There are plenty of times when you want to see something closer, to get a good look at the texture of a sculpture, or find out if that’s a reflection or a scratch on that used car you’re looking at.
Seadragon, implemented as the Deep Zoom feature of Silverlight, allows you to do that. But what if you’re not using the Silverlight platform? That’s what Seadragon Ajax is for.

Seadragon Ajax, written from the ground up in JavaScript, gives you the ability to add a Deep Zoom viewer into your blog, web site, or even your eBay listing.

Let’s see an example of how this works.

First of all, you should be confortable with the DZI (deep zoom image) Schema definition and you should download Microsoft’s DeepZoom composer and, of course, read its manual.

The DeepZoom generated files

Lets suppose you have done all this and generated a new dzi image. Now you should have a directory (I call it ./pagina1) which looks like this:
Deep Zoom Composer results

Let’s take a closer look at this files:

  • dzc_output.xml: this is just a file which shows the XSD used and the image-related data, such as width, height, tilesize, etc…

    <?xml version="1.0" encoding="utf-8"?><Image TileSize="256" Overlap="1" Format="jpg" xmlns="http://schemas.microsoft.com/deepzoom/2008"><Size Width="1820" Height="2312" /></Image>

  • SparseSceneImageGraph: this file contains information related to generation of the image tile.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    <?xml version="1.0"?>
    <SceneGraph version="1">
      <AspectRatio>0,78719723183391</AspectRatio>
      <SceneNode>
        <FileName>C:\Documents and Settings\jrey\Mis documentos\Expression\Deep Zoom Composer Projects\ANTIFON2\Source Images\ms00418_0001.jpg</FileName>
        <x>0</x>
        <y>0</y>
        <Width>1</Width>
        <Height>1</Height>
        <ZOrder>1</ZOrder>
      </SceneNode>
    </SceneGraph>

  • scene.xml: this file indicates how the tiles are composed to obtain complete images. The composed image takes into account the zoom level.
  • dzc_output_files: it contains the tiles in a directory structure like this:
    Deep Zoom dzc_output_images folder

Aditional and necessary files

First of all, download and unzip the seadragon viewer and some other files to the parent directory.

You must create the thumbnails for the page too. The parent directory of thumnails must be called mini. For each image we will have two images: for instance, for thumb called pagina1 we will generate the thumbnails pagina1_rest.png and pagina1 _selected.png.

Now, you must build a file (I call it data2.js) which contains a directory structure like the one showed above for each image.
Here is an example of data2.js file which tells SEADragon the title of the image, the thumb, which points to the folder containing the structure (this is, pagina1), the description for that image and where to find the dzi file and the xml contents of that dzi file.

1
2
3
4
5
6
7
8
9
var data = [
    {
        title: 'Antifonario moz&aacute;rabe',
        thumb: 'pagina1',
        desc: 'Antifonario moz&aacute;rabe: p&aacute;gina 1. Procede del Monasterio de San Juan de la Pe&ntilde;a',
        dzi: 'pagina1/dzc_output.xml',
        xml: '<?xml version="1.0" encoding="utf-8"?><Image TileSize="256" Overlap="1" Format="jpg" xmlns="http://schemas.microsoft.com/deepzoom/2008"><Size Width="1820" Height="2312" /></Image>'
	}
]

How to use it in a web page

Now that are familiar to the directory structure and the generated files, lets face the problem of generating an html page which shows the files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
 
<script type="text/javascript">var _sf_startpt=(new Date()).getTime()</script>  
 
		<title>Gallery : Microsoft Live Labs</title>
		<link rel="stylesheet" href="/files/themes/labs/styles.css" type="text/css" media="screen" />
 
 
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://livelabs.com/api/rsd.ashx" />
<link rel="wlwmanifest" type="application/wlwmanifest+xml" title="WLWManifest" href="http://livelabs.com/api/wlwmanifest.ashx" />
 
</head>
<body>
 
	<div id="wrapper">
 
	<div id="content_box"> 
		<div id="content_area" >
 
<div id="middle_column" class="content_double_column">
 
<!-- CMS Start -->
<p><style type="text/css">
 
                    #viewer
                    {
                        width: 640px;   
                        height: 480px;  
                        background-color: black;
                        color: white;   /* for error messages, etc. */ 
                        /* border -- on all sides */
                        border: 1px solid black
                    }
 
                    #metadata
                    {
                        /* dimensions */
                        /*width: 564px;   /* plus (1px border + 7px padding) on each side */
			width: 626px;
                        height: 80px;
                        padding: 7px;
 
                        /* color */
                        background-color: #888888;
                        color: white;
 
                        /* border -- don't duplicate border with viewer */
                        border-bottom: 1px solid black;
                        border-left: 1px solid black;
                        border-right: 1px solid black;
                    }
 
                    #metadata #title
                    {
                        font-size: large;
                    }
 
                    #metadata #desc
                    {
                        font-size: small;
                    }
 
                    #desc a:link, 
                    #desc a:visited, 
                    #desc a:hover, 
                    #desc a:active
                    {
                        color:#ffffff;
                        text-decoration:underline;
                    }
 
                    #thumbs
                    {
                        width: 580px;
                        margin-top: 20px;
                    }
 
                    .thumb
                    {
                        position: relative;
                        cursor: pointer;
                    }
 
                </style></p>
<div id="viewer">&nbsp;</div>
<div id="metadata">
<div>
<div id="title">&nbsp;</div>
 
<div id="desc">&nbsp;</div>
</div>
</div>
<div id="thumbs">&nbsp;</div>
<script type="text/javascript" src="ajax/0.8/seadragon-branded.js"></script>
<script type="text/javascript" src="data2.js"></script>
<script type="text/javascript">
 
                    var viewerDiv = document.getElementById('viewer');  
                    var thumbsDiv = document.getElementById('thumbs');
                    var titleDiv = document.getElementById('title');
                    var descDiv = document.getElementById('desc');
 
                    viewerDiv.innerHTML = "";       // for the CMS
 
                    var viewer = new Seadragon.BrandedViewer(viewerDiv);   
                    var selectedItem = null;
		    var thumbsPath = './mini/'; 
 
                    function doOpen(item, anchor)
                    {
                        if(selectedItem)
                            selectedItem.button.src = thumbsPath + selectedItem.thumb + '_rest.png';
 
                        if(item)
                        {
                            viewer.openDzi(item.dzi, item.xml);
                            titleDiv.innerHTML = item.title;
                            descDiv.innerHTML = item.desc;
 
                            item.button.src = thumbsPath + item.thumb + '_selected.png';
                            selectedItem = item;
 
                            if (anchor) {
                                window.location.hash = "#" + item.thumb;
                            }
                        }
                    }
 
                    var count = data.length;
                    var dataMap = {};
                    var a;
                    var x = 0; 
                    var y = 0;
 
                    for (a = 0; a < count; a++)
                    {
                        var item = data[a];
                        var image = document.createElement('img');
 
                        // index item by its thumb name
                        dataMap[item.thumb] = item;
 
                        image.src = thumbsPath + item.thumb + '_rest.png';
                        image.className = "thumb";
                        image.style.left = (16 * x) + 'px';
                        image.style.top = (16 * y) + 'px';
                        image.title = item.title;
 
                        Seadragon.Utils.addEvent(image, "click",
                                Seadragon.Utils.createCallback(null, doOpen, item, true));
 
                        thumbsDiv.appendChild(image);
                        item.button = image;
 
                        x++;
                        if(x >= 9)
                        {
                            x = 0;
                            y++;
                            var br = document.createElement('br');
                            thumbsDiv.appendChild(br);
                        }
                    }
 
                    // if the page's hash is set, open image with that hash
                    // if there is one. otherwise, just open the first image.
                    // note that if there's a hash, it'll begin with #.
                    var hash = (window.location.hash || " ").substr(1);
                    doOpen(dataMap[hash] || data[0], false);    // don't anchor
 
                </script><!-- CMS End -->
 
</div>
</div>
</div>
 
<div id="cap_bottom"></div>
</div>
 
</div><!-- END wrapper -->
 
 
	<!-- SiteCatalyst code version: H.1. Copyright 1997-2005 Omniture, Inc. More info available at http://www.omniture.com -->
  <script language="JavaScript">var s_account="msnportallivelabs";</script>
  <script language="JavaScript" src="http://stj.msn.com/br/om/js/s_code.js"></script>
  <script language="JavaScript">
    <!--
        s.linkInternalFilters="javascript:,.live.,.livelabs.";s.trackExternalLinks=true;
    s.server="livelabs.com";s.channel="livelabs.com";
        s.prop1="livelabs.com";s.prop2="en-us";
        /************* DO NOT ALTER ANYTHING BELOW THIS LINE ! **************/
        var s_code=s.t();if(s_code)document.write(s_code)//-->
  </script>
 
  <script language="JavaScript"> <!-- if(navigator.appVersion.indexOf('MSIE')>=0)document.write(unescape('%3C')+'\!-'+'-')//--> </script>
  <noscript>
    <img src="http://msnportallivelabs.112.2O7.net/b/ss/msnportallivelabs/1/H.1--NS/0" height="1" width="1" border="0" alt="" />
  </noscript>
  <!--/DO NOT REMOVE/-->
  <!-- End SiteCatalyst code version: H.1. -->
	</body>
 
</html>

If you are familiar with javascript and html the code shown is quite self-explainable.

You can see a working example here.

IMHO the thumnails visualization quite ugly, so I decided to add a JCarousel to view these thumnails. This process is quite simple. I will show it how to do it in a subsequent post 😉

Want to know more about Deep Zoom images?

Visit the following links:
[1] Detailed mathematical explanation on how the tile algorithm works
[2] Python implementation of Deep Zoom tile images
More interesting info