How to make a Flickr slideshow using DXperience ASP.NET

ctodx
29 January 2010

One of the chapters I wrote for Professional DevExpress ASP.NET Controls was chapter 11 on asynchronous programming. In essence, I showed how to use the client-side functionality of the DevExpress ASP.NET controls, as well as using callbacks.

One of the example programs I developed for that chapter was a slideshow application using an ASPxCallbackPanel, an ASPxRoundPanel (because I wanted it to look good), an ASPxImage, a few ASPxButtons, and an ASPxTimer. I glossed over the "where the photos come from" problem by just having an array of pre-initialized URLs. I always thought it was a bit of a daft excuse -- I cop to it being a cop-out, if you like -- but then again I was really describing the use of client-side JavaScript and AJAX and all that good stuff and didn't want to muddy it with a bunch of photo-handling code.

Well, no more. Let's modify the app to get our photos from Flickr. The first thing we need is a great little open-source library called Flickr.NET so go download it from CodePlex. You only really need the binaries, but if you want to see how it's built, download the source as well. Add the FlickrNet assembly to the solution, a reference to it, and add the using FlickrNet; statement to the code-behind file.

Now let's quickly build the visual part of the application. Drop an ASPxCallbackPanel onto the web project's main form. Drop an ASPxRoundPanel inside that, and then an ASPxImage inside of that. Create a <div> element underneath all that and drop four ASPxButtons  and an ASPxTimer inside that <div>. Set the various properties and names such that you get the following HTML code:

  <form id="form1" runat="server">
  <div>
    <dx:ASPxCallbackPanel ID="CallbackPanel" runat="server"  
      ClientInstanceName="callbackPanel" HideContentOnCallback="False" 
      oncallback="CallbackPanel_Callback" ShowLoadingPanel="False">
      <PanelCollection>
        <dx:PanelContent runat="server">
          <dx:ASPxRoundPanel ID="RoundPanel" runat="server">
            <PanelCollection>
              <dx:PanelContent runat="server">
                <dx:ASPxImage ID="ImageViewer" runat="server">
                </dx:ASPxImage>
              </dx:PanelContent>
            </PanelCollection>
          </dx:ASPxRoundPanel>
        </dx:PanelContent>
      </PanelCollection>
    </dx:ASPxCallbackPanel>
  </div>
  <div id="buttons">
    <div style="float:left;">
      <dx:ASPxButton ID="Prev" runat="server" Text="Previous" 
ClientInstanceName="prevButton" EnableClientSideAPI="True" AutoPostBack="False"> <ClientSideEvents Click="function(s, e) {slideshowEngine.movePrev();}" /> </dx:ASPxButton> </div> <div style="float:left;"> <dx:ASPxButton ID="Play" runat="server" Text="Play"
ClientInstanceName="playButton" EnableClientSideAPI="True" AutoPostBack="False"> <ClientSideEvents Click="function(s, e) {slideshowEngine.play();}" /> </dx:ASPxButton> </div> <div style="float:left;"> <dx:ASPxButton ID="Stop" runat="server" Text="Stop"
ClientInstanceName="stopButton" EnableClientSideAPI="True" AutoPostBack="False"> <ClientSideEvents Click="function(s, e) {slideshowEngine.stop();}" /> </dx:ASPxButton> </div> <div style="float:left;"> <dx:ASPxButton ID="Next" runat="server" Text="Next"
ClientInstanceName="nextButton" EnableClientSideAPI="True" AutoPostBack="False"> <ClientSideEvents Click="function(s, e) {slideshowEngine.buttonMoveNext();}" /> </dx:ASPxButton> </div> <dx:ASPxTimer ID="Timer" runat="server" ClientInstanceName="slideshowTimer" Interval="4000" Enabled="False"> <ClientSideEvents Init="function(s, e) {slideshowEngine.play();}" Tick="function(s, e) {slideshowEngine.tickMoveNext();}" /> </dx:ASPxTimer> </div> </form>

In essence I named everything nicely (server-side and client-side) and set the client-side behavior of the buttons and timer to call various methods in a JavaScript object called slideshowEngine. I also created a callback method called CallbackPanel_Callback() in the code-behind C# file.

I created a new JScript file called slideshow.js to the solution and placing it in the website folder. By dragging the file from the Solution Explorer to the default.aspx file and dropping it after the <form> element's end tag, Visual Studio created the <script> element for me.

  <script src="slideshow.js" type="text/javascript"></script>

The JavaScript code looks like this:

var slideshowEngine = function() {
  var timerOn = false;

  var setTimer = function(enable) {
    slideshowTimer.SetEnabled(enable);
    timerOn = true;
  };

  var moveNext = function() {
    if (!callbackPanel.InCallback()) {
      callbackPanel.PerformCallback("next");
    }
  };

  var movePrior = function() {
    setTimer(false);
    if (!callbackPanel.InCallback()) {
      callbackPanel.PerformCallback("prev");
    }
  };

  return {
    tickMoveNext: function() {
      if (timerOn) {
        moveNext();
      }
    },
    buttonMoveNext: function() {
      setTimer(false);
      moveNext();
    },
    movePrev: function() {
      movePrior();
    },
    play: function() {
      setTimer(true);
    },
    stop: function() {
      setTimer(false);
    }
  };
} ();

