Wednesday, 10 August 2011

Seaside with HTML5 for iPad & iPhone

We did a demo recently on a iPad showing how, with HTML and some javascript, we could make a Seaside client web site look native-ish. It's impressive what a minimalist layout, large fonts and big buttons can do.

This is new system development: the client had already seen a simple Seaside 'portal' web site which gave employees access to their shifts, with selected shift details and client information (each shift is a service to a specific client). All done as one integrated system for which they have a VW fat client.

For the iPad and iPhone we wanted to be aware of the screen real estate, include some device orientation sensitivity and to make sure that all selections were fat figure friendly (I'm a good tester for the 'fat finger' part).

For device orientation, you can use @media rule in CSS, or javascript, which is what I did. I set up a landscape and portrait DIVs and then used javascipt to hide and show the appropriate one. The layout was simple: in landscape I have a 75% / 25% horizontal split. In portrait right 25% area is rendered below in a wide layout. You can do some cool stuff with orientation. In most cases you don't what the display to flip around too much. But you can set up a image, like a logo, that responds to each small tablet movement on any axle. I didn't use that in the demo, but it looks cool.

This is the orientation script I used.

if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', function(eventData) {
deviceOrientationHandler();
}, false);
} else {
       makeLanscape();
}
function deviceOrientationHandler() {
if ( orientation == 0 ) {
makePortrait();
}
else if ( orientation == 90 ) {
makeLanscape();
}
else if ( orientation == -90 ) {
makeLanscape();
}
else if ( orientation == 180 ) {
makePortrait();
}
}
function makePortrait() {
document.getElementById("tabletLandscape").style.display = 'none';
document.getElementById("tabletPortrait").style.display = 'block';
}
function makeLanscape() {
document.getElementById("tabletLandscape").style.display = 'block';
document.getElementById("tabletPortrait").style.display = 'none';
}

And this does the funky real time image orientation, with an image with id "imgLogo".
if (window.DeviceOrientationEvent) {
  window.addEventListener('deviceorientation', function(eventData) {
        var LR = eventData.gamma;
        var FB = eventData.beta;
        var DIR = eventData.alpha;
        deviceOrientationHandler(LR, FB, DIR);
}, false);
} else {
alert("Not supported on your device or browser.  Sorry.");
}
function deviceOrientationHandler(LR, FB, DIR) {
   //for webkit browser
   document.getElementById("imgLogo").style.webkitTransform = "rotate("+ LR +"deg) rotate3d(1,0,0, "+ (FB*-1)+"deg)";
   //for HTML5 standard-compliance
   document.getElementById("imgLogo").style.transform = "rotate("+ LR +"deg) rotate3d(1,0,0, "+ (FB*-1)+"deg)";
}

To get the screen size, I dug up some code that Lukas had posted on how to get screen size from the browser. It's one of those code snippets that really helped me understand how Seaside gets the browser to trigger callback blocks on the server.

renderScreenSizeOn: html
self screenX isNil ifTrue: [
self screenX: 0.
self screenY: 0.
html
script: ('window.location.href="' , html context actionUrl asString ,
'&' , (html callbacks registerCallback: [:v | self screenX: v asNumber]) , '=" + screen.width + "'
, '&' , (html callbacks registerCallback: [:v | self screenY: v asNumber]) , '=" + screen.height') ].
html text: self screenX printString, ' x ', self screenY printString

...which generates the script...

window.location.href="/Test?_s=sOMeODiqoU20GBt4&_k=pbWTZltjW35uekru&1=" + screen.width + "&2=" + screen.height

So, I triggered href="/Test?_s=sOMeODiqoU20GBt4&_k=pbWTZltjW35uekru&1=4288&2=1143 which Seaside reads and sends '4288' as the #value: to block [:v | self screenX: v asNumber] and '1143' to  [:v | self screenY: v asNumber].

If I edit the URL and change the values in the same session...
 ?_s=sOMeODiqoU20GBt4&_k=pbWTZltjW35uekru&1=1234&2=4567
..my screen will render the view values: 1234 x 4567. Neat.

Anyway, it was simpler to just use the request 'user agent' information to redirect to different applications, in the same image, for each device. This also made it easy to test the tablet and phone URLs on a full browser by using the appropriate URL.

