tacow

Karl and I launched the Toronto Area Cocoa and WebObjects (tacow) developers group today. OK, we registered a domain and created the website, but come on, it’s something!

We took a road trip to Chicago last month to sit in on the CAWUG meeting and had a blast, so we’re really hoping to create a similar group here. We’re not shooting for anything overly structured, just a venue to geek out, learn some stuff, and share stories.

Karl’s putting the final touches on the announcement for our first meeting and that’ll go up soon. If you’re interested in attending, or want to help, please head over to the tacow.org site and sign up and post a comment.

Selecting a subset of Objects in a WORepetition

Often you need to allow a user to select a subset of items from a WORepetition to be able perform an action on them. This example will do that for you:

public Object item; // The "item" binding on your WORepetition
public NSMutableArray selections = new NSMutableArray();
 
public void setSelectedItem(boolean selected) {
    boolean hasItem = selections.containsObject(item);
    if (selected) {
        if (! hasItem) {
            selections.addObject(item);
        }
    } else if (hasItem) {
        selections.removeObject(item);
    }
}
	
public boolean selectedItem() {
    return selections.containsObject(item);
}

Add a WOCheckbox to your WORepetition and bind its checked binding to selectedItem. Any item selected will be added to the selections array when the form is submitted.

WebObjects Beginners project posted

I’ve posted an Xcode project to accompany my previous rant. The zip archive is available here.

