söndag 28 juli 2013

Learning Angular, part one

While learning web app development, there is something that has bothered me - it seems hard to structure HTML and Javascript code in a good way. Much of my experiment code is quite entangled and messy, and with much global variables. This is partly just because I've sometimes rushed out experimental things without caring about design quality or maintainability, but partly also because I simply haven't had much good ideas of how a web app can be structured in a good way.

AngularJS is a client-side HTML/Javascript framework that aims to solve some of this.

With Angular you typically write your app following the MVC (Model View Controller) design pattern, with the benefits that comes from using MVC. Your View code is all in the HTML markup, and the business logic (i.e. Javascript functions and variables) can be kept separate from it. This means good separation of concerns.

Also, Angular provides two-way data bindings, i.e. when the user changes something in the View, the corresponding data inside the Model is automatically updated, and vice versa. Angular can update the contents of a web page dynamically.

So what you do when you create an app using Angular is that you think of your app in terms of Views, Controllers and Models, then you write those components (using Angular's declarative syntax) and you wire these parts together (using Angular concepts like directives). Angular will take care of keeping data in sync and updating the Views.

A lot of functionality is abstracted away by Angular and so you don't need much code to do dynamic web-page apps.

The tricky thing with Angular is that it is quite hard to learn and wrap your head around. At least this is what I've felt so far. Documentation on the official homepage is quite sparse, and there are not many good introductions and tutorials around. Perhaps this is because the framework is still quite new.

There is at least one good tutorial around though: searching for bits and pieces of information to get to understand the framework, I stumbled upon this great YouTube video by Dan Wahlin: AngularJS Fundamentals In 60-ish Minutes. This is the best resource I've found teaching the fundamentals of Angular and showing how to do an app using it.

After watching Dan's video I tried to think of a simple app I could try to make using Angular. I decided to make an app called Fridge, where I can see on a web-page which things I have in my fridge, so that when I'm leaving work in the evening I know what items I need to buy to cook dinner. The fridge contents will be stored in a database on my server, and from the web-page it should be possible to add and remove items, so that when I go shopping I add those items to the database, and when I take out stuff from the fridge I remove them from the database. All of this using the web-page. (So, hooking up a web-cam in my fridge to automatically keep track of what it contains is something I leave for the future...)

I thought this app is a good way to experiment with a server-side database and learning some more about how to do simple CRUD style web-services. So besides from utilizing Angular, this small project included use of MongoDB and its Node.js client library, for handling the database on the server, as well as websockets (via SocketIO) for sending CRUD-style messages between the client and the server whenever the client wants to read or modify the database. A benefit of using websockets is that if there are multiple clients connected to the server, the clients can be updated in realtime whenever the database is updated. So that when my girlfriend puts something in the fridge, I can see it directly. Nice!

My next blog post will show how I made the app.

lördag 27 juli 2013

Getting acquainted with MongoDB


This week I've spent some time checking out AngularJS - the popular Javascript framework for structuring single page web applications. I will not talk much about Angular in this blog post though, I'll get back to that later. The example app I made for learning Angular needed to store some data on the server-side, and for this I needed some database running on my server. I had already decided my server app would be a Node.js app, and so I thought this was a good opportunity to start looking into MongoDB, which seems to be a popular database format for web apps recently.

So this blog post is just a description of what I did to install and get acquainted with MongoDB, as preparation for making an app using AngularJS. And I'll get back to describe the app implementation in my next posting.

As a super short introduction, MongoDB is an open source database system which stores data in the JSON format, which makes it suitable for Javascript apps. And since you can now run Javascript on the server using Node.js, MongoDB seems to be a popular database choice for Node.js server apps.

To install MongoDB on my server, I followed these instructions:
http://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/

After installation, you can run Mongo as an executable, and create and edit databases, from the shell.

To start the executable, in the server shell I just wrote:
mongo

Then, I wanted to create a database named fridge. To do so, I wrote:
use fridge

I learnt that even though you do this, the database is actually not created on disc until you write some data to it. So I decided to put in some initial data.

I also learnt that Mongo has a concept of collections. If I understand correctly, a database can contain one or more collections. When you insert or retrieve data, you need to specify which collection you are working with. (Perhaps collections are similar to tables in the world of SQL?)

You don't need to create a collection explicitly. When you insert an element and specify which collection to put the element in, the collection will be created for you if it does not already exist.

So, as a test, I inserted two elements (or documents as Mongo seems to call it) like this:
db.products.insert( {product:"Tomatoes", quantity:"2"} )
db.products.insert( {product:"Eggs", quantity:"6"} )

  • db is a reference to the database you are currently working with.
  • products is the name of the collection you want to insert data to.
  • insert is the command for putting in data.
  • And within the parentheses you can see the JSON formatted data we are putting in. For my database I wanted two fields, "product" and "quantity" and as you can see I've put some tomatoes and eggs there. :-)

If you want to make sure you are working with the right database, you can write just db and Mongo will answer which is the active database. (In our example it should answer fridge.)

You can ask Mongo to list the collections available in the current database, like this:
show collections

And you can ask Mongo to list all the data in the collection called "products", like this:
db.products.find()

That should show you the tomatoes and eggs we inserted just before.

To exit Mongo, just type:
exit


As I wrote in the beginning, I wanted to use MongoDB from a Node.js app. So now that I had created a database, as a next step I installed a Node.js package for using Mongo from Node.js, like this:
npm install mongodb

And that's it for our preparations as of now. In the next posting I'll talk about how I put this to use.

Realtime communication using SocketIO

This is a short introduction to SocketIO.

SocketIO is a Javascript library for realtime communication over Internet. Using SocketIO you can very easily write client-server apps where data is exchanged between a client and a server, or between multiple clients connected to the server at the same time. Some applications that spring to mind are chat applications and simple multiplayer games.

SocketIO tries to use WebSockets for its communication, but can fallback to other techniques in situations where WebSockets can not be used. So you actually don't have to mind about the communication medium when you write your app.

For the server-side of your app, you write your app in Javascript and run it using the Node.js execution environment.

Here below I have written a small tutorial to show how easy it is to create a simple, browser-based, realtime chat application.

What will the application do?
  • The application will let a client, running in a browser, connect to a server and send messages and get responses from the server in realtime.
  • If multiple clients, running in separate browsers and potentially on separate computers, are connected to the server at the same time, the clients will be able to send text messages to each other.
  • The client-side app will have two text boxes, one for writing text messages to send to the server, and one for writing text messages that will be broadcasted to all connected clients.
  • The client-side app will also have a text label. Whenever the client receives a text message (either from the server, or from another client) the message will be shown in the text label.

First some pre-requisites before we start with the implementation:
  • First of all, this requires that you have some server where you can upload and run your server-side app. See for example my earlier posting about Linode on how you can rent a private virtual server: http://mattiaserlo.blogspot.com/2013/06/virtual-private-server.html (Or I guess you could run the server also on your computer using "localhost" for test purposes.)
  • You'll also need to have Node.js installed on your server. See my earlier posting about how to install Node.js: http://mattiaserlo.blogspot.com/2013/06/nodejs.html
  • You also need to install the SocketIO package on your server. On your server, write: npm install socketio

Now we can get started writing some code! As explained above, the application will consist of two parts: one server-side app that will handle incoming connections and messages from clients, and one client-side app that will run on a client in a browser.
Let's start with the server-side app. Create a file called serverapp.js with the following contents:


var io = require('socket.io').listen(8083);

io.sockets.on('connection', function (socket) {
 console.log("Someone connected");
 
 socket.on('messageToServer', function (data) {
  console.log("Got messageToServer message");
  
  if (data.type != undefined) {
   console.log("data.type = " + data.type);
  }
  
  // Do some work on the server...
  // ...
  
  // Then send a reply to the client
  socket.emit('messageFromServer', { type: 'someOtherType',
       text: 'Hello!' });
 });
 
 socket.on('broadcast', function (data) {
  console.log("Got broadcast message");
  
  // Broadcast the message to all connected clients
  io.sockets.emit('broadcast', data);
 });
});

The first line creates a socketIO object and tells socketIO to listen to incoming connections on port 8083. I chose this port number arbitrarily - you can use any number you want as long as it is not already used by some other app or service on your server.

The second line, io.sockets.on('connection', function (socket) {, declares an anonymous function that will be called whenever some client connects to the app on the port we are listening to. As you see there is a function argument, socket. This socket object is used for communicating with the client from now on. If another clients connects to the server later, the anonymous function will be called again with a new socket used for that new client.
Inside the anonymous function you can see socket.on('messageToServer', function (data) { ... Here we are telling socketIO that whenever a message of type messageToServer comes to the server, execute this function. By the way, message names are just text string and you can decide what to name your messages on your own.

So what we do when "messageToServer" is received, is we do a debug print and then we call socket.emit. Socket.emit is used for sending data via the socket. In our example we are sending a message of another type, messageFromServer, back to the client, with some additional data. Again, you are free to name your messages whatever you want, and you can basically pass any number of parameters in your message. Message data should be JSON formatted.

You can also see that we are listening to another message type, broadcast. I came up with this name for messages that I want to broadcast to all connected clients. So if a client wants to send some text that should be received by all clients, the client will send this message to the server. When the server receives a message of this type, it calls io.sockets.emit('broadcast', data);

io.sockets.emit means that a message will be sent to all the sockets currently open on the server app. And we are reusing the name "broadcast" for this message type. We don't need to modify the data, we just pass on whatever data the client wishes to broadcast.

And this is actually all the code we need for the server-side.

To run this code on your server, upload the code to your node folder on your server, open a shell on your server and write: node serverapp.js

Now we need to write the client-side of the application.

Create a file named index.html, containing the following:


<!DOCTYPE html>
<html>
<head>
 <title>SocketIO test</title>
</head>
<body>
 <script src="http://106.187.52.148:8083/socket.io/socket.io.js"></script>
 <h1>SocketIO test</h1>
 Message to send to server: <input type="text" id="toServerTextBox" onchange="toServerTextChanged()">
 <br>
 Message to broadcast to clients: <input type="text" id="broadcastTextBox" onchange="broadcastTextChanged()">
 <br>
 <label id="textLabel"></label>
 <br>
 <script>
  
  var socket = null;
  
  function toServerTextChanged() {
   var textBox = document.getElementById("toServerTextBox");
   if (socket) {
    socket.emit('messageToServer', { text: textBox.value });
   }
  }
  
  function broadcastTextChanged() {
   var textBox = document.getElementById("broadcastTextBox");
   if (socket) {
    socket.emit('broadcast', { text: textBox.value });
   }
  }

  socket = io.connect('http://106.187.52.148:8083');

  if (socket != null) {
   socket.on('messageFromServer', function (data) {
    console.log("Got messageFromServer");
    document.getElementById('textLabel').innerHTML =
    "Got message from server: " + data.text;
   });

   socket.on('broadcast', function (data) {
    console.log("Got broadcast message");
    document.getElementById('textLabel').innerHTML =
    "Got broadcast message: " + data.text;
   });


   console.log("Now send a message to the server");
   
   socket.emit('messageToServer', { type: 'someType' ,
       moreData: 'someOtherData' });
  }
 </script> 
</body>
</html>


Looking at this code, you can see a line that fetches a script, script src="http://...

You should exchange the IP number with the IP number to your server, and use the same port number as you chose for your server-app to listen to. The socket.io.js script will be magically served by your server when the clients need the script.

Further below in the code you can see that we are calling io.connect:
socket = io.connect('http://106.187.52.148:8083');
Again, make sure here to use your server's IP number and port number.
When this line is executed, the client will connect to the server, and your server-app's line io.sockets.on('connection'... will be called with a socket to use for communicating with this client.

Further below in the code you can see similar code as we wrote for the server-side - we tell which messages we want to listen to and associate each message with an anonymous function to be called. In our example implementation here we simply update the text label with the message contents.

Now that you are running your server-side app on your server, you are ready to receive connections from clients. So on your local computer, start a browser, and open the index.html file you just wrote. Hopefully you'll see the text label being updated with a "hello" from the server, as a response from the client's connection request.

Now, either in another browser tab, or on another computer or smartphone, open up the index.html file. Now both clients should be connected to the server. Writing a message in the broadcast text box (and pressing Enter) should make the message pop up on all clients' screens.

Neat huh? :-)

If you want to download the code from GitHub, here is the link: https://github.com/mattiaserlo/socketiotest

söndag 21 juli 2013

Tutorial: Using the MVC design pattern in Visual C#


This is a small tutorial to explain about the MVC (Model View Controller) design pattern, and to show how it can be implemented in Visual C#. The example also makes use of the Observer pattern and the Publish/Subscribe pattern.

The application we will implement is a simple calculator. Actually it won't be a very useful calculator - to keep the tutorial simple the calculator will only have two functions: addition and subtraction. How's that for a calculator? Well, this will be enough to illustrate the usage of the design patterns.

First let's say a few words about MVC and how to design our application. (If you already know well enough about the MVC pattern, you can skip this section and go directly to "1. Start Visual Studio..." further down below.)

MVC stands for Model View Controller and it is a way to separate the user interface (UI) logic from the business logic. Taking a calculator as an example, the UI consists of the buttons and the number display, while the business logic corresponds to the internal handling of numbers in the calculator: addition, subtraction, multiplication, division, and so on.

Why do we want to separate the UI and the business logic from each other? Well, the idea is that the code will be easier to maintain by doing so.

  • If the UI needs to be extended in the future, it is easier to do so if the UI is not entangled with business logic.
  • We could also write a completely new UI, without having to rewrite any of the business logic, or perhaps let the user choose between different UIs, without having to duplicate the business logic for each one of them.
  • If the UI and business logic and very clearly separated, we could even have them running on different computers if we want to. A server could handle the business logic and a client could handle the UI.
  • Also, having the UI and business logic separate means development work can be parallellized more efficiently: a UI developer can work with the UI while a business logic developer works with the business logic, and they will not be stepping in each others files.

In the MVC pattern, an application is split into three parts:

  • (M)odel: this is where the data representations and business logic is at. The model keeps track of which state the application is in, and has logic for transitioning between states
  • (V)iew: this is the UI, i.e. this is what the user sees and interacts with. Here might be buttons, text boxes, labels, and so on.
  • (C)ontroller: this is responsible for taking user actions and pushing them to the Model so that the Model can do its work and calculate a new state.

So, in order to have the UI decoupled from the business logic, we don't want any strong references between the View and the Model. It should be the Controller who is responsible for pushing the user actions to the Model. But then again it is actually the View that is wired by the OS to receive user input. (Considering Visual C#, the View is typically the window form and thus it is the receiver of all user input events for the application.) How can we make the Controller aware of the user input, so that it can notify the Model about it? Here it is fitting to use the Observer pattern. With the Observer pattern, one entity can notify another entity - the Observer - when something interesting happens. To implement this, we will let the View offer a registration function, where the Controller can register itself. Then when interesting things happen in the View, the View will call the registered entities to notify them about the user actions. This way the Controller can be kept up to date with what the user does, and notify the Model so that the Model can do its work.

Now let's say the Model has done some work - for example it has calculated the result of adding two numbers that the user has chosen. We don't want the result to just stay inside the Model - it should of course be shown to the user. It is the View who has all the UI elements and so it is finally the View that should show the result to the user. How do we pass the calculation result from the Model to the View, without using strong references between them? Here we can use the Publish/Subscribe pattern. In this pattern, there is an additional entity that offers others to both publish, and subscribe to, data of different types. It is the Publish/Subscribe entity that keeps track of who subscribes to what, and who publishes what, i.e. a publisher and a subscriber do not have direct references to each other, they just post and receive data independently. Nice! Applying this to the calculator example, let's have the Model publishing its results to the Pub/Sub entity, and the View subscribing to such results so that it can update the UI when necessary.

Now let's move on to creating our calculator application.

1. Start Visual Studio and select New Project -> Visual C# -> Windows Application. I named the app MVCexample.

2. Let's design the UI. I added:
* buttons for numbers 0 to 9, named button0, button1, and so on
* two buttons for adding and subtracting, named buttonAdd and buttonSubtract
* one button for clearing, named buttonClear
* one button for calculating results, named buttonEquals
* one text box for showing the results, named textBox



3. Let's create an event class to use when we implement the Observer pattern. Events like this will be passed from the View to the Controller to let the Controller know which user actions are happening. To create the event class, I selected menu Project -> Add Class, and named the new file Event.cs. And here is the code it should contain:


using System;
using System.Collections.Generic;
using System.Text;
 
namespace MVCexample
{
  public enum EventType
  {
    eventTypeNumberClick,
    eventTypeAddClick,
    eventTypeSubtractClick,
    eventTypeClearClick,
    eventTypeEqualsClick
  }
 
  public class Event
  {
    private EventType mEventType;
    private int mData = 0;
 
    public Event(EventType eventType)
    {
      mEventType = eventType;
    }
 
    public Event(EventType eventType, int data)
    {
      mEventType = eventType;
      mData = data;
    }
 
    public EventType getEventType()
    {
      return mEventType;
    }
 
    public int getData()
    {
      return mData;
    }
  }
}

4. We'll also create an interface that the UI event listener (i.e. the Controller) can implement to receive UI notifications: go to menu Project -> Add Class, choose name IEventListener.cs. This interface will be very small: it will just declare one function, receivedEvent, which will be called whenever something interesting happens in the UI. The function will take one parameter of the Event class type, which we just wrote.


namespace MVCexample
{
  public interface IEventListener
  {
    void receivedEvent(Event ev);
  }
}

5. Now we'll add UI event handling code to Form1.cs to handle the user's button click events. Also we'll add code to Form1.cs to let someone (the Controller) register in order to receive UI event updates. The button click handlers should call the registered listeners.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
 
namespace MVCexample
{
  public partial class Form1 : Form
  {
    List<IEventListener> mNumberClickListeners = new List<IEventListener>();
    List<IEventListener> mAddClickListeners = new List<IEventListener>();
    List<IEventListener> mSubtractClickListeners = new List<IEventListener>();
    List<IEventListener> mClearClickListeners = new List<IEventListener>();
    List<IEventListener> mEqualsClickListeners = new List<IEventListener>();
 
    public Form1()
    {
      InitializeComponent();
    }
 
    public void registerListener(IEventListener listener, Event ev)
    {
      switch (ev.getEventType())
      {
        case EventType.eventTypeNumberClick:
          mNumberClickListeners.Add(listener);
          break;
        case EventType.eventTypeAddClick:
          mAddClickListeners.Add(listener);
          break;
        case EventType.eventTypeSubtractClick:
          mSubtractClickListeners.Add(listener);
          break;
        case EventType.eventTypeClearClick:
          mClearClickListeners.Add(listener);
          break;
        case EventType.eventTypeEqualsClick:
          mEqualsClickListeners.Add(listener);
          break;
        default:
          Debug.WriteLine("Tried to register listener for unknown event type " + ev.getEventType());
          break;
      }
    }
 
    private void button1_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mNumberClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeNumberClick, 1);
        mNumberClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void button2_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mNumberClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeNumberClick, 2);
        mNumberClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void button3_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mNumberClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeNumberClick, 3);
        mNumberClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void button4_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mNumberClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeNumberClick, 4);
        mNumberClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void button5_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mNumberClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeNumberClick, 5);
        mNumberClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void button6_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mNumberClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeNumberClick, 6);
        mNumberClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void button7_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mNumberClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeNumberClick, 7);
        mNumberClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void button8_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mNumberClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeNumberClick, 8);
        mNumberClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void button9_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mNumberClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeNumberClick, 9);
        mNumberClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void button0_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mNumberClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeNumberClick, 0);
        mNumberClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void buttonClear_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mClearClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeClearClick);
        mClearClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void buttonAdd_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mAddClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeAddClick);
        mAddClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void buttonSubtract_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mSubtractClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeSubtractClick);
        mSubtractClickListeners[i].receivedEvent(ev);
      }
    }
 
    private void buttonEquals_Click(object sender, EventArgs e)
    {
      for (int i = 0; i < mEqualsClickListeners.Count; i++)
      {
        Event ev = new Event(EventType.eventTypeEqualsClick);
        mEqualsClickListeners[i].receivedEvent(ev);
      }
    }
  }
}

