SlideshowGallery


I've made a first version of the AjaxGallery using some minor Java Script techniques. I always wanted to have a gallery that offers a slideshow so I can quickly browse through the pictures of one gallery. Some time ago I stumbled about a really neat javascript that does just that but the list of image files was hardcoded into it. So I took this and extended it with php. I think this combination is really cool.
I finally managed to get this Slideshow widget into the wiki without any need for popups like they were used in the last version.

What it can do


- Creates a table with thumbnails from any directory that contains image files (jpg, gif and png)
- generates the thumbnails and saves them in a .thumbs directory
- on click shows the SlideShow Widget right over the wiki page and sticks to the window (which basically means even if you scrolled down before you click a picture the position is fixed, so you might change the size and information underneath could still be read)
- more than one gallery per page is possible

slideshow script:

- automatically zooms to the size of the image or to the size of the window (if the image is larger)
- lets you click through the images manually or continues automatically after a few seconds (this is deactivated by default)
- a title or additional text could be displayed dynamically with any picture (this is deactivated but if you're JavaScript-Experienced you can turn it on, just need to know where the text comes from

What it can't do


- doesn't work in IE 6 (or earlier)
- no fallback for non-Ajax browsers (which might be good)

planned features


- close button inside the picture (like the two others)
- any other ideas?

Demo


See it work on https://yodahome.de/wiki/SandBox

Prerequisites / Installation


Your server needs GD support for PHP installed for the thumbnails. Furthermore this extension depends on the JavaScript library called Prototype and script.aculo.us. prototype.js, scriptaculous.js and the other .js files of the the script.aculo.us package need to be placed in a subdirectory scripts in the action directory. These two lines then need to be put into header.php:

<script src="actions/scripts/prototype.js" type="text/javascript"></script>
<script src="actions/scripts/scriptaculous.js" type="text/javascript"></script>



All in all there should be these files on your wikka installation:

wikka/actions

gallery.php (you may choose another name for the action)
grafx.php

wikka/actions/scripts

prototype.js
ajax-slideshow.js
net.js
scriptaculous.js
effects.js
builder.js
controls.js
dragdrop.js
effects.js
slider.js

Furthermore there are some images which I placed in a subdirectory images (I know, very creative).

I've created a handy zip file that includes all the additional files but NOT the action itself (gallery.php, grafx.php and css styles are listed below). Of course you also need 'pictures to show' located in some separate directory.

Including the action any wikka page:

{{gallery dir="path/to/pictures/" id="1|2|3|4..."}}

IMPORTANT: The id MUST BE SET. First time you use the action start with 1 and increase the number with any following usage per page.

This should work in any Browser with basic JavaScript. It's been tested sucessfully in Mozilla Firefox 1.5, Internet Explorer 7 (beta), Opera 8, Konqueror 3.5 and I think a friend of mine even tried it in Safari (probably latest version as of March 2006).
On Opera 9 Beta the switches at the sides to go forward and backward do not appear and you can't go forward or backward. Apart from that it works. Any further experiences are much appreciated.

The reason for putting some of the code in another file named grafx.php is that this way the necessary functions are included only once even if you include the action two or more times and so the functions don't bother each other. There's probably even a better or more object-oriented way to do that so feel free to improve it and post your changes.

Some Source Code


actions/gallery.php

<?php

require_once './actions/grafx.php';

//framework output
$dir = $vars['dir'];
$id = $vars['id'];

//$dir = $vars['dir']; // First thing to do, I suppose is to retrieve all of the filenames in the directory
    if ( !is_dir($dir) )
        {
        $output = "That directory does not exists (or it could be a file!)";
        } else
        { // Does the thumbs directory exist? If not then make it...
        $thumbdir = $dir."/.thumbs";
        if ( true !== file_exists($thumbdir) )
        {
        mkdir($thumbdir);
        }
        $files = GetFileList($dir);
        // Right, with that done, we need to see if a thumb exists for each pic
        // If it doesn't, then we gots to create one!
        // Thumbs will be called tn_filename.jpg
echo'   <script type="text/JavaScript" charset="utf-8">
            photoDir['
.$id.'] = "'.$dir.'";
            photoArray['
.$id.'] = new Array(
        // Source, Width, Height, Caption
'
;     
        $files = GetFileList($dir);
        $count = 1;
        foreach ( $files as $filename )
            {
            $size = getimagesize($dir.$filename);
            if ($count>=count($files)) {
                echo "new Array(\"".$filename."\", \"".$size[0]."\", \"".$size[1]."\", \"\")\n";
            } else echo "new Array(\"".$filename."\", \"".$size[0]."\", \"".$size[1]."\", \"\"),\n";
            $count=$count+1;
            }

echo '
);
   
    // Number of photos in this gallery
    photoNum = photoArray['
.$id.'].length;
    </script>'
;

       
        $position = 0;
        $output .= "<center><table cellpadding=\"0\" cellspacing=\"5\" width=\"75%\"> <tr>";
        foreach ( $files as $filename )
            {
            $thumbname = "tn_".$filename;
            if ( true !== file_exists($thumbdir."/".$thumbname) )
                {
                createthumb($dir,$filename);
                }
            $output .= "<td><a href=\"\" style=\"border:0px;\" onclick=\"galId=$id;photoId=$counter;cyclePhoto(".$id.",photoId);Effect.toggle('viewer','appear');return false;\"><img src=\"$thumbdir/$thumbname\" border=\"0px\" style=\"border : 0px;\"/></a></td>\n";
            $position += 1; $counter += 1;
            if ( $position == "4" )
                {
                $output .= "</tr>";
                $position = 0;
                $output .= "<tr>";
                }
            }
            $output .= "</tr>";
        }
        $output .= "</table></center>"; print($output);


?>


grafx.php

<?php

$counter = 0;

function GetFileList($dirname, $ext = FALSE)
    {
    if(!$ext) //EXTENSIONS OF FILE YOU WANNA SEE IN THE ARRAY
    $ext = array("jpg", "png", "jpeg", "gif");
    $files = array();
    $dir = opendir($dirname);
    while(false !== ($file = readdir($dir)))
        { //GET THE FILES ACCORDING TO THE EXTENSIONS ON THE ARRAY
        for ($i = 0; $i < count($ext); $i++)
            {
            if (eregi("\.". $ext[$i] ."$", $file))
                { $files[] = $file; }
            }
        } //CLOSE THE HANDLE
        closedir($dir); //ORDER OF THE ARRAY
        sort($files);
        return $files;
    }      

//thumbnail: generate thumbnails  http://www.alt-php-faq.org/local/105/
  function createthumb($dir,$filename)
{  
    $thumb_path = $dir."/.thumbs/tn_".$filename;
    $thumb_width = 100;
    if(strtolower(substr($filename,strlen($filename)-4,strlen($filename))) == ".png"){
        $src_img = imagecreatefrompng("$dir/$filename");
    }elseif(strtolower(substr($filename,strlen($filename)-4,strlen($filename))) == ".jpg" || substr($filename,strlen($filename)-4,strlen($filename)) == "jpeg" ){
        $src_img = imagecreatefromjpeg("$dir/$filename");
    }elseif(strtolower(substr($filename,strlen($filename)-4,strlen($filename))) == ".gif"){
        $src_img = imagecreatefromgif("$dir/$filename");
    }
    $origw=imagesx($src_img);
    $origh=imagesy($src_img);
    $new_w = $thumb_width;
    $diff=$origw/$new_w;
    //$new_h=$new_w;
    $new_h=$origh/$diff;

    // the folowing line is commented out to get a better thumbnail,  but the imagecreatetruecolor only works with gd 2 or higher
    //$dst_img = imagecreate($new_w,$new_h);
    $dst_img = imagecreatetruecolor($new_w,$new_h);
    imagecopyresized($dst_img,$src_img,0,0,0,0,$new_w,$new_h,imagesx($src_img),imagesy($src_img));

    imagejpeg($dst_img, "$thumb_path");
    return true;
}

echo '

<script src="actions/scripts/ajax-slideshow.js" type="text/JavaScript" charset="utf-8"></script>

    <script type="text/JavaScript" charset="utf-8">
    // <![CDATA[
   
    // -----------------------------------------------------------------------------------
    //
    // This page coded by Scott Upton
    // http://www.uptonic.com | http://www.couloir.org
    //
    // This work is licensed under a Creative Commons License
    // Attribution-ShareAlike 2.0
    // http://creativecommons.org/licenses/by-sa/2.0/
    //
    // Associated API copyright 2002, Travis Beckham (www.squidfingers.com)
    //
    // -----------------------------------------------------------------------------------
    // --- version date: 04/30/05 ------------------------------------------------------
   
    var photoDir = new Array(); // Location of photos for gallery
    var borderSize = 6;  // = 2x CSS border size
    var photoId;
    // if no id in query string then set to 0
    photoId = (!photoId)? 0:photoId;
       
    // Define each photo\'s name, height, width, and caption
    var photoArray = new Array();
   
    var photoNum;
    var galId;
   
    // Create access to \'Detect\' object and a place to put instances of \'HTMLobj\'
    API = new Detect();
   
    // CREATE INSTANCES & LOAD
    loadAPI = function(){
        // Instantiate HTMLobj
        API.Container       = new HTMLobj(\'Container\');
        API.Photo           = new HTMLobj(\'Photo\');
        API.PhotoContainer  = new HTMLobj(\'PhotoContainer\');
        API.LinkContainer   = new HTMLobj(\'LinkContainer\');
        API.PrevLink        = new HTMLobj(\'PrevLink\');
        API.NextLink        = new HTMLobj(\'NextLink\');
        API.CaptionBlock    = new HTMLobj(\'CaptionBlock\');
        API.Counter         = new HTMLobj(\'Counter\');
        API.Caption         = new HTMLobj(\'Caption\');
        API.LoadImg         = new HTMLobj(\'LoadImg\');
       
        // Show initial photo - now unnecessary
        //cyclePhoto(photoId);
    }
    onload = loadAPI;

    var h,w;
    function get_viewport ()
    {
        if (self.innerHeight) // all except Explorer
            {
                w = self.innerWidth;
                h = self.innerHeight;
            }
        else if (document.documentElement && document.documentElement.clientHeight)
    // Explorer 6 Strict Mode
            {
                w = document.documentElement.clientWidth;
                h = document.documentElement.clientHeight;
            }
        else if (document.body) // other Explorers
            {
                w = document.body.clientWidth;
                h = document.body.clientHeight;
            }
    }
   
    // Fade in photo when it is loaded from the server
    initFade = function(){
        // Show PhotoContainer again
        API.PhotoContainer.show();
       
        // Be certain the tween is complete before fading, too
        var fade_timer = setInterval(\'startFade()\', 1000);
                       
        // Fade photo in when ready and clear listener
        startFade = function(){
            if(API.Container._tweenRunning == false){
                clearInterval(fade_timer);
               
                // Be certain fade is done running before allowing next/previous links to work
                // This avoids rapid fade-in when users click next/previous links in quick succession
                var adv_timer = setInterval(\'permitNextPrev()\', 500);
               
                // Permit next/previous links to function normally when fade is completed
                permitNextPrev = function(){
                    if(API.Photo._fadeRunning == false){
                        clearInterval(adv_timer);
                       
                        // Only show links if there is more than one photo in array
                        if(photoNum > 1){
                            API.LinkContainer.displayShow();
                            document.getElementById(\'NextLink\').onclick = nextPhoto;
                            document.getElementById(\'PrevLink\').onclick = prevPhoto;
                        }
                    } else {
                        return;
                    }
                }
                // Swap out loading animation to spare CPU cycles when hidden anyway
                API.LoadImg.setSrc("actions/images/slideshow/start.gif");
               
                // Show caption again
                //API.CaptionBlock.show();
               
                // Fade photo in
                API.Photo.fadeIn(0,15,33);
            } else {
                return;
            }
        }
    }
   
    // Prevent next/previous
    falsify = function(){
        return false;
    }
   
    // Go to next photo
    nextPhoto = function(){
        // Go to next photo
        if(photoId == (photoArray[galId].length - 1)){
            photoId = 0;
        } else {
            photoId++;
        }
        cyclePhoto(galId,photoId);
    }
   
    // Go to previous photo
    prevPhoto = function(){
        // If at start, go back to end
        if(photoId == 0){
            photoId = photoArray[galId].length - 1;
        } else {
            photoId--;
        }
        cyclePhoto(galId,photoId);
    }
   
    // Alter class of elements
    changeElementClass = function(objId,setClass) {
        document.getElementById(objId).className = setClass;
    }
   
    // Function to load subsequent photos in gallery
    cyclePhoto = function(galId,photoId){
               
        // Swap in loading animation
        API.LoadImg.setSrc("actions/images/slideshow/loading_ani2.gif");
       
        // Hide link container if it is not already hidden
        API.LinkContainer.displayHide();
       
        // Hide photo container and caption temporarily
        API.Photo.hide();
        API.Photo.setOpacity(0);
        API.CaptionBlock.hide();
       
        // Get dimensions of photo
        var wNew = photoArray[galId][photoId][1];
        var hNew = photoArray[galId][photoId][2];
        get_viewport();
        w=w-60;h=h-40;
        if (wNew>w && hNew>h) {
        if (wNew>hNew) {
                hNew = (w * hNew) / wNew;
                wNew = w;
                if (hNew>h)
                    {
                        wNew = (h * wNew) / hNew;
                        hNew = h;
                    }
            } else {
                wNew = (h * wNew) / hNew;
                hNew = h;
               
           
            }
       
        }      
       
        // Start tween on a delay
        var wCur = API.Container.getWidth() - borderSize;
        var hCur = API.Container.getHeight() - borderSize;
       
        // Begin tweening on a short timer
        setTimeout(\'API.Container.tweenTo(easeInQuad, [\'+wCur+\', \'+hCur+\'], [\'+wNew+\',\'+hNew+\'], 7)\',500);
        setTimeout(\'API.LinkContainer.sizeTo(\'+wNew+\',\'+hNew+\')\',500);
        setTimeout(\'API.PrevLink.sizeTo(\'+wNew/2+\',\'+hNew+\')\',500);
        setTimeout(\'API.NextLink.sizeTo(\'+wNew/2+\',\'+hNew+\')\',500);
        //setTimeout(\'API.CaptionBlock.sizeTo(\'+wNew+\',18)\',500);
        setTimeout(\'API.CaptionBlock.sizeTo(0,0)\',500);
   
        // Get new photo source
        var newPhoto = photoDir[galId] + photoArray[galId][photoId][0];
       
        // Set source, width, and height of new photo
        API.Photo.setSrc(newPhoto);    
        API.Photo.sizeTo(wNew,hNew);
       
        // Set links to new targets based on photoId
        //API.NextLink.setHref("#" + (photoId+1));
        //API.PrevLink.setHref("#" + (photoId+1));
        //API.Counter.setInnerHtml((photoId+1)+" of "+photoNum+" |");
        //API.Caption.setInnerHtml(photoArray[photoId][3]);
        API.Counter.setInnerHtml("");
        API.Caption.setInnerHtml("");
       
        // Event listeners for onload and onclick events
        document.getElementById(\'Photo\').onload = initFade;
       
        // Block next/previous links until permitNextPrev() has fired
        document.getElementById(\'NextLink\').onclick = falsify;
        document.getElementById(\'PrevLink\').onclick = falsify;
    }
   
     //self.setTimeout(\'setInterval("nextPhoto()", 15000)\', 15000)
        //var ourInterval = setInterval(\'nextPhoto()\', 10000);
    // ]]>
    </script>

<div style="display:none;position:fixed;top:0px;left:0px;width:98%;height:98%;" id="viewer" onresize = cyclePhoto(galId,photoId);>
    <!-- resizable container -->
    <div id="Container">
        <div id="LinkContainer">
        <a id="PrevLink" onfocus="this.blur();" accesskey="[" title="&laquo; Previous Photo" class="plainlink"><span>Previous</span></a><a id="NextLink" onfocus="this.blur();" accesskey="]" title="Next Photo &raquo;" class="plainlink"><span>Next</span></a>
        </div>
        <div id="PhotoContainer"><img id="Photo" src="images/7sm.gif" alt="" width="50" height="50" /></div>
        <div id="LoadContainer"><img id="LoadImg" src="images/loading_ani2.gif" alt="Loading..." width="48" height="54" /></div>
    </div>
<!-- counter and caption -->
<p id="CaptionBlock"><span id="Counter"></span> <span id="Caption"></span></p>
<div style="position:absolute;bottom:10px;right:30px;background-color:#ceceb5;border:4px #ceceb5 solid"><a href="#" onclick="Effect.toggle(\'viewer\',\'appear\');return false;">Close</a></div>
</div>
'
;
?>



These need to go to your Stylesheet (css/whatever.css)

#Container{
margin:10px auto;
padding: 0;
position:relative;
width:100px;
height:100px;
background-color:#fff;
border:3px solid #CECEB5;
overflow:hidden
}

#LoadContainer{
height:25%;
width:50%;
position:absolute;
top:40%;
left:25%;
text-align:center;
z-index:1
}

#PhotoContainer{
visibility:hidden
}

#CaptionBlock{
height:10px;
width:10px;
text-align:left;
margin:0 auto
}
#Caption{
color:#333
}

#License{
margin:0 auto;
padding-top:10px;
font-size:10px;
color:#666;
border-top:1px solid #CECEB5;
width:50px;
text-align:left;
line-height:1.4em;
}

#LinkContainer{
display:none;
position:absolute;
top:0;left:0;
height:50px;
width:50px;
z-index:100;
background:url(../actions/images/slideshow/start.gif) 50% 50% no-repeat
}

#PrevLink{
z-index:100;
position:absolute;
top:0%;
left:0%;
height:50px;
width:50%;
display:block
}

#NextLink{
z-index:100;
position:absolute;
top:0%;
left:50%;
height:50px;
width:50%;
display:block
}

#PrevLink:hover,#NextLink:hover{
text-decoration:none
}

#PrevLink:hover{
background:transparent url(../actions/images/slideshow/prev_rounded_sidebar2.gif) left 50% no-repeat
}

#NextLink:hover{
background:transparent url(../actions/images/slideshow/next_rounded_sidebar2.gif) right 50% no-repeat
}

#PrevLink span,#NextLink span{
display:none
}

img{
border:none
}

p{
font-size:11px;
padding:1em 0
}

#Wrapper{
    margin:0 auto;
    height:50px;
    width:100%;
    overflow:hidden;
    position:relative;
    background-color: #8D977E;
}

#Wrapper[id]{
display:table;
position:static
}

#InnerWrapper{
position:absolute;
top:50%;
left:0;
}

#InnerWrapper[id]{
display:table-cell;
vertical-align:middle;
position:static
}

#OuterContainer{
position:relative;
top:-50%
}


As usual, if you encounter any errors during usage or by reading the code please let me know.


CategoryUserContributions
Comments
Comment by DarTar
2006-05-03 02:33:27
Hi Yoda, your new version doesn't work for me as well as the old one. Now I get a normal popup window within which the zoom thing happens. All the nice AJAX features that I remember from the past version are gone. Tested with Safari and FF / Mac OS 10.3.9
Comment by YodaHome
2006-05-04 13:44:51
Well, yes, this is not really a follow-up but rather another approach. Actually I thought about simply adding the slideshow feature to the AjaxGallery but then I wondered whether this makes sense at all. I even tried to dynamically load the slideshow instead of just showing the picture but that failed to work for several reasons. Furthermore during testing I got the impression that putting the slideshow in a separate window is much nicer to work with. I implemented this solution for another website so it was also less work to develop it into a wikka action and as you can see it works very well in nearly every common browser which I couldn't achieve yet for AjaxGallery. However if you think it would be nice to combine these two functions I'll think about it once more.
Comment by NilsLindenberg
2006-07-24 09:25:25
A small comment to grafix.php: instead of echoing 2/3 of it, wouldn't it be easier to end the php part?
Comment by YodaHome
2006-08-01 11:58:53
Well, I guess it would still work. But (apart from readability, which is of course a good point, since it's pretty unpretty code) would there be any benefit by doing so? I know, I'm lazy... *g*
Comment by NilsLindenberg
2006-08-02 19:09:27
I think it is faster because the text is simply printed to the screen without having a layer in between. Well, at least that's what I've read somewhere :)
Comment by YodaHome
2006-08-13 17:06:58
OK, I'll keep that in mind and give it a try next time I touch the code. Thx!
Comment by MegaMaigre
2007-08-23 10:07:39
Hi!
Pretty nice your gallery!
All the image i use are updated with action {Files}
To simplify the use i put that at the top of gallery.php:

//framework output
$dir = $vars['page'];
if ($dir == '')
{$dir = $this->config['upload_path'].'/'.$this->GetPageTag().'/';}
else {$dir = $this->config['upload_path'].'/'.$dir.'/';}

So if the user want to load the pictures he updated in the current page,
he just put {gallery}. If he want load picture from another page,
he just have to know the name of the page: {gallery page="MyPage"}

That works because the action {files} put updated files into
$this->config['upload_path'].'/'.$this->GetPageTag();

But I have a question have a really simple use of your wonderful gallery,
what is the Id ??? Where do you use it ??
'cause if it is really usefull i would like to make something to auto-increment
or to use a random id. But if it is not.. don't care!
Thanks!!
Comment by MegaMaigre
2007-08-23 10:21:59
why don't you give the action/slideshow.php code?

It's important to change the link to the CSS at the top
of action/slideshow.php if you want a well run.
Link to the css files where you put the given css code (see below)
Comment by YodaHome
2007-08-27 05:23:00
Not sure I got your question right, but the id variables are simply there to tell the script which picture to show. (So yes, they're useful). If you look at the generated gallery code, you might see that every gallery and every picture in the gallery has its own id so you can have multiple galleries on one page. It should be simple to alter the JavaScript to choose a random id (from the ones available of course).

However, I should probably confess here (*g*) that I have discontinued using this extension in favour of a similar hack to integrate LightBoxEx (or even Thickbox). It does the very same thing except it does it more user-friendly and is better coded and so I decided to use that instead.
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki