Tag Archives: Ajax

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

Peticiones AJAX a SERVIDORES EXTERNOS

El otro día os hablaba de cómo invocar servicios de nuestro propio servidor usando request’s de Ajax.

Podéis ver un ejemplo de código que realiza peticiones Ajax [AQUI]. Como véis, NO FUNCIONA. Si utilizáis firebug veréis que la ejecución llega hasta la línea 27.

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
function makeRequest(url,param){
               url = url + param;
               http_request = false;
               if(window.XMLHttpRequest){ // Mozilla, Safari,...
                  http_request = new XMLHttpRequest();
                  if (http_request.overrideMimeType){
                      http_request.overrideMimeType('text/html');
                  }
               }
               else if (window.ActiveXObject){// IE
                  try{ http_request = new ActiveXObject("Msxml2.XMLHTTP");}
                  catch (e){
                        try{
                           http_request = new ActiveXObject("Microsoft.XMLHTTP");
                        }
                        catch (e2) {}
                   }
               }
               if (!http_request){
                  // uncomment next line if you want to show any error messages
                  alert('Cannot create XMLHTTP instance');
                  return false;
               }
               //alert("makeRequest(): url='"+url+"'");
               http_request.onreadystatechange = fromServer;
               <b>http_request.open('GET', url, false);
               // En este punto URL contiene una URL EXTERNA
               //(fuera de nuestro servidor)</b>
               http_request.send(param);
               return http_request;
          }

Además, si abrimos la consola de errores de Firefox, veremos algo parecido a


Error de seguridad: el contenido en

http://www.leccionespracticas.com/trabajo/buscador_zaguan.php

no puede cargar datos de http://olmo.unizar.es/search?ln=es&sc=1&p=water.

Error: uncaught exception:
[Exception... "Access to restricted URI denied"  
code: "1012" nsresult: "0x805303f4 (NS_ERROR_DOM_BAD_URI)" location: "http://www.leccionespracticas.com/trabajo/buscador_zaguan.php Line: 110"]

Buscando encontré el manual para hacer proxy desde javascript.

Ésto me dio la pista definitiva para solucionar el problema. Os cuento cómo lo he resuelto:

1. Creo el archivo php_proxy2.php con [éste código fuente] y MODIFICAMOS $vfileañadiendo la url BASE del sitio web al que queremos invocar mediante Ajax, esto es:

&lt;?php
   <font color="red">   $vfile = "http://olmo.unizar.es"; // Esta línea tiene la URL base del sitio </font>
      if (isset($_GET['dst'])){
         $vfile = $vfile.$_GET['dst'];
      }
      header("Content-type: text/xml");
      readfile($vfile);
?&gt;

Nota: Además, por motivos de seguridad es IMPRESCINDIBLE que utilicéis este parámetro base, ya que sino vuestra máquina se podría convertir en un proxy -desde el que se podrían hacer cosas malas, you know what I mean-

2. Hago el encode de la URL que deseo invocar y se la paso como parámetro al proxy ANTES de invocar a http_request.open (mira la línea 26). Ésto es, modificamos la funcion makeRequest, que quedará asi:

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
function makeRequest(url,param){
               url = url + param;
               http_request = false;
               if(window.XMLHttpRequest){ // Mozilla, Safari,...
                  http_request = new XMLHttpRequest();
                  if (http_request.overrideMimeType){
                      http_request.overrideMimeType('text/html');
                  }
               }
               else if (window.ActiveXObject){// IE
                  try{ http_request = new ActiveXObject("Msxml2.XMLHTTP");}
                  catch (e){
                        try{
                           http_request = new ActiveXObject("Microsoft.XMLHTTP");
                        }
                        catch (e2) {}
                   }
               }
               if (!http_request){
                  // uncomment next line if you want to show any error messages
                  alert('Cannot create XMLHTTP instance');
                  return false;
               }
               http_request.onreadystatechange = fromServer;
               var url_mod ='http://www.leccionespracticas.com/trabajo/php_proxy2.php?dst=' + encodeURIComponent(param);
               http_request.open('GET', url_mod, false);
               http_request.send(param);
               return http_request;
          }

El resultado podéis verlo [EN ESTE LINK]

Ficheros utilizados en este tutorial:
[1] Proxy PHP: php_proxy2.php
[2] Fichero ORIGINAL desde el que hago los Ajax Requests: buscador_zaguan_bueno.php

Javascript: depurando errores

Depurar errores al programar en Javascript puede ser un verdadero engorro. Si no utilizas ningún debugger tienes que llenar el código de alerts, borrando las cachés del navegador, …

Por eso yo utilizo dos herramientas muy completas:
1. JSLint:
Es un parser Javascript. Verifica que el código es sintácticamente correcto, informa de las variables no declaradas, etc.
Modo de funcionamiento: copiar tu código fuente (encerrado en tags <html><head> ... </head></html>) y pulsar el botón JSLint (en la parte inferior de la página).
JSLint javascript

2. Firebug:
Es un plugin para Firefox. Permite ejecución paso a paso del código Javascript, la inspección de variables, …
Modo de funcionamiento: Instalar el plugin, reiniciar Firefox, ir a la página que aloja el código que deseemos ejecutar, pulsar la pestaña “Scripts” y, en el fuente, añadir los breakpoints (botón derecho). Más tarde, disparar el evento que ejecute el código y usar los controles de ejecución y el inspector de variables.