(If you think it looks bad to have explicit functions for all the number buttons like this, I guess you could create the buttons programatically instead and put them in an array. That way you could reuse one click event handler for all the number buttons.)

Please do not forget that you have to map all the buttons' click events to the click functions you just wrote. You do this from Properties, choosing each button and assigning the Click event to the corresponding click function.

6. Now let's create the Controller: from Project -> Add Class, I just named the new file Controller.cs.
In the Controller, we want to call the View's registration function so that we can receive the button events. Let's also add a private reference to a Model, and a function for setting the model. We will call this function later to associate the Controller with the Model. And we should implement the IEventListener interface to act on those button events. When we receive button events we will call the Model using our private reference. For now I have commented out all the references to the Model, we'll activate those later.


using System;
using System.Diagnostics;
 
namespace MVCexample
{
  public class Controller : IEventListener
  {
       // private Model mModel = null;
 
    public Controller(Form1 viewForm)
    {
      Event myNumberClickEvent = new Event(EventType.eventTypeNumberClick);
      viewForm.registerListener(this, myNumberClickEvent);
 
      Event myAddEvent = new Event(EventType.eventTypeAddClick);
      viewForm.registerListener(this, myAddEvent);
 
      Event mySubtractEvent = new Event(EventType.eventTypeSubtractClick);
      viewForm.registerListener(this, mySubtractEvent);
 
      Event myClearEvent = new Event(EventType.eventTypeClearClick);
      viewForm.registerListener(this, myClearEvent);
 
      Event myEqualsEvent = new Event(EventType.eventTypeEqualsClick);
      viewForm.registerListener(this, myEqualsEvent);
    }
 
