Feeds:
Posts
Comments

Archive for March, 2010

There are times when you want to prevent copying

I found when developing a painting application that I did not want to allow text copying on a canvas element. With the iPad and iPhone this can be done with CSS.

.no-copy {
  -webkit-user-select: none;
}

Now simply add this to the elements you do not want to have the copy dialog appear.

  <canvas id="touchpaint" class="no-copy"></canvas>

Read Full Post »

After creating TouchPaint I quickly realized that there were some users out there putting a lot of work into there pictures and they would lose them with a simple page reload.

There is an easy solution. Many of the latest generation of browsers including the IPad / iPhone, Safari 4+, Chrome, Firefox 3.5+ already support the HTML 5 Storage.

What HTML 5 Storage Provides

  • 5 megabytes of storage per domain
  • A SQL Light Database
  • JavaScript APIs to access the storage

I found this plenty for storing images however after going over the Webkit Data Storage Example I thought there might be something a little more approachable for Java developers. So I created a simple Data Access Object (DAO) that does the following

  • Intializes a database connection with a callback for successful attempts. Unsupported browsers see nothing.
  • Creates the site Database and tables on the first access.
  • Provides a Read and Update API

Lets look at each piece. If you want to see the raw JavaScript source you can find it here.

SQL Statements

First of all I keep the SQL statements which are prepared statements all together in a JavaScript map. This keeps the DAO pretty clean and it makes the SQL easy to update.

    var sql = {
      CREATE : "CREATE TABLE paintings (id REAL UNIQUE, json TEXT )",
      INSERT : "INSERT INTO paintings (id, json ) VALUES ( ?, ?)",
      UPDATE : "UPDATE paintings SET json = ? WHERE id = ?",
      GET : "SELECT json FROM paintings",
      COUNT : "SELECT COUNT(*) FROM paintings"
    };

Database Creation

New users will always be checked if the Database is intialized.

function initDB( _callback ) {
        try {
            if ( window.openDatabase ) {
                db = openDatabase("Storage", "1.0", "Painting Database", 2200000);
                checkIfDBInitialized();
                if ( !db ) {
                    alert( "Failed to open the database. Have you allocated enough space?" );
                } else {
                    if ( typeof _callback === "function" ) {
                        _callback.apply({}, []);
                    }
                }
            }
        } catch( error ) { 
            alert( "Error trying to open database : " + error );
        }
    }

I added the callback so those that want to then query or start polling for data can do so. A refactoring might also provide a failure case callback as well.

Check if Initialized

This code tries to see how many items have been put in the database. You can pull the count out if you choose and use it to assign numeric ids to future items.

  function checkIfDBInitialized() {
        db.transaction( 
               function(tx) {
                tx.executeSql( sql.COUNT, [],
                            function(tx, result) {
                                // do nothing 
                            },
                            function( tx, error) {
                               tx.executeSql( sql.CREATE, [],
                                              function(result) {
                                   // create our single row
                                   tx.executeSql( sql.INSERT, [ dataId, "[]" ]);
                             });
            });
        });
    }

Read

My example stores a JSON string which could be any text really.

function load( _callback) {
        db.transaction(function(tx) {
            tx.executeSql( sql.GET, [], function(tx, result) {
                if (result.rows.length > 0) {
                    var _row = result.rows.item(0);
                    var _json = _row['json'];
                    if ( typeof _callback === "function" ) {
                        _callback.apply( {}, [ _json ] );
                    }
                }
            }, function(tx, error) {
                alert( "Failed to retrieve paintings from database - " + error.message );
                return;
            });
        });
    };

Update

    function save( _json, _callback ) {
        db.transaction(function (tx) {
            tx.executeSql( sql.UPDATE, [ _json, dataId ], _callback);
        });
    };

Sample code :

Here the is an example which you may find at http://gregmurray.org/ipad/storage.

