<?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-36815431</id><updated>2012-01-30T13:35:53.551-08:00</updated><category term='wanted'/><category term='scheme'/><category term='squeak'/><category term='smalltalk'/><category term='tutorial'/><category term='want'/><category term='design'/><category term='htdp'/><category term='lisp'/><category term='magritte'/><category term='seaside'/><category term='review'/><category term='squeak reading'/><category term='screencast'/><title type='text'>Inching Forward</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>16</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-36815431.post-3824346151029610580</id><published>2009-03-15T09:34:00.001-07:00</published><updated>2009-03-15T10:54:55.930-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='lisp'/><category scheme='http://www.blogger.com/atom/ns#' term='htdp'/><category scheme='http://www.blogger.com/atom/ns#' term='scheme'/><title type='text'>Notes on getting started with HtDP</title><content type='html'>I've been working my way through &lt;a href="http://htdp.org/"&gt;How to Design Programs&lt;/a&gt;.  I'm about 1/3 of the way finished, and have some recommendations for people who attempt to study the book on their own:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;If you are using the dead tree version of the book, be warned that it contains errata.   &lt;a href="http://htdp.org/2003-09-26/typos.html"&gt;This page&lt;/a&gt; is required reading.  The &lt;a href="http://htdp.org/"&gt;online version&lt;/a&gt; also contains hints that are not in the physical books.&lt;/li&gt;&lt;li&gt;There is a &lt;a href="http://htdp.org/2003-09-26/Companion/"&gt;DrScheme companion&lt;/a&gt; to the book that offers helpful tips.&lt;/li&gt;&lt;li&gt;Obviously, you should download the latest version of &lt;a href="http://download.plt-scheme.org/"&gt;DrScheme&lt;/a&gt; to do the exercises with.  The book lets you know which language level you should be using by displaying an image like the following (this image is from the online version, which, when clicked, will display instructions on how to change the language level):&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/Sb1AEiwQpJI/AAAAAAAAARY/45WNQ_jC4-8/s1600-h/language-level.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 107px; height: 112px;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/Sb1AEiwQpJI/AAAAAAAAARY/45WNQ_jC4-8/s400/language-level.png" alt="" id="BLOGGER_PHOTO_ID_5313473582159930514" border="0" /&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;The design recipes introduced in the book are not just something to glance at.  They are the heart and soul of learning how to create solutions.  If you are not creating data definitions, contracts, purposes, templates, examples, and tests, you are doing it wrong.  Not following the design recipes may get you lost.&lt;/li&gt;&lt;li&gt;Collecting the design recipes and printing them out provides a useful reference.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;When writing tests, use &lt;a href="http://docs.plt-scheme.org/htdp-langs/Test_Cases.html#%28form._%28%28lib._lang/htdp-beginner..ss%29._check-expect%29%29"&gt;check-expect&lt;/a&gt;.  The format is: (&lt;span style="color: rgb(51, 51, 255);"&gt;check-expect&lt;/span&gt; &lt;span style="color: rgb(204, 0, 0);"&gt;expr1&lt;/span&gt; &lt;span style="color: rgb(204, 0, 0);"&gt;expr2&lt;/span&gt;), where expr1 is the value of the function you are testing, and expr2 is what you expect the function to evaluate to.   Your check-expect tests go in the definitions window.  When you click the run button, a summary of your tests will be displayed to you:&lt;/li&gt;&lt;li&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/Sb09N2nG2II/AAAAAAAAARQ/y638sjdjQ2w/s1600-h/add-error.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 237px;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/Sb09N2nG2II/AAAAAAAAARQ/y638sjdjQ2w/s400/add-error.png" alt="" id="BLOGGER_PHOTO_ID_5313470443574188162" border="0" /&gt;&lt;/a&gt; There are 2 mailing lists that can provide help with HtDP self study:  the &lt;a href="http://www.plt-scheme.org/maillist/"&gt;PLT Scheme&lt;/a&gt; discussion mailing list, and the &lt;a href="http://groups.google.com/group/study-htdp"&gt;Study-HTDP&lt;/a&gt; google group.  The PLT Scheme list contains Scheme experts (including the folks that wrote the book) and contributors to the environments, languages and modules making up PLT Scheme.  The Study-HTDP list contains folks like you working through the book.  I've found the PLT community to be incredibly smart, helpful, and friendly.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;For each section, I create a new file in DrScheme ("10.1.ss", "10.2.ss"&lt;filename here=""&gt;, etc) using the File-&gt;Save Definitions As... menu item.  If old definitions are required for the current exercise, the text is good about referring you back to the original exercise.  This means opening up the old section file and copy/pasting those definitions into your current file.&lt;br /&gt;&lt;/filename&gt;&lt;/li&gt;&lt;li&gt;Do not give up.  If you are struggling with an exercise, take it one step at a time.  You should be focusing on the data definition for the class of data you are dealing with, the design recipe for that class of data, your contract and purpose statements, and creating examples and tests.  If these don't help, re-read the section.  Have you missed something important that the section or previous sections detailed?  If none of this turns on the light bulb, ask for help.  I've taken the stance that the harder an exercise is to solve, the more important it is for me to solve it, both for being able to continue on with the rest of the book and for personal reasons.  Some of the exercises are hard--challenge yourself!&lt;/li&gt;&lt;/ul&gt;If you're reading this and have some tips to share, leave a comment.  Good luck!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-3824346151029610580?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/3824346151029610580/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=3824346151029610580' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/3824346151029610580'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/3824346151029610580'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2009/03/notes-on-getting-started-with-htdp.html' title='Notes on getting started with HtDP'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_pouiJLhtxFE/Sb1AEiwQpJI/AAAAAAAAARY/45WNQ_jC4-8/s72-c/language-level.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-2966248480075952393</id><published>2008-02-21T20:00:00.000-08:00</published><updated>2008-02-21T20:31:34.805-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='squeak reading'/><title type='text'>To Read</title><content type='html'>It's been a while since I've messed around with Squeak/Smalltalk.  The Squeak community has been busy!  After searching just a little bit,  I've found a lot of great catch-up reading:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://squeakbyexample.org/"&gt;Squeak by Example&lt;/a&gt; - I haven't gotten far in this, but love it already&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://squeak.preeminent.org/tut2007/html/"&gt;Squeak Development Example&lt;/a&gt; tutorial by Steven Wessels&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.swa.hpi.uni-potsdam.de/seaside/tutorial"&gt;Seaside Tutorial&lt;/a&gt; by the Hasso-Plattner-Institut&lt;/li&gt;&lt;li&gt;&lt;a href="http://onsmalltalk.com/"&gt;Ramon Leon's blog&lt;/a&gt; is always on point&lt;/li&gt;&lt;li&gt;&lt;a href="http://tekkie.wordpress.com/"&gt;Mark Miller&lt;/a&gt; has written some thoughtful stuff&lt;/li&gt;&lt;/ul&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/36815431-2966248480075952393?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/2966248480075952393/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=2966248480075952393' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/2966248480075952393'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/2966248480075952393'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2008/02/to-read.html' title='To Read'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-1250802610556867332</id><published>2007-12-21T22:36:00.000-08:00</published><updated>2007-12-22T11:06:05.368-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='review'/><title type='text'>2007 personal study/projects review</title><content type='html'>Here's a break down of how I spent my personal study/projects time in 2007&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;The good&lt;/span&gt;&lt;br /&gt;# of new programming languages studied:  2&lt;br /&gt;Studied programming languages grokked:  Squeak Smalltalk&lt;br /&gt;Studied programming languages mostly understood:  Common Lisp&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;The bad&lt;/span&gt;&lt;br /&gt;# of fiction books read:  3&lt;br /&gt;# of personal projects completed: 0&lt;br /&gt;# of personal projects started: 0&lt;br /&gt;&lt;br /&gt;The fact that I didn't start and finish a single project this year is really disappointing.   The reason for this was lack of time.  The time that I do have to study and work on projects occurs late at night, usually after the kids have gone to sleep.  This year I spent all of it on learning the two languages.&lt;br /&gt;&lt;br /&gt;In retrospect, I am very happy to have focused on Smalltalk and Lisp.  There is a lot to be learned from both, and the mind expansion you get from studying them is worth it even if you don't end up using them.   &lt;br /&gt;&lt;br /&gt;For 2008, I'd like to focus on projects.  There are several ideas that I have been thinking about for a while, and I'd like to get them out of my brain and functional.   Initially these will be done using Django, but who knows where the next year will take things.   &lt;div&gt; &lt;/div&gt;&lt;div&gt;Writing the Seaside tutorial was a great experience:  it forced me to focus on understanding the concepts and techniques behind the framework more than just doing recipe-style programming.  I highly recommend this approach to learning something new.   I plan to keep doing this, and would be interested in reading anything you create.&lt;br /&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For those who are still reading:  thanks for your positive comments, sorry I didn't write much in the second half of the year, and hope you didn't mind the focus shifts.  Here's to hoping there are some good posts in me for 2008. &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-1250802610556867332?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/1250802610556867332/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=1250802610556867332' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/1250802610556867332'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/1250802610556867332'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2007/12/2007-personal-studyprojects-review.html' title='2007 personal study/projects review'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-8442582059449760473</id><published>2007-04-09T20:00:00.000-07:00</published><updated>2007-04-10T06:24:58.466-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='smalltalk'/><category scheme='http://www.blogger.com/atom/ns#' term='wanted'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='seaside'/><category scheme='http://www.blogger.com/atom/ns#' term='squeak'/><title type='text'>Wanted: Migrating To a Newer Image Part 2</title><content type='html'>In the last post we migrated the Wanted source code to a newer image by using a local  Monticello repository.  That gave us a working Wanted application, but the WantedItems that we created in the old image weren't copied over.  This post will focus on copying the data out of the old image and into the new one.&lt;span style="font-size:130%;"&gt;&lt;br /&gt;&lt;br /&gt;Write Out the Collection of WantedItems &lt;/span&gt;&lt;br /&gt;Fire up the old Squeak image, open a workspace and execute the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;| out |&lt;br /&gt;out := DataStream newFileNamed:&lt;br /&gt;         '/path-to-your-directory-here/wanted-items.dat'.&lt;br /&gt;out nextPutAll: WantedDatabase wantedItems.&lt;br /&gt;out close&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This creates the temp variable &lt;span style="font-weight: bold;"&gt;out&lt;/span&gt; to hold a DataStream object pointing to a new "&lt;span style="font-weight: bold;"&gt;wanted-items.dat&lt;/span&gt;" file.  The &lt;span style="font-weight: bold;"&gt;nextPutAll&lt;/span&gt; method takes a collection of objects and writes it to the receiving stream before closing it.&lt;br /&gt;&lt;br /&gt;Exit the old Squeak image--this will be the last time we need it.  From now on, all things in these tutorials will be done in the new image.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Read In the Collection of WantedItems&lt;/span&gt;&lt;br /&gt;Fire up the shiny new image, open a new workspace, and execute the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;|in|&lt;br /&gt;in := DataStream fileNamed:&lt;br /&gt;        '/path-to-your-directory-here/wanted-items.dat'.&lt;br /&gt;[in atEnd] whileFalse:  &lt;br /&gt;             [WantedDatabase wantedItems add: in next].&lt;br /&gt;in close&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The above creates a new DataStream off of the "&lt;span style="font-weight: bold;"&gt;wanted-items.dat&lt;/span&gt;" file (notice we're using the &lt;span style="font-weight: bold;"&gt;fileNamed&lt;/span&gt; message to create the DataStream instead of &lt;span style="font-weight: bold;"&gt;newFileNamed&lt;/span&gt;).  The &lt;span style="font-weight: bold;"&gt;[in atEnd]&lt;/span&gt; block will return false until all objects have been read from the file.   We pass that block another block that adds each object read in (using the &lt;span style="font-weight: bold;"&gt;next&lt;/span&gt; message) to the WantedDatabase wantedItems collection.  Then we close the DataStream.&lt;br /&gt;&lt;br /&gt;We now have our old WantedItems in the new image.  Open up a browser and head to http://localhost:8080/seaside/wanted.  You should see something like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/RhsMUm0FQ3I/AAAAAAAAALI/0SQ57YZrj20/s1600-h/migrated-wanted-items.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/RhsMUm0FQ3I/AAAAAAAAALI/0SQ57YZrj20/s400/migrated-wanted-items.jpg" alt="" id="BLOGGER_PHOTO_ID_5051644955182056306" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;Enable File Serving in Commanche&lt;/span&gt;&lt;br /&gt;Our new image is not serving our css since Commanche is not enabled to serve files.   Paste the following into a new workspace and "do it":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;| ma seaside |&lt;br /&gt;HttpService allInstancesDo: &lt;br /&gt;   [:each | each stop. each unregister].&lt;br /&gt;WAKom  stop.&lt;br /&gt;seaside := WAKom  default.&lt;br /&gt;ma := ModuleAssembly core.&lt;br /&gt;ma serverRoot: FileDirectory default fullName.&lt;br /&gt;ma alias: '/seaside' to: &lt;br /&gt;   [ma addPlug: [:request | seaside process: request]].&lt;br /&gt;ma documentRoot: FileDirectory default fullName.&lt;br /&gt;ma directoryIndex: 'index.html index.htm'.&lt;br /&gt;ma serveFiles.&lt;br /&gt;(HttpService startOn: 8080 named: 'httpd') plug: ma rootModule&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That should get you to this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/RhsMUm0FQ4I/AAAAAAAAALQ/J40SjNbPht0/s1600-h/migrated-wanted-items-css.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/RhsMUm0FQ4I/AAAAAAAAALQ/J40SjNbPht0/s400/migrated-wanted-items-css.jpg" alt="" id="BLOGGER_PHOTO_ID_5051644955182056322" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;That Entered Date Format Has Been Bugging Me&lt;/span&gt;&lt;br /&gt;Me too.  We can solve that by adding a &lt;span style="font-weight:bold;"&gt;formatBlock&lt;/span&gt; to the WAReportColumn that handles the enteredDate in WantedList&gt;&gt;initialize:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;add: &lt;br /&gt;  ((WAReportColumn selector: #enteredDate title: 'Entered Date')&lt;br /&gt;      formatBlock: &lt;br /&gt;         [:enteredDate | enteredDate asDate mmddyyyy]; &lt;br /&gt;      yourself);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We get the date to show up in mm/dd/yyyy format by sending the &lt;span style="font-weight:bold;"&gt;asDate&lt;/span&gt; message to the enteredDate of the WantedItem, then sending the &lt;span style="font-weight:bold;"&gt;mmddyyyy&lt;/span&gt; message to that date.&lt;br /&gt;&lt;br /&gt;While we're there, let's modify the "Can Buy" WAReportColumn to show 'Yes' or 'No' instead of the robot-like 'true' or 'false':&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;add: &lt;br /&gt;   (WAReportColumn&lt;br /&gt;       renderBlock: &lt;br /&gt;         [:item | item isPurchasable &lt;br /&gt;                          ifTrue: ['Yes'] ifFalse: ['No']]&lt;br /&gt;       title: 'Can Buy');&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is accomplished by sending the isPurchasable predicate the ifTrue:ifFalse: message with a "Yes" or "No" value respectively.&lt;br /&gt;&lt;br /&gt;Those changes should give you something like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RhsMU20FQ5I/AAAAAAAAALY/Fr6Qq1yCA7E/s1600-h/wanted-items-entered-date-and-can-buy-changes.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RhsMU20FQ5I/AAAAAAAAALY/Fr6Qq1yCA7E/s400/wanted-items-entered-date-and-can-buy-changes.jpg" alt="" id="BLOGGER_PHOTO_ID_5051644959477023634" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Saving Changes&lt;/span&gt;&lt;br /&gt;Since we made source changes, we should commit them to our local Monticello repository.  Open a Monticello Browser and select the Wanted-Item package in the left pane, and the local repository in the right pane:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_pouiJLhtxFE/RhsOaW0FQ6I/AAAAAAAAALg/e_zUVZBR5Pg/s1600-h/wanted-changes-monticello.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_pouiJLhtxFE/RhsOaW0FQ6I/AAAAAAAAALg/e_zUVZBR5Pg/s400/wanted-changes-monticello.jpg" alt="" id="BLOGGER_PHOTO_ID_5051647252989559714" border="0" /&gt;&lt;/a&gt;Notice that an asterisk appears before the package name:  this signifies that changes have occured in the package.  Click the Save button and enter a version message describing the changes we made:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_pouiJLhtxFE/RhsQxW0FQ8I/AAAAAAAAALw/RfVA0RsF0R8/s1600-h/wanted-item-monticello-changes-commit.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_pouiJLhtxFE/RhsQxW0FQ8I/AAAAAAAAALw/RfVA0RsF0R8/s400/wanted-item-monticello-changes-commit.jpg" alt="" id="BLOGGER_PHOTO_ID_5051649847149806530" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;When you hit Accept, a version info window will display letting you know that the changes were successfully saved.  At this point, save the image so that the WantedItems will be persisted.  And that's it for this post--we are now fully migrated over to the new image.   By now it feels like we've kinda worn out the simplicity of saving the WantedItems in a collection in the image.  If we had had the data in a store somewhere, we could have just pointed the new image there instead of this file reading/writing stuff. Future posts will address this.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-8442582059449760473?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/8442582059449760473/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=8442582059449760473' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/8442582059449760473'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/8442582059449760473'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2007/04/wanted-migrating-to-newer-image-part-2.html' title='Wanted: Migrating To a Newer Image Part 2'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_pouiJLhtxFE/RhsMUm0FQ3I/AAAAAAAAALI/0SQ57YZrj20/s72-c/migrated-wanted-items.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-3340590768530189267</id><published>2007-04-01T07:09:00.000-07:00</published><updated>2007-04-01T23:18:12.728-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='smalltalk'/><category scheme='http://www.blogger.com/atom/ns#' term='wanted'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='seaside'/><category scheme='http://www.blogger.com/atom/ns#' term='squeak'/><title type='text'>Wanted: Migrating to a Newer Image</title><content type='html'>Damien Cassou recently &lt;a href="http://lists.squeakfoundation.org/pipermail/seaside/2007-March/011390.html"&gt;announced&lt;/a&gt; the release of a new Squeak-Web image which can be found &lt;a href="http://damien.cassou.free.fr/squeak-web/squeak-web-95-2.zip"&gt;here&lt;/a&gt;.  This post will focus on migrating the Wanted application from the image we've been using to his new one.  Before this post, I was keeping my squeak stuff spread out all over the place, and since I'm migrating to a newer image I thought I'd get a little more organized and detail my setup here.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;A More Disciplined Directory Structure&lt;/span&gt;&lt;br /&gt;Instead of scattering everything all over the place, I created a proj directory with the following directory tree:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RhB2TmiF9TI/AAAAAAAAAJ4/QO7J60F5d74/s1600-h/directory_layout.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RhB2TmiF9TI/AAAAAAAAAJ4/QO7J60F5d74/s400/directory_layout.jpg" alt="" id="BLOGGER_PHOTO_ID_5048665261415462194" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;proj/squeak&lt;/span&gt; is where all Squeak-related stuff goes&lt;/li&gt;&lt;li&gt;each directory in &lt;span style="font-weight: bold;"&gt;apps&lt;/span&gt; contains a combination of an image plus a vm&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;doc&lt;/span&gt; is for all Squeak/Smalltalk related pdfs like &lt;a href="http://www.iam.unibe.ch/%7Educasse/FreeBooks.html"&gt;free books&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;images&lt;/span&gt; and &lt;span style="font-weight: bold;"&gt;vms&lt;/span&gt; contain different versions of squeak images and vms&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;repository&lt;/span&gt; is for the local Monticello repository, which we'll get to in a bit&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;tmp&lt;/span&gt; should be obvious&lt;/li&gt;&lt;/ul&gt;The "apps/wanted" directory is a new directory created for the new squeak-web image.  My old image that I've been using for the Wanted tutorial is sitting in a different location.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Setting Up a Local Monticello Repository&lt;/span&gt;&lt;br /&gt;We're going to use Monticello to handle the versioning of Wanted  (there's some overview info on Monticello &lt;a href="http://www.wiresong.ca/Monticello"&gt;here&lt;/a&gt;).   Fire up the image you've been using for the Wanted tutorial, then click on the World and select open... -&gt; Monticello Browser.  You should see this window:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RhB9jmiF9UI/AAAAAAAAAKA/3NKv4AsIjCY/s1600-h/monticello_browser.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RhB9jmiF9UI/AAAAAAAAAKA/3NKv4AsIjCY/s400/monticello_browser.jpg" alt="" id="BLOGGER_PHOTO_ID_5048673232874763586" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Click the "&lt;span style="font-weight: bold;"&gt;+Package&lt;/span&gt;" button.  Enter "Wanted-Tutorial" (or the name of the package containing the Wanted tutorial objects in your image) in the "Name of package" dialog:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_pouiJLhtxFE/RhB-iGiF9VI/AAAAAAAAAKI/BZHiptNgKSs/s1600-h/name_of_package.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_pouiJLhtxFE/RhB-iGiF9VI/AAAAAAAAAKI/BZHiptNgKSs/s400/name_of_package.jpg" alt="" id="BLOGGER_PHOTO_ID_5048674306616587602" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;and click the Accept button.  Then add a local repository by clicking the "&lt;span style="font-weight: bold;"&gt;+Repository&lt;/span&gt;" button, and choosing "&lt;span style="font-weight: bold;"&gt;directory&lt;/span&gt;" from the "Repository type" dialog:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RhCAP2iF9WI/AAAAAAAAAKQ/Lk3X6O0ESPQ/s1600-h/repository_type.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RhCAP2iF9WI/AAAAAAAAAKQ/Lk3X6O0ESPQ/s400/repository_type.jpg" alt="" id="BLOGGER_PHOTO_ID_5048676192107230562" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The "Repository folder" dialog will then display:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_pouiJLhtxFE/RhCA6GiF9XI/AAAAAAAAAKY/KJmsGdwCIy0/s1600-h/repository_folder.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_pouiJLhtxFE/RhCA6GiF9XI/AAAAAAAAAKY/KJmsGdwCIy0/s400/repository_folder.jpg" alt="" id="BLOGGER_PHOTO_ID_5048676917956703602" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Navigate to the folder you want to store the local Monticello repository in and hit the "ok" button.  You should now be looking at the Monticello browser with the Wanted-Tutorial package selected in the left pane and the repository folder selected in the right pane.  Click the "&lt;span style="font-weight: bold;"&gt;Save&lt;/span&gt;" button in the row of buttons in the Monticello browser.  Enter you initials in the dialog that pops up and Accept.  The "Edit Version Name and Message" dialog will pop up:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RhCM7miF9YI/AAAAAAAAAKg/-bz2RdzL5PM/s1600-h/edit_version_name_and_message.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RhCM7miF9YI/AAAAAAAAAKg/-bz2RdzL5PM/s400/edit_version_name_and_message.jpg" alt="" id="BLOGGER_PHOTO_ID_5048690137866040706" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Enter a comment and Accept.  If all went well you should end up with a window like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/RhCOHWiF9ZI/AAAAAAAAAKo/T89rNJBzJyU/s1600-h/version.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/RhCOHWiF9ZI/AAAAAAAAAKo/T89rNJBzJyU/s400/version.jpg" alt="" id="BLOGGER_PHOTO_ID_5048691439241131410" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;We now have a working versioning repository for our Wanted tutorial code.  As you'll see, migrating it to newer images will be a snap.  At this point, close the old image but keep it around--we'll need it later.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Setting Up the New Image&lt;br /&gt;&lt;/span&gt;After downloading Damien's &lt;a href="http://damien.cassou.free.fr/squeak-web/squeak-web-95-2.zip"&gt;new image&lt;/a&gt; and unpacking it into the "images/squeak-web-95-2" directory (see the directory layout above), I copied the&lt;br /&gt;&lt;ol&gt;&lt;li&gt;squeak-web-95-2.image&lt;/li&gt;&lt;li&gt;squeak-web-95-2.changes &lt;/li&gt;&lt;/ol&gt;files into the "apps/wanted" directory.  I then copied the&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Squeak3.8.xxx&lt;/li&gt;&lt;li&gt;SqueakV39.sources &lt;/li&gt;&lt;/ol&gt;files from the  current vm in the "vms" directory into the "apps/wanted" directory.  I also copied the&lt;br /&gt;&lt;ol&gt;&lt;li&gt;wanted.css &lt;/li&gt;&lt;/ol&gt;file from the old image directory into "apps/wanted".   Once all 5 files are copied, in "apps/wanted", drag the squeak-web-95-2.image file over the Squeak executable to open Squeak with the new image.  Collapse the Script Manager and Preference Browser windows to make some room.&lt;br /&gt;&lt;br /&gt;Open a Monticello Browser and click the "&lt;span style="font-weight: bold;"&gt;+Repository&lt;/span&gt;" button.  Choose "directory" from the "Repository type" and select the directory you created above.  With the directory repository highlighted in the right pane in the Monticello Browser, click the "&lt;span style="font-weight: bold;"&gt;Open&lt;/span&gt;" button.  You should see a window like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RhCUqmiF9aI/AAAAAAAAAKw/W0Nj5XbAz9s/s1600-h/wanted_repository.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RhCUqmiF9aI/AAAAAAAAAKw/W0Nj5XbAz9s/s400/wanted_repository.jpg" alt="" id="BLOGGER_PHOTO_ID_5048698641901286818" border="0" /&gt;&lt;/a&gt;Highlight the "Wanted-Tutorial...1.mcz" file in the right pane (the version information will appear in the bottom pane when you do so), then click the "&lt;span style="font-weight: bold;"&gt;Load&lt;/span&gt;" button.  After the progress dialogs go away, open a new Refactoring Browser and scroll to the bottom of the Packages list:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RhCWQmiF9bI/AAAAAAAAAK4/chNlXe89KoY/s1600-h/wanted_from_repo_rb.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RhCWQmiF9bI/AAAAAAAAAK4/chNlXe89KoY/s400/wanted_from_repo_rb.jpg" alt="" id="BLOGGER_PHOTO_ID_5048700394247943602" border="0" /&gt;&lt;/a&gt;Well looky there!  I'll wait for you if you want to get up and do a happy dance.&lt;br /&gt;&lt;br /&gt;In this new image, Seaside doesn't know about our Wanted app yet because we haven't registered it.  If you open a web browser to http://localhost:8080/seaside/wanted you will get a listing of the registered Seaside apps, and Wanted isn't one of them.  Let's fix that.  Open a Shout Workspace, enter the following, highlight and "do it":&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;WantedList registerAsApplication: 'wanted'&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Now go to http://localhost:8080/seaside/wanted.  Notice that our app shows up, but all of our WantedItems are missing!  Stay tuned--we'll fix that in the next post.  For now, save the new image.  I'm just going to use the default "squeak-web-95-2.image" name instead of naming it "wanted.image" since it's already in the "wanted" directory.  If you wanted you could name it "why-is-sanjaya-not-voted-off-yet.image" and the world would still turn and make bad decisions and watch bad TV.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-3340590768530189267?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/3340590768530189267/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=3340590768530189267' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/3340590768530189267'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/3340590768530189267'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2007/04/wanted-migrating-to-newer-image.html' title='Wanted: Migrating to a Newer Image'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_pouiJLhtxFE/RhB2TmiF9TI/AAAAAAAAAJ4/QO7J60F5d74/s72-c/directory_layout.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-4053624794626771656</id><published>2007-02-21T02:06:00.000-08:00</published><updated>2007-02-21T00:08:55.196-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='want'/><category scheme='http://www.blogger.com/atom/ns#' term='smalltalk'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='seaside'/><category scheme='http://www.blogger.com/atom/ns#' term='squeak'/><title type='text'>Wanted:  Beautification is Easy</title><content type='html'>This post will focus on adding style to Wanted with some very simple changes.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 1: Enable File Serving in Commanche&lt;/span&gt;&lt;br /&gt;If you've been following the Wanted tutorials and are using the squeak-web image, you'll have to restart the Commanche server and enable file serving.  Enter the following in a new workspace, highlight it, and "do it":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;| ma seaside |&lt;br /&gt;WAKomEncoded39  stop.&lt;br /&gt;seaside := WAKomEncoded39  default.&lt;br /&gt;ma := ModuleAssembly core.&lt;br /&gt;ma serverRoot: FileDirectory default fullName.&lt;br /&gt;ma alias: '/seaside' to: [ma addPlug: [:request | seaside process: request]].&lt;br /&gt;ma documentRoot: FileDirectory default fullName.&lt;br /&gt;ma directoryIndex: 'index.html index.htm'.&lt;br /&gt;ma serveFiles.&lt;br /&gt;(HttpService startOn: 8080 named: 'httpd') plug: ma rootModule&lt;br /&gt;&lt;/pre&gt;See David Shaffer's  Seaside &lt;a href="http://www.shaffer-consulting.com/david/Seaside/GettingSoftware/index.html"&gt;tutorial&lt;/a&gt; for more details.&lt;br /&gt;&lt;br /&gt;This will stop Commanche and restart it with support for serving files and Seaside applications.  Note that it sets the document root to &lt;span style="font-weight: bold;"&gt;FileDirectory default&lt;/span&gt;.  If you are starting Squeak by dragging the image file onto the Squeak executable, this should be the directory of the Squeak executable.  You can find out exactly what it is by entering "&lt;span style="font-weight: bold;"&gt;FileDirectory default fullName&lt;/span&gt;" in a workspace, highlighting it, then doing a "Print It".&lt;br /&gt;&lt;br /&gt;Test it by flipping to your browser and going to http://localhost:8080.  You should see a directory listing of the default directory.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 2: Create a CSS File&lt;/span&gt;&lt;br /&gt;Create a new text file called "&lt;span style="font-weight: bold;"&gt;wanted.css&lt;/span&gt;" in the default directory that contains the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;body {&lt;br /&gt;  margin:50px 0px; padding:0px;&lt;br /&gt;  text-align:center;&lt;br /&gt;  font: 12px arial, sans;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;table {&lt;br /&gt;  margin-left: auto;&lt;br /&gt;  margin-right: auto;&lt;br /&gt;  border: 1px solid #848370;&lt;br /&gt;  border-collapse: collapse;&lt;br /&gt;  margin-bottom: 20px;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;th {&lt;br /&gt;  background-color: #b4bfcc;&lt;br /&gt;  padding: 4px;&lt;br /&gt;  font-size: 14px;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;td {&lt;br /&gt;  padding: 4px;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;a {&lt;br /&gt;  color: #103b61;&lt;br /&gt;  text-decoration: none;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;a:hover {&lt;br /&gt;  text-decoration: underline;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;form {&lt;br /&gt;  margin-left: auto;&lt;br /&gt;  margin-right: auto;&lt;br /&gt;  width: 400px;&lt;br /&gt;  text-align: left;&lt;br /&gt;  background-color: #dee3e4;&lt;br /&gt;  border: 1px #ccc solid;&lt;br /&gt;  padding: 10px;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;form input, textarea {&lt;br /&gt;  border: 1px solid #b4bfcc;&lt;br /&gt;  padding: 2px;&lt;br /&gt;  width: 98%;&lt;br /&gt;  margin-bottom: 10px;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;form textarea {&lt;br /&gt;  height: 150px;&lt;br /&gt;  font: 12px arial, sans;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;form input.submit {&lt;br /&gt;  margin: 0;&lt;br /&gt;  width: 50%;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 3: Wire the CSS File Up to the Components&lt;/span&gt;&lt;br /&gt;To link to the css file, we'll implement updateRoot in WTComponent:&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;updateRoot: anHtmlRoot&lt;br /&gt;   super updateRoot: anHtmlRoot.&lt;br /&gt;   anHtmlRoot linkToStyle: '/wanted.css'&lt;br /&gt;&lt;/pre&gt;Since both of our view components (WantedList and WantedEditor) inherit from WTComponent, they will both contain the link to the css file.&lt;br /&gt;&lt;br /&gt;Flip back to the browser and refresh:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/Rdv4ZnOyugI/AAAAAAAAAIE/fExMo1aDbWw/s1600-h/styled_todo_list.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/Rdv4ZnOyugI/AAAAAAAAAIE/fExMo1aDbWw/s400/styled_todo_list.jpg" alt="" id="BLOGGER_PHOTO_ID_5033890127428106754" border="0" /&gt;&lt;/a&gt;Much better, huh?  The application looks completely different and we didn't have to touch any rendering code or a single line of html.   I'll be the first to admit that my design skills aren't that inspiring--if someone comes up with something better they'd like to share, I'll post it here.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 4: Tweak the WATableReport Row Colors&lt;/span&gt;&lt;br /&gt;If you create more than 3 wanted items, you will see that yellowish background appear.  That is WATableReport doing some styling for us.  If we wanted to change it so that every other line was shaded with a light gray color, we would edit WantedList&gt;&gt;intialize  by finding where the wantedReport instance is being assigned and changing it to the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;wantedReport := WATableReport new rows: rows;&lt;br /&gt;  columns: columns;&lt;br /&gt;  yourself.&lt;br /&gt;wantedReport rowPeriod: 1.&lt;br /&gt;wantedReport rowColors: {'#ffffff'. '#eee'}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;See David Shaffer's WATableReport &lt;a href="http://www.shaffer-consulting.com/david/Seaside/WATableReport/index.html"&gt;tutorial&lt;/a&gt; for more about styling the table report object.   After clicking the "New Session" link, you should see the following:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/Rdv403OyuhI/AAAAAAAAAIM/mzJr8mZGSeg/s1600-h/styled_todo_list_2.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/Rdv403OyuhI/AAAAAAAAAIM/mzJr8mZGSeg/s400/styled_todo_list_2.jpg" alt="" id="BLOGGER_PHOTO_ID_5033890595579542034" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Conclusion&lt;/span&gt;&lt;br /&gt;Making Wanted look decent was pretty simple using an external css file.  Since Wanted really only consists of one table component and one form component, I was able to get away with not using divs, classes or ids.  In an application with more depth these would be required.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-4053624794626771656?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/4053624794626771656/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=4053624794626771656' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/4053624794626771656'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/4053624794626771656'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2007/02/wanted-beautification-is-easy.html' title='Wanted:  Beautification is Easy'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_pouiJLhtxFE/Rdv4ZnOyugI/AAAAAAAAAIE/fExMo1aDbWw/s72-c/styled_todo_list.jpg' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-2673264882549021754</id><published>2007-02-17T02:12:00.000-08:00</published><updated>2007-02-20T23:26:43.438-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='want'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='seaside'/><category scheme='http://www.blogger.com/atom/ns#' term='squeak'/><title type='text'>Wanted -- A Seaside Tutorial: Part 5</title><content type='html'>In the &lt;a href="http://inchingforward.blogspot.com/2007/01/wanted-seaside-tutorial-part-4.html"&gt;last post&lt;/a&gt; we created a constructor for the WantedItem class that made it easier to add WantedItems to the WantedDatabase in a workspace.     In this entry we'll create a WantedEditor component that will allow us to create and edit WantedItems using the web app.  Grab your beverage of choice and fire up Squeak with the &lt;span style="font-weight: bold;"&gt;want-tutorial.image&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 1: Refactor the WantedItem Class&lt;/span&gt;&lt;br /&gt;One of the things that the WantedItem &lt;span style="font-weight: bold;"&gt;title:notes:&lt;/span&gt; constructor does is initialize the enteredDate instance variable.  If we create a WantedItem using the &lt;span style="font-weight: bold;"&gt;new&lt;/span&gt; message, the enteredDate will not get set.  Let's change WantedItem to guarantee that any new WantedItem instance automatically gets an enteredDate.  We'll do that by adding an &lt;span style="font-weight: bold;"&gt;initialize&lt;/span&gt; method to WantedItem:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;initialize&lt;br /&gt;  super initialize.&lt;br /&gt;  enteredDate := DateAndTime now&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now that the initialize method handles initializing the enteredDate, we can take it out of the &lt;span style="font-weight: bold;"&gt;title:notes:&lt;/span&gt; constructor on the class side:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;title: aTitle notes: someNotes&lt;br /&gt;  ^ self new title: aTitle;&lt;br /&gt;             notes: someNotes;&lt;br /&gt;             yourself&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;Step 2: Create the WantedEditor Class&lt;/span&gt;&lt;br /&gt;Create a new class called &lt;span style="font-weight: bold;"&gt;WantedEditor&lt;/span&gt; that subclasses WTComponent and add a &lt;span style="font-weight: bold;"&gt;wantedItem&lt;/span&gt; instance variable:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/RcwKyHOyuaI/AAAAAAAAAG4/fJzRcug6fDY/s1600-h/Wanted-Editor.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/RcwKyHOyuaI/AAAAAAAAAG4/fJzRcug6fDY/s400/Wanted-Editor.jpg" alt="" id="BLOGGER_PHOTO_ID_5029406739916831138" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Highlight the wantedItem instance variable, then right-click or option-click it and choose "selection...", then "create accessors".  Change the wantedItem getter to the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;wantedItem&lt;br /&gt;  ^ wantedItem&lt;br /&gt;      ifNil: [wantedItem := WantedItem new]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The purpose of WantedEditor is to either create a new WantedItem instance, or edit an exising one.  Having a getter that lazy initializes wantedItem to a new WantedItem instance if it doesn't already exist allows for creation.  Having a setter that allows us to set wantedItem to an existing instance allows for editing.  We'll get to this in a bit.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Call And Answer&lt;/span&gt;&lt;br /&gt;Before we continue finishing the WantedEditor component, we need to learn how WantedList will be able to transfer control from itself to an instance of WantedEditor.    Seaside provides the   &lt;a href="http://seaside.st/Documentation/CallandAnswer/"&gt;call and answer&lt;/a&gt; messages for this. We'll have WantedList send itself the &lt;span style="font-weight: bold;"&gt;call:&lt;/span&gt; message with an instance of  a WantedEditor as the argument.  Control of the application will then switch to the  WantedEditor instance which will display its interface and wait for user action.  When WantedEditor is done with its work, it can revert control back to WantedList by sending itself the &lt;span style="font-weight: bold;"&gt;answer:&lt;/span&gt; message.    The answer: message accepts an argument that is returned back to the site of the &lt;span style="font-weight: bold;"&gt;call:&lt;/span&gt;.  This way a WantedList can ask a WantedEditor to create a WantedItem and return it back to the WantedList with the &lt;span style="font-weight: bold;"&gt;answer:&lt;/span&gt; message:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RdKu-3OyufI/AAAAAAAAAH4/9irNbeQmdac/s1600-h/call_and_answer.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RdKu-3OyufI/AAAAAAAAAH4/9irNbeQmdac/s400/call_and_answer.jpg" alt="" id="BLOGGER_PHOTO_ID_5031276128727382514" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;We're going to work a little backwards by implementing answer: before call: since we're already in WantedEditor.  Let's start with creating WantedEditor's renderContentOn: method.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;renderContentOn: html&lt;br /&gt;  html&lt;br /&gt;     form: [html text: 'Title: '.&lt;br /&gt;        html textInput on: #title of: self wantedItem.&lt;br /&gt;        html break.&lt;br /&gt;        html text: 'Notes: '.&lt;br /&gt;        html textArea on: #notes of: self wantedItem.&lt;br /&gt;        html break.&lt;br /&gt;        html submitButton on: #save of: self.&lt;br /&gt;        html cancelButton on: #cancel of: self]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This will produce the following form:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/Rc_rn3OyudI/AAAAAAAAAHY/J7_j9K7GHho/s1600-h/wanted_editor_for_call_and_answer.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/Rc_rn3OyudI/AAAAAAAAAHY/J7_j9K7GHho/s400/wanted_editor_for_call_and_answer.jpg" alt="" id="BLOGGER_PHOTO_ID_5030498378869553618" border="0" /&gt;&lt;/a&gt;The WantedEditor renders an html form that contains text form elements for title and notes  and two submit buttons for saving and cancelling.  Each html element is created by asking the html canvas for the control it wants, then sending the control the &lt;span style="font-weight: bold;"&gt;on:of:&lt;/span&gt; message.&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-weight: bold;"&gt;on:of:&lt;/span&gt; messages wire each form element to a selector (the &lt;span style="font-weight: bold;"&gt;on&lt;/span&gt; argument) of an object (the &lt;span style="font-weight: bold;"&gt;of &lt;/span&gt;argument) when the form is submitted.  For the buttons, this means sending the WantedEditor instance the &lt;span style="font-weight: bold;"&gt;save&lt;/span&gt; or &lt;span style="font-weight: bold;"&gt;cancel&lt;/span&gt; message.  For the text elements, this means sending the value in the form element to the selector (&lt;span style="font-weight: bold;"&gt;title&lt;/span&gt; and &lt;span style="font-weight: bold;"&gt;notes&lt;/span&gt;) of  "&lt;span style="font-weight: bold;"&gt;self wantedItem&lt;/span&gt;".  Remember, if a wantedItem hasn't been set, a new instance of WantedItem will be created in the first call to "self wantedItem".&lt;br /&gt;&lt;br /&gt;We need to create the two methods that correspond to the selectors used for the buttons:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;cancel&lt;br /&gt;  self answer: nil&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;save&lt;br /&gt;  self answer: wantedItem&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notice that both methods end by sending the WantedEditor instance (self) the &lt;span style="font-weight: bold;"&gt;answer:&lt;/span&gt; message.  Both return an object back to the calling site:   cancel returns a nil object since nothing has changed or been created, and save returns the wantedItem we were editing. &lt;br /&gt;&lt;br /&gt;Now we need to wire up WantedList to WantedEditor.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 3: Call WantedEditor to Add WantedItems&lt;/span&gt;&lt;br /&gt;Back in WantedList, add a link to add new wanted items in renderContentOn:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;renderContentOn: html&lt;br /&gt;  html render: wantedReport.&lt;br /&gt;  html anchor on: #add of: self&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;When a user clicks the link, the &lt;span style="font-weight: bold;"&gt;add&lt;/span&gt; message will be sent to self (the WantedList instance).  Let's go ahead and create &lt;span style="font-weight:bold;"&gt;add&lt;/span&gt;:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;add&lt;br /&gt;  | wantedItem |&lt;br /&gt;  wantedItem := self call: WantedEditor new.&lt;br /&gt;  wantedItem&lt;br /&gt;     ifNotNil: [WantedDatabase wantedItems add: wantedItem]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We start by creating a wantedItem temporary variable.  We then send the WantedList instance the  &lt;span style="font-weight: bold;"&gt;call:&lt;/span&gt; message with a new instance of WantedEditor.  The WantedEditor will assume control of the web application and display the user the form that allows them to set the wantedItem instance attributes.  The first call to "&lt;span style="font-weight:bold;"&gt;self wantedItem&lt;/span&gt;" will lazy initialize a new wantedItem instance (which will now have the enteredDate set). If the user clicks the Cancel button a nil object will be returned (&lt;span style="font-weight: bold;"&gt;answer&lt;/span&gt;ed).  If the user clicks the Save button, the wantedItem instance they were editing will be returned (&lt;span style="font-weight: bold;"&gt;answer&lt;/span&gt;ed).  The result of either cancel or save is assigned to our wantedItem temporary variable.  If it is not nil (they pressed the Save button), we add it to the WantedDatabase wantedItems collection.&lt;br /&gt;&lt;br /&gt;Let's add a new WantedItem to make sure it works.  Open a web browser to http://localhost:8080/seaside/wanted.  Click the "Add" link, fill in the form, then click the "Save" button.  You should be returned to the wanted list which now contains the new wanted item at the bottom.  Woohoo!&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 4:  Call WantedEditor to Edit WantedItems&lt;br /&gt;&lt;/span&gt;To let the user select a wanted item to edit, we'll turn the Titles of the wanted items into links that when clicked call a WantedEditor instance:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/Rc_vjXOyueI/AAAAAAAAAHg/betMHQe_Tf0/s1600-h/links.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/Rc_vjXOyueI/AAAAAAAAAHg/betMHQe_Tf0/s400/links.jpg" alt="" id="BLOGGER_PHOTO_ID_5030502699606653410" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;To turn the Title text into links, we'll need to revisit the initialize method of WantedList where we created the WATableReport.  Change the line where the title WAReportColumn is being created by adding a clickBlock and returning yourself:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;initialize&lt;br /&gt;  | rows columns |&lt;br /&gt;  super initialize.&lt;br /&gt;  columns := OrderedCollection new &lt;br /&gt;    add: ((WAReportColumn selector: #title title: 'Title')&lt;br /&gt;               clickBlock: [:item | self edit: item];&lt;br /&gt;           yourself);&lt;br /&gt;&lt;br /&gt;    add: (WAReportColumn selector: #enteredDate&lt;br /&gt;                         title: 'Entered Date');&lt;br /&gt;&lt;br /&gt;    add: (WAReportColumn&lt;br /&gt;            renderBlock: [:item | item isPurchasable asString]&lt;br /&gt;            title: 'Can Buy');&lt;br /&gt;          yourself.&lt;br /&gt;  rows := WantedDatabase wantedItems.&lt;br /&gt;  wantedReport := WATableReport new rows: rows;&lt;br /&gt;                columns: columns;&lt;br /&gt;                yourself&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The clickBlock will receive the WantedItem that was clicked on (we name the block argument "item").  The block sends the &lt;span style="font-weight:bold;"&gt;edit&lt;/span&gt; message with the item to self (the WantedList instance).  We need to create the &lt;span style="font-weight:bold;"&gt;edit&lt;/span&gt; method:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;edit: aWantedItem&lt;br /&gt;  self call: (WantedEditor new wantedItem: aWantedItem;&lt;br /&gt;                 yourself)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here we are &lt;span style="font-weight: bold;"&gt;call&lt;/span&gt;ing a new WantedEditor instance again, but this time we are setting the wantedItem instance instead of having a new one lazy initialized.  When WantedEditor's renderContentOn: method is called, it will use the values from the wantedItem  object we passed to it to fill in the form values.&lt;br /&gt;&lt;br /&gt;Go back to the browser and click on the "&lt;span style="font-weight:bold;"&gt;New Session&lt;/span&gt;" link at the bottom of the page.  We have to do this since our WantedList instance has already been initialized--clicking the "New Session" link will force a new WantedList instance to intialize, creating a new wantedReport that has the clickBlock changes we made.  Click on one of the title links and you should get the wanted editor page.  If you edit the title and click Save you will be taken back to the wanted list page and the title for that wanted item should have changed in the wanted list.&lt;br /&gt;&lt;br /&gt;We're almost there:  the last piece of functionality is to remove WantedItems.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 5:  Add Remove Functionality &lt;/span&gt;&lt;br /&gt;To add remove functionality, we're going to add a "Remove" link for each wanted item row.  To do this, we'll have to add another column to our wantedReport in the WantedList&gt;&gt;initialize method:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;initialize&lt;br /&gt;  | rows columns |&lt;br /&gt;  super initialize.&lt;br /&gt;  columns := OrderedCollection new &lt;br /&gt;    add: ((WAReportColumn selector: #title title: 'Title')&lt;br /&gt;               clickBlock: [:item | self edit: item];&lt;br /&gt;           yourself);&lt;br /&gt;&lt;br /&gt;    add: (WAReportColumn selector: #enteredDate&lt;br /&gt;                        title: 'Entered Date');&lt;br /&gt;&lt;br /&gt;    add: (WAReportColumn&lt;br /&gt;            renderBlock: [:item | item isPurchasable asString]&lt;br /&gt;            title: 'Can Buy');&lt;br /&gt;    &lt;br /&gt;    add: (WAReportColumn new title: 'Remove';&lt;br /&gt;            valueBlock: [:item | 'Remove'];&lt;br /&gt;            clickBlock: [:item | self remove: item];&lt;br /&gt;            yourself);&lt;br /&gt;      yourself.&lt;br /&gt;  &lt;br /&gt;  rows := WantedDatabase wantedItems.&lt;br /&gt;  wantedReport := WATableReport new rows: rows;&lt;br /&gt;                     columns: columns;&lt;br /&gt;                     yourself&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We set the title to "Remove", set the value to render (the link text) to "Remove", and the action to take when clicked to sending self the &lt;span style="font-weight:bold;"&gt;remove&lt;/span&gt; message.&lt;br /&gt;&lt;br /&gt;Let's add the &lt;span style="font-weight:bold;"&gt;remove:&lt;/span&gt; method:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;remove: aWantedItem&lt;br /&gt;  WantedDatabase wantedItems remove: aWantedItem&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This simply removes the WantedItem instance from the WantedDatabase wantedItems collection.&lt;br /&gt;&lt;br /&gt;Flip back to the browser and click the "New Session" link. You should see a "Remove" link next to each wanted item row.  Go ahead and click on of them.  The item should be removed from the WantedDatabase, then the page should re-render with the item removed from the table.&lt;br /&gt;&lt;br /&gt;And that's it!  Go forth and buy less!&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Conclusion&lt;/span&gt;&lt;br /&gt;At a basic level, this tutorial is complete.  We have a first cut at a working application:  we can Create, Report, Update and Delete  WantedItems.   The posts so far have shown how to build a simple application using the basic building blocks of Seaside.  The app is by no means finished: it's ugly, it's not using the latest and greatest ideas and tools available for seaside (things like Magritte and  Scriptaculous), there aren't any unit tests, we're using the image to store our items , we haven't provided a way to import or export the data, etc.  Future posts will focus on some of these concerns, but may not use the Wanted material.  Stay tuned!&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Update&lt;/span&gt;&lt;br /&gt;There was a bug in WantedEditor where pressing the Cancel button would persist any changes made to the title or notes fields.  To change this, the Cancel "submitButton" has been changed to a "cancelButton".  If you are using the same squeak-web image version downloaded in the first part of this tutorial or you do not have  WARenderCanvas&gt;&gt;cancelButton, you will need to upgrade the Seaside version.  Here's how to do it step by step:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;Left-click on the world and choose "open..." then "Monticello Browser".&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click on the "Seaside2" entry in the left list pane.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click on the "http://www.squeaksource.com/Seaside" url entry on the right list pane so that it is highlighted.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click the Open button above the right list pane.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Find and click on the entry marked "Seaside2.7a1-mb.142.mcz" in the right list pane so that it is highlighted.  This was the commit that added the cancelButton functionality.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Click the Load button above the right list pane.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Close the Monticello windows and change the Cancel submitButton to a cancelButton in WantedEditor&gt;&gt;renderContentOn:.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;I apologize for not finding this earlier.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-2673264882549021754?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/2673264882549021754/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=2673264882549021754' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/2673264882549021754'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/2673264882549021754'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2007/02/wanted-seaside-tutorial-part-5.html' title='Wanted -- A Seaside Tutorial: Part 5'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_pouiJLhtxFE/RcwKyHOyuaI/AAAAAAAAAG4/fJzRcug6fDY/s72-c/Wanted-Editor.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-3259857575567274600</id><published>2007-01-27T11:15:00.000-08:00</published><updated>2007-01-29T08:07:07.107-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='want'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='seaside'/><category scheme='http://www.blogger.com/atom/ns#' term='squeak'/><title type='text'>Wanted -- A Seaside Tutorial: Part 4</title><content type='html'>In the &lt;a href="http://inchingforward.blogspot.com/2007/01/wanted-seaside-tutorial-part-3.html"&gt;last post&lt;/a&gt; we created our WantedItem model object and built a preliminary main page that lists the WantedItems stored in our WantedDatabase.  In this entry, we'll refactor the WantedItem class a little bit, create an html table to display the wanted items, and make it look better.  As usual, for those playing at home, open Squeak with the &lt;span style="font-weight: bold;"&gt;want-tutorial.image&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;Step 1: Refactor the WantedItem Class&lt;/span&gt;&lt;br /&gt;At this point, our WantedItem  is just a data holder.  As Ramon Leon &lt;a href="http://onsmalltalk.com/programming/smalltalk/objects-classes-and-constructors-smalltalk-style/"&gt;points out&lt;/a&gt;, having a constructor encapsulates how one should instantiate an object.  Something else to notice is that the WantedItem class has an enteredDate, which is the date the WantedItem was created.  We don't need to have the user assign this value--we know when a WantedItem object is created, so we can assign the value ourselves.&lt;br /&gt;&lt;br /&gt;Select the WantedItem class in the Refactoring Browser, and click on the class button.  Then select the "-- all --" message category so that the method creation template displays in the code pane.  Enter the following code:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;title: aTitle notes: someNotes&lt;br /&gt;  ^ self new title: aTitle;&lt;br /&gt;       notes: someNotes;&lt;br /&gt;       enteredDate: DateAndTime now;&lt;br /&gt;       yourself&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and save. We create a new WantedItem by passing it the new message, then set the fields on the new instance using the arguments the  class message receives, then set the current date and time by passing the &lt;span style="font-weight: bold;"&gt;now&lt;/span&gt; message to the DateAndTime class.   Notice that our new method went into a category named "&lt;span style="font-weight: bold;"&gt;as yet unclassified&lt;/span&gt;".   Right-click or option-click the &lt;span style="font-weight: bold;"&gt;title:notes:&lt;/span&gt; selector in the message pane, select "&lt;span style="font-weight: bold;"&gt;more...&lt;/span&gt;", then "&lt;span style="font-weight: bold;"&gt;change category...&lt;/span&gt;".  A large list of possible categories pops up.  Choose "&lt;span style="font-weight: bold;"&gt;instance creation&lt;/span&gt;".&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RbZb4TPxsBI/AAAAAAAAAFE/3srmF1XvIvo/s1600-h/title-notes-change-category-menus.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RbZb4TPxsBI/AAAAAAAAAFE/3srmF1XvIvo/s320/title-notes-change-category-menus.jpg" alt="" id="BLOGGER_PHOTO_ID_5023303457175613458" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Now that we have a constructor that will create a new WantedItem with all the required fields, it's easier to create WantedItems and users of the class will have more confidence that they are creating an instance correctly.&lt;br /&gt;&lt;br /&gt;One of the goals of this project is to figure out what items we really want based on how long we hold a desire for them.  A WantedItem object instance already knows the date on which it was entered.    If we provide the WantedItem class a with a time threshold, the instances can calculate whether or not they have passed the time test of being a true wanted item.&lt;br /&gt;&lt;br /&gt;First we need to identify what the time threshold is.  To do this, create a new instance side method on WantedItem called "&lt;span style="font-weight: bold;"&gt;daysToWait&lt;/span&gt;".  Make sure the instance button is selected, then click on the "-- all --" message category and replace the code pane with the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;daysToWait&lt;br /&gt;  ^ 14&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The daysToWait method returns the time threshold in days, which we have set to two weeks.  The daysToWait method was put into the "as yet unclassified" category.  Change the category to "&lt;span style="font-weight: bold;"&gt;defaults&lt;/span&gt;".&lt;br /&gt;&lt;br /&gt;We then create a method called isPurchasable:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;isPurchasable&lt;br /&gt;  ^ (DateAndTime now - enteredDate) days &gt;= self daysToWait&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The &lt;span style="font-weight: bold;"&gt;isPurchasable&lt;/span&gt; method takes the current DateAndTime and subtracts the enteredDate DateAndTime from it.    If you look at the "&lt;span style="font-weight: bold;"&gt;-&lt;/span&gt;" method in the DateAndTime class you will see that it returns a &lt;span style="font-weight: bold;"&gt;Duration&lt;/span&gt; object.  Duration conveniently has a "&lt;span style="font-weight: bold;"&gt;days&lt;/span&gt;" method, which we use to compare against our daysToWait (14).  If the item has been around the number of days to wait or longer, we can withdraw some money from the bank and get in trouble with the significant other.  You'll be fine--just point them to the isPurchasable method.&lt;br /&gt;&lt;br /&gt;Our WantedItem has grown a bit.  Instead of just holding data, it now provides an obvious way for users to create it and knows whether it is a purchasable item or not.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;Step 2: Create the  HTML Table &lt;/span&gt;&lt;br /&gt;On the main page of the Wanted app we want to display a list of wanted items: their titles, enteredDates and whether or not we can purchase them yet.  Since we'll be displaying tabular data, it makes sense to use an html table.  Seaside comes with a component that makes handling tables pretty easy:  &lt;span style="font-weight: bold;"&gt;WATableReport&lt;/span&gt;.    David Shaffer created a &lt;a href="http://www.shaffer-consulting.com/david/Seaside/WATableReport/index.html"&gt;good tutorial on WATableReport&lt;/a&gt; based on some emails sent to the &lt;a href="http://www.seaside.st/Community/MailingList/"&gt;Seaside Mailing List&lt;/a&gt; from Radoslav Hodnicak and Dan Winkler.  Basically you supply a WATableReport object with a collection of columns and a collection row data objects, and it will build the table for you.&lt;br /&gt;&lt;br /&gt;Click on the WantedList class and make sure the  instance button is selected.  Add an  instance variable named "wantedReport" and save:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/RbZcGzPxsCI/AAAAAAAAAFM/lrHXQd_LfmE/s1600-h/wanted-list-wantedReport-instance.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/RbZcGzPxsCI/AAAAAAAAAFM/lrHXQd_LfmE/s320/wanted-list-wantedReport-instance.jpg" alt="" id="BLOGGER_PHOTO_ID_5023303706283716642" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Then click on the "-- all --" message category and replace the code pane with the &lt;span style="font-weight: bold;"&gt;initialize&lt;/span&gt; method:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;initialize&lt;br /&gt;  | rows columns |&lt;br /&gt;  super initialize.&lt;br /&gt;  columns := OrderedCollection new&lt;br /&gt;  add: (WAReportColumn selector: #title &lt;br /&gt;                       title: 'Title');&lt;br /&gt;  add: (WAReportColumn selector: #enteredDate &lt;br /&gt;                       title: 'Entered Date');&lt;br /&gt;  add: (WAReportColumn selector: #isPurchasable &lt;br /&gt;                       title: 'Can Buy');&lt;br /&gt;  yourself.&lt;br /&gt;  rows := WantedDatabase wantedItems.&lt;br /&gt;  wantedReport := WATableReport new rows: rows;&lt;br /&gt;        columns: columns;&lt;br /&gt;        yourself&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and save. The first line is obviously the name of the initialize method.  The second line contains two temp variables that exist for the life of the method--we introduce them to make it easier to work with the objects we will be using.   Our WATableReport uses two collections to display data:  a collection of WAReportColumns and a collection of  row objects.  The third line makes sure ancestors get initialized correctly. To create the columns, on the fourth line we create a new OrderedCollection by sending the OrderedCollection class the "new" message.   Lines 5, 6, and 7 add  WAReportColumns to that collection.   Each WAReportColumn is constructed by sending the "&lt;span style="font-weight: bold;"&gt;selector:title:&lt;/span&gt;" class-side message.  For &lt;span style="font-weight: bold;"&gt;selector&lt;/span&gt;, we pass a symbol (see &lt;a href="http://inchingforward.blogspot.com/2006/10/grokking-grammar.html"&gt;this post&lt;/a&gt; for links to articles on Smalltalk syntax) representing the name of the message selector to call on each row data object.   For &lt;span style="font-weight: bold;"&gt;title&lt;/span&gt;, we pass in the column heading that we want to display at the top of the table.   Why is that "yourself" message there on line 8?   We couldn't put a period directly after the last WAReportColumn because it would be returned by the parentheses and assigned to the columns temp variable.  So we add a cascade operator after the last WAReportColumn, which will return the OrderedCollection, then send it the  "yourself" message which just returns itself (you can't have the cascade operator as the last token).  Line 9 assigns the WantedItems collection from our WantedDatabase to our rows temp variable.  Lines 10 and 11 construct the WATableReport object using the columns and rows temp variables and assign it to the wantedReport instance variable.  Line 11 returns yourself so that the columns temp variable is not assigned to the wantedReport intance variable.&lt;br /&gt;&lt;br /&gt;Notice that when you saved the initialize method,  it was assigned to the intialization category.  When the WantedList component is created, the intialize method will automatically be called, building a WATableReport and assigning it to our wantedReport instance variable.&lt;br /&gt;&lt;br /&gt;Since WATableReport is doing the heavy lifting to create the table html, our WantedList&gt;&gt;renderContentOn: method gets much smaller.  Replace the renderContentOn: code with this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;renderContentOn: html&lt;br /&gt;    html render: wantedReport&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and save.   The renderContentOn: method just asks the wantedReport to render itself (build the html table).&lt;br /&gt;&lt;br /&gt;Since WATableReport is a descendent of WAComponent, we now have a subcomponent in our WantedList.  Seaside expects components to tell it when they have child components through the &lt;span style="font-weight: bold;"&gt;children&lt;/span&gt; method.  Add the following instance-side method to WantedList:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;children&lt;br /&gt;    ^ Array with: wantedReport&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The children method gets assigned to the "&lt;span style="font-weight: bold;"&gt;children&lt;/span&gt;" message category.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 3: Test the Table&lt;br /&gt;&lt;/span&gt;We are now finally at a point where we can switch back to the browser and see what we've got. Point your browser to http://localhost:8080/seaside/wanted and you should see something like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/Rbb5QTPxsEI/AAAAAAAAAFo/FgkkZR7a13A/s1600-h/wanted-table.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/Rbb5QTPxsEI/AAAAAAAAAFo/FgkkZR7a13A/s400/wanted-table.jpg" alt="" id="BLOGGER_PHOTO_ID_5023476492818034754" border="0" /&gt;&lt;/a&gt;So it's not the prettiest looking table in the world--we'll get to that in another post. Notice that the EnteredDate contains the date that we entered the item on, and that the "Can Buy" row value is "false". Let's test the isPurchasable method to see if it is working.&lt;br /&gt;&lt;br /&gt;Flip back to the Refactoring Browser and open WantedItem&gt;&gt;daysToWait. Change the return value to 1. Since the comparison in isPurchasable is a greater than or equal test, this should pass all WantedItems. Save, then flip back to the browser and refresh. You should see "true" now for the "Can Buy" row value:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/Rbt_aDPxsGI/AAAAAAAAAGA/SrCHxhi0TNA/s1600-h/wanted-list-showing-true.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/Rbt_aDPxsGI/AAAAAAAAAGA/SrCHxhi0TNA/s400/wanted-list-showing-true.jpg" alt="" id="BLOGGER_PHOTO_ID_5024749894786723938" border="0" /&gt;&lt;/a&gt;Make sure to change the daysToWait method back to returning 14.&lt;br /&gt;&lt;br /&gt;What happens when we try to display multiple items in the table?  What happens if we click on the column headers (they are links)?&lt;br /&gt;&lt;br /&gt;First, to test out how the table will look with multiple rows of data, let's create some more wanted items and add them to the WantedDatabase collection.  We can use the new WantedItem  constructor.  Open a workspace and enter the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;WantedDatabase wantedItems&lt;br /&gt;add: (WantedItem title: 'Big Screen TV' &lt;br /&gt;                 notes: 'Need this for the Wii');&lt;br /&gt;add: (WantedItem title: 'Extra Wii Controller' &lt;br /&gt;                 notes: 'For playing with a friend').&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then highlight and "do it".  Flip back to the browser and refresh.  You should now see three WantedItem rows.  Note that if you click on the headings, the table will sort by that column value.  WATableReport gives us that for free.  Wait a minute--did you try clicking on the "Can Buy" header column?  You should have seen this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RbhCbjPxsFI/AAAAAAAAAF0/g9rK3sdf6-8/s1600-h/message-not-understood-false.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RbhCbjPxsFI/AAAAAAAAAF0/g9rK3sdf6-8/s400/message-not-understood-false.jpg" alt="" id="BLOGGER_PHOTO_ID_5023838425417101394" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;In order to sort the rows when we click the "Can Buy" header column, it looks like the &lt;span style="font-weight:bold;"&gt;&lt;=&lt;/span&gt; comparison message is being used.  When  the isPurchasable method is called for each row data item, a Boolean value (False in this case) is returned.  The Boolean object does not have the &lt;span style="font-weight:bold;"&gt;&lt;=&lt;/span&gt; comparison message.   Flip back to the WantedList&gt;&gt;initialize method.  You can see that the WAReportColumn that is created for the "Can Buy" column is using the &lt;span style="font-weight: bold;"&gt;selector:title:&lt;/span&gt; constructor.  This is what is causing our problem:  the isPurchasable selector is returning a Boolean value. Fortunately, the WAReportColumn has another constructor:   &lt;span style="font-weight: bold;"&gt;renderBlock:title:&lt;/span&gt;.  For each data object in the rows collection, the renderBlock will be evaluated instead of calling a selector.  Let's use this to return a String instead of a Boolean.  Change the WAColumnReport constructor for the "Can Buy" column to this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;(WAReportColumn&lt;br /&gt;    renderBlock: [:item | item isPurchasable asString]&lt;br /&gt;    title: 'Can Buy');&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;When the renderBlock message is called, the row data object is passed into the block (we're calling it "item" here). We call the isPurchasable message off the item which will return a True or False Boolean object.  We then send that Boolean object the asString message which will return either "true" or "false".  Save the initialization method, flip back to the browser, and refresh.  You should be able to sort all the columns now.&lt;br /&gt;&lt;br /&gt;That's it for this entry--make sure to save the &lt;span style="font-weight: bold;"&gt;want-tutorial.image.  &lt;/span&gt;We're getting closer to having a working application.  The next post will focus on creating and editing WantedItems by using an editor component.  &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-3259857575567274600?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/3259857575567274600/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=3259857575567274600' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/3259857575567274600'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/3259857575567274600'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2007/01/wanted-seaside-tutorial-part-4.html' title='Wanted -- A Seaside Tutorial: Part 4'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_pouiJLhtxFE/RbZb4TPxsBI/AAAAAAAAAFE/3srmF1XvIvo/s72-c/title-notes-change-category-menus.jpg' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-5125908613018054485</id><published>2007-01-19T01:00:00.000-08:00</published><updated>2007-01-18T23:23:02.100-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='want'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='seaside'/><category scheme='http://www.blogger.com/atom/ns#' term='squeak'/><title type='text'>Wanted -- A Seaside Tutorial: Part 3</title><content type='html'>In this entry we'll create our wanted item model object, handle storing instances of it, and also create a main page that will display a list of them.  If you're not running Squeak, open it with the &lt;span style="font-weight: bold;"&gt;want-tutorial.image&lt;/span&gt; image file we've been saving our changes to.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 1:  Create the Model&lt;/span&gt;&lt;br /&gt;The Wanted model is pretty simple:  a WantedItem object that contains 3 attributes:&lt;ol&gt;&lt;li&gt;a title&lt;/li&gt;&lt;li&gt;notes about the item&lt;br /&gt;&lt;/li&gt;&lt;li&gt;the date the item was entered&lt;/li&gt;&lt;/ol&gt;With the Refactoring Browser open, click the &lt;span style="font-weight: bold;"&gt;Wanted-Tutorial&lt;/span&gt; package so that it is selected and the object creation template shows in the code pane.  Replace &lt;span style="font-weight: bold;"&gt;NameOfSubclass&lt;/span&gt; with &lt;span style="font-weight: bold;"&gt;WantedItem&lt;/span&gt;.  Inside the single quotes after &lt;span style="font-weight: bold;"&gt;instanceVariableNames&lt;/span&gt;:, enter &lt;span style="font-weight: bold;"&gt;title&lt;/span&gt;, &lt;span style="font-weight: bold;"&gt;notes&lt;/span&gt; and &lt;span style="font-weight: bold;"&gt;enteredDate&lt;/span&gt;. Save the new class definition.  The class should appear in the class pane:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RaAeCaCDoEI/AAAAAAAAACM/8ILuF3sTDbQ/s1600-h/wanted-item.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RaAeCaCDoEI/AAAAAAAAACM/8ILuF3sTDbQ/s320/wanted-item.jpg" alt="" id="BLOGGER_PHOTO_ID_5017043011587252290" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;One of the nice things about the Refactoring Browser is that it can create accessor methods for instance variables.  Double-click the &lt;span style="font-weight: bold;"&gt;title&lt;/span&gt; instance variable (inside the single quotes) so that it is highlighted, then right-click or option-click &lt;span style="font-weight: bold;"&gt;title&lt;/span&gt; and choose &lt;span style="font-weight: bold;"&gt;selection...&lt;/span&gt; then &lt;span style="font-weight: bold;"&gt;create accessors&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RaAeKaCDoGI/AAAAAAAAACc/xdwHbwX106Y/s1600-h/create-accessors.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RaAeKaCDoGI/AAAAAAAAACc/xdwHbwX106Y/s320/create-accessors.jpg" alt="" id="BLOGGER_PHOTO_ID_5017043149026205794" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The Refactoring Browser should have created two additional methods in your WantedItem class:  &lt;span style="font-weight: bold;"&gt;title&lt;/span&gt; and &lt;span style="font-weight: bold;"&gt;title:&lt;/span&gt;.  These act as your getter and setter respectively.  Whenever you see a colon in a message selector it means the message expects an argument--see the links in &lt;a href="http://inchingforward.blogspot.com/2006/10/grokking-grammar.html"&gt;this post&lt;/a&gt; for help on understanding Smalltalk syntax.  Notice that the message category &lt;span style="font-weight: bold;"&gt;accessing&lt;/span&gt; was created and that our accessors were put into it.  Create accessors for &lt;span style="font-weight: bold;"&gt;notes&lt;/span&gt; and &lt;span style="font-weight: bold;"&gt;enteredDate &lt;/span&gt;and save.  We now have an initial version of our WantedItem class that we can start using.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 2: Create an Object to Abstract Storage&lt;/span&gt;&lt;br /&gt;One of the cool things about working with a Smalltalk image is that when you save the image, you save the state of all the objects in it.  We can take advantage of this and use the image as a database by creating an object that will store our WantedItems as a collection.   As long as you save the image each time before you exit it, all changes to the collection will be persisted.   Once we get the application fleshed out and working the way we want, we'll move on to other types of storage.&lt;br /&gt;&lt;br /&gt;The storage object is simple:  it holds a collection of wanted items in a class instance variable.   A class method will allow us to retrieve the collection and make changes to it.&lt;br /&gt;&lt;br /&gt;Click the &lt;span style="font-weight: bold;"&gt;Wanted-Tutorial&lt;/span&gt; package so that it is selected and the object creation template shows in the code pane.  Change &lt;span style="font-weight: bold;"&gt;NameOfSubclass&lt;/span&gt; to &lt;span style="font-weight: bold;"&gt;WantedDatabase&lt;/span&gt;  and  save.    Click the &lt;span style="font-weight: bold;"&gt;class&lt;/span&gt; button in the class pane.  The code pane should look like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/RaWgIDPxr4I/AAAAAAAAADM/6NpIRdEa_sg/s1600-h/wanted-database-class-vars.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/RaWgIDPxr4I/AAAAAAAAADM/6NpIRdEa_sg/s400/wanted-database-class-vars.jpg" alt="" id="BLOGGER_PHOTO_ID_5018593419945160578" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Inside the single quotes after &lt;span style="font-weight: bold;"&gt;instanceVariableNames:&lt;/span&gt;, enter &lt;span style="font-weight: bold;"&gt;wantedItems&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RaWgMjPxr5I/AAAAAAAAADU/ZrWW4buog9U/s1600-h/wanted-database-wanted-items-class-var.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RaWgMjPxr5I/AAAAAAAAADU/ZrWW4buog9U/s400/wanted-database-wanted-items-class-var.jpg" alt="" id="BLOGGER_PHOTO_ID_5018593497254571922" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;and save.&lt;br /&gt;&lt;br /&gt;Now we need to create an accessor.  With the class button still selected, click on the "&lt;span style="font-weight: bold;"&gt;-- all --&lt;/span&gt; " message category.  Replace the method creation template in the code pane with the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;wantedItems&lt;br /&gt;   ^ wantedItems ifNil: [wantedItems := OrderedCollection new]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and save.   Notice that the Refactoring Browser created a new message category "&lt;span style="font-weight: bold;"&gt;as yet unclassified&lt;/span&gt;".   To keep things clean, right-click or option-click the "as yet unclassified" category, choose &lt;span style="font-weight: bold;"&gt;rename...&lt;/span&gt; and enter "&lt;span style="font-weight: bold;"&gt;accessing&lt;/span&gt;". We now have a very simple way of persisting our WantedItems:  we ask the WantedDatabase for the wantedItems class instance variable by sending the wantedItems class message and  add or make changes.  Notice that if the variable has not been accessed before, a new empty OrderedCollection is created and assigned to the wantedItems variable before being returned.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 3:  Create a Component for the Wanted List Page&lt;/span&gt;&lt;br /&gt;Our main page in the Wanted application will display a list of saved WantedItems.  From this page we will eventually provide links  to add, delete, and edit WantedItems.  Let's create a component to display the list.&lt;br /&gt;&lt;br /&gt;In the Refactoring Browser, click the Wanted-Tutorial package so that the object creation template is displayed in the code pane.  Change &lt;span style="font-weight: bold;"&gt;Object&lt;/span&gt; to &lt;span style="font-weight: bold;"&gt;WTComponent&lt;/span&gt; and &lt;span style="font-weight: bold;"&gt;NameOfSubclass&lt;/span&gt; to &lt;span style="font-weight: bold;"&gt;WantedList&lt;/span&gt; and save the component.  Since WantedList is a subclass of WTComponent, we do not have to add the class canBeRoot method:  it is inherited.  We want WantedList to render differently than WTComponent, so click on the "-- all --" item in the message category pane so that the method creation template displays in the code pane.   Create the renderContentOn: method by entering the following into the code pane:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;renderContentOn: html&lt;br /&gt;   WantedDatabase wantedItems&lt;br /&gt;       do: [:wantedItem | html paragraph: wantedItem title]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and save:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RbBZyzPxr_I/AAAAAAAAAEg/_j5H1xfZvOE/s1600-h/wanted-list-render-content-on.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RbBZyzPxr_I/AAAAAAAAAEg/_j5H1xfZvOE/s320/wanted-list-render-content-on.jpg" alt="" id="BLOGGER_PHOTO_ID_5021612313802813426" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Our renderContentOn: method asks the WantedDatabase for the wanted items collection, then for each wanted item we ask the html context to emit the title of the item in a paragraph tag.   This is very simplistic, but for now allows us to view our stored wanted items.&lt;br /&gt;&lt;br /&gt;Now flip back to your web browser and go to http://localhost:8080/seaside/config.  Click on the &lt;span style="font-weight: bold;"&gt;configure&lt;/span&gt; link next to the wanted application entry point:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RaAeGKCDoFI/AAAAAAAAACU/BMdx_8tDIGI/s1600-h/wanted-application-configure.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RaAeGKCDoFI/AAAAAAAAACU/BMdx_8tDIGI/s320/wanted-application-configure.jpg" alt="" id="BLOGGER_PHOTO_ID_5017043076011761746" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;In the &lt;span style="font-weight: bold;"&gt;General&lt;/span&gt; section of the page, change the &lt;span style="font-weight: bold;"&gt;Root Component&lt;/span&gt; dropdown to &lt;span style="font-weight: bold;"&gt;WantedList&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RaWYyTPxr1I/AAAAAAAAAC0/5rALX-tV0AU/s1600-h/wanted-list-in-dropdown.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RaWYyTPxr1I/AAAAAAAAAC0/5rALX-tV0AU/s320/wanted-list-in-dropdown.jpg" alt="" id="BLOGGER_PHOTO_ID_5018585349701611346" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;and click the save button.  After the page refreshes to notify you that the changes were saved, click the done button.  This will take you back to the main config page.  Click on the wanted link.   You should see nothing since the only thing we are displaying are wanted items in paragraph tags, and we haven't created any.  Let's create one.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 4:  Create a WantedItem to Display&lt;/span&gt;&lt;br /&gt;Flip back to Squeak, left click on the world, choose &lt;span style="font-weight: bold;"&gt;open...&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RbBZ6jPxsAI/AAAAAAAAAEo/5iVhsRqrjIM/s1600-h/world-open-menu.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RbBZ6jPxsAI/AAAAAAAAAEo/5iVhsRqrjIM/s320/world-open-menu.jpg" alt="" id="BLOGGER_PHOTO_ID_5021612446946799618" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;then &lt;span style="font-weight: bold;"&gt;Shout Workspace&lt;/span&gt;&lt;span style="font-size:130%;"&gt;:&lt;br /&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/RbBZmTPxr8I/AAAAAAAAAEI/lxay5Uo3hSU/s1600-h/open-shout-workspace-menu.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/RbBZmTPxr8I/AAAAAAAAAEI/lxay5Uo3hSU/s320/open-shout-workspace-menu.jpg" alt="" id="BLOGGER_PHOTO_ID_5021612099054448578" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;You should see a window like this one:&lt;span style="font-size:130%;"&gt;&lt;span style="font-size:100%;"&gt;&lt;span style="font-size:100%;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RbBZuzPxr-I/AAAAAAAAAEY/BCNiu5O64qQ/s1600-h/shout-workspace.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RbBZuzPxr-I/AAAAAAAAAEY/BCNiu5O64qQ/s320/shout-workspace.jpg" alt="" id="BLOGGER_PHOTO_ID_5021612245083336674" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The workspace is kind of like a command line for your Smalltalk image with full access to all of the objects in it.  You can evaluate code snippets, write scripts, start services, etc.  The Shout Workspace adds some extra features like syntax coloring.   We're going to use the workspace to add a WantedItem to our WantedDatabase.  Enter the following code into the workspace:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;WantedDatabase wantedItems add:&lt;br /&gt;   (WantedItem new&lt;br /&gt;       title: 'Nintendo Wii';&lt;br /&gt;       enteredDate: (DateAndTime  now);&lt;br /&gt;       notes: 'Get some exercise while having fun')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;On the first line we're asking the WantedDatabase for the wantedItems (which will be an empty OrderedCollection since this is the first time we're accessing it).  We immediately send the collection the &lt;span style="font-weight: bold;"&gt;add:&lt;/span&gt; message, passing a new WantedItem as the argument.  The wanted item is created inside the parentheses by passing the WantedItem class the new message, then setting the 3 instance variables.  The semicolons are a Smalltalk language feature called the &lt;span style="font-weight: bold;"&gt;cascade operator&lt;/span&gt; that returns the receiver of the last message (again, see &lt;a href="http://inchingforward.blogspot.com/2006/10/grokking-grammar.html"&gt;this post&lt;/a&gt; for links to understanding the Smalltalk syntax).  Highlight the entire block of code, then right-click or option-click and choose "do it":&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/RbBZrTPxr9I/AAAAAAAAAEQ/1d16ai4EUBA/s1600-h/shout-workspace-doit.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/RbBZrTPxr9I/AAAAAAAAAEQ/1d16ai4EUBA/s320/shout-workspace-doit.jpg" alt="" id="BLOGGER_PHOTO_ID_5021612184953794514" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;To make sure that the item was added, append the following block on its own line to the workspace:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;WantedDatabase wantedItems size&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Highlight it in the workspace, right-click or option-click, then select "print it".  You should see the number 1 printed to the right of the block of code, letting us know that our item was added to the collection.&lt;br /&gt;&lt;br /&gt;Now flip back to your browser and refresh.  You should see the title of the item we just added in the workspace.&lt;br /&gt;&lt;br /&gt;Congratulations--we're one step closer to world domination!  Make sure to save your image (&lt;span style="font-weight:bold;"&gt;want-tutorial.image&lt;/span&gt;).   Note that by saving the image, you'll be saving the Wii WantedItem we created.  If you exit Squeak then fire it back up and go back to the wanted seaside page, it will show up again.  Even cooler:  install Squeak on another machine (even under a different operating system), copy the tutorial image to it, open the image and point your web browser to the seaside wanted app.  You will see the same results.  We now have a completely portable platform-agnostic development environment and database for our project.&lt;br /&gt;&lt;br /&gt;In the next post, we'll look at tweaking the display of the WantedList component.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-5125908613018054485?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/5125908613018054485/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=5125908613018054485' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/5125908613018054485'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/5125908613018054485'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2007/01/wanted-seaside-tutorial-part-3.html' title='Wanted -- A Seaside Tutorial: Part 3'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_pouiJLhtxFE/RaAeCaCDoEI/AAAAAAAAACM/8ILuF3sTDbQ/s72-c/wanted-item.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-6518708328697317025</id><published>2007-01-03T20:32:00.001-08:00</published><updated>2007-01-04T19:42:53.372-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='want'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='seaside'/><category scheme='http://www.blogger.com/atom/ns#' term='squeak'/><title type='text'>Wanted -- A Seaside Tutorial: Part 2</title><content type='html'>If you're following along from the &lt;a href="http://inchingforward.blogspot.com/2007/01/wanted-tutorial-to-kick-off-new-year-i.html"&gt;last blog post&lt;/a&gt;, you should now have a working Squeak environment that has Seaside installed and preconfigured.  I'm skipping over a lot of introductory Squeak material since there are plenty of resources out there.  If you are new to Squeak, you may find it an odd, foreign, and sometimes frustrating experience initially.  I certainly did, but the more I use it (and Smalltalk in general) the more amazed I am with it.  Squeak is almost criminally passed over, with most people taking one look at the interface (especially images pre 3.9) and bolting.  Stick around and give it a chance--there's a lot of power under that weird interface, and a huge learning opportunity for those with an open mind.&lt;br /&gt;&lt;br /&gt;In this entry we will write the first Wanted Seaside component.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 1: Create the Component&lt;/span&gt;&lt;br /&gt;A Seaside Component is an object that responds to user actions, holds state relating to those actions, and renders itself as html.  A component may be responsible for rendering an entire page or act as an embeddable piece of html that another component includes.  Components that answer the &lt;span style="font-weight: bold;"&gt;canBeRoot&lt;/span&gt; class message with a true value are given special treatment:  they show up as application entry point components in the Seaside config screen and can be mapped  to a url--we'll use the config screen later.&lt;br /&gt;&lt;br /&gt;If you don't already have Squeak running with the &lt;span style="font-weight: bold;"&gt;wanted-tutorial.image&lt;/span&gt; we created in the last post, open it now. Right-click (Windows) or option-click (Mac) in the Packages pane (the first list view) in the Refactoring browser.  You should see this menu:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/RZyPXGCqwwI/AAAAAAAAAA8/mCHWJa7XdZ4/s1600-h/category-menu.gif"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/RZyPXGCqwwI/AAAAAAAAAA8/mCHWJa7XdZ4/s400/category-menu.gif" alt="" id="BLOGGER_PHOTO_ID_5016041711905456898" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Choose "&lt;span style="font-weight: bold;"&gt;add item...&lt;/span&gt;" and enter "&lt;span style="font-weight: bold;"&gt;Wanted-Tutorial&lt;/span&gt;" in the dialog box that pops up.  This will create the package that we will put all our Wanted source in. The Refactoring Browser should show our new package and should have changed the code pane to an object creation template:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RZySmmCqwxI/AAAAAAAAABI/HFwlQ8xO420/s1600-h/new-package-and-source.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RZySmmCqwxI/AAAAAAAAABI/HFwlQ8xO420/s400/new-package-and-source.jpg" alt="" id="BLOGGER_PHOTO_ID_5016045276728312594" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;We want our object to be a Seaside component, so highlight the first word "&lt;span style="font-weight: bold;"&gt;Object&lt;/span&gt;" in the code pane by double-clicking it and change it to "&lt;span style="font-weight: bold;"&gt;WACom&lt;/span&gt;".  Notice that the text you are typing is italicized and underlined.  This is &lt;a href="http://uncomplex.net/ecompletion"&gt;eCompletion&lt;/a&gt; doing a helpful service for you, searching out class objects that begin with the text you have entered.  If you hit the tab key, eCompletion will display a matching list of two objects (WAComponent and WACompound)  under your cursor.  Make sure &lt;span style="font-weight: bold;"&gt;WAComponent&lt;/span&gt; is highlighted and press enter.  Change "&lt;span style="font-weight: bold;"&gt;NameOfSubclass&lt;/span&gt;" to "&lt;span style="font-weight: bold;"&gt;WTComponent&lt;/span&gt;".  This will be our ancestor that we will use for all of our Wanted components.  Having a common ancestor allows us to reuse application-wide settings that we don't want to have to redefine in each component. Hit ctrl-s (Windows) or command-s (Mac) to save the object.  Your Refactoring Browser should now look like this:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RZyU6WCqwyI/AAAAAAAAABQ/t92-vnMrfSs/s1600-h/wtcomponent.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RZyU6WCqwyI/AAAAAAAAABQ/t92-vnMrfSs/s400/wtcomponent.jpg" alt="" id="BLOGGER_PHOTO_ID_5016047815053984546" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 2:  Make the Component Available as an Application Root&lt;/span&gt;&lt;br /&gt;Click on the &lt;span style="font-weight: bold;"&gt;class&lt;/span&gt; button in the Class pane (2nd list view) in the Refactoring Browser.  Now click on "--all--" in the Message Category pane  (3rd list view).  The code pane now shows a default message template.  Replace the text in the code pane with the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;canBeRoot&lt;br /&gt;    ^ true&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;What we've done is added a class-side message &lt;span style="font-weight: bold;"&gt;canBeRoot&lt;/span&gt; that returns a true value  ("^" means return in Smalltalk).  Save the message with a ctrl-s or command-s.  Enter your initials in the dialog if you are prompted.  Seaside will now know to treat this component as an application root entry point (we'll get to this in a bit).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 3:  Have the Component Display Itself&lt;/span&gt;&lt;br /&gt;Click the &lt;span style="font-weight: bold;"&gt;instance&lt;/span&gt; button, select the "--all--" message category,  change the message template to the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;renderContentOn: html&lt;br /&gt;    html text: 'Seaside Rocks!'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and save.  The &lt;span style="font-weight: bold;"&gt;renderContentOn:&lt;/span&gt; message asks the component to display itself using a WAHtmlRenderer object.   WAHtmlRenderer knows about html.  It acts like a stream that you can keep appending html attributes to in order to build a whole or part of an html page.  For now, we are telling the renderer to emit a simple text string.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 4:  Wire the Component up to a Seaside URL&lt;/span&gt;&lt;br /&gt;Now, point your web browser to &lt;span style="font-weight: bold;"&gt;http://localhost:8080/seaside/config&lt;/span&gt;.   You should see the Seaside config page:&lt;br /&gt;&lt;pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RZycomCqwzI/AAAAAAAAABg/1gPscr1GvCQ/s1600-h/seaside-config-page.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RZycomCqwzI/AAAAAAAAABg/1gPscr1GvCQ/s320/seaside-config-page.jpg" alt="" id="BLOGGER_PHOTO_ID_5016056306204328754" border="0" /&gt;&lt;/a&gt;&lt;/pre&gt;&lt;br /&gt;At the top of the page you will see the current Seaside version displayed.  Underneath that is a list of applications that have entry points into Seaside.  Underneath the entry points is a Settings section.  In the Path text field, enter "&lt;span style="font-weight: bold;"&gt;wanted&lt;/span&gt;", leave the Type as &lt;span style="font-weight: bold;"&gt;Application&lt;/span&gt;,  and click the Add button.&lt;br /&gt;&lt;br /&gt;You should be taken to the wanted Application config page:&lt;br /&gt;&lt;pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RZyctmCqw0I/AAAAAAAAABo/OiFM_SmltLo/s1600-h/wanted-config-page.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RZyctmCqw0I/AAAAAAAAABo/OiFM_SmltLo/s320/wanted-config-page.jpg" alt="" id="BLOGGER_PHOTO_ID_5016056392103674690" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Click on the Root Component drop down.  This list contains all the Seaside components that answer the canBeRoot message with a true value.  For now, we want our wanted entry point url to be handled by the WTComponent object we just created, so choose WTComponent from the list and click the Save button.  The application config screen should refresh to reflect the change we just made.  Click the Done button.  You should be taken back to the Seaside config screen which now contains a wanted application entry point:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_pouiJLhtxFE/RZyepWCqw1I/AAAAAAAAABw/bF-PYpf5mM4/s1600-h/wanted-entry-point.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_pouiJLhtxFE/RZyepWCqw1I/AAAAAAAAABw/bF-PYpf5mM4/s320/wanted-entry-point.jpg" alt="" id="BLOGGER_PHOTO_ID_5016058518112486226" border="0" /&gt;&lt;/a&gt;&lt;/pre&gt;Click the wanted link and you should see the "Seaside Rocks!" text we entered in our component's renderConentOn: message.&lt;br /&gt;&lt;br /&gt;Congratulations--you've just created your first working Seaside application!   Google's not going to acquire your startup just yet, but it's a first step.  Think about what we just did:  In more or less four steps and less than 10 lines of code we created a simple web application without editing a single xml file and without reloading the server to deploy our component.   The only time we left the Squeak environment was to configure our component as an application entry point using the Seaside config utility.&lt;br /&gt;&lt;br /&gt;That's the end of this entry.  Left click on the world and choose &lt;span style="font-weight: bold;"&gt;save&lt;/span&gt; to persist the changes to the &lt;span style="font-weight: bold;"&gt;wanted-tutorial.image&lt;/span&gt; file.  Next time we'll create the Wanted model and some more sophisticated components to manage some CRUD operations on the model.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Required Reading&lt;/span&gt;&lt;ul&gt;&lt;li&gt;The &lt;a href="http://seaside.st/"&gt;Seaside home page&lt;/a&gt;, of course!  Read and re-read the &lt;a href="http://seaside.st/Documentation/"&gt;documentation&lt;/a&gt; and &lt;a href="http://seaside.st/Tutorial/AWalkontheSeaside/"&gt;tutorial&lt;/a&gt; pages.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;David Shaffer's &lt;a href="http://www.shaffer-consulting.com/david/Seaside/"&gt;Seaside Tutorial&lt;/a&gt; will help you when you get stuck. &lt;a href="http://www.shaffer-consulting.com/david/Seaside/"&gt;&lt;br /&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Ramon Leon's blog &lt;a href="http://onsmalltalk.com/"&gt;On Smalltalk&lt;/a&gt; is helping bring new developers (this one included) into the Squeak/Seaside fold.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The Seaside &lt;a href="http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside"&gt;mailing list&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.motionobj.com/seasidefaq/"&gt;Seaside FAQ&lt;/a&gt;.  I don't know if this is being actively maintained, but there is some good info there.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-6518708328697317025?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/6518708328697317025/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=6518708328697317025' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/6518708328697317025'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/6518708328697317025'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2007/01/wanted-seaside-tutorial-part-2.html' title='Wanted -- A Seaside Tutorial: Part 2'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_pouiJLhtxFE/RZyPXGCqwwI/AAAAAAAAAA8/mCHWJa7XdZ4/s72-c/category-menu.gif' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-6510914628029656164</id><published>2007-01-01T17:11:00.000-08:00</published><updated>2007-01-02T10:17:33.211-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='want'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='seaside'/><category scheme='http://www.blogger.com/atom/ns#' term='squeak'/><title type='text'>Wanted -- A Seaside Tutorial</title><content type='html'>To kick off the new year, I thought I'd start writing a walkthrough of creating a simple Seaside application.  Since I'm still pretty new to a lot of this stuff, the tutorial will be from a newbie discovering perspective.  My hope is that it will be useful both for myself and for others new to Seaside.  Each blog entry will reflect one step in the advancement of the application.&lt;br /&gt;&lt;br /&gt;The application is called Wanted.  Each time you find something that you think you want, you add it to your Wanted list.  The Wanted list shows items that have one of two statuses:  those that have passed a time threshold (say, two weeks) that are considered true wants, and those that have not. The theory is that you hold off on opening your wallet for a Wanted item until it has been around long enough that it is considered a true want.  Hopefully in the time it takes for an item to pass the time threshold, you will have changed your mind about its importance, saving you some money.  The Wanted application has a simple model (Wanted items) and a CRUD interface.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 1 -- Preparation&lt;/span&gt;&lt;br /&gt;To get started, we need to have a Smalltalk implementation and Seaside installed. The Smalltalk implementation used for this tutorial is Squeak, but Seaside is also available for &lt;a href="http://www.cincomsmalltalk.com/"&gt;Cincom Smalltalk&lt;/a&gt; and &lt;a href="http://www.object-arts.com/content/navigation/home.html"&gt;Dolphin Smalltalk&lt;/a&gt;--consult the &lt;a href="http://seaside.st/Download/"&gt;Seaside download page&lt;/a&gt; to see if your favorite implementation has a port.  Using Smalltalk actually requires two things:  a VM that executes Smalltalk code, and an image that stores the Smalltalk code the VM executes.  You can find the latest release-ready Squeak VM + Image combination on the &lt;a href="http://squeak.org/"&gt;Squeak homepage&lt;/a&gt; in the upper right corner (as of this writing, the latest file is Squeak-3.9xxx.zip, where "xxx" is some OS-specific designation).  Download the appropriate version for your OS and unpack it.&lt;br /&gt;&lt;br /&gt;This tutorial will be using the Squeak.org VM, but not the default Squeak.org image.  We're going to use Damien Cassou's squeak-web image found &lt;a href="http://damien.cassou.free.fr/squeak-web/"&gt;here&lt;/a&gt;.  It contains Seaside, plus several developer niceties that the default image doesn't.  Download the latest version (as of this writing, the latest file is "squeak-web-72.zip") and unpack it.&lt;br /&gt;&lt;br /&gt;Once you have downloaded both files and unpacked them, copy the Squeak VM executable and the SqueakV39.sources file from the Squeak3.9 directory into the squeak-web directory:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_pouiJLhtxFE/RZm4YmCqwvI/AAAAAAAAAAk/nkQcAmgJeO4/s1600-h/squeak-web-folder-mac.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_pouiJLhtxFE/RZm4YmCqwvI/AAAAAAAAAAk/nkQcAmgJeO4/s400/squeak-web-folder-mac.jpg" alt="" id="BLOGGER_PHOTO_ID_5015242392721867506" border="0" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_pouiJLhtxFE/RZm21mCqwsI/AAAAAAAAAAM/PyEE1QiIbcI/s1600-h/squeak-web-folder-win.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_pouiJLhtxFE/RZm21mCqwsI/AAAAAAAAAAM/PyEE1QiIbcI/s400/squeak-web-folder-win.jpg" alt="" id="BLOGGER_PHOTO_ID_5015240691914818242" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 2 -- Up and Running&lt;/span&gt;&lt;br /&gt;To run Squeak, click on the squeak-web-72.image file and drag it on top of the Squeak executable file.  You should see something like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_pouiJLhtxFE/RZm3XWCqwtI/AAAAAAAAAAU/aea5KzhPokM/s1600-h/up-and-running.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_pouiJLhtxFE/RZm3XWCqwtI/AAAAAAAAAAU/aea5KzhPokM/s400/up-and-running.jpg" alt="" id="BLOGGER_PHOTO_ID_5015241271735403218" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 3 -- A Couple of Tweaks&lt;/span&gt;&lt;br /&gt;Collapse the two windows that are showing by clicking on the circle in the upper right corner of each window.   Once the two windows are collapsed, you should see mostly white space.  The white space is called the World.  Left click on it to show the World menu.  Click on "Open.." on the World menu, then click on "Refactoring Browser".  This should pop this window up:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_pouiJLhtxFE/RZm3i2CqwuI/AAAAAAAAAAc/RyLzJYxsDdo/s1600-h/refactoring-browser.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_pouiJLhtxFE/RZm3i2CqwuI/AAAAAAAAAAc/RyLzJYxsDdo/s400/refactoring-browser.jpg" alt="" id="BLOGGER_PHOTO_ID_5015241469303898850" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The Refactoring Browser allows you to see and edit all the source stored in the currently running image.  Running along the top of the browser window are four list views.&lt;br /&gt;&lt;br /&gt;The first list contains the image's Packages.  Packages provide a way to group related classes together.  Click on the Kernel-Numbers package and the second list view will display the classes in that package.  Click on the Complex class and the third view will display the Complex class Categories.  Categories provide a way to group related messages the class/object responds to.  The fourth list displays those messages. By default, when clicking on a class, all messages are displayed.  Clicking on a specific category allows you to filter the messages.  Select the "arithmetic" category, then select the "abs" message.  The bottom pane should show the source code for the abs message.  This pane is where you will change the world.&lt;br /&gt;&lt;br /&gt;Notice the three buttons in the bottom of the Class list view (the 2nd list):  instance, ?, and class.  By default, the instance button should be selected.  The instance and class buttons toggle which messages are displayed:  instance messages (messages that a class's object instance responds to), and class messages (those that the class itself responds to). The Complex class responds to 3 messages, all of which create a Complex instance object:  abs:arg:, new, and real:imaginary:.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Step 4 -- Saving&lt;/span&gt;&lt;br /&gt;At this point, left click on the World and choose "save as...".   In the "New File Name?" dialog, enter "want-tutorial.image" and click the Accept button.  If you look in the squeak-web-72 directory, you should see the new image we created.  This is where we will store all code related to this tutorial.  Notice we didn't save over the squeak-web-72.image file--this was left intentionally as a starting point for other projects.&lt;br /&gt;&lt;br /&gt;We have made three changes to the want-tutorial.image that will persist for the next time we run:  The two collapsed windows that were open, and the opened Refactoring Browser.  That's it for this entry.  Feel free to look around--you can't really mess anything up as long as you don't choose one of the "save" options from the World menu before exiting.  We'll start looking at Seaside in the next entry.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Useful links:&lt;/span&gt;&lt;br /&gt;&lt;a href="http://squeak.org/"&gt;Squeak.org&lt;/a&gt;&lt;br /&gt;&lt;a href="http://wiki.squeak.org/squeak/377"&gt;Introductions to Squeak&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-6510914628029656164?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/6510914628029656164/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=6510914628029656164' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/6510914628029656164'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/6510914628029656164'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2007/01/wanted-tutorial-to-kick-off-new-year-i.html' title='Wanted -- A Seaside Tutorial'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_pouiJLhtxFE/RZm4YmCqwvI/AAAAAAAAAAk/nkQcAmgJeO4/s72-c/squeak-web-folder-mac.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-8475854269219463575</id><published>2006-12-25T20:31:00.000-08:00</published><updated>2006-12-25T21:22:08.473-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='smalltalk'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Navigating the World of Objects</title><content type='html'>So I've been happily lost in a world of objects lately, with not much to report on until I can get accustomed to thinking in Smalltalk.  Thinking productively in Smalltalk means learning good OO design.  To get there, I'm working my way through these books:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.amazon.com/Smalltalk-Best-Practice-Patterns-Kent/dp/013476904X/"&gt;Smalltalk Best Practice Patterns by Kent Beck&lt;/a&gt;&lt;br /&gt;For me, this has been a must read.  It helps prepare you for the idioms you will see in other people's Smalltalk code.  If you haven't really thought about code structure, or if you're just dense like me, it will provide you with one "aha!" moment after another as Kent Beck gives names to decisions that you should be making.  I think that the naming patterns allow you to think in a more abstract, strategic manner--instead of just writing code to accomplish a task, Beck provides you with a vocabulary of decisions to choose from to accomplish the task, and a reason why it should be done that way. This book has been the handle on the door to the Smalltalk world for me, and has already changed the way I write code for the better.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.amazon.com/Object-Design-Roles-Responsibilities-Collaborations/dp/0201379430/"&gt;Object Design: Roles, Responsibilities, and Collaborations by Rebecca Wirfs-Brock, Alan McKean &lt;/a&gt;&lt;br /&gt;I haven't yet started this one, and at first glance it looks a little dry, but it is highly recommended by several Smalltalk bloggers out there.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-8475854269219463575?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/8475854269219463575/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=8475854269219463575' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/8475854269219463575'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/8475854269219463575'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2006/12/navigating-world-of-objects.html' title='Navigating the World of Objects'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-3573462019032353071</id><published>2006-11-21T12:05:00.000-08:00</published><updated>2006-11-21T12:33:32.290-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='seaside'/><category scheme='http://www.blogger.com/atom/ns#' term='screencast'/><category scheme='http://www.blogger.com/atom/ns#' term='squeak'/><category scheme='http://www.blogger.com/atom/ns#' term='magritte'/><title type='text'>Ramon Leon Screencast</title><content type='html'>Ramon Leon has posted his first screencast showing how to &lt;a href="http://onsmalltalk.com/programming/smalltalk/screencast-how-to-build-a-blog-in-15-minutes-with-seaside/"&gt;create a simple blog with comments using Seaside and Magritte&lt;/a&gt;.  It's a nice look into how someone experienced with the Squeak environment designs, creates and wires up several objects.   It also shows off the power and speed of development that Seaside offers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-3573462019032353071?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/3573462019032353071/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=3573462019032353071' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/3573462019032353071'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/3573462019032353071'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2006/11/ramon-leon-screencast.html' title='Ramon Leon Screencast'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-373065817254745950</id><published>2006-11-10T22:42:00.000-08:00</published><updated>2006-11-10T23:05:17.476-08:00</updated><title type='text'>Setting a Background Image in Squeak</title><content type='html'>If you're tired of the default background in Squeak and want to use your favorite wallpaper, do the following:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Left-click anywhere on the squeak desktop and choose "open..." then "file list".&lt;/li&gt;&lt;li&gt;Navigate to the directory containing your image.&lt;/li&gt;&lt;li&gt;Click on the image. The buttons in the top-right corner of the file list window are context sensitive and will add three buttons when an image is selected: "import", "open", and "background".&lt;/li&gt;&lt;li&gt;With your image highlighted, click on the "background" button.&lt;/li&gt;&lt;li&gt;Enjoy!&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;If you're looking for some original wallpapers, check out &lt;a href="http://www.deviantart.com/"&gt;deviantART&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://photos1.blogger.com/blogger2/583/4494/1600/squeak-background-ss.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://photos1.blogger.com/blogger2/583/4494/200/squeak-background-ss.jpg" alt="" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-373065817254745950?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/373065817254745950/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=373065817254745950' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/373065817254745950'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/373065817254745950'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2006/11/setting-background-image-in-squeak.html' title='Setting a Background Image in Squeak'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-116217452548133805</id><published>2006-10-29T18:04:00.000-08:00</published><updated>2006-11-10T20:07:19.168-08:00</updated><title type='text'>Grokking the Grammar</title><content type='html'>The Smalltalk language is deceptively simple.  These two guides will get you started on the grammar:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.eli.sdsu.edu/courses/spring01/cs635/readingSmalltalk.pdf"&gt;I Can Read C++ and Java But I Can't Read Smalltalk&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.jera.com/techinfo/readingSmalltalk.pdf"&gt;Reading Smalltalk&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Syntax is just the tip of the iceberg, but you should have a feel of the flavor of the language after reading the above.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-116217452548133805?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/116217452548133805/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=116217452548133805' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/116217452548133805'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/116217452548133805'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2006/10/grokking-grammar.html' title='Grokking the Grammar'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-36815431.post-116217376677222176</id><published>2006-10-29T17:31:00.000-08:00</published><updated>2006-11-10T20:07:19.040-08:00</updated><title type='text'>Intro</title><content type='html'>The purpose of this blog is to chronicle my learning experiences with &lt;a href="http://squeak.org"&gt;Squeak&lt;/a&gt; and &lt;a href="http://www.seaside.st"&gt;Seaside&lt;/a&gt;.  I've been doing web development using Java for the last six years, and find Squeak + Seaside a breath of fresh air.  Even though I've been involved in writing software for several years, I've only recently become a student of the game.  It started with researching different languages to write personal projects in, taking detours through Python, Smalltalk, Ruby, Lisp and Haskell.  Researching those languages uncovered several blogs and articles that opened my eyes to how little I've really known about my own profession (and how much I've been able to overachieve).  I decided to settle on Squeak due to its roots of being focused as a learning tool, the &lt;a href="http://www.cincomsmalltalk.com/userblogs/avi/blogView?showComments=true&amp;amp;entry=3284695382"&gt;turtles all the way down&lt;/a&gt; approach to the environment, its ability to do amazing simulations, and the Seaside web framework.&lt;br /&gt;&lt;br /&gt;I plan to use this space to help others who want to learn Squeak and Seaside.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36815431-116217376677222176?l=inchingforward.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://inchingforward.blogspot.com/feeds/116217376677222176/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=36815431&amp;postID=116217376677222176' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/116217376677222176'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/36815431/posts/default/116217376677222176'/><link rel='alternate' type='text/html' href='http://inchingforward.blogspot.com/2006/10/intro.html' title='Intro'/><author><name>mj</name><uri>http://www.blogger.com/profile/01534954159169119635</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry></feed>