This uses what you might term an advanced style of JavaScript programming. In essence the slideshow object is defined as the result of running an anonymous function (although it looks to be declared as a function, look to the last line and you'll see that it is immediately executed -- you can see the function call parentheses). The reason for doing this is to strictly control what's public in the object: the function invocation will form a closure and return the anonymous object defined by the return statement. The only members that are visible in the created slideshow object will be tickMoveNext(), buttonMoveNext(), movePrev(), play(), and stop(). The rest of the code you can see will be in the function closure and invisible to the outside world.

Anyway, moving on, let's see what we have to do in the code-behind file.

In order to use anything in the Flickr API you have to have what's known as an apiKey. You can get these from Flickr, by logging in with your user ID, and going to http://www.flickr.com/services/api/keys. I created one specially for this demo, as you'll see below, but if you want to use this technique, you should create and use your own (you'd really hate it if I deleted mine on a whim).

For this example, I just wanted to do a tagged search. This returns 100 photos at a time (or, more strictly, the details for 100 photos -- the actual images are available elsewhere), so it seemed to be a good idea to save the results of a search operation locally on the server in the Session object. I decided to encapsulate all the Flickr API work in a special class to make this easier, and to just expose a few methods that would enable the slideshow app to cycle through the images.

  public class FlickrResults {
    const string apiKey = "e28762539e823e30baedcee67e64cae4";
    private int loadedPage;
    private int number;
    private Flickr flickr = new Flickr(apiKey);
    private Photos photos;

    public FlickrResults() {
      loadedPage = -1;
    }

    public Photo GetPhoto() {
      int pageNum = number % 100;

      if (pageNum != loadedPage) {
        PhotoSearchOptions options = new PhotoSearchOptions();
        options.Tags = "yorkshire";
        options.PerPage = 100;
        options.Page = pageNum;
        options.SortOrder = PhotoSearchSortOrder.DateTakenDesc;
        photos = flickr.PhotosSearch(options);
        loadedPage = pageNum;
      }

      if (number > photos.TotalPhotos - 1)
        number = (int)photos.TotalPhotos - 1;
      return photos.PhotoCollection[number];
    }

    public void MoveNext() {
      number++;
    }

    public void MovePrior() {
      if (number > 0)
        number--;
    }

    public void Reset() {
      number = 0;
    }
  }

Simple enough; the majority of the work is done in the GetPhoto() method. It works out the page number needed (at 100 photos per page) for the number of the photo requested. If the page of results hasn't been loaded yet, it'll make the call to Flickr to get the required page of photos. (Here I'm requesting photos tagged with "yorkshire", at 100 photos in a page, and ordering the results by the date taken in reverse order. Feel free to play around with your search options.) If the photo number happens to be greater than the total count of photos in the result set, I upper-bound the photo number. Finally return the current photo object from the result set.

Next up is an easy little method to update the image and details in the browser:

    public void UpdateSlideShow() {
      var photo = results.GetPhoto();
      ImageViewer.ImageUrl = photo.MediumUrl;
      RoundPanel.HeaderText = photo.Title;
    }

It gets the current photo object, sets the image URL to the medium-sized image and sets the round panel's header text to the photo's description. The results variable is created in the Page_Load() method:

    private const string ResultsName = "flickrResults";
    private FlickrResults results;

    protected void Page_Load(object sender, EventArgs e) {
      if (!IsCallback && !IsPostBack) {
        results = new FlickrResults();
        UpdateSlideShow();
        Session[ResultsName] = results;
      }
    }

Of course, once created, the results object gets saved in the Session object, ready for being retrieved (and subsequently saved) during the callback:

    protected void CallbackPanel_Callback(object sender, CallbackEventArgsBase e) {
      results = (FlickrResults)Session[ResultsName];
      switch (e.Parameter) {
        case "prev":
          results.MovePrior();
          break;
        case "next":
          results.MoveNext();
          break;
        default:
          results.Reset();
          break;
      }
      UpdateSlideShow();
      Session[ResultsName] = results;
    }

And that's pretty much it. Compiling and running the application produces a nice little web slideshow of photos tagged "yorkshire".

DX-Flickr slideshow

Happy programming!

2 comment(s)
Brendon Muck [DevExpress MVP]
Brendon Muck [DevExpress MVP]

You write code too? I always assumed you whiled away the day in some Bernard Lee-esque mahogany paneled room while you smoked cigars and teleconferenced with the rest of SPECTRE.

Nevertheless, I'll have to try this before I begin my foray (back) into ASP.NET with the DXperience suite.

30 January, 2010
Miro Nagy
Miro Nagy

One thing I have found useful is to actaully load the next image on screen with a css tag of display: none.

That way the longest wait time is actaully on the very first load ( as it loads both pictures ).

After that, as the timer cycles through - if the next image is bigger than the last ( slightly bigger i suppose ), its already loaded while the user was looking at the current image.

Cheers'

Miro

3 February, 2010

Please login or register to post comments.