window.onload = function() {
    pictureDao.init( function() {
        // we have succesffuly loaded.
        pictureDao.load( function( text ) {
            var dataDiv = document.getElementById( "data" ).value = text;
        });
    });
};

function save() {
    var dataDiv = document.getElementById( "data" );
    pictureDao.save( dataDiv.value );
}

The window.onload fires and the database is initialized. If successful it will load text in the text area in the screen. Enter something and click save which results in the savefunction being invoked. The save function takes the text from the div and put it in local storage. You can verify by reloading the page.

That is pretty much it.

Resources :

Full source used in the examples in this page
What WG Web Storage Specification
HTML 5 Data Storage Example on gregmurray.org

Read Full Post »

Happy Greg

A portrait of me by a friend.

The JavaScript touch APIs can be used with the canvas tag to easily create a painting application in a web page that work in multiple clients which include the iPad / iPhone / and Nexus One. Given the iPad will have such a large working are a touch painting application would be very fun.

The Touchpaint application which you can find here is pretty straight forward. The key code for event tracking is :

function move( e ) {  
  if ( _widget.drawing ) {  
      var te =  e.touches.item(0);  
      var _current = { x : te.clientX - _widget.pos.x, y : te.clientY - _widget.pos.y };  
      if ( _widget.last !== null) {  
              _widget.drawLine( _widget.last, _current );  
      }  
      _widget.last = _current;  
    }  
    e.preventDefault();  
}  

The main code of interest is the access to the e.touches to get the location of the touch event and the e.preventDefault(); which prevents the event from propagating to the page and causing a page scroll.

And for non touch users the equivalent is :

function moveMouse( e ) {  
  if ( _widget.drawing ) {  
      var _current = { x : e.pageX - _widget.pos.x, y : e.pageY - _widget.pos.y };  
      if ( _widget.last !== null) {  
              _widget.drawLine( _widget.last, _current );  
      }  
      _widget.last = _current;  
    }  
    e.preventDefault();  
}  

In a future post I will show how to store the drawing in the HTML 5 based local storage and or send to a server component. For now if you want to play with the source go to http://gregmurray.org/ipad/touchpaint.

Read Full Post »

I had the chance to get my hands on a Nexus One and was able to run though my touch and gesture tests at http://gregmurray.org/ipad. Given both the Nexus one and the iPhone / iPad all use a variant of Webkit I found the following :

  • ontouchstart, ontouchmove, ontouchend work fine.
  • ongesturestart, ongesturechange, ongestureend do not work.

What does this mean?

You can create some UIs that use patterns such page swipe / flick gestures as seen in my carousel widget but some other gestures like stack spread and pinch to zoom will not. See the gesture page if you want to try yourself. While limited there is still the chance to develop some HTML interfaces that work across a wide array of touch devices.

Hope for Standardized Touch Support

Let’s hope going forward that the Nexus One ( Android ) browser will expand it’s support for gestures. For now we will have to work with touch events which are the basis for the gestures. Let’s keep our fingers crossed that other touch based operating system providers expose gestures and touch events in their browsers. An even better solution would be to have gesture and touch events in a future HTML standard.

Read Full Post »

Bill Scott directed me to a catalog of user interactions that the user experience community is creating to track new patterns seen with the iPad. To that end I have begun working on some widgets to that end.

Following are two patterns from the catalog as a Stack widget implemented in using JavaScript and CSS. The JavaScript event handlers are Safari Extensions to JavaScript. I also used some Webkit CSS transforms for rotating the image and creating a light box.

Tap to Open

Pretty self explanatory. This is achieved by adding a onclick event to the top level image and expand the widget with a user tap. I added a window surrounding the expanded images with a close button styled using webkit gradients. The images are also randomly rotated plus or minus 15 degrees to make it look like a stack of images. The initial rotation and location is saved so when the stack is closed it can return to its initial state.

This pattern can be seen in any Webkit based browser or the iPad simulator.