    /* public void setModel(Model model)
    {
      mModel = model;
    }*/
 
    public void receivedEvent(Event ev)
    {
      switch (ev.getEventType())
      {
        case EventType.eventTypeNumberClick:
          numberClick(ev.getData());
          break;
        case EventType.eventTypeAddClick:
          addClick();
          break;
        case EventType.eventTypeSubtractClick:
          subtractClick();
          break;
        case EventType.eventTypeClearClick:
          clearClick();
          break;
        case EventType.eventTypeEqualsClick:
          equalsClick();
          break;
        default:
          Debug.WriteLine("Received unknown event: " + ev.getEventType());
          break;
      }
    }
 
    private void numberClick(int number)
    {
      Debug.WriteLine("You clicked number " + number);
      if (mModel != null)
      {
        // mModel.pushNumber(number);
      }
    }
 
    private void addClick()
    {
      Debug.WriteLine("You clicked add");
      // mModel.add();
    }
 
    private void subtractClick()
    {
      Debug.WriteLine("You clicked subtract");
      // mModel.subtract();
    }
 
    private void clearClick()
    {
      Debug.WriteLine("You clicked clear");
      // mModel.clear();
    }
 
    private void equalsClick()
    {
      Debug.WriteLine("You clicked equals");
      // mModel.equals();
    }
  }
}

7. Now let's create the Model! This is where all the cool and complicated calculations of the application will be going on. Only in our case they're not so cool and complicated after all: we'll only have functions for adding and subtracting numbers. Hm. (Let's make a game another time! Then we would put all the cool physics calculations and collision detection routines here.)

In general, if the application can have states, the state implementation should typically be here in the Model. Our simple calculator will have three different possible states: Idle, Adding, and Subtracting. We'll also keep a private member for the "current number" of the calculator. And we'll have functionality for modifying this value (pushNumber), and for temporarily storing a value, as well as adding, subtracting, clearing, and calculating results.

To create the Model, I selected menu Project -> Add Class, and I named the new file Model.cs. Here is the code it should contain:


using System;
using System.Collections.Generic;
using System.Text;
 
namespace MVCexample
{
  public class Model
  {
    private enum State
    {
      IDLE,
      ADDING,
      SUBTRACTING
    }
 
    private int mCurrentNumber = 0;
    private int mStoredNumber = 0;
 
    private bool mClearOnNextNumber = false;
 
    private State mCurrentState = State.IDLE;
 
    public void pushNumber(int number)
    {
      if (mClearOnNextNumber == true)
      {
        mCurrentNumber = 0;
        mClearOnNextNumber = false;
      }
 
      mCurrentNumber *= 10;
      mCurrentNumber += number;
    }
 
    public void add()
    {
      mCurrentState = State.ADDING;
      mStoredNumber = mCurrentNumber;
      mClearOnNextNumber = true;
    }
 
    public void subtract()
    {
      mCurrentState = State.SUBTRACTING;
      mStoredNumber = mCurrentNumber;
      mClearOnNextNumber = true;
    }
 
    public void clear()
    {
      mCurrentNumber = 0;
    }
 
    public void equals()
    {
      if (mCurrentState == State.ADDING)
      {
        mCurrentNumber = mStoredNumber + mCurrentNumber;
      }
      else if (mCurrentState == State.SUBTRACTING)
      {
        mCurrentNumber = mStoredNumber - mCurrentNumber;
      }
      mCurrentState = State.IDLE;
    }
  }
}

Now that we have the Model in place, we can go back to the Controller and activate all the references to the Model. Open up Controller.cs again and turn on the code that we commented out before, i.e:

 
private Model mModel = null;
 
    public void setModel(Model model)
    {
      mModel = model;
    }
 
    private void numberClick(int number)
    {
      Debug.WriteLine("You clicked number " + number);
      if (mModel != null)
      {
        mModel.pushNumber(number);
      }
    }
 
    private void addClick()
    {
      Debug.WriteLine("You clicked add");
      mModel.add();
    }
 
    private void subtractClick()
    {
      Debug.WriteLine("You clicked subtract");
      mModel.subtract();
    }
 
    private void clearClick()
    {
      Debug.WriteLine("You clicked clear");
      mModel.clear();
    }
 
    private void equalsClick()
    {
      Debug.WriteLine("You clicked equals");
      mModel.equals();
    }

By now we have most of the functionality in place:

  • We have a UI.
  • We are taking care of the user's button click events.
  • We can call the Model's calculator functionality to add or subtract numbers.

What we are lacking is UI updates when the calculator updates its result. For this, we will use the Publish/Subscribe pattern: the Model will publish its results, and the View will subscribe to such updates and update its UI accordingly. For this mechanism to work, we will create one interface and one class, in steps 8 and 9 below.

8. Create a new interface that the subscriber will implement, and which contains the different things we can subscribe to. In our example we will just have one thing we can subscribe to: calculator number updates. We'll name this type NUMBER_UPDATED.
Go to menu Project -> Add Class, choose name ISubscription.cs.

 
namespace MVCexample
{
  public enum SubscriptionType
  {
    NUMBER_UPDATED
  }
 
  public interface ISubscription
  {
    void numberUpdated(int number);
  }
}

9. Create a new class that will handling publishing and subscriptions: menu Project -> Add Class, choose name PubSub.cs. The class should have one function that the publisher calls to publish data, and one function the subscriber calls to register subscriptions of a certain type. In our simple example, NUMBER_UPDATED will be published whenever the Model updates its number representing the calculator's value to be displayed. Here is the code of the file:


using System;
using System.Collections.Generic;
using System.Text;
 
namespace MVCexample
{
  public class PubSub
  {
    private List<ISubscription> mSubscribers = new List<ISubscription>();
 
    public void subscribe(ISubscription subscriber, SubscriptionType type)
    {
      if (type == SubscriptionType.NUMBER_UPDATED)
      {
        mSubscribers.Add(subscriber);
      }
    }
 
    public void publish(SubscriptionType type, int data)
    {
      if (type == SubscriptionType.NUMBER_UPDATED)
      {
        for (int i = 0; i < mSubscribers.Count; i++)
        {
          mSubscribers[i].numberUpdated(data);
        }
      }
    }
  }
}

Now we need to make use of the PubSub class. It's time to add code for actually starting a subscription, and for publishing data.