renderContentOn: html | string |  string := self requestContext request userAgent.
(string includesSubString: 'iPad') ifTrue: [^self requestContext redirectTo: self tabletURL].
(string includesSubString: 'iPhone') ifTrue: [^self requestContext redirectTo: self phoneURL].
(string includesSubString: 'webOS') ifTrue: [^self requestContext redirectTo: self tabletURL].
^self requestContext redirectTo: self portalURL

I subclasses most of my components with *Tablet and *Phone suffixed classes, which made for easy code reuse and kept all of the device specific code in Seaside.

The portal required some diagram annotation, so I used javascript to detect figure movement and added more javascript for mouse movement. This way images could be annotated from any device. I started with a canvas ...


html div id: 'container'; with: [
html canvas id: 'sketchpad'; width: 350; height: 350; style: 'border: 1px solid black; background: white';
with: [html strong: 'Your browser does not support canvas.']].

...and then used this script for the iPad & iPhone figure drawing...

// get the canvas element and its context
var sketchpadCanvas = document.getElementById('sketchpad');
var context = sketchpadCanvas.getContext('2d');
context.fillStyle = 'red'; // red
context.strokeStyle = 'red'; // red
context.lineWidth = 4;
//Load the image object in JS, then apply to canvas onload
var myImage = new Image();
myImage.onload = function() {
context.drawImage(myImage, 0, 0, 350, 350);
};
// create a drawer which tracks touch movements
var drawer = {
  isDrawing: false,
  touchstart: function(coors){
     context.beginPath();
     context.moveTo(coors.x, coors.y);
     this.isDrawing = true;
  },
  touchmove: function(coors){
     if (this.isDrawing) {
        context.lineTo(coors.x, coors.y);
        context.stroke();
     }
  },
  touchend: function(coors){
     if (this.isDrawing) {
        this.touchmove(coors);
        this.isDrawing = false;
     }
  }
};
// create a function to pass touch events and coordinates to drawer
function draw(event){
  // get the touch coordinates
  var coors = {
     x: event.targetTouches[0].pageX,
     y: event.targetTouches[0].pageY
  };
  // pass the coordinates to the appropriate handler
  drawer[event.type](coors);
}
// attach the touchstart, touchmove, touchend event listeners.
sketchpadCanvas.addEventListener('touchstart', draw, false);
sketchpadCanvas.addEventListener('touchmove', draw, false);
sketchpadCanvas.addEventListener('touchend', draw, false);
// prevent elastic scrolling
document.body.addEventListener('touchmove',function(event){
 event.preventDefault();
},false); // end body:touchmove

To work on a non-touch browser, I loaded scripts to do the annotation with a mouse based on this tutorial.

Setting a background image, which also cleared the annotation, was done with a script
myImage.src = "/files/TSwaFileLibrary/body.png";
Saving the image was done with #onClick: script from a button.... the 'toDataURL()' is the interesting bit.
html jQuery ajax
serializeForm;
callback: [:value | self saveImageFile: value]
value: (Javascript.JSStream on: 'sketchpadCanvas.toDataURL()')

