View this PageEdit this PageAttachments to this PageHistory of this PageHomeRecent ChangesSearch the SwikiHelp Guide
Hotspots: Admin Pages | Turn-in Site |
Current Links: Cases Final Project Summer 2007

Getting started with WebVelocity by Patrick Dillon

WebVelocity is a brand_new (as of 2009) web application development framework designed and distributed by our friends at Cincom Systems inc. Version 1.0 went public back in June, so there's very limited documentation available.

You can download it here, and there are also links to a bunch of tutorials, but they are missing a few very necessary topics.

WebVelocity runs as a standalone application, but editing can also be done with Firefox 3.5. (others are not supported but mostly still work). By default all you need to do is start up WebVelocity and it opens up your default browser. In the WebVelocity application window, if you go to the System menu you can choose to open the System Browser which looks eerily like VisualWorks (but does look a little newer).

After you have WebVelocity, you need a database server running on your machine. Our group used Postgres, but WebVelocity supports a nice range of databases, and it doesn't matter too much which one you choose. You can find the list of supported databases when you make your first Web Application.

Making your first Web Application
In Firefox, get to the Web Velocity Developement Home Page by going to "http://localhost:7777/deveopment". Now you click on the New menu link and go to Application. Then enter a name and make sure generate schema is checked (this allows your program to access a database). Now you are on the main development page for your application. Click Database (which is red) and choose the database running on your computer (if none, then pick one and install it). Once that is done, you can start defining the tables in your database. Go to Classes and click on Schema1, this is where your database tables are defined. Now click on Source Code, go down to the "tables" protocol and click the arrow to open up that protocol, and click the arrow for (new method). It will auto generate a generic table example.

example table definition:
tableForTREATMENTS: aTable
	| patient_id user_id doctor_id|
	(aTable createFieldNamed: 'id' type: platform serial) bePrimaryKey.
	aTable addAsLockKeyField: (aTable createFieldNamed: 'last_modified' type: platform timestamp).
	aTable createFieldNamed: 'date_created' type: platform timestamp.
	aTable createFieldNamed: 'current' type: (platform boolean).
	aTable createFieldNamed: 'cost' type: (platform float).

	patient_id := aTable createFieldNamed: 'patient_id' type: platform integer.
	aTable addForeignKeyFrom: patient_id to: ((self tableNamed: 'patients') fieldNamed: 'id').
	doctor_id := aTable createFieldNamed: 'doctor_id' type: platform integer.
	aTable addForeignKeyFrom: doctor_id to: ((self tableNamed: 'doctors') fieldNamed: 'id').
	user_id := aTable createFieldNamed: 'entered_by' type: platform integer.
	aTable addForeignKeyFrom: user_id to: ((self tableNamed: 'application_users') fieldNamed: 'id').

	aTable createFieldNamed: 'created' type: platform timestamp.
	aTable createFieldNamed: 'last_updated' type: platform timestamp.
	aTable createFieldNamed: 'content' type: platform text.

(aTable createFieldNamed: 'id' type: platform serial) bePrimaryKey.

	"Primary Key: used to identify the table row (glorp object instance)."

relationship_id := aTable createFieldNamed: 'object_id' type: platform integer.
aTable addForeignKeyFrom: relationship_id to: ((self tableNamed: 'Table2') fieldNamed: 'id').

	"Foreign Key: used to reference a row in another table (i.e. if each Person has one Address).
	You would put the foreign key in the Address table to a Patient. Attribute must be named
	'classname_id' unless you want to write a descriptor."

Once you have all that information filled out, you can start making your classes and gui. Click on Actions and then click on recreate tables. If there is no option to recreate tables you must have made an error in defining your tables. Once you recreate your tables, it will take you to the Mappings section for Schema1. From here it will give you a list of unmapped tables and will ask you if it should make classes for those objects. Click on the singular version of the class you want create (e.g. Patient or Student) and then in the dialog choose only "EditUI" "ViewUI", "ListUI", "Create variables from columns", and
"Create accessors for variables" (you can always go back and add UnitTests later).

Now you are inside your new class's definition under "Mappings". If there are any "unmapped variables" that you want to be able to access from your object, you should click on them and tell it to add them. Now you can take a look at the UI generated by the scaffolding by running yor webapplication by going to Browse > classname.html . If after you do this you try running your program and it doesn't work, WebVelocity could not resolve the mappings for that variable, so you have to define it yourself in mappingForCLASSNAME in your schema, which is beyond the scope of this tutorial.