10. Let's start with the publishing: modify Model.cs by adding a private reference to a PubSub object, and adding a function to set the PubSub object, and adding code to call the PubSub object's publishing function whenever the Model has updated its value.

 
private PubSub mPubSub = null;
 
    public void setPubSub(PubSub pubSub)
    {
      mPubSub = pubSub;
    }
 
    public void pushNumber(int number)
    {
      if (mClearOnNextNumber == true)
      {
        mCurrentNumber = 0;
        mClearOnNextNumber = false;
      }
 
      mCurrentNumber *= 10;
      mCurrentNumber += number;
 
      if (mPubSub != null)
      {
        mPubSub.publish(SubscriptionType.NUMBER_UPDATED, mCurrentNumber);
      }
    }
 
    public void clear()
    {
      mCurrentNumber = 0;
      if (mPubSub != null)
      {
        mPubSub.publish(SubscriptionType.NUMBER_UPDATED, mCurrentNumber);
      }
    }
 
    public void equals()
    {
      if (mCurrentState == State.ADDING)
      {
        mCurrentNumber = mStoredNumber + mCurrentNumber;
      }
      else if (mCurrentState == State.SUBTRACTING)
      {
        mCurrentNumber = mStoredNumber - mCurrentNumber;
      }
 
      if (mPubSub != null)
      {
        mPubSub.publish(SubscriptionType.NUMBER_UPDATED, mCurrentNumber);
      }
 
      mCurrentState = State.IDLE;
    }

11. Now add subscription code to the View. We will let Form1.cs implement the ISubscription interface and update the textbox in the UI when something gets published. Open Form1.cs and add this in it:


public partial class Form1 : Form, ISubscription
 
    private PubSub mPubSub = null;
 
    public void setPubSub(PubSub pubSub)
    {
      mPubSub = pubSub;
      mPubSub.subscribe(this, SubscriptionType.NUMBER_UPDATED);
    }
 
    public void numberUpdated(int number)
    {
      textBox.Text = "" + number;
    }

12. Now we need to wire some things together. Open Program.cs (which should have been autogenerated when you created the project), and modify it to look like this:


using System;
using System.Collections.Generic;
using System.Windows.Forms;
 
namespace MVCexample
{
  static class Program
  {
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      PubSub myPubSub = new PubSub();
      Form1 myForm = new Form1();
      myForm.setPubSub(myPubSub);
      Controller myController = new Controller(myForm);
      Model myModel = new Model();
      myModel.setPubSub(myPubSub);
      myController.setModel(myModel);
      Application.Run(myForm);
    }
  }
}

To recap what we are doing here, we create a PubSub object, and we attach it to both the View and the Model, so that they can subscribe and publish independently of each other. We create the Controller and make it aware of the View, so that the Controller can register and listen to UI events using the Observer pattern. We also make the Controller aware of the Model, because the Controller needs a reference to the Model in order to give user data to it.

And that's it, we're done! Now you can run the project and play around with the calculator.

In case you get compile errors, make sure you are using the right namespace in all of your files.

And in case you have a problem that nothing happens when you click a button, it's likely you have forgotten to map the buttons' click events with the corresponding click functions. (See "Please do not forget..." above in point 5.)


Note that the UI is unaware of the Model, and the Model has no direct references to the View either. (MVC mission accomplished!) So if you would want to make a more fancy UI, you would only have to update the View (Form1.cs) without messing around with the internals of the calculator.

And in case you would want to run the Model and the View on separate computers (for example running the Model on a server), you could modify the PubSub implementation so that it passes subscription messages asynchronously over the network instead, for example using TCP or UDP.

If you'd want to use fewer classes, I guess you could skip using the event listener and instead reuse the PubSub object for sharing UI events with the Controller from the View.

One important shortcoming with this calculator (other than that it only handles plus and minus, ehum...) is that it uses a signed int for storing its value. This means it can't handle numbers larger than 2^31 minus 1. If you'd want to implement a real calculator you should use a larger datatype.

If you want to get hold of the files for this project, I have uploaded them here: https://github.com/mattiaserlo/MVCexample

torsdag 18 juli 2013

The shape of things to come

So this is how they will look, the new Xbox (a.k.a. Xbox One) and the new PlayStation.

Don't they look a bit similar? It this a new design trend? To split the device in two differently designed pieces? It's interesting that both new consoles are sporting this design. Personally I'm not very fond of it. I prefer a more homogenic design.

Comparing the two, I'd say the PS4 design wins.

Then again, the new Xbox One is a thing of pure beauty compared to the original Xbox. That thing was just hideous!

torsdag 11 juli 2013

Tutorial on how to use the Flickr API


Inspired by one of Apple TV's screen savers, which fetches pictures from a Flickr account and moves them elegantly across the screen, I decided some time ago to see if I could do a similar app using web technologies. I named the app Flickrfeed, and here I thought I'd share how I made it.

Let's think about what we need to do to make such an app.

1. We need some HTML skeleton code so that we have a page to load and somewhere to put the Javascript code that will handle the logic.

2. We'll need to connect to Flickr to ask for photos from an account, and download those photos to our app.

3. We'll need a graphics surface to draw the photos on.

4. We'll need an animation loop for moving the photos across the screen.


I created an empty file called index.html, and I put some HTML skeleton code there, as well as a Canvas element:


<!DOCTYPE HTML>
<html>
        <head>
                <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
                <style type="text/css">
                        body {
                                margin-top: 0px;
                                margin-right: 0px;
                                margin-bottom: 0px;
                                margin-left: 0px;
                                padding: 0px;
                        }
                        canvas {
                                display: block;
                        }
                </style>
        </head>
        <body bgcolor="#000000">
                <canvas id="myCanvas" width="600" height="400"></canvas>
        </body>
</html>

You can see some CSS styling code there as well, to ensure that there will be no additional border space between the window and the canvas element.

There are many different devices out there, and consequently a lot of different screen sizes to support. Independently of screen size, I wanted the canvas to always take up as much space as possible, even if the user resizes the window, so I added some code for listening to the window resize event and automatically setting the canvas size accordingly.

This script code can be put in the head section:


<script>
        function pageLoaded() {
                window.onresize = function() {
                        var canvas = document.getElementById("myCanvas");
                        if (canvas.width != (window.innerWidth)) {
                                canvas.width = window.innerWidth;
                        }
                        if (canvas.height != (window.innerHeight)) {
                                canvas.height = window.innerHeight;
                        }
                }
                window.onresize();
        }
</script>

The explicit call to window.onresize() on the last line is to ensure that the canvas size is set at least once, i.e. even if the user does not resize anything.

Now to make sure the pageLoaded function is indeed called when the page is loaded, lets hook it up using the onload event:


<body bgcolor="#000000" onload="pageLoaded()">

Great! Now the canvas will scale automatically to whichever screen size or browser window size is being used.

Next, I turned to Flickr and started reading their instructions for developers on www.flickr.com/services/api/.
By registering as a developer on Flickr you get an API key, which is basically a long string that uniquely identifies you as a user of the Flickr APIs. When you call Flickr APIs from your app you need to use this key so that Flickr recognizes you.

It actually turned out to be quite simple to use Flickr's API to download photos from an account.