Stack Spread

Basically a user can sneak a peak at what is inside a stack using a spread gesture and close the stack using a pinch gesture.

This gesture was the most difficult to integrate. It basically required tracking two events at both the widget and page level. When a spread or pinch gesture occurs the widget will scale accordingly. I wanted to know the location of touch events and all the changes.

You will need an iPhone or an iPad (not the simulator) to see this in action.  I created some internal hooks into the widget to allow for manual scaling of the widget so I could develop this widget using Safari 4.x.

Tracking Gestures and Touch Events in web pages

A gesture such as a spread or pinch fires the following events :

  1. touchstart ( first finger )
  2. gesturestart
  3. touchstart (second finger)
  4. gesturechange ( many times )
  5. gestureend (when second finger is removed)
  6. touchend ( second finger )
  7. touchend ( first finger)

Confused yet? I was which led me to create a gesture object that is created following the second touchstart event.

I created a wrapper for the touch events that I could use to track through the life of the gesture. The event once created looks like this.

window.currentGesture {
   touch1 :  { id : 7, y : 325 }
   touch2 :  { id : 4r, y : 200 },
   top : // reference too touch2
   bottom : // reference too touch1
   scale : 125 // distance between top and bottom
}

This object is build with this function :

    function touchStart( e ) {
    	if ( window.inTouch !== true  ) {
    	    window.currentGesture = {
    	        touch1 : { id : e.touches.item(0).identifier, y : e.touches.item(0).pageY }
    	    };
    	} else {
    		var wc = window.currentGesture ;
           	wc.touch2 =   { id : e.touches.item(0).identifier, y : e.touches.item(0).pageY }
          	wc.scale = Math.abs( wc.touch2 - wc.touch1 );
        	if ( wc.touch2.y &gt; wc.touch1.y ) {
        	    wc.top =  wc.touch1;
        	    wc.bottom = wc.touch2;
        	} else {
                    wc.top =  wc.touch2;
        	    wc.bottom = wc.touch1;
        	}
    	}
    }

This code gets called for each finger touch and will only complete when a gesture is in process ( window.inTouch === true ) .

The next difficulty was tracking the changes in the pageY events. At first I thought I could do this by tracking the identifiers for events though there were cases when they were the same for both so I went with an easier approach. If the number is less than the top it becomes the top and vise-versa with the bottom. Next I needed to track the changes as they happened. I thought the gesturechange event would be the event for me but it did not contain references to the touches so I instead used the touchmove events which are what the gesturechange events are built on.

    function touchMove( e ) {
        if ( window.inTouch !== true) {
            return;
        }
        var t1 = e.changedTouches.item(0);
        if ( t1.pageY   window.currentGesture.bottom.y) {
        	window.currentGesture.bottom.y = t1.pageY;
        }
         window.currentGesture.scale = window.currentGesture.bottom.y - window.currentGesture.top.y ;
        e.preventDefault();
    }

The code above fires many times for each finger as you gesture occurs. At first I used tried to access the e.touches.item(0) but found the events were from the original touch event while I wanted the current event. The problem was solved by getting the changed events e.changedTouches.item(0).

One thing I noticed was that while the first touch may have been in the widget sometime the second might fall slightly outside and once the touchmove events went outside the widget they would not get detected. I was able to get around this by attaching the touchMove event listener to both the document and the top image.

<b>So what did we learn?</b>

  • You can detect events and gestures in a web page.
  • Touch Events are the basis for gestures
  • Touch Events can be used to find additional information about a gesture such as the location
  • In some cases an additional level of abstraction might be needed to track a gesture

Find the working touch stack widget and source and working example here.

Let me know if you have any questions comments.

Additional Reading :

iPad Interesting Moments (Bill Scott)
New Multi-touch Interactions (Luke Wroblewski)
Touching and Gesturing on the iPhone (Neil Roberts)

Read Full Post »