Complete this assignment with the same team you worked with for Type Inference (Written). You and your partner must each understand the answers to all the problems, so don't just split up the work.

Functional Reactive Programming with Flapjax

Drag and drop is an effective technique for various interactions. It is so common on the desktop that it is packaged into standard APIs. In this assignment, you will do the same for web applications. You will build a generic, reusable drag and drop API. You will use your API to implement two actions based on drag and drop—moving and resizing windows.

Using Flapjax

For this assignment, you will write your applications in Flapjax. Flapjax is a JavaScript framework for writing functional reactive applications. The Flapjax API reference lists the functions you have at your disposal. There are variety of demos for you to try as well.

This assignment is an exercise in programming with event streams. You will not need behaviours, nor will you need to communicate with the server.

Since Flapjax is a framework, you will be writing JavaScript code. If you aren't familiar with JavaScript, reading the Flapjax examples should help you get started. If you want more information, search the web. The Mozilla Developer Center (MDC) is a good place to start. As always, don't hesitate to ask the TA's.

Two important details:

  1. Use Firefox 3 with Firebug for this assignment. Flapjax is designed for all major browsers. However, an application can use a vendor-specific feature and break compatibility. This is not a web programming assignment, so you need not target multiple browsers.

  2. You may not use the sendEvent and sendBehavior (or sendBehaviour) functions in Flapjax. They are intended for building primitive events and for unprincipled hacks in large systems.

DOM Background

We will give you directions and hints on using the DOM throughout this assignment.

Positioning Elements

Usually, the position of an element on a web page is computed dynamically. This calculation is determined by factors such at the position of neighboring elements and the size of the window. When dragging an element, we will often abandon the normal positioning routine to set the position beneath the mouse. In CSS terminology, we will use absolute positions.

The following code positions a black box 80 pixels off the left edge and 50 pixels off the top edge of the window:

<div id="dragTarget0"
     style="background-color: #000000; color: #ffffff; padding: 10px; 
            font-family: sans-serif; cursor: move;
            position: absolute; left: 80px; top: 50px">
Drag Me!
</div>

Note that the element has the name dragTarget0. In JavaScript, you can reference named DOM elements by:

document.getElementById('dragTarget0')
Alternatively, you can use Flapjax's shorthand, $('dragTarget0').

This is your template for draggable elements. You're free to modify it as you wish. Remember to include position: absolute. If you have multiple drag-targets on screen, you may wish to assign them distinct initial positions with left: intial-x; top: initial-y.

More Information

For more information on the DOM, CSS, etc. the web is your best resource. In particular, w3schools.com has a comprehensive reference.

Dragging Without Dropping

The mousedown event fires whenever a mouse button is pressed over a specified element. In Flapjax, you can retrieve an event stream of mousedown events with:

extractEvent_e(element,'mousedown')

Similarly, the mousemove event fires whenever the mouse is moved over an element (or the entire document):

extractEvent_e(element,'mousemove')

  1. Use mousedown and mousemove as your primitive event sources to write the function:

    drag_e :: Element -> EventStream Dragging
    
    Dragging = { dragging: true, left: integer, top: integer }
    
    After the mouse button is pressed, the returned event stream should fire a Dragging event for each mousemove event. Since we haven't handled the mouseup event, the Dragging events will continue firing after the mouse button is released. We will tackle this later.

  2. Include a drag-target on your page, such as the Drag Me! element. Write the coordinates from each Dragging event back to the element so that it moves when dragged.

  3. Is anything wrong with your implementation of dragging? Does it behave like drag and drop on a desktop operating system? (Does it?)

DOM Hints

To compute the position of an element, use the cumulativeOffset function.

The mousemove event has the clientX and clientY properties. You may use them to compute the left and top properties of Dragging.

To set the position of an element, write to the style.left and style.top properties. For example:

$('dragTarget0').style.left = 50;
$('dragTarget0').style.top = 100;

Basic Drag and Drop

We will now handle mouseup events so that drop works correctly.

  1. Extend drag_e to fire a drop event when a mouseup event fires:

    drag_e :: Element -> EventStream Dragging
    
    Dragging = { dragging: true, left: integer, top: integer }
             | { drop: true, left: integer, top: integer } 
    
    You may wish to use one_e to fire a single event.

  2. Try to drag the element multiple times. Draw multiple elements and ensure that they can be dragged independently.

  3. Extend drag_e to fire a startDrag event:

    drag_e :: Element -> EventStream Dragging
    
    Dragging = { startDrag: true, left: integer, top: integer }
             | { dragging: true, left: integer, top: integer }
             | { drop: true, left: integer, top: integer } 
    

Window Manager

You will use drag_e to implement two basic operations that window managers provide: moving windows and resizing windows. A window has a title bar which can be dragged to move the window and a resize handle on the bottom-right corner which can be dragged to resize the window.

The rest of the window contains an application. For simplicity, all your windows will contain the same application, a stripped-down version of Notepad. We can define the Notepad window as:

<div style="position: absolute; border: 1px solid black">
  <div style="background-color: #003333; color: #ffffff; cursor: move;
              padding: 5px">
    <!-- This DIV is the title bar -->
    Notepad
  </div>
  <div style="padding: 10px">
    <!-- This DIV is the container -->
    <textarea style="height: 100%; width: 100%">
    It's all about Windows 3.1
    </textarea>
  </div>
  <div style="position: absolute; cursor: move; background-color: black;
              width: 10px; height: 10px; bottom: 0px; right: 0px">
    <!-- This DIV is the resize handle -->
  </div>
</div>

  1. Create a document that displays a link/button entitled "New Window." When the link is clicked, create and display a new Notepad window.

    Don't stack your windows at a single location. You should either cascade or tile your new windows.

  2. Use drag_e to allow the user to drag the title bar to move the window. In addition, when the dragStart event fires, bring the selected window to the front. (Use style.zIndex.)

    Ensure that dragging works correctly when you have multiple windows open. In addition, ensure that you can still enter text into each window.

  3. Use drag_e to allow the user to resize the window. You will need to set the style.height and style.width properties of the container, not the window.

    Don't let the window get too small. Choose and enforce a reasonable minimum size.

DOM Hints

Flapjax defines functions that allow you to construct HTML elements compositionally. These functions are defined for most HTML elements. You may find them useful. For example, the HTML above may be written using Flapjax's combinators as:

DIV({ style: { position: 'absolute', border: '1px solid black'} },
  DIV({ style: { backgroundColor: '#003333', color: '#ffffff', cursor: 'move',
                 padding: '5px' } },
    // This is the title bar
    'Notepad'),
  DIV({ style: { padding: '10px' }},
    // This is the container
    TEXTAREA({ style: { height: '100%', width: '100%' } },
      "It's all about Windows 3.1")),
  DIV({ style: { position: 'absolute', cursor: 'move', backgroundColor: 'black',
                 width: '10px', height: '10px', bottom: '0px', right: '0px' } }
    // This is the resize handle
    ))

Handin

Turn in all files needed to run your program.