Say you created a class called Patient. Now navigate back to the Patient class definition and click on Source Code. On your screen should be some accessors for your Patient object. To see all the inherited methods, to go Visibility > ActiveRecord or lower, and you will see all the things your Patient object can do by default.

Now click on Classes > PatientEditUI. This is the definition for the Edit and New UI for Patients. This should be very empty at the moment.
To see all the inherited methods, change the Visibility to GenericEditUI. Here you will se a ton of rendering methods. Here's how the rendering works.

Under "Class Methods", initialize will define some entry points, and components if you added any components. If you done want this UI to be a entry point, you can just redefine initialize to do nothing. Any changed you make will only be made in your class or classes that inherit from it. With visibility set at your classes super, if you edit something, it will not overwrite the method in for instance GenericUI, your class will get its own modified copy.

Now that you have entry points, you will have those links in the Browse menu. When you click on one of those it sends a request to WebVelocity to serve you that page, this sends a message to the class PatientEditUI,ListUI or whichever approprite. The message is initialRequest: aRequest . This method decodes part of the URL to make an object if necessary. Say you click on the "new.html". Then a message "renderHtmlContentOn: html" gets sent to the PatientEditUI class. Which then calls renderPageHeaderOn: html, renderPageContentOn: html, renderPageSidebarOn: html, and renderPageFooterOn: html.

GenericEditUI >> renderPageContentOn: html
	" Render the HTML version of our class. "

	html form
		id: 'form';
			[html div class: #actions; with: [self renderActionsOn: html].
			html div class: #details; with: [self renderDetailsOn: html]]

This creates an form on the html with an id:'form' and which contains whatever "renderActionsOn: html" and "renderDetailsOn: html" render.
renderActionsOn: creates the buttons at the top of the table, and renderDetailsOn: creates the table of attribtues and editors of your object. renderDetailsOn: html iterates through all of your instance variables of a Patient object and creates a row label and an editor for the instance variable.

Modifying Labels, Editor, Rendering Order:
The code in GenericEditUI tries to call a couple methods relating to each instance variable you have in your object. Creating these methods will give you control over what is rendered and how.

Protocol: accessing
PatientEditUI > variableNames
Returns an OrderedCollection of the instance variables. This is where you can order or exclude instance variables from showing up as editable.

PatientEditUI > variableNames
	|ordered remove|
	ordered := OrderedCollection withAll: #('firstName' 'lastName' 'doctor' 'room' 'treatments' 'patientNotes' 'current').
	remove := OrderedCollection withAll:  #('currentAudit' 'edited').
	ordered := self variableNamesOrder: ordered remove: remove.

	(self currentUser canRead: 'patient_notes') ifFalse: [
		ordered remove: 'patientNotes' ifAbsent: []].

	(self currentUser canRead: 'treatments') ifFalse: [
		ordered remove: 'treatments' ifAbsent: []].
	(object isInDatabase) ifFalse: [
		object current: true.
		ordered remove: 'current' ifAbsent: []].

PatientEditUI > variableNamesOrder: ordered remove: remove
	vars := super variableNames.
	vars := vars reject:[:v | (remove anySatisfy: [:r | v = r ]) | (ordered anySatisfy:[:o | v = o])].
	^ordered addAll: vars; yourself.

PatientEditUI > shouldRenderVARIABLENAME
	"returns true by default, return false if you dont want the variable to show up (called by super variableNames)"
PatientEditUI > renderLabelVARIABLEOn: html
	"renders what the label for that varaible name should be."

PatientEditUI > renderLabelDoctorOn: html
	^html span with:'Prefered Doctor'

PatientEditUI > renderEditVARIABLENAME: id on: html
PatientEditUI > renderEditVARIABLENAMEOn: html

PatientEditUI > renderEditGroup: id on: html
	"some condition"
			[html select
				id: id;
				list: UserGroup findAll;
				class: UserGroup;
				selected: (object group)]
			[html select
				id: id;
				list: object group;
				class: UserGroup;
				callback: [:value |];
				selected: (object group);

In the above code, if the condition is false, the group select input is disabled, but that would be re-enabled using javascript on the clients end, so the callback is set to an empty block.

PatientEditUI > validateVARIABLENAME
PatientEditUI > validateVARIABLENAME:value

PatientEditUI > validateDoctor: value
	value ifNil: [^true].
	self validateMapping: 'doctor'

This allows a relationship to be nil but still checks that it is a Doctor.

Sending an error message of your own:
errors at: variable put: (self mandatoryErrorFor: variable)

PatientEditUI > yourErrorFor: variable
	^#yourError << self class environment name  >> 'your message'

to be continued....

Link to this Page