First of all, we need to download a script from Flickr to our web app and call their search API.
This can be added to the body of the HTML code:


<script src='http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=<YOUR_API_KEY>&user_id=<YOUR_USER_ID>&content_type=1&per_page=1000&format=json'></script>

(You need to exchange YOUR_API_KEY and YOUR_USER_ID to your own values that you get when you register as a Flickr developer.)

When this line is executed, the Flickr search API will be called. Next, Flickr will call a predetermined function in your app to let you know the search result. So for this to work, you need to have such a function in your code. The function must be named jsonFlickrApi.


function jsonFlickrApi(object) {
        console.log("Hey, I got a reply from Flickr!");
        // We'll add more code here later...
}

Actually, by the time this function is called, no photos have been downloaded yet. The received object contains only references to the photos and information about where you can download them from. You can find this information in an array of object: "object.photos" and thus you can check how many references you received by checking "object.photos.length".

Next we want to add code to download some photos. We may not want to download all the photos, because there could be tons of them and it could take a long time. It's probably better to download just a few photos, render those, and then download a few more photos later when we have finished showing the first ones.

For downloading photos I added a helper function like this:


function generateURL(farmID, serverID, id, secret) {
        // Thumbnail size, 100 pixels on longest side:
        // return "http://farm" + farmID + ".static.flickr.com/" + serverID + "/" + id + "_" + secret + "_t.jpg";
                      
        // medium size, 500 pixels on longest side:
        return "http://farm" + farmID + ".static.flickr.com/" + serverID + "/" + id + "_" + secret + ".jpg";

        // large size, 1024 pixels on longest side, not 100% supported...:
        // return "http://farm" + farmID + ".static.flickr.com/" + serverID + "/" + id + "_" + secret + "_b.jpg";
}

As you see, most lines are actually commented out. Flickr allows for different sizes of images and here I went for the medium size, 500 pixels on longest side. Choosing the larger size would obviously mean longer download times.

Now before adding code to actually load the photos, let's first add a few variables to the global scope:


var photoArray = null;
var myMovingImages = new Array();
var MAX_NUMBER_OF_PHOTOS_ON_SCREEN = 3;

  • photoArray will be used to refer to the array of photo references that we receive from Flickr.
  • myMovingImages will be an array of images that are moving across the screen.
  • MAX_NUMBER_OF_PHOTOS_ON_SCREEN defines how many images we want to be shown at a time. We don't want to flood the screen with hundreds of images.

Also, let's add a class for a moving image.

Which properties does a moving image need? Basically, it needs:

  • a reference to a displayable Image,
  • a position for where to draw the image
  • a velocity at which the image moves.
  • we'll also add an index so that we know which element in the photoArray it is associated with.


function MovingImage(image, photoArrayIndex) {
        this.image = image;
        this.photoArrayIndex = photoArrayIndex;

        this.x = 0;
        this.y = 0;

        this.velocityX = 0;     // in pixels per second
        this.velocityY = 0;     // in pixels per second

        this.initialized = false;
}

We will get back to explain the initialized variable later.

Now we can modify the jsonFlickrApi function so that it actually loads some photos using the generateURL helper function.

Instead of always drawing the photos in order, it might be fun to randomize the order.
So let's add a function that pulls a random index from our photo array:


function getRandomPhotoNumber() {
        return Math.floor(Math.random() * photoArray.length);
}

And here's the modified jsonFlickrApi function:


function jsonFlickrApi(object){
        console.log("Hey, I got a reply from Flickr!");
        if (object.stat == 'ok') {
                photoArray = object.photos.photo;
                console.log("Number of photos: " + photoArray.length);
                // Download the first few photos
                for (var i=0; i<MAX_NUMBER_OF_PHOTOS_ON_SCREEN; i++) {
                        var myImage = new Image();
                        var randomPhotoNumber = getRandomPhotoNumber();
                        myMovingImages[i] = new MovingImage(myImage, randomPhotoNumber);
                        myImage.src = generateURL(object.photos.photo[randomPhotoNumber].farm,
                                                                                object.photos.photo[randomPhotoNumber].server,
                                                                                object.photos.photo[randomPhotoNumber].id,
                                                                                object.photos.photo[randomPhotoNumber].secret);                                               
                }
  }
  else {
         alert('Flickr search failed');
        }
}

So, what did we do here? We generated URLs for some photos we want to show, and we created some instances of our MovingImage class. Also we created Image objects with src set to the corresponding URL. Doing so will trigger the browser to download the image from the URL.

Hey, this means we have gotten quite far! We have sent a request to Flickr, we have retrieved a list of images, and we have started downloading a few of those images. It's time to add code for drawing some photos!

Let's add code in pageLoaded() for setting up a drawing loop that we wish to be run 60 times a second:


        window.requestAnimFrame = (function(callback){
                return window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                function(callback){
                        window.setTimeout(callback, 1000 / 60);
                };
        })();

...and let's add a function where we will do our actual drawing. This function will take one parameter
that tells at which time the function was called last time. Inside the function we can get the current time and compare that with the last time, to know how much time has elapsed since the last animation call. Later we will use this time to determine how long animation step we need to take. (I.e. how far should we move the images that are moving across the screen.)


function animate(lastTime) {
        var date = new Date();
        var time = date.getTime(); // milliseconds
        var timeDiff = time - lastTime;

        // TODO: We will add our actual drawing here later...

        // Finally, request a new animation frame
        requestAnimFrame(function() {
                animate(time);
        });
}

Let's also add one explicit call to our animate function so that it is called once when our page is loaded.
Add this code to the end of pageLoaded():


var date = new Date();
var time = date.getTime();
animate(time);

Now we have set up an animation function, but so far we are not doing any actual drawing. So let's add some more code to "animate", to handle the drawing of one animation frame.

To draw graphics, we need access to the context of the canvas:


var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");

And before drawing, we need to clear the canvas of any previously drawn graphics. If we don't do this, each frame will simply add graphics on top of what we drew in the last frame, causing the photos to smear all over the canvas.


context.clearRect(0, 0, canvas.width, canvas.height);

It's finally time to actually draw some photos!


// Draw all moving images
for (var i=0; i<myMovingImages.length; i++) {
        if (myMovingImages[i].image && myMovingImages[i].image.width > 0) {
                if (!myMovingImages[i].initialized) {
                        initializeMovingImage(myMovingImages[i], canvas.width, canvas.height);
                }

                context.fillStyle = "rgba(0, 0, 0, 0.5)";
                context.fillRect(myMovingImages[i].x-20, myMovingImages[i].y-20, myMovingImages[i].image.width+40, myMovingImages[i].image.height+40);
                context.drawImage(myMovingImages[i].image, myMovingImages[i].x, myMovingImages[i].y);
        }
}

So what does this drawing code do?

We loop over all the moving images. For each photo we initialize its MovingImage instance if that hasn't been done already. We then make two drawing calls. The first one draws a rectangle slightly larger than the photo itself, with a alpha value of 0.5. This will create something of a shadow behind the photo. The second call is for drawing the actual photo on top of the shadow.