...and... (removing the first 22 characters is a hack to strip out the ''data:image/png;base64,'' MIME declaration). The code is in VW.
saveImageFile: anImageString
| writestream string | writestream := (self newImageFilename asFilename withEncoding:  #binary) writeStream.
string := anImageString copyFrom: 23 to: anImageString size.
[writestream nextPutAll: (Seaside.GRPlatform current base64Decode: string) asByteArray] ensure: [writestream close].
The demo went well. I'm always surprised how making something pretty makes people think it's better. In this case I just added a new facade on a web site, and it was considered a big thing, yet it was only a few days work. It's like my buddy in marketing says: ya gotta sell the sizzle, not the steak. There's a lesson in there somewhere for Smalltalk.

(here is a good summary of iPod HTML5 javascript development)

Simple things should be simple. Complex things should be possible.


Sunday, 3 July 2011

Simple jQuery contact application

I continue to be impressed with how easy it is to build nice, useful applications with Seaside using the jQuery support.  We have an old Unix character based contact application that those who don't want to change (and you know how you are) continue to use. I gave up on character based interfaces the late 80's, so I created a simple Seaside app to access the contact data.
The contact data is dumped daily into a special character delimited file, with 40 fields per row. Trivial to parse in Smalltalk...
array := aString subStrings: 1 asCharacter.
...where aString is one row of the data file.

Accessing the fields is just an array access, like...

postalCode
^self dataArray at: 16

The collection of contacts (about 8500) is stored in a contact library object, in three collections, each sorted by a key stored on the object as a lower case string (by first name, by last name and by company). At first I tried using some fancy nested tree structure, then I tried large complex key dictionaries, but doing a match on 8500 instances takes less than 10 ms. Trying to optimize that just added complexity. Storing the search keys as lower case strings rather than translating them on each iteration was helpful. Search times dropped from 60 ms. The search method also has a limit to keep the auto-complete list is to a reasonable size. A '*' is appended to the string to support the general 'match' case.

string := aString asLowercase.
string last = $* ifFalse: [string := string copyWith: $*].
list := aCollection select: [:each | string match: each nameKey].
list size > limit ifTrue: [^list copyFrom: 1 to: limit].
^list

We match on #nameKey, but the displayed list uses #displayFullName, the mixed case version of the string.

OK, so the domain in as simple as it gets. The fun part was the Seaside code. JQAutocomplete>>search:labels:callback:  made it easy (props to its author). The method wraps a lot of complexity into a tight, useful package. In my case, the code looks like this...

html textInput
id: anId;
style: 'width: 300px; font-size: 14pt';
script: (html jQuery this autocomplete
search: [:string | self buildSearchResponse: aSearchBlock with: string]
labels: aLabelBlock
callback: [:value :script | self redirectToContact: value script: script])
#buildSearchResponseh:with: is a hack to show error messages in the auto-complete list. An exception answers an error object which is a subclass of the contact class, which is then displayed in the drop-down list.

^[aBlock value: aString]
on: Error
do: [:ex | Array with: (TSroloError newForErrorMessage: ex displayString)]

#aLabelBlock uses one of the three display methods for the drop down list (#displayFullName #displaySurname #displayCompany).

#redirectToContact:script: shows a nice URL with a ?contact=nnnn suffix.

| urlString |
urlString := aScript requestContext request url startingURL ,
'?contact=',
aContact displayContactIndex.
aScript goto: urlString.
self session unregister
In #renderContactOn: I check for the 'contact' parameter and render the contact details if found.

Here is what it looks like...
...typing 'bob n' into the full name fields shows...
...selecting 'Bob Nemec' then redirects to '?contact=3784', which shows the contact details...
...the '+' toggles a display of all the contact fields, using the jQuery toggle...
| moreId lessId |
moreId := html nextId.
lessId := html nextId.
html anchor
id: moreId;
onClick: (
html jQuery ajax script: [:s |
s << (html jQuery id: aComponentId) toggle: 0.3 seconds.
s << (html jQuery id: moreId) hide.
s << (html jQuery id: lessId) show]);
title: aMoreTitle;
with: [html image url: TSwaFileLibrary / #morePng].
html anchor
id: lessId;
style: 'display: none;';
onClick: (
html jQuery ajax script: [:s |
s << (html jQuery id: aComponentId) toggle: 0.5 seconds.
s << (html jQuery id: lessId) hide.
s << (html jQuery id: moreId) show]);
title: aLessTitle;
with: [html image url: TSwaFileLibrary / #lessPng]

Finally, reloading the list of contacts is done by adding a ?load parameter, which loads the contacts from a predefined file location. This way the contact load action does not need to be done by the application, but can be done by an OS scheduled task.

And again, a big thanks for the Seaside jQuery code which makes writing simple apps like this easy.

A bad day in [] is better than a good day in {}

Wednesday, 1 June 2011

Low-tech Seaside graphs

I really like the basic 'how to' tutorials people post, whether they are about Seaside, C#, VisualStudio or motorcycle maintenance (that pretty much covers where my head has been lately).

So here are a couple of things I've done in Seaside that I think are simple yet proved handy.

The first example is from a home financial application that I wrote a few years ago as a way to learn Seaside. My wife and I use it to track our budget and daily transactions, which it imports from a scheduled download done by an iMacro script.

I wanted a bar graph to show how much of the budget for a category was spent for the month. And I wanted parts of the graph to support drilling down for more details. Here is what I ended up with (with some random data)...

...each square is one day of the month. The narrow grey line is to the right of today. If spending in a category is running ahead of the budget, it's red.

The graph is created as a two column table, with the title and the progress bar. The bar is created by rendering same sized (16x16) image buttons, one for each day, and a narrow divider button (3x16) for 'today'.

html imageButton
callback: [self openTransactions];
title: 'Show transactions';
url: TxFileLibrary / #progressredPng

The images were created with Paint.net, a handy tool for simple graphics.

I used a similar approach to create a Kanban graph for an issue tracking system. In this case, a table with fixed sized cells is filled with image buttons representing an issue, to a maximum of five rows. Selecting a cell shows the list of issues for that cell, and the title of each cell shows the display string of the corresponding issue.

I think it's cool what you can due with some dynamic HTML table generation and a few PNG files. 

Friday, 20 May 2011

User group marketing

The Toronto Smalltalk User Group has been active for 20 years. At our peak we had about 45 people attending meetings. Lately we've averaged about a dozen.

What can we do to get more people to attend?

I suspect we already know all Smalltalkers in the Greater Toronto Area, if not personally then by just one level of separation. Our best bet is to target new people that may have an interest. We've had some success with Ryerson students. The two professors that sponsor our group encourage the students that take their OO course to attend. It has some Smalltalk content (Pharo) and a couple of students have come out.

Some of the TSUG crowd have attended XP Toronto events. They have regular meetings, pair programming pub nights and they organize well run Agile conferences. Those that know about Smalltalk respect it because of XP's roots, but they tend to be surprised that it's still an active language.

Yanni Chiu has been something of  a TSUG ambassador, representing Smalltalk at things like the "Dynamic Languages Smack Down". He and Chris Cunnington signed up for Toronto Startup Weekend and plan to show some Seaside app. I had some tentative plans to talk to the Durham Ruby group (something I need to follow up on).

Whenever it's appropriate I hand out TSUG pamphlets. They list all the Smalltalk dialects, web frameworks and projects I know of. I print off a new batch for each TSUG meeting, with updated meeting schedules and conference information (ESUG in in the current one). It's an MS Publisher document which I print off on heavier stock paper which has a light blue tint. Here is what part of it looks like...


I'd love to get suggestions on how to promote the group.

Malcolm Gladwell in his book Outliers makes the point that people with very high IQs are not more likely to be successful than people with just 'good' IQs (120, evidently). Once you're smart enough, other factors like people skills and communication skills make the difference.  Smalltalk has an IQ above 180, the other tools are probably in the 120 range. Smalltalk needs to work on its people and communication skills.

Tuesday, 3 May 2011

Pushing Smalltalk

The next meeting of the Toronto Smalltalk User Group is May 9 with Don MacQueen talking about JWARS.
See the web site for details.

I've been involved with the Toronto Smalltalk User Group for 20 years now, the past dozen or so as the primary organizer. Lately we've had a few people show up that were new to Smalltalk and wanted to learn more. We talk to them about the simple syntax, show them Pharo and Seaside, tell them about the other dialects, and try whatever we can to get them enthused.

But there is one core strength that I value which I cannot easily demo: the lack of brick walls.

When working with tools like MS's Visual Studio, I'm constantly frustrated by the lack of universal object inspection and 'down to primitive' code tracking. Coding something new and with unfamiliar APIs gets a bit messy. You don't always know what you need until you trip over its absence. I find myself adding diagnostic code to show me stuff, which in Smalltalk I'd just debug and inspect. And sometimes in VS you just can't get what you want; you hit a brick wall. If it were not for Google and people posting esoteric workarounds, I don't know how I'd get anything done. And I love the examples that say "don't forget the comma, or else it won't work"... no error message, no warning, just no output.

Thing is, that's not the kind of thing you appreciate until you try something complex, which does not happen to a new user. I wonder if we would benefit from having code examples with pre-defined bugs, written in various languages, and then using them to show how you'd debug the problem. Show just how few barriers we have in Smalltalk to understanding what is going on in our code (and, more importantly, in other people's code).

I used to tell people that I saw a lot of similarity between programming in MVS 360 assembler and Smalltalk. For me, transparency was the big stick that I could use to whack the problem. From what I've seen of other IDEs (an admittedly short list), Smalltalk still does that the best.

Friday, 22 April 2011

Smalltalk trivia

I miss the days of The Smalltalk Report and discussions on comp.lang.smalltalk. It was a time of lively debate about syntax, naming conventions, coding patterns, dialects ... all the signs of an energetic community. It's not like that any more. I think it's due to the fragmentation of the Smalltalk forums. The threads are interesting, but specialised.

My coding is strongly influenced by Kent Beck and his coding patterns. I remember reading his column in The Smalltalk Report and seeing the evolving consensus of how to code Smalltalk. My iteration block still have :each or :each<something> as the binding variable.  In his "Fundamentals of Smalltalk Programming Technique",  Andres Valloud makes the point that code formatting is a pointless debate and that we should be comfortable reading any Smalltalk code. And he's right. But I'd still like to know what others find more readable. It's like learning to write gooder English.

For example, I trip over the extra white spaces inside blocks that most of the Seaside code has. Where I'd write a method like...

aBoolean

    ifTrue: [self doThis]
    ifFalse: [self doThat].
aList do: [:each | self doSomethingWith: each]

...I find the Seaside code is formatted as...

aBoolean

    ifTrue: [ self doThis ]
    ifFalse: [ self doThat ].
aList do: [ :each | self doSomethingWith: each ]

...I wonder why that convention was adopted. In my eyes the block is just not hugging the code enough. I don't find that style in other Smalltalk code. And it really does not matter; I'd just like to understand the though behind it. 

Another example: In our VW application,  the framework code uses abbreviations for local and parameter variables, so I see a lot of code like printOn: aStrm and | errMsg idx t v m s | ... drives me nuts. But it was adopted because of how easy it used to be to name a local variable the same as an instance variable. We do refactor code this kind of code now as we maintain it, but it's easier to accept when the thought behind it is understood. 

And here's a pattern question: when is it better to double-dispatch? In our VW code was have an #echo method which writes a printString of the receiver to the Transcript, like the newish VW #out method (unfortunate name conflict with an Opentalk method). We also have #echo: which writes both the receiver and the argument to the Transcript, so...
    'Time' echo: Time now
...shows in the Transcript as...
    Time 10:10:54 AM

I added the same code to our VA application. So what's the best way to write this method? We don't want strings to be printed with single quotes, like...

Transcript cr; show: 'this string' printString  

'this string'
vs.
Transcript cr; show: 'this string'
this string

...so we can implement #echo on Object as Transcript cr; show: self printString and on String as Transcript cr; show: self. But what about #echo: ? We could implement on Object as...

Object>>echo: anObject

    Transcript cr; show: self printString, ': ', anObject printString

...(I prefer the : delimiter to just a space) and we'd need...

String>>echo: anObject

    Transcript cr; show: self , ': ', anObject printString


...but what if anObject is a String? You would end up with superfluous single quotes. You could add a new method like #asEchoString with an Object and String implementation and send that instead of #printString, which is easy to read, or you could try double dispatching, which I decided to do (mostly out of curiosity). In this case I don't think it's a better pattern, but it's interesting (and very useful for more complex problems). And I like that they are all one line method.

Here is what my implementation (no value returned so that a := b echo answers b)...

Object>>echo
self printString echo

String>>echo

Transcript cr; show: self.

Object>>echo: anObject

anObject echoAfter: self printString

String>>echo: anObject

anObject echoAfter: self

Object>>echoAfter: aString

aString echoString: self printString

String>>echoAfter: aString


aString echoString: self

String>>echoString: aString

Transcript cr; show:  self , ': ', aString


'test' echo: 123    >> 'test: 123''test' echo: 'this' >> 'test: this'123 echo: 'this'    >> '123: this'123 echo: 456       >> '123: 456'  



Tuesday, 19 April 2011

VXML server isolation

Our IVR VXML Seaside server needs to handle up to 700 calls per hour. Normally that's not a problem, but we do get a few timout errors per week, which is why we're moving to a three image load balanced deployment.

Recently we had another issue with our current setup: the server hosting the file share, to which call files are written, failed. Calls were still processed, but the final write failed. Write are done in a forked block with an exception handler, so the image dealt with the errors cleanly. And the call data was all written to a log, so no data was lost.

However, there was a performance hit on the server image and we started to get timeout errors on more than 10% of the calls. So we turned off the Seaside server which triggered an automatic failover to our AWS hosted server. It works, but it's a bit slow. Callers could wait several seconds for the call prompts (we'll be upgrading it soon).

This was the first time we had an error with the file share and it pointed out a dependency that we'd prefer to avoid. Ideally, the VXML servers would continue to process calls even if other servers are down; they should as isolated as possible.

Our strategy is to use the VW Opentalk image to image interface and have a data server gather call information from the three VXML servers. It will also send them updated validation files. This way, the VXML servers have no folder share dependencies,  the data server can show status across all three VXML server, and the VXML servers can function even if the entire back office is down. And we can host the data & VXML servers with different service providers.

We've been using Opentalk in our client patch system for a couple of years now. It's familiar and reliable. And ya gotta love the deployment flexibility you get with Smalltalk.