A few notes:

  • This is a WebObjects 5.3/Xcode 2.2 project.
  • There is a ReadMe included, it outlines some of the project details.
  • I tweaked the code a little – read their comments.
  • The project demonstrates using a common parent for your components. Which is easy to do and useful for minimizing redundant code. (Though, it does seem to stop WOBuilder from seeing some page attributes :-()
  • The components all render using CSS.

WebObjects – Flawed beginner info

An EO out of an EC is like a fish out of water

The books WebObjects beginners usually read when they are starting out are the Apple Web Applications tutorial and Joshua Marker’s WebObjects 5 for Mac OS X: Visual QuickPro Guide.

Both are very good introductions. They cover a great deal of territory (and there is a great deal of territory to cover when you’re beginning).

However, they both share a fatal flaw. Both endorse the practice of inserting EnterpriseObjects (EOs) into their EOEditingContexts (ECs) late.

By late I mean: NOT RIGHT AWAY!

The EOF Commandments are very clear on this. Many – myself included – have the nasty experiences to back this up. It is never a good idea to defer adding a newly created EO into an EC. In fact, Chuck Hill is quoted as saying: “I’ll opine that an EO not in an EC is in fact not an EO just a plain old Object.”

That’s a lot to give up. No change notification, no EOGobalID, no undo, no guarantee that awakeFromInsertion is not going to hose any changes you’ve made…

OK, to be fair, the examples in these documents work. It is possible to set some values in an EO prior to inserting it into an EC and have it work.

Maybe.

Some of the time.

But don’t count on it.

And that’s the problem, there are no rules to dictate when it is safe and when it’s not. So, if you are looking for best practices: Just don’t do it – ever.

Learn the correct way first.

I find the Apple Web Applications tutorial is particularly bad in this instance. Not only do they advise inserting the EO into it’s EC late, but they develop a UI around the practice in such a way that if you try to reuse your learning in your own projects (by creating a similar UI) you will be trapped.

Take a look at the UI their Authors tutorial uses:

web_app.gif

Now, I’m not going to spend a lot of time on how confusing this is. I’ve got to assume this is meant just as a learning example, but jeez-louise they could have done it right without any more effort. Honestly, can you tell what the Update, Add, and Save buttons are suppose to do? I can’t.

Anyway, lets look at some of the code underneath this thing:

public Main(WOContext context) { 
    super(context); 
    // Build the fetch specification. 
    fetchSpec = new EOFetchSpecification("Author", null, null); 
    // Get the editing context. 
    editingContext = session().defaultEditingContext(); 
    // Fetch authors. 
    authorList = new NSMutableArray(
        editingContext.objectsWithFetchSpecification(fetchSpec)); 
    // Create an Author object (where form data is stored). 
    author = new Author(); 
}

OK, so this is the component constructor. It fetches the array of authors from using a fetchSpec – I can handle that. Then it creates an empty Author without inserting it into an EC. Bad, bad, bad.

public WOComponent addAuthor() { 
    // Add the author only when it isn’t in the list. 
    if (!authorList.containsObject(author)) { 
        // Add the author to the list. 
        authorList.addObject(author); 
        // Insert author into editing context. 
        editingContext.insertObject(author); 
        // Create a new author. 
        author = new Author(); 
    } 
    return null; 
}

Oh, look, there it is again. Let me check… yup still bad.

public WOComponent updateAuthor() { 
    // Create an Author object. 
    author = new Author(); 
    return null; 
}

And yet again. Umm… nope, still bad.

I can kinda see why they’re doing this. The author needed an object to bind to the form that wouldn’t get saved to the DB when the user clicked save unless explicitly added to the list first. Instead of dealing with the complexity of multiple ECs or using NSMutableDictionaries as proxy objects he did this. And yes, it works in this instance, but that doesn’t stop it from being a bad practice and furthermore it teaches inexperienced developers a bad lesson.

But really, I think the root of the problem is with the design of the UI. If we separate the list and edit functions into two user modes (Yes I know, very Web 1.0, but this is a beginners tutorial!) the need that led to this problem goes away.

Here let me show you.

First some mockups of the pages. AuthorList first:

authors_list.gif

Then AuthorEdit:

author_details.gif

Now the relevent code for the AuthorList (I’m assuming that the list is populated and the WORepetition has all it needs):

public WOComponent addAuthor() { 
    // Create an Author object (properly!). 
    Author author = (Author)EOUtilities.createAndInsertInstance(
        ec(), "Author"); 
    // Where are we going? (Planet 10!)
    AuthorEdit nextPage = (AuthorEdit)pageWithName("AuthorEdit");
    // Pass in the new author
    nextPage.setAuthor(author);
    // And go there
    return nextPage; 
}
 
public WOComponent editAuthor() { 
    // Where are we going?
    AuthorEdit nextPage = (AuthorEdit)pageWithName("AuthorEdit");
    // Pass in the selected author
    nextPage.setAuthor(authorItem);
    return nextPage; 
}
 
public WOComponent deleteAuthor() { 
    // Delete the selected author
    ec().deleteObject(authorItem);
    ec().saveChanges();
    return null; 
    // I prefer to return this.context().page();
    // 'cause it always works, even with complex 
    // embedded components
}
 
// We're gonna need this a lot. 
// Might as well make it easy on ourselves
public EOEditingContext ec() {  
    return session().defaultEditingContext(); 
}

Finaly the relevent code for the AuthorEdit:

public WOComponent backPage;
 
public AuthorEdit(WOContext context) {
    super(context);
    // This be magic!
    // Because the component constructor is called
    // from (Blah)pageWithName("Blah")
    // backPage gets a reference to the calling component
    // Very useful. Don't remember who I learned
    // that from, but thank you!
    backPage = this.context().page();
}
 
public WOComponent saveChanges() { 
    ec().saveChanges();
    return backPage; 
}
 
public WOComponent revert() { 
    ec().revert();
    return backPage; 
}
 
// We're gonna need this a lot. 
// Might as well make it easy on ourselves
public EOEditingContext ec() {  
    return session().defaultEditingContext(); 
}

To my thinking this code is no extra work. It fulfills the requirements of the EOF Commandments and it makes the UI clearer to boot. Most importantly it gives a novice WebObjects developer a strong foundation on which to build. This pattern can be repeated over and over again in their own projects without any risk of invoking the wrath of EOF.

When I get a chance I’ll build this as an Xcode project and post it.