There is one function call that we have not implemented yet: initializeMovingImage. In that one we need to add code to set the initial position and velocity of the moving image.

  • To have images moving across the screen from bottom to top, the image should initially be drawn just below the canvas, so that in the next few animation frames when it moves upwards we will start seing just the top of the image at the bottom part of the canvas.
  • As for the x position, let's randomize it a bit so that each image has a different x position.
  • Let's randomize the y velocity as well so that images move at slightly different speeds. Note that the y velocity needs to be negative since we are moving images upwards not downwards. (The HTML canvas uses a coordinate system where the top left pixel is pixel x=0, y=0.)


function initializeMovingImage(movingImage, canvasWidth, canvasHeight) {
        movingImage.x = Math.floor(Math.random() * (canvasWidth-movingImage.image.width));
        movingImage.y = canvasHeight;
        // Set velocity between -20 and -50
        movingImage.velocityY = -20 - Math.floor(Math.random() * 30);
        movingImage.initialized = true;
}

Now we are able to initialize and draw photos on their initial positions. But so far we are not actually moving the photos. To do that we need to update the photos' positions each animation frame. Let's add that in the animation function after drawing the photos, so that in each animation frame all photos are drawn on their current position, and then a new position is calculated. When calling the animation function repeatedly, this will make it look like the photos are moving upwards.

Let's also consider what should happen when a photo moves out from the screen. As a photo moves upwards acroess the screen, eventually only the lower portion of the photo will be visible in the upper part of the canvas, and shortly thereafter the photo will be completely above the canvas and thus not visible anymore. At that time we are done animating that photo, and if we don't do anything else we will simply run out of photos: the bunch of photos that we initially loaded will move across the screen and that's it. That's not very fun, so let's add code that loads a new photo each time one photo disappears from the screen. And have the new photo start at the initial position just below the canvas. Then it will look like there is a constant flow of photos, each popping up at the bottom and sliding upwards.

So, add this loop after the drawing loop:


// Update positions of all images
for (var i=0; i<myMovingImages.length; i++) {
        if (myMovingImages[i].image && myMovingImages[i].image.width > 0) {
                myMovingImages[i].x += myMovingImages[i].velocityX * (timeDiff/1000);
                myMovingImages[i].y += myMovingImages[i].velocityY * (timeDiff/1000);

                // Has the image scrolled out from the screen?
                if (myMovingImages[i].y < -(myMovingImages[i].image.height)) {
                        // Load a new image and put it on the initial position
                        myMovingImages[i].initialized = false;
                        var myImage = new Image();
                        randomPhotoNumber = getRandomPhotoNumber();
                        myMovingImages[i].image = myImage;
                        myMovingImages[i].photoIndex = randomPhotoNumber;
                        myImage.src = generateURL(photoArray[randomPhotoNumber].farm, photoArray[randomPhotoNumber].server, photoArray[randomPhotoNumber].id, photoArray[randomPhotoNumber].secret);
                }
        }
}

To recap what we just wrote, we loop through all the moving images and for each one we update the position. An image's new position is determined by its old position plus its velocity multiplied by the time passed, according to the formula distance = velocity * time.

Instead of using a constant time value, we use the timeDiff parameter, i.e. the measured amount of time passed between this and the last call to the animation function. Depending on the system's performance and workload, the animation function may not be called in a perfect framerate, and by taking timeDiff into account like this we can calculate the position correctly even if the animation framerate varies.

The second if-switch checks if the image has moved out from the screen. An image has moved out from the screen if it is drawn completely above the canvas. The canvas top pixel line is at y position 0, so an image that has moved out completely above the canvas would have its y position on the same value as its negative height, so that's the condition we check. If the image is indeed outside the canvas, we pick a new image from the photoArray and set it on an initial position.

And we're (almost!) done. Now we have automatic loading of new images as existing images move out from the screen, causing a seamlessly endless flow of photos moving from bottom to top.

Let's add just one more thing: When the user clicks on a photo, let's change the current URL to the page on Flickr that holds that photo.

To do so, we'll add this code to the end of pageLoaded:


var canvas = document.getElementById("myCanvas");
canvas.onclick = function(event) {
        var x = event.pageX - canvas.offsetLeft;
        var y = event.pageY - canvas.offsetTop;
        for (var i=myMovingImages.length-1; i>=0; i--) {
                if (myMovingImages[i].image) {
                        if (x >= myMovingImages[i].x &&
                                y >= myMovingImages[i].y &&
                                x < myMovingImages[i].x + myMovingImages[i].image.width &&
                                y < myMovingImages[i].y + myMovingImages[i].image.height) {
                                        location.href = "http://www.flickr.com/photo.gne?id=" + myMovingImages[i].flickrId;

                                        break;
                        }
                }
        }
}

You may wonder why we are looping backwards here. Let's think about the case where two or more photos are drawn on top of each other. If the user clicks such a place, we want to jump to the image that is "on top". Remember that we are drawing the images in the same order as they occur in the myMovingImages array, meaning if there are any images overlapping each other, the one seen "on top" will be the element closest to the end of the array. Thus we'll go through the list from end to beginning and as soon as we find an image covering the clicked area, we'll pick that image.

Flickr has a service where we can give the unique photo ID as argument and Flickr will handle the loading of that page, so we just have to set location.href to "http://www.flickr.com/photo.gne?id=" and add the photo id as argument, then the browser and Flickr will take care of the rest.

And that's it!

Some things that could be improved from here are:

  • Currently we don't take the screen size into account when deciding how many photos to show at the same time. We could add a bit of code in the resize function to recalculate this number dynamically, given the screen width (or height) as input.
  • Similarly, we don't take the screen size into account when deciding which resolution of photos to load and draw. To make the drawing a bit more dynamic, instead of always drawing medium size pictures we could sometimes (perhaps based on a random value, or the screen size) draw small or large images as well. This could be done either by downloading thumbnails and large images via the Flickr API, or we could change the draw function so that it draws the images with some scaling. There is a special prototype of drawImage that handles clipping and scaling, see for example http://www.w3schools.com/tags/canvas_drawimage.asp
  • To make sure that photos are not rendered too much on top of each other, we could spread them out by setting the initial positions more intelligently.
  • We could make the loading mechanism a bit more efficient so that instead of loading a new image for each disappeared image, a set of images could be loaded in advance so that new images are always instantly available.
  • To improve the drawing performance, the canvas and context references could be stored globally so that we don't have to retrieve them every time animate is called.
  • Currently the code that pulls random numbers does not care if the same number is pulled several times. Thus if we are unlucky we might see the same photo in several instances on the screen. To avoid this we could improve the random number pulling code so that it picks numbers more intelligently, for example by shuffling the indexes and then pulling numbers from the shuffled list.
  • We could add some effects, such as the 3D rotation effect used in Apple TV.

Finally, for reference I've written the complete code below (although without the improvements discussed above) to show how everything fits together. Remember to exchange the Flickr API key and user id if you want to make your own application referencing your own photos instead of mine.

If you want to see how it looks in motion, welcome to my site: http://mattiaserlo.com/flickrfeed/index.html


