<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2691799628167753841</id><updated>2012-02-16T03:31:15.744-05:00</updated><category term='Seaside'/><category term='Smalltalk HTML5'/><category term='Smalltalk jQuery'/><category term='Smalltalk'/><category term='MediaWiki'/><category term='Smalltalk SUnit'/><title type='text'>Digging in the dirt</title><subtitle type='html'>Things I do with Smalltalk</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>18</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-837311354679503379</id><published>2012-01-31T08:16:00.002-05:00</published><updated>2012-01-31T08:17:05.039-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk SUnit'/><title type='text'>VW Code Coverage</title><content type='html'>The past few days I've been working with the code coverage extensions to SUnitToo in VW (SUnitToo(verage) and SUnitToo(lsoverage). It is an impressive tool. And the metrics accurately reflected the way I developed some packages. Our in-house issue tracker and patch manager had tests added after the fact, with a resulting code coverage of 30%. The Report4PDF package, the PDF4Smalltalk based report writer I'm working on, was developed from the start with SUnit tests, with a code coverage of 65%.&lt;br /&gt;&lt;br /&gt;At first I thought that 65% seemed a bit low, since all the code has associated tests. But the code coverage is smart: it does not just measure method hits, but which paths within a method are&amp;nbsp;exercised. In many cases these were exception and error message branches; the paths less taken. But some cases should have been tested. These were very narrow special cases that I had simply not thought of. The code looks OK, and I'm confident that it will work, but I won't release it without an SUnit test.&lt;br /&gt;&lt;br /&gt;If feels like I've been given a flashlight to see the dark nooks and crannies of my code.&lt;br /&gt;&lt;br /&gt;I also like the idea of using the path count (the denominator in the code coverage number) as an application complexity metric. It could work well when used with number of classes, number of methods, and average method size. I wonder if 'number of paths per method' would be useful.&lt;br /&gt;&lt;br /&gt;It is an impressive tool. Thanks to all who made it available.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: Georgia, 'Times New Roman', serif; font-size: x-small;"&gt;Simple things should be simple. Complex things should be possible.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-837311354679503379?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/837311354679503379/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2012/01/vw-code-coverage.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/837311354679503379'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/837311354679503379'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2012/01/vw-code-coverage.html' title='VW Code Coverage'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-8558929380345159392</id><published>2012-01-08T19:05:00.004-05:00</published><updated>2012-01-09T07:06:47.055-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>PDF Report and the Law of Demeter</title><content type='html'>I'm finishing a small project which uses Christian Haider's &lt;a href="http://pdf4smalltalk.origo.ethz.ch/"&gt;pdf4smalltalk&lt;/a&gt; to build report output using a Seaside influenced coding style. A report with a header, text and footer would be coded as...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote class="tr_bq"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;| report |&lt;br /&gt;report := PRwReport new.&lt;br /&gt;report portrait.&lt;br /&gt;report page: [:page |&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;page header string: 'This is a header'.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;page text string: self someText.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;page footer string: 'This is a footer'].&lt;br /&gt;report saveAndShowAs: 'TestText.pdf'.&lt;/span&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;The tool supports the usual report output options, like fonts, alignment, tables, images, bullets and so on.&lt;br /&gt;&lt;br /&gt;I've built a couple of other Smalltalk report frameworks over the years. One used Crystal Reports for the layout with configurable data gathering, and another (much better tool) that used &lt;a href="http://www.totallyobjects.com/"&gt;Totally Object's&lt;/a&gt; Visibility (I did a &lt;a href="http://dl.dropbox.com/u/5602924/TSUG_Smalltalk.pub"&gt;presentation&lt;/a&gt; on that one at Smalltalk Solutions 2004). Both of those used a data + layout spec model, which, with the benefit of hindsight, was not be best choice. It was a challenge to keep the code and layout in sync. Maintenance was painful. For PDF Report I opted for Seaside's 'paint the content on a canvas' pattern. It is working nicely (I'll be presenting the details at&amp;nbsp;&lt;a href="http://www.stic.st/conferences/stic12/"&gt;Smalltalk Industry&amp;nbsp;Conference in Biloxi&lt;/a&gt;).&amp;nbsp;&lt;br /&gt;&lt;br /&gt;Here's the part that got me thinking about how nice objects are and the &lt;a href="http://en.wikipedia.org/wiki/Law_of_Demeter"&gt;Law of Demeter&lt;/a&gt;... &amp;nbsp;when building a report output, you have to deal with coordinating the size and position of layout components on the page. Do you give the responsibility to the page, or do you have the layout objects find their own place? I opted for a 'builder': it knows how much space is available on a page and which layout objects need to be processed.&lt;br /&gt;&lt;br /&gt;The interesting part was in deciding how much the builder needed to know about each layout. The first few iterations were&amp;nbsp;rudimentary: each layout had a calculated height (word wrapped text with a selected font) and the builder would output as much as would fit on one page, then trigger a page break and continue on to the next page.&lt;br /&gt;&lt;br /&gt;But that did not work with tables, since each row could have some cells that spanned pages. The builder could not blindly trigger a page break on a tall cell, since the next cell would be on the previous page. The table, row and cell had to communicate layout information to the builder, with the cell width and height dependent on neighbouring cells. And, to make things especially interesting, tables can be nested and cells can span rows and columns, like this...&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-Xxfudamv3Bw/TworOJ5dOCI/AAAAAAAABVU/BGn__GkItU4/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="105" src="http://2.bp.blogspot.com/-Xxfudamv3Bw/TworOJ5dOCI/AAAAAAAABVU/BGn__GkItU4/s320/2.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;...and this...&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-XnPa7MNCvrU/TwonhRJ6S9I/AAAAAAAABVM/R6Fr2F8qyyA/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="70" src="http://2.bp.blogspot.com/-XnPa7MNCvrU/TwonhRJ6S9I/AAAAAAAABVM/R6Fr2F8qyyA/s400/1.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Each time I added a new layout mix to the SUnit tests I had to rethink what each object knew. After several iterations a pattern emerged: the less the builder knew about the layout objects, the better. And as the builder got dumber, its code got simpler and new layout mixes just worked. A tricky part was in sequencing the layout calculation for nested objects: a cell's height is dependent on the row's height, but the row's height is the maximum of it's cell heights.&lt;br /&gt;&lt;br /&gt;Once the calculation sequence was correct, each layout object was able to answer it's layout values: position, margin and padding. The builder could ask if a layout could fit in the remaining space on a page without knowing what the layout objects was (text, table, bullet, image or line) and could create a new physical page without knowing how a layout object would be split. Now each refactoring cycle starts with me asking myself: how can I reduce what the builder needs to know? The latest version is much cleaner than the first. It's nice to apply well known object design rules and see real results.&lt;br /&gt;&lt;br /&gt;Still a lot of work to do, but I'm looking forward to showing it at the conference. And, if it's good enough, it will be added to the VW public store (long term plans are to port to other Smalltalk dialects).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: 'Trebuchet MS', sans-serif; font-size: x-small;"&gt;Simple things should be simple. Complex things should be possible.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-8558929380345159392?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/8558929380345159392/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2012/01/pdf-report-and-law-of-demeter.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/8558929380345159392'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/8558929380345159392'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2012/01/pdf-report-and-law-of-demeter.html' title='PDF Report and the Law of Demeter'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-Xxfudamv3Bw/TworOJ5dOCI/AAAAAAAABVU/BGn__GkItU4/s72-c/2.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-7277187036908633856</id><published>2011-11-15T10:39:00.001-05:00</published><updated>2011-11-21T10:36:12.272-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>Smalltalk head space</title><content type='html'>At the last Toronto Smalltalk User Group meeting we had a basic introduction to Seaside. It had been requested by some of the members, especially by the two profs that sponsor our group at Ryerson University. They teach an OO course with a Smalltalk component and felt it would be of interest to their students. A few did show up and the feedback was very positive. A bit surprising, given how basic the demo was (&lt;a href="http://dl.dropbox.com/u/5602924/Seaside_example.pdf"&gt;here is a link to the PDF&lt;/a&gt;), but it got me thinking about how easy it is to get out of touch with how other people see things. There is something to be said about baby steps.&lt;br /&gt;&lt;br /&gt;After the talk we spent a while talking about the sad sate of computer science education; how it's become a C syntax programming training school. Way back in my day (early 80's) there was no dominant computer language. Intro to comp. sci. was taught in FORTAN. We learned BASIC, PL/I, COBOL, SNOBOL, Lisp, 360 Assembler, Prolog and Smalltalk. My OS course was taught as a history lesson, explaining why and how each OS concept was introduced, and why some were abandoned. There was a big focus on 'why', not just 'how'. That does not seem to be the case today.&lt;br /&gt;&lt;br /&gt;During the conversation, I talked a bit about how selling Smalltalk can be a challenge. Smalltalk is different. If all you know is C and Java, it's really different. Why learn something different if it will not help you get a job? I made the case that learning Smalltalk helps you learn how to think in objects. A perspective that may be helpful when dealing with the other object hybrid languages (it would also be good to learn Lisp, Prolog and assembler). You can't help thinking that a Java-centric education gives students only one tool: a hammer, and they then view every task as a nail.&lt;br /&gt;&lt;br /&gt;But how do you communicate the value of a "Smalltalk head space"? A world where everything is a live object that you can see and change. A place where delegating behaviour to Integer and String makes perfect sense; where the language is minimalist and the libraries add the complexity; where working with a debugger feels natural. And how do you explain how sending messages is different than calling a function? The mechanics are simple enough, but the mental model is harder.&lt;br /&gt;&lt;br /&gt;I see functions as a handing off of total control of the world to something else. Within a function you change whatever data you need to. The world is a large,&amp;nbsp;porous bit universe that you manipulate. Whereas sending a message is asking another object to do something for you. You don't think about&amp;nbsp;tickling&amp;nbsp;the bits of another object. You do your job, send messages to other objects, and maybe answer something. You work in a &amp;nbsp;local scope, not the whole world.&lt;br /&gt;&lt;br /&gt;Every professional Smalltalk developer I know can tell talk about their "aha" moment, the point where object-think made sense. In my case it was a course at The Object People where Paul White was explaining how to model a transfer between a chequing and savings account: asking either account to do the transfer smelled wrong, so instead you could model the transaction. Really? You can do that? A 'transction' can be an object? Cool. A more whimsical example is modelling the milking of a cow. Do you ask the cow to milk itself? Do you ask the milk to un-cow itself? No. You model a farmer to do the milking 'transaction'.&lt;br /&gt;&lt;br /&gt;The problem is that those "aha" moments take time to learn. And who has time for that? As Dave Thomas said in his &lt;a href="http://channel9.msdn.com/Blogs/Charles/SPLASH-2011-Dave-Thomas-On-Modern-Application-Development"&gt;recent SPLASH video interview&lt;/a&gt;, the complexity that we've added to the development tools is not always a benefit to someone building a simple application. Do you really need to know about objects if a BASIC program will do? Is learning about classes and instances to big a hurdle? Would a prototype object model be easier to learn? I don't know, but we really have to find a better way to communicate the up side of using Smalltalk. Waiting for each student to experience their own "aha" moment is not good enough.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;&lt;i&gt;Simple things should be simple. Complex things should be possible.&lt;/i&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-7277187036908633856?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/7277187036908633856/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/11/smalltalk-head-space.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/7277187036908633856'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/7277187036908633856'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/11/smalltalk-head-space.html' title='Smalltalk head space'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-764399127185216484</id><published>2011-09-12T11:48:00.000-04:00</published><updated>2011-09-12T11:49:04.698-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Seaside'/><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>XML RESTful Seaside interface to legacy system</title><content type='html'>I'm mandated with creating web interfaces to two legacy frameworks, one written in VA and the other in VW.&lt;br /&gt;&lt;br /&gt;For the VW framework I started with a full application implementation; a Seaside interface that duplicated the entire application interface by parsing VW window specs and building Seaside components. But for both the VA and VW systems there is also a need for a 'portal' web site, one that exposes a limited view of the application but is intended for a larger user base.&lt;br /&gt;&lt;br /&gt;Both systems were designed over a dozen years ago, using GemStone and a fat client. Grafting a multi-user Seaside interface to the fat client designed for a singe user was not going to work. That's why the VW 'full user' implementation uses one 200MB VW image per Seaside session, a deployment model that was not an option for the portal: too many users (a resource issue), too much exposed (a security concern), too integrated (a maintenance problem).&lt;br /&gt;&lt;br /&gt;To work around these constraints I've implemented an XML based domain interface, where a Seaside image can gather the data it needs to render using a RESTful interface to GemStone. There is no domain model in the Seaside image, just components that know what part of the domain they represent. With GemStone this works&amp;nbsp;particularly&amp;nbsp;well since it eliminates the need to fault objects into the client. It's a common technique with GS, popularised by James Foster, to build strings on GS, fault them to the client, then parse and display the content. &amp;nbsp;Some displays, like lists of objects, can improve their performance from tens of seconds to sub-second.&lt;br /&gt;&lt;br /&gt;Each Seaside component knows which dedicated server method to use. Since the portals have a limited scope there are not that many XML data methods. I would not use this approach if I expected the portal application scope to increase&amp;nbsp;significantly.&lt;br /&gt;&lt;br /&gt;All of this was made easier by thinking of the final rendering as a limited set of display patterns:&amp;nbsp;lists, tables, trees, image and a single domain object. The XML has tags that identify the display pattern: &amp;lt;list&amp;gt; &amp;lt;table&amp;gt; &amp;lt;tree&amp;gt; &amp;lt;image&amp;gt; &amp;lt;domain&amp;gt;. These are used to build 'XmlObject' subclasses for each pattern. This makes accessing and debugging data easier than referencing the parsed XML code directly in the Seaside components.&lt;br /&gt;&lt;br /&gt;Parsing XML is very different between VA and VW. Wrapping the results in my own XML node objects made it easy to port my code between dialects. I just needed to abstract out the XML parser references. Predefined tags, like 'oop, objectclass, text' are stored in instance variables. Any other tags are stored in a dictionary. The Seaside component would expect certain tags to be present and will trigger an error otherwise.&lt;br /&gt;&lt;br /&gt;Some examples...&lt;br /&gt;&lt;br /&gt;&lt;b&gt;domain&lt;/b&gt;&lt;br /&gt;- only attributes the Seaside component needs are included&lt;br /&gt;- used in the list, table and tree patterns with minimal attributes; if selected the oop is used to get whatever else is needed&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;lt;domain&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;oop&amp;gt;461206257&amp;lt;/oop&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;objectclass&amp;gt;AlaItemWorksheet&amp;lt;/objectclass&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;text&amp;gt;ABTP Lab Results - D - DEN&amp;lt;/text&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;icon&amp;gt;AlaDataEntry.ico&amp;lt;/icon&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;label&amp;gt;ABTP Lab Results - D - DEN (TAB [Final Effluent])&amp;lt;/label&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;lt;/domain&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-mwUW7qFwvMM/Tm4nR01uMTI/AAAAAAAABTs/B0Da1wzUcQA/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="108" src="http://1.bp.blogspot.com/-mwUW7qFwvMM/Tm4nR01uMTI/AAAAAAAABTs/B0Da1wzUcQA/s400/1.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;list&lt;/b&gt;&lt;br /&gt;- stored as named lists in the collection of attributes for a domain node&lt;br /&gt;- each element of a list is another domain node, with enough information to be displayed (label, icon) and with the domain object oop in case it gets selected&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;lt;list type="actions"&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;domain&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;oop&amp;gt;291900165&amp;lt;/oop&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;...&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;domain&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;oop&amp;gt;291913433&amp;lt;/oop&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-4l_cFN3PqGo/Tm4npNmsbBI/AAAAAAAABT0/gZHjsO-TMPo/s1600/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-4l_cFN3PqGo/Tm4npNmsbBI/AAAAAAAABT0/gZHjsO-TMPo/s1600/3.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;table&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;- added tags for header and styles. The style contents is encoded as key, value pairs so that the server code can include style information that a generic table component will render&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;header&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;data&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;			&lt;/span&gt;&amp;lt;value&amp;gt;ABTP Lab Results - D - DEN&amp;lt;/value&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;/data&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/--gCBXiWzQaY/Tm4ndxCrsiI/AAAAAAAABTw/UIyPA09DvFc/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="199" src="http://1.bp.blogspot.com/--gCBXiWzQaY/Tm4ndxCrsiI/AAAAAAAABTw/UIyPA09DvFc/s320/2.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;tree&lt;/b&gt;&lt;br /&gt;- each tree node knows how to get its sub-tree&lt;br /&gt;- sub-trees are retrieved and cached when a tree node is expanded&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;lt;list type="productSet"&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;domain&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;oop&amp;gt;199226369&amp;lt;/oop&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;objectClass&amp;gt;INproductSet&amp;lt;/objectClass&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;text&amp;gt;By Function&amp;lt;/text&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;hasSubtree&amp;gt;true&amp;lt;/hasSubtree&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;hasProducts&amp;gt;false&amp;lt;/hasProducts&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;/domain&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A Seaside component is created for each tree node which reads the 'hasSubtree' attribute and, based on a configuration, gets the nested list of domain nodes when expanded.&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-NJYMWfIbxQs/Tm4n3j1QBmI/AAAAAAAABT8/9JQR1YFQlNA/s1600/5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-NJYMWfIbxQs/Tm4n3j1QBmI/AAAAAAAABT8/9JQR1YFQlNA/s1600/5.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;image&lt;/b&gt;&lt;br /&gt;- for the VA implementation, images are stored as byte arrays on GS&lt;br /&gt;- for the VW, the images are stored as server paths&lt;br /&gt;- in each case, the image node needs to answer a byte array that can then be rendered&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;lt;domain&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;oop&amp;gt;249729481&amp;lt;/oop&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;objectClass&amp;gt;AlaSiteMapImage&amp;lt;/objectClass&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;...&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;image&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;oop&amp;gt;249728229&amp;lt;/oop&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;&amp;lt;mimeType&amp;gt;image/gif&amp;lt;/mimeType&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;&amp;lt;/image&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-mAwy8m4FF2M/Tm4nxx_wi1I/AAAAAAAABT4/yXIfxi1Z7YI/s1600/4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="61" src="http://3.bp.blogspot.com/-mAwy8m4FF2M/Tm4nxx_wi1I/AAAAAAAABT4/yXIfxi1Z7YI/s320/4.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Building the XML on the server is done with a 'canvas' object which wraps tags around indented nested content. Formatting of the XML content is a debugging convenience.&lt;br /&gt;&lt;br /&gt;To add a domain object...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;aCanvas&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;addDomain: anItem&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;text: anItem product id&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;with: [&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;aCanvas add: 'description' put: anItem product description.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;To add a list...&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;aCanvas&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;addListNode: productSet&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;type: 'productSet'&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;with: [&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;collection do: [:each | self buildXmlProductSet: each on: aCanvas]].&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;For both apps a user identifier, in the form of a GS object oop, is included in each data request. Seaside stores the user object oop in the WASession subclass instance variable. Communication between the Seaside image and GS is behind a firewall, so I'm not that concerned about it being monitored.&lt;br /&gt;&lt;br /&gt;RESTful communication&amp;nbsp;works well with my Smalltalk message passing sensibilities. Objects passing messages feels so natural, and debugging is easy since I can record and replay any message. And I don't care what gets changed on the server, as long as the Seaside XML methods answer the same way.&lt;br /&gt;&lt;br /&gt;Although the code is written in both a VW and VA client, it's dialect agnostic; they could be swapped. I stuck with the two Smalltalks because I was replacing a domain model Seaside implementation in each, and there was enough sunk cost to leave that code as is.&lt;br /&gt;&lt;br /&gt;Ideally I would like to host the Seaside sessions from GS with a GLASS deployment. &amp;nbsp;It's a long term option with the VW application, but the VA application has to be hosted on a Windows server, which limits us to 32 bit GS and no GLASS.&lt;br /&gt;&lt;br /&gt;So far testing is going well. The next step is to test this under load and see how many concurrent Seaside sessions one image can handle. We already have a multi-Seaside image dispatcher implementation working with Apache that supports session affinity, so I'm confident that scaling will not be a problem.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Trebuchet MS', sans-serif; font-size: x-small;"&gt;&lt;i&gt;Simple things should be simple. Complex things should be possible.&lt;/i&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-764399127185216484?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/764399127185216484/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/09/xml-restful-seaside-interface-to-legacy.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/764399127185216484'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/764399127185216484'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/09/xml-restful-seaside-interface-to-legacy.html' title='XML RESTful Seaside interface to legacy system'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-mwUW7qFwvMM/Tm4nR01uMTI/AAAAAAAABTs/B0Da1wzUcQA/s72-c/1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-1480393134390102407</id><published>2011-08-10T10:42:00.001-04:00</published><updated>2011-08-11T09:05:54.318-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk HTML5'/><title type='text'>Seaside with HTML5 for iPad &amp; iPhone</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;br /&gt;For device orientation, you can use&lt;a href="http://matthewjamestaylor.com/blog/ipad-layout-with-landscape-portrait-modes"&gt; @media rule in CSS&lt;/a&gt;, 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.&lt;br /&gt;&lt;br /&gt;This is the orientation script I used.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;if (window.DeviceOrientationEvent) {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;window.addEventListener('deviceorientation', function(eventData) {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;deviceOrientationHandler();&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;}, false);&lt;br /&gt;} else {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;makeLanscape();&lt;br /&gt;}&lt;br /&gt;function deviceOrientationHandler() {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;if ( orientation == 0 ) {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;makePortrait();&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;}&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;else if ( orientation == 90 ) {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;makeLanscape();&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;}&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;else if ( orientation == -90 ) {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;makeLanscape();&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;}&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;else if ( orientation == 180 ) {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;makePortrait();&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;}&lt;br /&gt;}&lt;br /&gt;function makePortrait() {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;document.getElementById("tabletLandscape").style.display = 'none';&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;document.getElementById("tabletPortrait").style.display = 'block';&lt;br /&gt;}&lt;br /&gt;function makeLanscape() {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;document.getElementById("tabletLandscape").style.display = 'block';&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;document.getElementById("tabletPortrait").style.display = 'none';&lt;br /&gt;}&lt;/blockquote&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;And this does the funky real time image orientation, with an image with id "imgLogo".&lt;br /&gt;&lt;blockquote&gt;if (window.DeviceOrientationEvent) {&lt;br /&gt;&amp;nbsp; window.addEventListener('deviceorientation', function(eventData) {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var LR = eventData.gamma;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var FB = eventData.beta;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; var DIR = eventData.alpha;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; deviceOrientationHandler(LR, FB, DIR);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;}, false);&lt;br /&gt;} else {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;alert("Not supported on your device or browser. &amp;nbsp;Sorry.");&lt;br /&gt;}&lt;br /&gt;function deviceOrientationHandler(LR, FB, DIR) {&lt;br /&gt;&amp;nbsp; &amp;nbsp;//for webkit browser&lt;br /&gt;&amp;nbsp; &amp;nbsp;document.getElementById("imgLogo").style.webkitTransform = "rotate("+ LR +"deg) rotate3d(1,0,0, "+ (FB*-1)+"deg)";&lt;br /&gt;&amp;nbsp; &amp;nbsp;//for HTML5 standard-compliance&lt;br /&gt;&amp;nbsp; &amp;nbsp;document.getElementById("imgLogo").style.transform = "rotate("+ LR +"deg) rotate3d(1,0,0, "+ (FB*-1)+"deg)";&lt;br /&gt;}&lt;/blockquote&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;renderScreenSizeOn: html&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;self screenX isNil ifTrue: [&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;			&lt;/span&gt;self screenX: 0.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;			&lt;/span&gt;self screenY: 0.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;			&lt;/span&gt;html&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;				&lt;/span&gt;script: ('window.location.href="' , html context actionUrl asString ,&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;						&lt;/span&gt;'&amp;amp;' , (html callbacks registerCallback: [:v | self screenX: v asNumber]) , '=" + screen.width + "'&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;						&lt;/span&gt;, '&amp;amp;' , (html callbacks registerCallback: [:v | self screenY: v asNumber]) , '=" + screen.height') ].&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;html text: self screenX printString, ' x ', self screenY printString&lt;/blockquote&gt;&lt;br /&gt;...which generates the script...&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;window.location.href="/Test?_s=sOMeODiqoU20GBt4&amp;amp;_k=pbWTZltjW35uekru&amp;amp;1=" + screen.width + "&amp;amp;2=" + screen.height&lt;/blockquote&gt;&lt;br /&gt;So, I triggered&amp;nbsp;href="/Test?_s=sOMeODiqoU20GBt4&amp;amp;_k=pbWTZltjW35uekru&amp;amp;1=4288&amp;amp;2=1143 which Seaside reads and sends '4288' as the #value: to block&amp;nbsp;[:v | self screenX: v asNumber] and '1143' to&amp;nbsp;&amp;nbsp;[:v | self screenY: v asNumber].&lt;br /&gt;&lt;br /&gt;If I edit the URL and change the values in the same session...&lt;br /&gt;&lt;blockquote&gt;&amp;nbsp;?_s=sOMeODiqoU20GBt4&amp;amp;_k=pbWTZltjW35uekru&amp;amp;1=1234&amp;amp;2=4567&lt;/blockquote&gt;..my screen will render the view values: 1234 x 4567. Neat.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;renderContentOn: html 	| string |&amp;nbsp;&lt;/span&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;string := self requestContext request userAgent.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;(string includesSubString: 'iPad') ifTrue: [^self requestContext redirectTo: self tabletURL].&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;(string includesSubString: 'iPhone') ifTrue: [^self requestContext redirectTo: self phoneURL].&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;(string includesSubString: 'webOS') ifTrue: [^self requestContext redirectTo: self tabletURL].&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;^self requestContext redirectTo: self portalURL&lt;/blockquote&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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 ...&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;html div id: 'container'; with: [&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;html canvas id: 'sketchpad'; width: 350; height: 350; style: 'border: 1px solid black; background: white';&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;			&lt;/span&gt;with: [html strong: 'Your browser does not support canvas.']].&lt;/blockquote&gt;&lt;br /&gt;...and then used this script for the iPad &amp;amp; iPhone figure drawing...&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;// get the canvas element and its context&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;var sketchpadCanvas = document.getElementById('sketchpad');&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;var context = sketchpadCanvas.getContext('2d');&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;context.fillStyle = 'red'; // red&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;context.strokeStyle = 'red'; // red&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;context.lineWidth = 4;&lt;br /&gt;//Load the image object in JS, then apply to canvas onload&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;var myImage = new Image();&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;myImage.onload = function() {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;		&lt;/span&gt;context.drawImage(myImage, 0, 0, 350, 350);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;};&lt;br /&gt;// create a drawer which tracks touch movements&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;var drawer = {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; isDrawing: false,&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; touchstart: function(coors){&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp;context.beginPath();&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp;context.moveTo(coors.x, coors.y);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp;this.isDrawing = true;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; },&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; touchmove: function(coors){&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp;if (this.isDrawing) {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; context.lineTo(coors.x, coors.y);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; context.stroke();&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp;}&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; },&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; touchend: function(coors){&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp;if (this.isDrawing) {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.touchmove(coors);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; this.isDrawing = false;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp;}&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; }&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;};&lt;br /&gt;// create a function to pass touch events and coordinates to drawer&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;function draw(event){&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; // get the touch coordinates&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; var coors = {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp;x: event.targetTouches[0].pageX,&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp;y: event.targetTouches[0].pageY&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; };&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; // pass the coordinates to the appropriate handler&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp; drawer[event.type](coors);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;}&lt;br /&gt;// attach the touchstart, touchmove, touchend event listeners.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;sketchpadCanvas.addEventListener('touchstart', draw, false);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;sketchpadCanvas.addEventListener('touchmove', draw, false);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;sketchpadCanvas.addEventListener('touchend', draw, false);&lt;br /&gt;// prevent elastic scrolling&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;document.body.addEventListener('touchmove',function(event){&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt; &amp;nbsp;event.preventDefault();&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;},false);&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;// end body:touchmove&lt;/blockquote&gt;&lt;br /&gt;To work on a non-touch browser, I loaded scripts to do the annotation with a mouse &lt;a href="http://www.robodesign.ro/coding/canvas-paint/20090423/"&gt;based on this tutorial&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Setting a background image, which also cleared the annotation, was done with a script&lt;br /&gt;&lt;blockquote&gt;myImage.src = "/files/TSwaFileLibrary/body.png";&lt;/blockquote&gt;Saving the image was done with #onClick: script from a button.... the 'toDataURL()' is the interesting bit.&lt;br /&gt;&lt;blockquote&gt;html jQuery ajax&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;					&lt;/span&gt;serializeForm;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;					&lt;/span&gt;callback: [:value |&amp;nbsp;self saveImageFile: value]&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;					&lt;/span&gt;value: (Javascript.JSStream on: 'sketchpadCanvas.toDataURL()')&lt;/blockquote&gt;&lt;br /&gt;...and... (removing the first 22 characters is a hack to strip out the ''data:image/png;base64,'' MIME&amp;nbsp;declaration). The code is in VW.&lt;br /&gt;&lt;blockquote&gt;saveImageFile: anImageString&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;| writestream string | &lt;span class="Apple-tab-span" style="white-space: pre;"&gt;			&lt;/span&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;writestream := (self newImageFilename asFilename withEncoding: &amp;nbsp;#binary) writeStream.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;string := anImageString copyFrom: 23 to: anImageString size.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;	&lt;/span&gt;[writestream nextPutAll: (Seaside.GRPlatform current base64Decode: string) asByteArray] ensure: [writestream close].&lt;/blockquote&gt;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.&lt;br /&gt;&lt;br /&gt;(here is a good summary of&amp;nbsp;&lt;a href="http://matt.might.net/articles/how-to-native-iphone-ipad-apps-in-javascript/"&gt;iPod HTML5 javascript development&lt;/a&gt;)&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;em&gt;&lt;small&gt;Simple things should be simple. Complex things should           be possible.&lt;/small&gt;&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-1480393134390102407?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/1480393134390102407/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/08/seaside-with-html5-for-ipad-iphone.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/1480393134390102407'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/1480393134390102407'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/08/seaside-with-html5-for-ipad-iphone.html' title='Seaside with HTML5 for iPad &amp; iPhone'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-8621423951698949931</id><published>2011-07-03T11:06:00.001-04:00</published><updated>2011-08-11T09:04:50.711-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk jQuery'/><title type='text'>Simple jQuery contact application</title><content type='html'>I continue to be impressed with how easy it is to build nice, useful applications with Seaside using the jQuery support. &amp;nbsp;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.&lt;br /&gt;The contact data is dumped daily into a special character delimited file, with 40 fields per row. Trivial to parse in Smalltalk...&lt;br /&gt;&lt;blockquote&gt;array := aString subStrings: 1 asCharacter.&lt;/blockquote&gt;...where aString is one row of the data file.&lt;br /&gt;&lt;br /&gt;Accessing the fields is just an array access, like...&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;postalCode&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;^self dataArray at: 16&lt;/blockquote&gt;&lt;br /&gt;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.&amp;nbsp;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.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;string := aString asLowercase.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;string last = $* ifFalse: [string := string copyWith: $*].&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;list := aCollection select: [:each | string match: each nameKey].&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;list size &amp;gt; limit ifTrue: [^list copyFrom: 1 to: limit].&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;^list&lt;/blockquote&gt;&lt;br /&gt;We match on #nameKey, but the displayed list uses #displayFullName, the mixed case version of the string.&lt;br /&gt;&lt;br /&gt;OK, so the domain in as simple as it gets. The fun part was the Seaside code. JQAutocomplete&amp;gt;&amp;gt;search:labels:callback: &amp;nbsp;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...&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;html textInput&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;id: anId;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;style: 'width: 300px; font-size: 14pt';&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;script: (html jQuery this autocomplete&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;search: [:string | self buildSearchResponse: aSearchBlock with: string]&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;labels: aLabelBlock&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;callback: [:value :script | self redirectToContact: value script: script])&lt;/blockquote&gt;#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.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;^[aBlock value: aString]&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;on: Error&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;do: [:ex | Array with: (TSroloError newForErrorMessage: ex displayString)]&lt;/blockquote&gt;&lt;br /&gt;#aLabelBlock uses one of the three display methods for the drop down list (#displayFullName #displaySurname #displayCompany).&lt;br /&gt;&lt;br /&gt;#redirectToContact:script: shows a nice URL with a ?contact=nnnn suffix.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;| urlString |&lt;br /&gt;urlString := aScript requestContext request url startingURL ,&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;'?contact=',&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;aContact displayContactIndex.&lt;br /&gt;aScript goto: urlString.&lt;br /&gt;self session unregister&lt;/blockquote&gt;In #renderContactOn: I check for the 'contact' parameter and render the contact details if found.&lt;br /&gt;&lt;br /&gt;Here is what it looks like...&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-LblhIxXabCs/ThB8sjtGYFI/AAAAAAAABSQ/1jpt_8st2ZI/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="96" src="http://2.bp.blogspot.com/-LblhIxXabCs/ThB8sjtGYFI/AAAAAAAABSQ/1jpt_8st2ZI/s400/1.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;...typing 'bob n' into the full name fields shows...&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-TdQ_bJVxvNs/ThB-g6FnDpI/AAAAAAAABSU/rAFIvVrUFQo/s1600/2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="110" src="http://4.bp.blogspot.com/-TdQ_bJVxvNs/ThB-g6FnDpI/AAAAAAAABSU/rAFIvVrUFQo/s400/2.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;...selecting 'Bob Nemec' then redirects to '?contact=3784', which shows the contact details...&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-6jRoQBXfXI8/ThB-0FZhWBI/AAAAAAAABSY/XeVnuwah-Wo/s1600/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="127" src="http://2.bp.blogspot.com/-6jRoQBXfXI8/ThB-0FZhWBI/AAAAAAAABSY/XeVnuwah-Wo/s400/3.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;...the '+' toggles a display of all the contact fields, using the jQuery toggle...&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;| moreId lessId |&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;moreId := html nextId.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;lessId := html nextId.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;html anchor&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;id: moreId;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;onClick: (&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;   &lt;/span&gt;html jQuery ajax script: [:s |&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;s &amp;lt;&amp;lt; (html jQuery id: aComponentId) toggle: 0.3 seconds.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;s &amp;lt;&amp;lt; (html jQuery id: moreId) hide.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;s &amp;lt;&amp;lt; (html jQuery id: lessId) show]);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;title: aMoreTitle;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;with: [html image url: TSwaFileLibrary / #morePng].&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;html anchor&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;id: lessId;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;style: 'display: none;';&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;onClick: (&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;   &lt;/span&gt;html jQuery ajax script: [:s |&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;s &amp;lt;&amp;lt; (html jQuery id: aComponentId) toggle: 0.5 seconds.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;s &amp;lt;&amp;lt; (html jQuery id: lessId) hide.&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;s &amp;lt;&amp;lt; (html jQuery id: moreId) show]);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;title: aLessTitle;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;with: [html image url: TSwaFileLibrary / #lessPng]&lt;/blockquote&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;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.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;And again, a big thanks for the Seaside jQuery code which makes writing simple apps like this easy.&lt;/div&gt;&lt;br /&gt;&lt;i&gt;A bad day in [] is better than a good day in {}&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-8621423951698949931?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/8621423951698949931/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/07/simple-jquery-contact-application.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/8621423951698949931'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/8621423951698949931'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/07/simple-jquery-contact-application.html' title='Simple jQuery contact application'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-LblhIxXabCs/ThB8sjtGYFI/AAAAAAAABSQ/1jpt_8st2ZI/s72-c/1.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-6172220029427179407</id><published>2011-06-01T20:25:00.000-04:00</published><updated>2011-06-01T20:25:43.395-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>Low-tech Seaside graphs</title><content type='html'>I really like the basic 'how to'&amp;nbsp;tutorials&amp;nbsp;people post, whether they are about Seaside, C#, VisualStudio or motorcycle maintenance (that pretty much covers where my head has been lately). &lt;br /&gt;&lt;br /&gt;So here are a couple of things I've done in Seaside that I think are simple yet proved handy.&lt;br /&gt;&lt;br /&gt;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 &lt;a href="http://www.iopus.com/"&gt;iMacro &lt;/a&gt;script.&lt;br /&gt;&lt;br /&gt;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)...&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-aLHlPRHAu2g/TebQAeMFuFI/AAAAAAAABRg/7j158lu7YIo/s1600/Clipboard01.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="163" src="http://2.bp.blogspot.com/-aLHlPRHAu2g/TebQAeMFuFI/AAAAAAAABRg/7j158lu7YIo/s400/Clipboard01.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;...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.&lt;br /&gt;&lt;br /&gt;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'.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;html imageButton&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;       &lt;/span&gt;callback: [self openTransactions];&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;       &lt;/span&gt;title: 'Show transactions';&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;       &lt;/span&gt;url: TxFileLibrary / #progressredPng&lt;/blockquote&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;The images were created with &lt;a href="http://www.getpaint.net/"&gt;Paint.net&lt;/a&gt;, a handy tool for simple graphics.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;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.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-f5M2bgg60Fw/TebXAETfbXI/AAAAAAAABR0/pkVXKm92rwM/s1600/Clipboard02.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="300" src="http://1.bp.blogspot.com/-f5M2bgg60Fw/TebXAETfbXI/AAAAAAAABR0/pkVXKm92rwM/s320/Clipboard02.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;I think it's cool what you can due with some dynamic HTML table generation and a few PNG files.&amp;nbsp;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-6172220029427179407?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/6172220029427179407/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/06/low-tech-seaside-graphs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/6172220029427179407'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/6172220029427179407'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/06/low-tech-seaside-graphs.html' title='Low-tech Seaside graphs'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-aLHlPRHAu2g/TebQAeMFuFI/AAAAAAAABRg/7j158lu7YIo/s72-c/Clipboard01.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-8826165982821624551</id><published>2011-05-20T08:08:00.000-04:00</published><updated>2011-05-20T08:08:46.023-04:00</updated><title type='text'>User group marketing</title><content type='html'>The &lt;a href="http://www.smalltalk.ca/"&gt;Toronto Smalltalk User Group&lt;/a&gt; has been active for 20 years. At our peak we had about 45 people attending meetings. Lately we've averaged about a dozen.&lt;br /&gt;&lt;br /&gt;What can we do to get more people to attend?&lt;br /&gt;&lt;br /&gt;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.&amp;nbsp;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.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Some of the TSUG crowd have attended&amp;nbsp;&lt;a href="http://www.xptoronto.com/"&gt;XP Toronto&lt;/a&gt;&amp;nbsp;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.&lt;br /&gt;&lt;br /&gt;Yanni Chiu has been something of &amp;nbsp;a TSUG ambassador, representing Smalltalk at things like the "Dynamic Languages Smack Down". He and Chris Cunnington signed up for &lt;a href="http://toronto.startupweekend.org/"&gt;Toronto Startup Weekend&lt;/a&gt; and plan to show some Seaside app. I had some&amp;nbsp;tentative&amp;nbsp;plans to talk to the Durham Ruby group (something I need to follow up on).&lt;br /&gt;&lt;br /&gt;Whenever it's appropriate I hand out &lt;a href="http://dl.dropbox.com/u/5602924/TSUG_Smalltalk.pub"&gt;TSUG pamphlets&lt;/a&gt;. 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&amp;nbsp;MS Publisher document&amp;nbsp;which I print off on heavier stock paper which has a light blue tint. Here is what part of it looks like...&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-yYN4uDa5D3I/TdZWAShU8ZI/AAAAAAAABRc/pg0PALpf1O4/s1600/Clipboard02.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-yYN4uDa5D3I/TdZWAShU8ZI/AAAAAAAABRc/pg0PALpf1O4/s320/Clipboard02.jpg" width="286" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I'd love to get suggestions on how to promote the group.&lt;br /&gt;&lt;br /&gt;Malcolm Gladwell in his book &lt;a href="http://en.wikipedia.org/wiki/Outliers_(book)"&gt;Outliers &lt;/a&gt;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. &amp;nbsp;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-8826165982821624551?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/8826165982821624551/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/05/user-group-marketing.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/8826165982821624551'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/8826165982821624551'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/05/user-group-marketing.html' title='User group marketing'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-yYN4uDa5D3I/TdZWAShU8ZI/AAAAAAAABRc/pg0PALpf1O4/s72-c/Clipboard02.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-8699226405368322288</id><published>2011-05-03T17:09:00.000-04:00</published><updated>2011-05-03T17:09:30.632-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>Pushing Smalltalk</title><content type='html'>The next&amp;nbsp;meeting of the Toronto Smalltalk User Group is May 9 with Don MacQueen talking about JWARS.&lt;br /&gt;See the &lt;a href="http://www.smalltalk.ca/"&gt;web site&lt;/a&gt; for details.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;But there is one core strength that I value which I cannot easily demo: the lack of brick walls.&lt;br /&gt;&lt;br /&gt;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&amp;nbsp;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.&lt;br /&gt;&lt;br /&gt;Thing is, that's not the kind of thing you&amp;nbsp;appreciate&amp;nbsp;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&amp;nbsp;importantly, in other people's code).&lt;br /&gt;&lt;br /&gt;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&amp;nbsp;admittedly&amp;nbsp;short list), Smalltalk still does that the best.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-8699226405368322288?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/8699226405368322288/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/05/pushing-smalltalk.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/8699226405368322288'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/8699226405368322288'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/05/pushing-smalltalk.html' title='Pushing Smalltalk'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-4260586024851578147</id><published>2011-04-22T07:39:00.000-04:00</published><updated>2011-04-22T07:39:20.730-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>Smalltalk trivia</title><content type='html'>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&amp;nbsp;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.&lt;br /&gt;&lt;br /&gt;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&amp;nbsp;consensus of how to code Smalltalk. My iteration block still have :each or :each&amp;lt;something&amp;gt; as the binding variable. &amp;nbsp;In his "Fundamentals of Smalltalk Programming Technique", &amp;nbsp;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.&lt;br /&gt;&lt;br /&gt;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...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;aBoolean&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;ifTrue: [self doThis]&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;ifFalse: [self doThat].&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;aList do: [:each | self doSomethingWith: each]&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...I find the Seaside code is formatted as...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;aBoolean&lt;/span&gt;&lt;br /&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;ifTrue: [ self doThis ]&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;ifFalse: [ self doThat ].&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;aList do: [ :each | self doSomethingWith: each ]&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;...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.&amp;nbsp;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;Another example: In our VW application, &amp;nbsp;the framework code uses abbreviations for local and parameter variables, so I see a lot of code like &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;printOn: aStrm&lt;/span&gt; and &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;| errMsg idx t v m s |&lt;/span&gt; ... 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.&amp;nbsp;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;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...&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&amp;nbsp;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; &amp;nbsp;'Time' echo: Time now&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;...shows in the Transcript as...&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;Time 10:10:54 AM&lt;/span&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;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...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;Transcript cr; show: 'this string' printString &amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;'this string'&lt;/span&gt;&lt;br /&gt;vs.&lt;br /&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;Transcript cr; show: 'this string'&lt;/span&gt;&lt;/div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;this string&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...so we can implement #echo on Object as &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;Transcript cr; show: self printString &lt;/span&gt;and on String as &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;Transcript cr; show: self.&lt;/span&gt; But what about #echo: ? We could implement on&amp;nbsp;Object as...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;Object&amp;gt;&amp;gt;echo: anObject&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;Transcript cr; show: self printString, ': ',&amp;nbsp;anObject printString&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...(I prefer the : delimiter to just a space) and we'd need...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;String&amp;gt;&amp;gt;echo: anObject&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;Transcript cr; show: self , ': ', anObject printString&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;...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.&lt;br /&gt;&lt;br /&gt;Here is what my implementation (no value returned so that &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;a := b echo&lt;/span&gt; answers b)...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;Object&amp;gt;&amp;gt;echo&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;self printString echo&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;String&amp;gt;&amp;gt;echo&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;Transcript cr; show: self.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;Object&amp;gt;&amp;gt;echo: anObject&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;anObject echoAfter: self printString&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;String&amp;gt;&amp;gt;echo: anObject&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;anObject echoAfter: self&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;Object&amp;gt;&amp;gt;echoAfter: aString&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;aString echoString: self printString&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;String&amp;gt;&amp;gt;echoAfter: aString&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;aString echoString: self&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;String&amp;gt;&amp;gt;echoString: aString&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;Transcript cr; show: &amp;nbsp;self , ': ', aString&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;'test' echo: 123 &amp;nbsp; &amp;nbsp;&amp;gt;&amp;gt; 'test: 123'&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;'test' echo: 'this' &amp;gt;&amp;gt; 'test: this'&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;123 echo: 'this' &amp;nbsp; &amp;nbsp;&amp;gt;&amp;gt; '123: this'&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;123 echo: 456 &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt;&amp;gt; '123: 456' &amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-4260586024851578147?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/4260586024851578147/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/smalltalk-trivia.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/4260586024851578147'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/4260586024851578147'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/smalltalk-trivia.html' title='Smalltalk trivia'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-6069142557689156667</id><published>2011-04-19T10:14:00.000-04:00</published><updated>2011-04-19T10:15:41.590-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>VXML server isolation</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;Our&amp;nbsp;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,&amp;nbsp;&amp;nbsp;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 &amp;amp; VXML servers with different service providers.&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-6069142557689156667?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/6069142557689156667/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/vxml-server-isolation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/6069142557689156667'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/6069142557689156667'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/vxml-server-isolation.html' title='VXML server isolation'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-7008912878578680347</id><published>2011-04-15T10:21:00.000-04:00</published><updated>2011-04-15T10:21:34.084-04:00</updated><title type='text'>IVR VXML from Seaside</title><content type='html'>We use Seaside to serve anything that takes data from an http call. One of these services is an &lt;a href="http://en.wikipedia.org/wiki/Interactive_voice_response"&gt;IVR&lt;/a&gt; system hosted by &lt;a href="http://www.voxeo.com/"&gt;Voxeo&lt;/a&gt;. It requires a &lt;a href="http://www.vxml.org/"&gt;VXML &lt;/a&gt;file that defines the audio script and records answers. We have a sublcass of WARequestHandler that answers static VXML content for the initial request:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;handleRequest: aRequestContext&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;| response |&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;response := aRequestContext response.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;response&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;contentType: self contentType;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;nextPutAll: &lt;b&gt;self document requestVXML&lt;/b&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;aRequestContext respond.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;...and builds contents for the response, which contains a simple 'Thank you' if all the data is valid...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;handleResponse: aRequestContext&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;| response result request |&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;request := aRequestContext request.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;&lt;b&gt;result := self processCall: request.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;response := aRequestContext response.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;response&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;contentType: self contentType;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;nextPutAll: result.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;aRequestContext respond.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Our production system, which our client uses to record at-home service calls, peaks at about 600 calls per hour. And the same image that serves the VXML file also has a conventional Seaside interface for monitoring the system and for configuration. Very handy. For example, we have a Google graph to show one day's call distribution.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-d9ype5ko-DI/TahQSVFMQXI/AAAAAAAABQo/_v22IOyPJys/s1600/2011-04-15+10-02-24+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="197" src="http://1.bp.blogspot.com/-d9ype5ko-DI/TahQSVFMQXI/AAAAAAAABQo/_v22IOyPJys/s400/2011-04-15+10-02-24+AM.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Recently we added more validation to the VXML script, so that shifts and employee numbers could be checked during the IVR dialog. The new interaction places more demands on the Seaside image, so we're testing a deployment with three images and simple round-robin load balancing (the current single image deployment gets about two timeout errors per week). Each VXML call is RESTful so we have no need for session affinity. Testing all went well, until something interesting came up.&lt;br /&gt;&lt;br /&gt;Testing was all done on a Windows server and deployment is on a Linux box. No big deal for VW. But when we tried the new deployment, the IVR system said the VXML content had errors. Looking at the VXML file on our Seaside display showed extra blank lines, but the content was correct. Inspecting the file content as read showed that the &amp;lt;cr&amp;gt;&amp;lt;lf&amp;gt;s were all replaced with &amp;lt;cr&amp;gt;&amp;lt;cr&amp;gt; (you gotta love all the delimiter differences between Windows, Linux and Smalltalk). I didn't think that IVR server would have an issue with that, but to be sure I replaced the file read from...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;^file contentsOfEntireFile&lt;/span&gt;&lt;br /&gt;...to...&lt;br /&gt;&amp;nbsp;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp; &amp;nbsp;^file contentsOfEntireBinaryFile asString&lt;/span&gt;&lt;br /&gt;...and it turns out that fixed the problem.&lt;br /&gt;&lt;br /&gt;Makes we wonder what people do who work with tools that don't allow the deep diving that Smalltalk does. I suspect that when it all works their productivity is high, but there must be more frustration when things break.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-7008912878578680347?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/7008912878578680347/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/ivr-vxml-from-seaside.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/7008912878578680347'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/7008912878578680347'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/ivr-vxml-from-seaside.html' title='IVR VXML from Seaside'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-d9ype5ko-DI/TahQSVFMQXI/AAAAAAAABQo/_v22IOyPJys/s72-c/2011-04-15+10-02-24+AM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-3290400199890941880</id><published>2011-04-14T07:39:00.000-04:00</published><updated>2011-04-14T07:39:49.411-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>VA Smalltalk and Seaside</title><content type='html'>I support a VA Smalltalk app for which we're adding a web interface, using Seaside. The first feature we've ported is a 'Site Map' view, a hierarchy of GIF images with configured 'hot point' rectangles for navigation. Users click to drill down to more other images or to display data in a table.&lt;br /&gt;&lt;br /&gt;In VA the display is updated by replacing the 'image area' #labelPixmap and adjusting the window shell width and height. 'Hotpoints' are managed by mapping 'Pointer Motion' and 'Button Press' callbacks to a collection of objects that define the rectangle, which shows an action specific mouse pointer, and and triggers the action when the mouse button is pressed. All of this is user configurable using a view where they add image, draw rectangles and define actions.&lt;br /&gt;&lt;br /&gt;A few years ago we added a feature which optionally displayed the current value of a measurement in the displayed image. It eliminated the need to click on a data 'hot point' and was handy for images with several data points. We've now added the same capability to the Seaside view.&lt;br /&gt;&lt;br /&gt;In VA, the data is displayed by first defining instances of CwLabel in the imageArea (a&amp;nbsp;WkImageWidget) at the correct position. The #labelString and #backgroundColor are then set based on user parameters, like the display date. Background color is used to show the 'quality' of value, so a grey background indicates incomplete data. To hide the data, the CwLabel's #visible property is set to false.&lt;br /&gt;&lt;br /&gt;This is what it looks like in VA...&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-VKYiuGiYr7Y/TabZ0Bf5BVI/AAAAAAAABQc/9Y0a5ScKvgE/s1600/Clipboard01.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="208" src="http://4.bp.blogspot.com/-VKYiuGiYr7Y/TabZ0Bf5BVI/AAAAAAAABQc/9Y0a5ScKvgE/s320/Clipboard01.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;To do this in Seaside, we use a style 'position:absolute; left:10px; top:140px; z-index:-1;' for the GIF and then render the displayed data with...&lt;br /&gt;&amp;nbsp;'position: absolute; top: %1px; left: %2px; z-index:2; border: 2px solid black; background-color: %3'&lt;br /&gt;...using #bindWith:with:with: to set the top, left and background-color. The result looks like this...&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-_wT4IjaSL84/Tabb6DUl_kI/AAAAAAAABQk/4BTaltHxX2c/s1600/Clipboard02.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="206" src="http://2.bp.blogspot.com/-_wT4IjaSL84/Tabb6DUl_kI/AAAAAAAABQk/4BTaltHxX2c/s320/Clipboard02.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I continue to be impressed with how easy it is to do interesting web work in Seaside.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-3290400199890941880?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/3290400199890941880/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/va-smalltalk-and-seaside.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/3290400199890941880'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/3290400199890941880'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/va-smalltalk-and-seaside.html' title='VA Smalltalk and Seaside'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-VKYiuGiYr7Y/TabZ0Bf5BVI/AAAAAAAABQc/9Y0a5ScKvgE/s72-c/Clipboard01.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-1898964067650953611</id><published>2011-04-11T15:48:00.000-04:00</published><updated>2011-04-11T15:49:35.536-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MediaWiki'/><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>Dynamic Wiki</title><content type='html'>We use an internal MediaWiki web site for document and status reporting. Combining our static wiki text with dynamic output from our issue library and patch manager has worked very well for us. We use a&lt;a href="http://www.mediawiki.org/wiki/Extension:Webservice"&gt; MediaWiki webservice extension&lt;/a&gt;&amp;nbsp;to add graphs, tables and issue lists.&lt;br /&gt;&lt;br /&gt;For example, this code will get a list of my issues...&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;{{#webservice:&amp;lt;our internal web server&amp;gt;/Issues?wiki=user:Bob_N.| %//div%}}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...the output can be tested by using the URL directly in a browser, which answers wiki formatted output...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;==Bob N. - current==&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;{| ! ID ! System ! Client ! Title ! Stage ! Text ! P. ! Assigned ! Date ! Type |- | [http://&amp;lt;our internal web server&amp;gt;/Issues?issue=3353 3353] ||  BID || CS || HTS Customer Portal: changes to project display ||  [[image:Blueplay.png]] current ||  ''  ''  || 3 || Bob N. || 2011-04-11  || Modification |- |...etc.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...and, when parsed by MediaWiki, shows as a list of issues...&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-2jXirnjs7FU/TaNZpOwoBCI/AAAAAAAABQY/rXbhM7JyVik/s1600/2011-04-11+3-42-12+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="154" src="http://4.bp.blogspot.com/-2jXirnjs7FU/TaNZpOwoBCI/AAAAAAAABQY/rXbhM7JyVik/s320/2011-04-11+3-42-12+PM.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;...with links back to the issue library. We combine the web service extension with Google charts to add live charts to our wiki, using an entry like...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;{{#webservice:&amp;lt;our internal web server&amp;gt;/Issues?Issues?wikichart=backlog| %//div%}}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...which generates wiki formatted text that contains a Google Chart API call...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;lt;websiteFrame&amp;gt; website=http://chart.apis.google.com/chart?&amp;amp;cht=bvo&amp;amp;chs=800x350&amp;amp;chg=0,10,4,0&amp;amp;chtt=Working+Issue+Backlog&amp;amp;chts=19293D,20&amp;amp;chbh=24,30&amp;amp;chco=00FFFF,00CCFF,0099FF,0066FF,0033FF,0000CC,000066&amp;amp;chdl=a:  week|b: 2 weeks|c: month|d: 2 months|e: quarter|f: 2 quarters|g: 6  months&amp;amp;chd=t:0,0,0,0,0,0,0,0,0,0,0,0,795|0,0,0,0,0,0,0,0,0,0,0,766,767|0,0,0,0,0,0,0,0,0,0,0,730,699|0,0,0,0,0,0,0,0,0,0,660,661,639|0,0,0,0,0,0,0,0,0,540,543,491,474|0,0,0,0,0,0,290,465,456,398,381,344,335|141,182,214,254,282,289,256,235,76,72,69,54,54&amp;amp;chds=0,795&amp;amp;chxt=x,y&amp;amp;chxl=0:|Apr  2010|May 2010|Jun 2010|Jul 2010|Aug 2010|Sep 2010|Oct 2010|Nov 2010|Dec  2010|Jan 2011|Feb 2011|Mar 2011|Apr  2011|1:|0|79|158|237|316|395|474|553|632|711|790 name=Working Issue Backlog align=middle height=380 width=800 border=0 scroll=no longdescription=chart &amp;lt;/websiteFrame&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...again, this can be tested directly in a browse to show the expected charted&amp;nbsp;(the data shows a ramp up of using the issue library)...&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-th62sGoajFc/TaNYSHxHHUI/AAAAAAAABQU/OCdehLhHsBQ/s1600/2011-04-11+3-35-54+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="176" src="http://1.bp.blogspot.com/-th62sGoajFc/TaNYSHxHHUI/AAAAAAAABQU/OCdehLhHsBQ/s400/2011-04-11+3-35-54+PM.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-1898964067650953611?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/1898964067650953611/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/dynamic-wiki.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/1898964067650953611'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/1898964067650953611'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/dynamic-wiki.html' title='Dynamic Wiki'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-2jXirnjs7FU/TaNZpOwoBCI/AAAAAAAABQY/rXbhM7JyVik/s72-c/2011-04-11+3-42-12+PM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-2748935392304647937</id><published>2011-04-08T10:14:00.000-04:00</published><updated>2011-04-08T10:14:08.790-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>Patch Manager</title><content type='html'>We use a VW / Seaside based Patch Manager to send patches to our clients. Some of the design ideas may be of interest.&lt;br /&gt;&lt;br /&gt;We cannot access our clients machines directly, but we can get permission for outbound ports through their firewalls. So our patch management process is based on polling: the remote patch clients connect to a patch server every minute, get a list of patch names and timestamps, and then request updates or send changes (for patches that are filed out at a client site). Commands, status, logs, exception walkback files are also passed along.&lt;br /&gt;&lt;br /&gt;User access is though a Seaside interface to the patch server. &amp;nbsp;Developers select patches to send to a client from a table of published patches, filtered by applications and versions. Remotely filed-out patches can also be retrieved and published. If a patch is updated, the developer can choose to see the difference and can then send the updated patch. Our code file-out methods add a user and timestamp comment, which is extracted by the patch server.&lt;br /&gt;&lt;br /&gt;In this example, = shows a patch that is published, &amp;gt; &amp;lt; shows an updated patch (current for one version, different for another), pressing an arrow either retrieves the updated patch or sends the older version (grey button for older). And + is for patches that have not been sent, pressing + sends the patch. Each patch is linked to an issue. Some developers prefer to prefix their patches with issues numbers, but it's not required.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-P9GSq2NVFGc/TZ8UE8RPhoI/AAAAAAAABQI/HEuHU6_85Sk/s1600/2011-04-08+9-56-12+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="71" src="http://4.bp.blogspot.com/-P9GSq2NVFGc/TZ8UE8RPhoI/AAAAAAAABQI/HEuHU6_85Sk/s400/2011-04-08+9-56-12+AM.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Communication between the client and server images is by VW's Opentalk. As of version 7.7 it has been rock solid (version 7.6 has issues with brokers timing out).&lt;br /&gt;&lt;br /&gt;Every ten minutes a 'retrieve client state' command is put on a 'pending commands' queue. At the next polling action, the command is retrieved by the patch client and a 'state file', generated by the client application, is send back to the server. If any of the client states indicate a problem an email is sent to the support team and a red notification icon is displayed on both the patch manager and issue library web page, as well as our in-house wiki. Pressing the icon shows a formatted view of the state file with errors in red. We are similarly notified if communication with the client fails, which is detected by an&amp;nbsp;absence&amp;nbsp;of polling activity.&lt;br /&gt;&lt;br /&gt;A similar mechanism is used to retrieve client walkback files. The client image already emails the walkback file, so this is a backup process. It retrieves walkback files that were not able to be sent, and it builds a useful history of client errors (which, fortunately, continue to decline).&lt;br /&gt;&lt;br /&gt;Not having to manage the patch files manually has proven to be a big productivity gain.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-2748935392304647937?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/2748935392304647937/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/patch-manager.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/2748935392304647937'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/2748935392304647937'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/patch-manager.html' title='Patch Manager'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-P9GSq2NVFGc/TZ8UE8RPhoI/AAAAAAAABQI/HEuHU6_85Sk/s72-c/2011-04-08+9-56-12+AM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-2730272471270550765</id><published>2011-04-07T14:00:00.000-04:00</published><updated>2011-04-08T09:42:22.706-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>Graphing with Google Charts</title><content type='html'>&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;We use Google charts to show various issue metrics. As is typical of a Smalltalk developer, I built my own class to manage the API calls. When I first looked into Google charts (two years ago, I think) I could not find community sourced options and building my own proved educational. Props to those that do build general purpose Seaside components, it's not trivial. My stuff is narrowly focused, and even that took some work.&lt;/span&gt;&lt;br /&gt;My latest update added 'age' colours to an issue backlog bar graph. Something that may look like this...&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-EuVBYecvifg/TZ8Qd3ob2BI/AAAAAAAABQE/f-j-Ifua4Ug/s1600/2011-04-08+9-40-35+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="254" src="http://3.bp.blogspot.com/-EuVBYecvifg/TZ8Qd3ob2BI/AAAAAAAABQE/f-j-Ifua4Ug/s320/2011-04-08+9-40-35+AM.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;Simple enough to do, but I tripped over an interesting API quirk. Originally, if you wanted to show a stacked bar graph, each value needed to have the lower value added to it (using cht=bvo, for chart type bar, vertical, overlap). So, if you wanted to show a bar with 10, 20, 15, 12 as stacked colours, the series was defined as 10, 30, 45, 57. Works great and it's easy to write the code to adjust the numbers. But, if one of the numbers is zero, the simple adjustment fails. If we want to graph 10, 20, 0, 12, the adjustment would be 10, 20, 20, 32. &amp;nbsp;But, that second 20 ends up overlaying the colour of the first 20. &amp;nbsp;What you really want is 10, 20, 0, 32.&lt;br /&gt;&lt;br /&gt;At least that was true back then. &amp;nbsp;Using the &lt;a href="http://code.google.com/apis/chart/docs/chart_wizard.html"&gt;Chart Wizard&lt;/a&gt; shows that you can use...&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;chxt=y&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;chbh=a&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;chs=300x225&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;cht=bvs&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;chco=00CCFF,0099FF,0066FF,0000FF&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;chd=t:10,10|20,20|15,0|12,12&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;chtt=Vertical+bar+chart&lt;/span&gt;&lt;br /&gt;...to get as stacked chart like this in the &lt;a href="http://code.google.com/apis/chart/docs/chart_playground.html"&gt;Live Chart Playground&lt;/a&gt;...&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-ENAaLFpTxn4/TZ35cxmas0I/AAAAAAAABQA/NsTZZTq112E/s1600/2011-04-07+1-50-25+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="253" src="http://1.bp.blogspot.com/-ENAaLFpTxn4/TZ35cxmas0I/AAAAAAAABQA/NsTZZTq112E/s320/2011-04-07+1-50-25+PM.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;It's stuff like this that makes the general purpose Seaside graph components impressive. &amp;nbsp;My original charts used cht=bvo instead of cht=bvs. Maybe it was supported, but I didn't see it (Actually, I still use the cht=bvo in order to keep the legend order the same as the graph colours). It takes time to keep up on all this, while still doing your regular job. &amp;nbsp;Thanks to those that do.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-2730272471270550765?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/2730272471270550765/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/graphing-with-google-charts.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/2730272471270550765'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/2730272471270550765'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/graphing-with-google-charts.html' title='Graphing with Google Charts'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-EuVBYecvifg/TZ8Qd3ob2BI/AAAAAAAABQE/f-j-Ifua4Ug/s72-c/2011-04-08+9-40-35+AM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-6880071903490300717</id><published>2011-04-06T13:32:00.000-04:00</published><updated>2011-04-06T13:32:53.043-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>Issue Library - how it started</title><content type='html'>&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;A To Do list grows up.&amp;nbsp;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;Some background on one of our projects: our in-house Issue Library, a Seaside application to track workflow.&amp;nbsp;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;The Issue Library started off as a simple online 'to do' list, a way to track my own workload and to experiment with Seaside code. Others started to use it, and eventually it became a real application. A nice bit of&amp;nbsp;guerilla&amp;nbsp;marketing; a home grown issue tracking project would never have been approved. We did look at some open source options, but&amp;nbsp;in order to make an issue tracker useful to a wide audience, it seems that you need to have a lot of options, with the app eventually getting really big and complicated. &amp;nbsp;Gjaller&amp;nbsp;&lt;a href="http://www.gjallar.se/"&gt;http://www.gjallar.se/&lt;/a&gt;&amp;nbsp;looked promising: I really liked the idea of working with a configurable Seaside code base. But I had no luck getting it to work. &amp;nbsp;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;The design goals were to make it web based, simple, easy to update and more than a toy. &amp;nbsp;It needed to be real enough to learn the solid Seaside development details. Data is stored in XML files, one per issue. &amp;nbsp;Files are read at image startup and written when changed. &amp;nbsp;Since the Seaside image is the only thing that updates the XML files, the data in memory and on disk is consistent (and trivial to check). &amp;nbsp;Object are always in memory which give us excellent performance. Writes are fast, about 10ms. I've used this technique with my home 'Quicken' type app. &amp;nbsp;As long as the image size is reasonable, it works great. Right now the image, which also runs our Patch Manager, is about 225MB. As it grows, old issues will be archived.&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-size: 13px;"&gt;&lt;div style="font-family: arial, sans-serif;"&gt;Objects which map to XML files are subclassed from our an abstract class with all the XML support code. Read / write is done with #loadFile + #parseXML and #saveFile + #buildXML.&amp;nbsp;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;Parsing is done with VW's XMLParser&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;[xml := [XML.XMLParser new&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt;    &lt;/span&gt;validate: false;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt;    &lt;/span&gt;parseElements: stream]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt;     &lt;/span&gt;on: XML.BadCharacterSignal&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt;     &lt;/span&gt;do: [:ex | ex resume]]&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;Values are then extracted by interating over the XML elements. &amp;nbsp;Each element is sent as an argument to #parseElement: which subclasses implement. &amp;nbsp;As each element is found, the corresponding value is set and the next element processed.&amp;nbsp;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;A utility method is used to find the tags.&amp;nbsp;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;parseElement: anElement tag: aString doText: aBlock&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;anElement tag isNil ifTrue: [^false].&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;anElement tag type = aString ifFalse: [^false].&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;anElement elements isEmpty ifTrue: [^true].&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;aBlock value: anElement elements first text.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;^true&lt;/span&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;For example, extracting the 'title' element is done with...&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;(self parseElement: anElement tag: 'title' doText: [:value | self title: value]) ifTrue: [^self].&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;...title is initilized to a new String, so if none in found in the XML file, or if it's empty, the title will stay as initialized. &amp;nbsp;Other string attributes are set to '&amp;lt;none&amp;gt;' so show that they were never set.&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;Collection attributes are extracted by iterating over the nested collection, using another utility method...&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;parseElement: anElement tag: aString doEach: aBlock&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;anElement tag isNil ifTrue: [^false].&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;anElement tag type = aString ifFalse: [^false].&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;anElement elements isEmpty ifTrue: [^true].&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;anElement elements do: [:each | aBlock value: each].&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;^true&lt;/span&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;To extract the list of comments, we use...&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;(self parseElement: anElement tag: 'comments' doEach: [:value | self parseCommentElement: value]) ifTrue: [^self].&lt;/span&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;...we create a new instance of a comment object and pass on the XML elements for it to parse...&lt;/div&gt;&lt;div style="font-family: arial, sans-serif;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;parseCommentElement: anElement&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;| comment |&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;((anElement tag type) = 'issuecomment') ifFalse: [^self error: 'XML parse error... expected ''issuecomment'' tag'].&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;comment := TSissueComment newForElement: anElement issueFile: self.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;self comments add: comment.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;Building the XML is done by adding XML nodes to an XML document. &amp;nbsp;This could probably be made cleaner with some utility methods, but it's a very static method.&lt;/div&gt;&lt;div style="font-size: 13px;"&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;buildXML&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;| document node |&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;document := XML.Document new.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;document addNode: (XML.PI name: 'xml' text: 'version="1.0"').&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;document addNode: (node := XML.Element tag: 'issue').&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;node addNode: (XML.Element tag: 'id' elements: (Array with: (XML.Text text: self id printString))).&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;node addNode: (XML.Element tag: 'title' elements: (Array with: (XML.Text text: self title asOneByteString))).&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;...&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;#asOneByteString is a hack to eliminate any characters over an integer value of 255. &amp;nbsp;We have Mac users that are able to paste in characters that the XML.Document will not tolerate.&amp;nbsp;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;Collections are just nested nodes...&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-size: 13px;"&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;buildCommentsXML: aNode&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;"allow for comments as strings, for initial conversion to comment objects"&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;| listNode |&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;self comments notEmpty ifTrue: [&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt;  &lt;/span&gt;aNode addNode: (listNode := XML.Element tag: 'comments').&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt;  &lt;/span&gt;self comments do: [:each |&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;span style="white-space: pre;"&gt;   &lt;/span&gt;listNode addNode: (each buildCommentsXML)]].&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;Where #buildCommentsXML adds text content to a new node...&lt;/div&gt;&lt;div style="font-size: 13px;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&lt;span style="white-space: pre;"&gt; &lt;/span&gt;node := XML.Element tag: 'issuecomment'.&lt;/span&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;Simple, low tech and robust enough to handle our needs.&amp;nbsp;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-6880071903490300717?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/6880071903490300717/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/issue-library-how-it-started.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/6880071903490300717'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/6880071903490300717'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/issue-library-how-it-started.html' title='Issue Library - how it started'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2691799628167753841.post-7723607957374319941</id><published>2011-04-06T13:25:00.000-04:00</published><updated>2011-04-06T13:25:04.612-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Smalltalk'/><title type='text'>Getting started</title><content type='html'>&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;Hello.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Maybe it's time to start writing this stuff down. Over the years I've&amp;nbsp;benefited&amp;nbsp;from the public postings of others, and I've done a few of experience reports at conferences, usually with positive feedback. I'd like to do more, hence this blog. Let's see how this goes.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I work in Smalltalk. &amp;nbsp;Learned it in a York University night school AI class in 1990 and switched to it professionally in 1993. Something of an&amp;nbsp;orthogonal&amp;nbsp;move from my work as an MVS systems programmer at EDS. Lately I've spent a lot of time with Seaside. &amp;nbsp;I really like where browser technology is going: smart remotely connected clients, obviating the need to install remote code. &amp;nbsp;And on the Internet, no one knows you're writing Smalltalk. &amp;nbsp;I buy in to Avi Briant's view of Seaside 4.0: lots of Javascript talking JSON to the server (check out his talk at Smalltalk Solutions 2011&amp;nbsp;(hopefully the videos will be available soon)&lt;/div&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;Some of the stuff I've tripped over my be of value to others. I'll write about various Seaside projects, my work with automating SUnit tests and running the Toronto Smalltalk User Group. &amp;nbsp;All Smalltalk; no sports, travel or home renovation. As with anything, I believe this will get better with experience. Eventually this may even by useful.&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-family: arial, sans-serif; font-size: 13px;"&gt;For those in the Toronto area, check out the Toronto Smalltalk User Group at&amp;nbsp;&lt;a href="http://www.smalltalk.ca/"&gt;www.smalltalk.ca&lt;/a&gt;&amp;nbsp;(or&amp;nbsp;&lt;a href="http://www.smalltalk.toronto.on.ca/"&gt;www.smalltalk.toronto.on.ca&lt;/a&gt;)&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2691799628167753841-7723607957374319941?l=smalltalk-bob.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://smalltalk-bob.blogspot.com/feeds/7723607957374319941/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/getting-started.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/7723607957374319941'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2691799628167753841/posts/default/7723607957374319941'/><link rel='alternate' type='text/html' href='http://smalltalk-bob.blogspot.com/2011/04/getting-started.html' title='Getting started'/><author><name>Bob Nemec</name><uri>https://profiles.google.com/102853280217466592933</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-0ae-Ft5CPPE/AAAAAAAAAAI/AAAAAAAABVE/_DtGIAEku0c/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry></feed>