firebug -1

firebug - 2

Ajax (I): peticiones HTTP – HTTP Requests

A menudo es útil realizar peticiones HTTP al servidor, para, por ejemplo, invocar un programa (sea php, python, asp…).
El siguiente ejemplo muestra el código Ajax para realizar una petición de este estilo.
Los resultados se almacenarán en el div RESULTSDIV

<script>
         function fromServer(){
                 if( http_request.readyState==4 ){
                     if( http_request.status==200 ){
                           // results from call to the server appear here
                           // in http_request.responseText;
                           e = document.getElementById("RESULTSDIV");
                           e.innerHTML = http_request.responseText;
                     }
                     else{
                           // uncomment next line if you want to show any error messages
                           alert('error: ' + http_request.responseText);
                     }
                 }
         }

         function makeRequest(url,param){
               url = url + param;
               http_request = false;
               if(window.XMLHttpRequest){ // Mozilla, Safari,...
                  http_request = new XMLHttpRequest();
                  if (http_request.overrideMimeType){
                      http_request.overrideMimeType('text/html');
                  }
               }
               else if (window.ActiveXObject){// IE
                  try{ http_request = new ActiveXObject("Msxml2.XMLHTTP");}
                  catch (e){
                        try{
                           http_request = new ActiveXObject("Microsoft.XMLHTTP");
                        }
                        catch (e2) {}
                   }
               }
               if (!http_request){
                  // uncomment next line if you want to show any error messages
                  alert('Cannot create XMLHTTP instance');
                  return false;
               }
               //alert("makeRequest(): url='"+url+"'");
               http_request.onreadystatechange = fromServer;
               http_request.open('GET', url, false);
               http_request.send(param);
          }
</script>

Un inciso para comentar algo importante:
Ese último parametro de http_request.open() indica si la petición será síncrona o asíncrona.
Es fácil de entender.
SINCRONA: Significa que la funcion javascript no seguirá interpretandose hasta que la operacion ajax finalize.

Código PHP:
var algo = newajax();
algo.open(algo,algo,false);
algo... //blabla
#Este alert solo se ejecutara cuando todo lo de arriba se haya interpretado y finalizado.
alert(222)

ASINCRONA: Significa que la funcion javascript pasará de largo de proceso ajax sin necesidad de esperar a que esté finalizado para seguir interpretandose es por eso que se usa los onreadystatechange osea… en que estado va tu proceso.

Código PHP:
var algo = newajax();
algo.open(algo,algo,true);
algo... //blabla

#Este alert se ejecutará sin importar que el proceso ajax haya finalizado .. pasa de largo.
alert(222)

Otra definición.
ASINCRONA: le vale un pepino tu proceso ajax y continua el interprete javascript.
SINCRONA: espera a que todo termine para continuar.

En el caso que me ocupa utilizo la siguiente función para invocar el procedimiento makeRequest:


        function busca(pal){
               var valor=document.forms[0].group1y.checked;
               //alert("busca(): "+valor);
               if (valor!=true){
                  document.getElementById("RESULTSDIV").innerHTML = "";
               }
               else{
                  var base='http://olmo.unizar.es';
                  var param='/search?ln=es&sc=1&p='+pal;
                  makeRequest(base,param);
               }
           }

También puede hacerse interpretando los valores que devuelve en la función busca. Para ello el código sería:


          function fromServer(){
                 if( http_request.readyState==4 ){
                     if( http_request.status==200 ){
                           // results from call to the server appear here
                           // in http_request.responseText;
                           //e = document.getElementById("RESULTSDIV");
                           //e.innerHTML = http_request.responseText;
                           //alert('fromServer(): responseText='+http_request.responseText);
                           var salida=http_request.responseText;
                           return salida;
                     }
                     else{
                           // uncomment next line if you want to show any error messages
                           alert('error: ' + http_request.responseText);
                     }
                 }
         }

         function makeRequest(url,param){
               url = url + param;
               http_request = false;
               if(window.XMLHttpRequest){ // Mozilla, Safari,...
                  http_request = new XMLHttpRequest();
                  if (http_request.overrideMimeType){
                      http_request.overrideMimeType('text/html');
                  }
               }
               else if (window.ActiveXObject){// IE
                  try{ http_request = new ActiveXObject("Msxml2.XMLHTTP");}
                  catch (e){
                        try{
                           http_request = new ActiveXObject("Microsoft.XMLHTTP");
                        }
                        catch (e2) {}
                   }
               }
               if (!http_request){
                  // uncomment next line if you want to show any error messages
                  alert('Cannot create XMLHTTP instance');
                  return false;
               }
               //alert("makeRequest(): url='"+url+"'");
               http_request.onreadystatechange = fromServer;
               http_request.open('GET', url, false);
               http_request.send(param);
               return http_request;
          }
          function busca(pal){
               var valor=document.forms[0].group1y.checked;
               //alert("busca(): "+valor);
               if (valor!=true){
                  document.getElementById("RESULTSDIV").innerHTML = "";
               }
               else{
                  var base='http://olmo.unizar.es';
                  var param='/search?ln=es&sc=1&p='+pal;
                  var out=makeRequest(base,param);
                  if (out!==undefined){
                       document.getElementById("RESULTSDIV").innerHTML = out.responseText;
                  }
             }

Un ejemplo de todo ello funcionando a la vez como elemento programando en Python disponible para descarga [aquí]