<!DOCTYPE HTML>
<html>
        <head>
                <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
                <style type="text/css">
                        body {
                                margin-top: 0px;
                                margin-right: 0px;
                                margin-bottom: 0px;
                                margin-left: 0px;
                                padding: 0px;
                        }
                        canvas {
                                display: block;
                        }
                </style>
        </head>
        <script>
      
                var photoArray = null;
                var myMovingImages = new Array();
                var maxNumberOfPhotosOnScreen = 3;
              
                var shadowBorder = 14;
              
                function MovingImage(image) {
                        this.image = image;
                  this.flickrId = 0;
                
                        this.x = 0;
                        this.y = 0;
              
                        this.velocityX = 0;     // in pixels per second
                        this.velocityY = 0;     // in pixels per second
              
                        this.initialized = false;
                }
      
                function initializeMovingImage(movingImage, canvasWidth, canvasHeight) {
                        movingImage.x = Math.floor(Math.random() * (canvasWidth-movingImage.image.width));
                        movingImage.y = canvasHeight;
                        // Set velocity between -20 and -50
                        movingImage.velocityY = -20 - Math.floor(Math.random() * 30);
                        movingImage.initialized = true;
                }
              
                function animate(lastTime) {
                        var date = new Date();
                        var time = date.getTime(); // milliseconds
                        var timeDiff = time - lastTime;

                        var canvas = document.getElementById("myCanvas");
                        var context = canvas.getContext("2d");
                      
                        context.clearRect(0, 0, canvas.width, canvas.height);
                      
                        // Draw all moving images
                        for (var i=0; i<myMovingImages.length; i++) {
                                if (myMovingImages[i].image && myMovingImages[i].image.width > 0) {
                                        if (!myMovingImages[i].initialized) {
                                                initializeMovingImage(myMovingImages[i], canvas.width, canvas.height);
                                        }

                                        context.fillStyle = "rgba(0, 0, 0, 0.5)";
                                        context.fillRect(myMovingImages[i].x-shadowBorder, myMovingImages[i].y-shadowBorder, myMovingImages[i].image.width+(2*shadowBorder), myMovingImages[i].image.height+(2*shadowBorder));
                                        context.drawImage(myMovingImages[i].image, myMovingImages[i].x, myMovingImages[i].y);
                                }
                        }
                      
                        // Update positions of all images
                        for (var i=0; i<myMovingImages.length; i++) {
                                if (myMovingImages[i].image && myMovingImages[i].image.width > 0) {
                                        myMovingImages[i].x += myMovingImages[i].velocityX * (timeDiff/1000);
                                        myMovingImages[i].y += myMovingImages[i].velocityY * (timeDiff/1000);
                      
                                        // Has the image scrolled out from the screen?
                                        if (myMovingImages[i].y < -(myMovingImages[i].image.height + shadowBorder)) {
                                                // Load a new image and put it on the initial position
                                                myMovingImages[i].initialized = false;
                                                var myImage = new Image();
                                                randomPhotoNumber = getRandomPhotoNumber();
                                                myMovingImages[i].image = myImage;
                                                myImage.src = generateURL(photoArray[randomPhotoNumber].farm, photoArray[randomPhotoNumber].server, photoArray[randomPhotoNumber].id, photoArray[randomPhotoNumber].secret);                                 
                                                myMovingImages[i].flickrId = photoArray[randomPhotoNumber].id;
                                        }
                                }
                        }
                      
                        // Finally, request a new animation frame
                        requestAnimFrame(function() {
                                animate(time);
                        });
                }
      
                function pageLoaded() {
                        window.onresize = function() {
                                var canvas = document.getElementById("myCanvas");
                                if (canvas.width != (window.innerWidth)) {
                                        canvas.width = window.innerWidth;
                                }
                                if (canvas.height != (window.innerHeight)) {
                                        canvas.height = window.innerHeight;
                                }
                        }
                        window.onresize();
                      
                        window.requestAnimFrame = (function(callback){
                                return window.requestAnimationFrame ||
                                window.webkitRequestAnimationFrame ||
                                window.mozRequestAnimationFrame ||
                                window.oRequestAnimationFrame ||
                                window.msRequestAnimationFrame ||
                                function(callback){
                                 window.setTimeout(callback, 1000 / 60);
                 };
                        })();           // Betyder det att den exekveras en g蚣g automatiskt? Behs det?
                      
                        var date = new Date();
                        var time = date.getTime();
                        animate(time);                          // Behs detta?
                      
                        var canvas = document.getElementById("myCanvas");
                        canvas.onclick = function(event) {
                                var x = event.pageX - canvas.offsetLeft;
                                var y = event.pageY - canvas.offsetTop;
                                for (var i=myMovingImages.length-1; i>=0; i--) {
                                        if (myMovingImages[i].image) {
                                                if (x >= myMovingImages[i].x &&
                                                                y >= myMovingImages[i].y &&
                                                                x < myMovingImages[i].x + myMovingImages[i].image.width &&
                                                                y < myMovingImages[i].y + myMovingImages[i].image.height) {
                                                                        location.href = "http://www.flickr.com/photo.gne?id=" + myMovingImages[i].flickrId;
                                                                        break;
                                                }
                                        }
                                }
                        }
                }
              
                function getRandomPhotoNumber() {
                        return Math.floor(Math.random() * photoArray.length);
                }
              
                function jsonFlickrApi(object){
                        console.log("Hey, I got a reply from Flickr!");
                        if (object.stat == 'ok') {
                                photoArray = object.photos.photo;
                                console.log("Number of photos: " + photoArray.length);
                              
                                // Download the first few photos
                                for (var i=0; i<maxNumberOfPhotosOnScreen; i++) {
                                        var myImage = new Image();
                                        var randomPhotoNumber = getRandomPhotoNumber();
                                        myMovingImages[i] = new MovingImage(myImage);
                                        myMovingImages[i].flickrId = object.photos.photo[randomPhotoNumber].id;
                                        myImage.src = generateURL(object.photos.photo[randomPhotoNumber].farm,
                                                                                                object.photos.photo[randomPhotoNumber].server,
                                                                                                object.photos.photo[randomPhotoNumber].id,
                                                                                                object.photos.photo[randomPhotoNumber].secret);
                                }
                 }
                 else {
                         alert('Flickr search failed');
                        }
                }

                function generateURL(farmID, serverID, id, secret) {

                        // Thumbnail size, 100 pixels on longest side:
                        // return "http://farm" + farmID + ".static.flickr.com/" + serverID + "/" + id + "_" + secret + "_t.jpg";
                      
                        // medium size, 500 pixels on longest side:
                        return "http://farm" + farmID + ".static.flickr.com/" + serverID + "/" + id + "_" + secret + ".jpg";

                        // large size, 1024 pixels on longest side, not 100% supported...:
                        // return "http://farm" + farmID + ".static.flickr.com/" + serverID + "/" + id + "_" + secret + "_b.jpg";
                }
              
        </script>
        <body bgcolor="#000000" onload="pageLoaded()">
                <canvas id="myCanvas" width="600" height="400"></canvas>
        <script src='http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=9d7128ec941d553156644efb41a7dc7e&user_id=73384519@N00&content_type=1&per_page=1000&format=json'></script>
        </body>
</html>