|
|
AJAX is a process by which a browser can communicate with the server without loading a new page or reloading the current page. This allows a substantially richer user interface within the context of a given page, because the page may be dynamically updated in response to information received from the server. In general, manual programming of AJAX requires a lot of tedious mucking about in Javascript.
In Water, AJAX is instead written using biz objects, which use AJAX but do so in a manner by which you, the programmer, do not have to deal with the gory details of Javascript. The biz object also provides several other features relating both to user interface development and to organization of code. To outline some of its benefits, it:
The biz class helps you create complex web applications that behave more like non-web applications in that you can "refresh" just part of a page rather than refresh the whole page when it changes, as is common in web applications. This is quicker and gives the user a more graphically "solid" experience. It also gives you a lot more power, especially as your application grows in complexity.
By sticking to this technique of building an application you can avoid having to know the details of how the client and server communicate and keep all of your code in Water rather than resorting to Javascript, as is common for dynamic web pages.
A biz application consists of water classes which you write that all inherit from the 'biz' class. Usually, each of the classes will have:
Understanding Model/View/Controller
Model/View/Controller (MVC) is a programming pattern in which data, presentation, and business logic are kept separate. The "model" is your business data, as structured in objects. The "view" is the presentation code which outputs your data in a useful format, such as HTML. The "controller" is the business logic, represented as program source, which alters or updates the model. When used, the user interacts with the view, which causes a controller to update the model, and then updates the view. We'll get back to how this relates to biz objects.
Related objects
The biz object uses ui_view objects to position content on the web page, style objects to apply color and font formatting to the content, and layout objects to position objects with respect to each other. We'll get back to those objects, you don't have to know how they work to get started.
In Water, AJAX may be programmed integrally, so that no herculean effort is required to make AJAX work. However, a specific programming pattern is required.
Programming Pattern for AJAX in Water
Subclass biz
You'll generally create a subclass of biz for your purpose:
<class biz.boat name="Titanic"=string/>Where to put your content
In your class, there must be a method htm_inst which returns the content which is to represent the object on the page. This outputs the "view" part of the MVC pattern. An example:
<class biz.boat name="Titanic"=string> <method htm_inst> <P>The name of the boat is <do .name/></P> </method> </class>A default htm_inst method is provided for you that fulfills this requirement, so you don't always have to write one yourself if you don't want to. It returns the content of all the vector keys of the biz object.
Please note that when subclassing the biz object, you generally shouldn't shadow the default htm_inst method, which does much of the work of the biz object for you.
Separating logic from presentation
You also have to have a method that is called via AJAX to update the data model. This is the "controller" part of the MVC pattern. This method must also tell the browser what to do in response to its biz method call, even if you just want it to do nothing. (If you don't put in something to tell the browser what to do in response to its biz method call, an error will occur on the browser.) For our example, let's say that we want the user to be able to change the name of the boat, and when they do we want to update the object's representation on their browser to reflect the change:
<method change_name new_name=req=string> .<set name=new_name/> .<refresh/> </method>Note the use of the refresh method to update the content on the browser. That will generate fresh html using the htm_inst method, which will be sent to the browser with instructions to replace the existing content of the browser's representation of the object with the fresh content. This is how we tell the browser what to do as a response to the method call. Responses such as refresh are cumulative because they have side effects ont he server object, so you may in fact choose to refresh several objects in response to a single method call. The returned result of the method must be a response to the browser (such as a call to refresh), but due to the above mentioned side effects, not all responses must be returned by the method. For example, this would accomplish exactly the same thing as the preceding example:
<method change_name new_name=req=string> .<set name=new_name/> .<refresh/> .<do_nothing/> </method>It is required that your Water method in your biz object which is called by the browser must return a response to the browser because the browser is expecting a specifically formatted response, and any other returned value from the method would not be correctly formatted, and would therefore cause an error to occur on the browser.Make an instance
For our example, we will need to make an instance of our object:
<set myBoat=biz.<boat/> />Using form elements
For the user to update anything they have to have some interface elements for that, so the htm_inst method should have form elements. There are several differences between doing forms with AJAX in Water versus doing ordinary HTML forms.
No form tags in the biz content
The first is, in Water, you don't put a
formtag around the content that you want to represent your "form" for the biz object. Form fields which are presented by an individual biz object are assumed to be related and act as a "virtual form" for that object instance. (If you examine the HTML output, you will find that the presentation of the biz object is wrapped in adivtag, which encloses the "virtual form".) Just present the biz object and think of its form fields as if they are their own form, distinct from others. Here is an example of how this looks to you in Water:<method htm_inst> <P>The name of the boat is <do .name/>.<BR/> Change it to: <input type="text" id="boat_name" value=.name/> </P> </method>Some notes about this: It's okay to use the same field name in different classes, knowing that they'll appear on the same actual web page. Water's client-side code can determine the difference. Also, using several instances of the same class on the same page is also fine: Water's client-side code can determine the difference between those too. However, it is a bad idea to cause a single instance of a biz object to appear on the same page multiple times, as it becomes impossible to determine which copy of the instance is meant when AJAX method calls are prepared. Doing this will cause incorrect behaviors and is therefore strongly advised against.
Nested forms
The second difference is, "virtual forms" may be nested. A biz object's presentation may include as part of its content the presentation of another biz object. Each may have form elements. When the inner object's form is used and its content is refreshed, the outer object is not affected. When the outer object's form is used and its content is refreshed, the inner object's complete representation is refreshed as part of the content of the outer object.
No submit buttons, use handlers instead
The third difference is, instead of using a submit button to cause the web page to submit and retrieve a new page, handler attributes on hypertext elements are used to cause the action of an AJAX method call to the Water server, and the Water server determines which, if any, parts of the page should be altered or refreshed. The submit element should not be used. A special method called h2o is used to wrap around the method call that you wish to use when the action is performed on the browser. Here is an example of an HTML button which emulates the behavior of a
submitbutton in the Water AJAX context, for our boat example:<INPUT type="button" value="Submit" onClick=.<h2o> .<change_name new_name=boat_name/> </h2o> />In the above example, new_name is the key of the parameter of the call to change_name for which we are setting a value, and boat_name is the
idof the html form element from which the value to be set is to be retrieved. Instead of theidof an html form element, a literal value may also be used if the literal is a string or a number.Serving your page with biz: minimum page requirements
When you use a biz object in a web page, there are some requirements which must be fulfilled in order for the biz functionality to work.
Requirements of HTML output
First, there is a required method call for the page head which imports required Javascript. Then, specific handlers must be placed in the body tag of the page. Finally, the content of the page must be wrapped in a form tag, which should be configured not to automatically submit. Here is a sample of the bare minimum html page required to use biz objects in Water, as it would be seen by the browser:
<HTML> <HEAD> <do biz.<include_scripts/> /> <TITLE>Example</TITLE> </HEAD> <BODY onMouseDown="handleMouseDown(event);" onMouseUp="handleMouseUp(event);" onMouseMove="handleMouseMove(event);" > <FORM action="foo" onSubmit="return false;"> Content here </FORM> </BODY> </HTML>Relax: You don't have to remember that!
There are two methods by which you can generate a page which has all these requirements in it without having to do it manually. First, there is an object which will make the required alterations to an HTML page for you, so you don't have to worry about accidentally forgetting a detail. Instead of using the plain
htmlobject, just use the biz.html object instead, as shown in this corresponding example:biz.<html> <HEAD> <TITLE>Example</TITLE> </HEAD> <BODY> Content here </BODY> </html>Please note that the two preceding examples output approximately identical HTML to the browser. Also, the
biz.htmlclass applies basic HTML formatting for you, so if you didn't care about the title, the preceding example could be represented as follows:biz.<html> Content here </html>An even easier method
There's an easier way to get the benefits of biz.html without having to call it directly: simply make sure that the object that is served which represents the page is a biz object, and its output will be wrapped with a biz.html for you.
Keep in mind that because the entire contents of the page as output to the browser are contained within a
formtag, you can't use the form object in your page content. However, it's unnecessary when using biz objects, so please don't be concerned.Complete example
Finally, we make a page with our object in it. Here is a complete example showing our working boat object, using the biz.html method. Please note that when we display the object, we need to call its htm_inst method.
<class biz.boat name="Titanic"=string> <!-- The structure of the class represents the model --> <method change_name new_name=req=string> <!-- This is the controller method --> .<set name=new_name/> <!-- This updates the model --> .<refresh/> <!-- This updates the view --> </method> <method htm_inst> <!-- This method creates the view --> <P>The name of the boat is <do .name/>. <BR/> Change it to: <input type="text" id="boat_name" value=.name/> <INPUT type="button" value="Submit" onclick=.<h2o> .<change_name new_name=boat_name/> </h2o> /> <!-- Notice the use of "button" --> </P> </method> </class> <set a_Boat=biz.<boat/> /> <!-- Here we have created an instance --> <!-- Next we'll make our page --> wob.<set a_page=biz.<html> <do a_Boat /> </html> /> <!-- That's the end of the set statement for "a_Page" --> <server root=wob port=8080/> <!-- That starts the server --> <open_browser_window "http://localhost:8080/a_page"/> <!-- That opens the browser to view our page -->Now, here's the same example using the method of serving up a biz object.
<class biz.boat name="Titanic"=string> <!-- The structure of the class represents the model --> <method change_name new_name=req=string> <!-- This is the controller method --> .<set name=new_name/> <!-- This updates the model --> .<refresh/> <!-- This updates the view --> </method> <method htm_inst> <!-- This method creates the view --> <P>The name of the boat is <do .name/>. <BR/> Change it to: <input type="text" id="boat_name" value=.name/> <input type="button" value="Submit" onclick=.<h2o> .<change_name new_name=boat_name/> </h2o> /> <!-- Notice the use of "button" --> </P> </method> </class> biz.<boat/> <!-- Here we have created an instance --> <server root=wob port=8080/> <!-- That starts the server --> <open_browser_window "http://localhost:8080/biz/boat/of/0.htm"/> <!-- That opens the browser to view our page -->This example demonstrates how the biz object works, but does not show many of the advantages of using it, because it has only one object. A large advantage of using biz objects on a more complex page is that objects can update small parts of the user interface without necessarily needing to update the whole thing. This is advantageous because it provides a smoother experience for the user (their page is not constantly being re-written in whole) and because it reduces load on the server (the server is not constantly generating entire pages, just small snippets).
Related Methods
You may use CSS positioning to precisely position the output of a biz object on a web page to the precise pixel you desire, and to set its size to the precise size you desire. This is easily accomplished with the ui_view object. Simply create a ui_view with the properties you desire and pass it into the biz object's ui_view attribute, and the biz object will use the ui_view for positioning and sizing.
<class biz.position_example/> biz.<position_example ui_view=<ui_view x=200 y=100/> >Hello World</position_example> <server root=wob port=8080/> <open_browser_window "http://localhost:8080/biz/position_example/of/0.htm"/>
To arrange the contents of the vector keys of a biz object, create an instance of layout or one of its subclasses and pass this into the biz object's layout attribute. The contents of the vector keys will be arranged according to the specifications of the layout used.
<class biz.layout_example _other_unkeyed=opt/> biz.<layout_example layout=layout.<column/>> <H1>Water</H1> <H1>rocks</H1> </layout_example> <server root=wob port=8080/> <open_browser_window "http://localhost:8080/biz/layout_example/of/0.htm"/>
Note that if you shadow the default htm_inst method for your biz object, objects which are not biz or ui_view objects may not appear in their correct places, because the default ui_view method is part of the process of wrapping such objects in ui_views. A solution to this is to place any such objects which you're placing in the vector keys of the biz object in a ui_view. Here's another version of our previous example with a new htm_inst, which will work in this manner:
<class biz.layout_example _other_unkeyed=opt=span>
<method htm_inst>
.<for_each combiner=join>
<if>
value.<is_a ui_view/>
<do value.<htm_inst/> />
else
<do value/>
</if>
</for_each>
</method>
</class>
biz.<layout_example layout=layout.<column/>>
<ui_view><H1>Water</H1></ui_view>
<ui_view><H1>rocks</H1></ui_view>
</layout_example>
<server root=wob port=8080/>
<open_browser_window "http://localhost:8080/biz/layout_example/of/0.htm"/> Related fields
Each biz object has a ui_view for positioning the content on the page. If you do not supply a ui_view, one will be instantiated for you. (A ui_view with no specified parameters doesn't do much, so don't worry about it.)
Details of how the ui_view functions can be found in its documentation. For purposes of use with a biz object, the ui_view will behave as if the content of the biz is the content of the ui_view.
Holds a layout object. A layout's purpose is to arrange the objects in the vector keys of the biz object. Details of how the layout functions can be found in its documentation. For purposes of use with a biz object, the layout will behave as if the content of the biz is the content of the layout.
For a biz object to be draggable, it must be positioned using a ui_view as described in the above section on positioning. Then, set its draggable attribute to true. The user will be able to drag the object's HTML representation around the page, and it will stay where it is put, unless you've done something to alter this behavior (as described below).
For a biz object to be a drop area, it must be positioned using a ui_view as described in the above section on positioning. Then, set its accepts_drops_of attribute to either a class or a vector of classes which are the parents of whatever types of objects you want the drop area to accept. If you want it to accept drops of all draggable objects, set accepts_drops_of to biz .
In this example, we create a grey drop area and three colorful blocks which may be dropped on it. This illustrates default behaviors of draggable and drop area objects. We also make the drop area draggable, to illustrate that a drop area itself may be a draggable (and drop-able) object.
<class biz.color_box acolor=req=color draggable=true>
<method htm_inst>
<span>
<TABLE border=0 cellpadding=0 cellspacing=0 bgcolor=.acolor>
<TR><TD width=50 height=50> </TD></TR>
</TABLE>
</span>
</method>
</class>
<class biz.drop_area/> <!-- That defines our drop area. -->
<class biz.drag_drop>
<method make>
.<set a_drop_area=biz.<drop_area
draggable=true
style=<style background-color="#ddeedd"/>
accepts_drops_of=biz.color_box
ui_view=<ui_view x=10 y=100 width=300 height=300/>
/>
/> <!-- end of set a_drop_area -->
.<set red_box=biz.<color_box acolor=<color 255 0 0/> ui_view=<ui_view x=100 y=10/> /> />
.<set green_box=biz.<color_box acolor=<color 0 255 0/> ui_view=<ui_view x=200 y=10/> /> />
.<set blue_box=biz.<color_box acolor=<color 0 0 255/> ui_view=<ui_view x=300 y=10/> /> />
.<next_method/>
_subject
</method>
<method htm_inst>
<BODY>
<do .a_drop_area />
<do .red_box />
<do .green_box />
<do .blue_box />
</BODY>
</method>
</class>
biz.<drag_drop/> <!-- That creates our instance. -->
<server root=wob port=8080/>
<open_browser_window "http://localhost:8080/biz/drag_drop/of/0.htm"/> | Parameter key | Default value | Type |
| draggable | opt | boolean |
| Parameter key | Default value | Type |
| accepts_drops_of | opt | wob |
May be set to a class or a vector of classes. Instances of these classes will be accepted as "drops" by this biz object, making this biz object a droparea.
A subwindow is an HTML construct that has the appearance and behaviors of a window, but which appears inside the display area of a web browser. To cause a biz to be represented as a subwindow, set the chrome attribute of its ui_view to true.
<!-- First we'll define an object which lets us select a color and shows a sample. -->
<class biz.color_picker a_color="7777ff"=string draggable=true>
<method change_color new_color=req=string>
<!-- This method is the controller that updates our model. -->
.<set a_color=new_color/>
<!-- As usual, it ends by refreshing the content. -->
.<refresh/>
</method>
<!-- To illustrate altering the presentation, here's a controller method that makes
the subwindow jump to the X position of 500 from wherever it is. -->
<method jump>
<!-- This is another controller method that updates our model. -->
.ui_view.<set x=500 />
<!-- In this case, we want to update the formatting of the content, not the content itself.
We do that using the ui_refresh method. -->
.<ui_refresh/>
</method>
<method htm_inst>
<!-- This method presents our view. -->
<v
<TABLE border=1 cellpadding=0 cellspacing=0 bgcolor=.a_color>
<TR><TD width=50 height=50> </TD></TR>
</TABLE>
<INPUT type="text" id="the_color" value=.a_color/>
<INPUT type="button" value="save" onclick=.<h2o> .<change_color new_color=the_color/> </h2o> />
<INPUT type="button" value="jump" onclick=.<h2o> .<jump/> </h2o> />
/>
</method>
</class>
<!-- Next we'll make an instance of our class. -->
biz.<color_picker
draggable=true
style=<style background-color="#dddddd"/>
ui_view=<ui_view x=350 y=100 width=200 height=100 chrome=true resizeable=true/>
/>
<server root=wob port=8080/>
<open_browser_window "http://localhost:8080/biz/color_picker/of/0.htm"/> | Parameter key | Default value | Type |
| resizeable | false | boolean |
The parameters onload, onunload, and onchange are presently unused by biz. They are included for planned future functionality.
In this example, we'll create a system by which the user may select something from a hierarchical list, one leval at a time. When they've made their final selection, we'll display a message indicating what it is.
<class biz.selection
title=req=string <!-- That will store the name of this selection -->
selected=opt=number <!-- That will store the item they've selected -->
_other_unkeyed=opt=wob=ekind.code <!-- That will store sub selections -->
>
<!-- This controller method allows the user to update the model. -->
<method change_to chosen=req>
.<set selected=<execute source=chosen/> />
.<refresh/>
</method>
<!-- Our view method first constructs the SELECT item, then displays stuff. -->
<method htm_inst>
<!-- First we create an instance of hypertext.select -->
<set a_select=<select
size=5
id="choice"
<!-- This will update the server when the user makes a selection -->
onchange=.<h2o> .<change_to chosen=choice/> </h2o>
/> <!-- end of select -->
/> <!-- end of set -->
<!-- Now we'll insert options into it for each sub choice after this. -->
.<for_each>
<if>
value.<is_a biz.selection/>
a_select.<insert <option
value=key
selected=<if>
key.<equal .selected/>
true
else
false
</if>
<!-- That sets the default selection. -->
>
<do value.title/>
</option>
/> <!-- end of insert -->
else
a_select.<insert <option
value=key
selected=<if>
key.<equal .selected/>
true
else
false
</if>
<!-- That sets the default selection. -->
>
<do value/>
</option>
/> <!-- end of insert -->
</if>
</for_each>
<!-- now that we've constructed the select, we'll output some hypertext. -->
<span>
<!-- Show the current selection title -->
<h1><do .title/></h1>
<!-- Next, show the selections if there is a next level of choices. -->
<if>
.<length/>.<more 0/>
<do a_select/>
</if>
<!-- Now show the selected item. -->
<if>
.selected.<is_not opt/>
<if>
.<get .selected/>.<is_a string/>
<blockquote><h1>You selected <do .<get .selected/> /></h1></blockquote>
else
<blockquote><do .<get .selected/> /></blockquote>
</if>
</if>
</span>
</method>
</class>
<!-- Here's a sample structure to toy with. -->
<set structure=biz.<selection title="Restaurant Supplies">
biz.<selection title="For the Office">
biz.<selection title="Paper">
"Note pads"
"Printer paper"
"Envelopes"
"Yellow stickies"
</selection>
biz.<selection title="Pens">
"Disposable ball point"
"Permanent marker"
</selection>
</selection>
biz.<selection title="For the Kitchen">
"flour"
"sugar"
biz.<selection title="Milk">
"whole"
"2%"
"skim"
</selection>
"chicken"
</selection>
</selection>
/>
<!-- Finally, we'll serve our example to the browser. -->
<server root=structure port=8080/>
<open_browser_window "http://localhost:8080/.htm"/> Debugging a biz object can be a bit unusual, because you're viewing the application through the browser rather than the development environment. Three error messages are included in the browser-side code which handles biz objects on the browser to assist you in understanding problems. The language of these error messages is written to try to sound unthreatening to an average web user, rather than to directly be highly informative, so you may find it helpful to be familiar with the errors so you know what causes them. The first:
| We're sorry, but the browser is not compatible with the web standards required by the page you are viewing. |
If the above message appears on the browser, the browser was unable to create an AJAX connection object as required by biz. This generally indicates that the browser used is not supported. In other words, it's not your fault.
| We're sorry, but the server has experienced a difficulty, and this page is consequently unable to perform one of its tasks. Please try again later. |
If the above message appears on the browser, an error has occurred on the Water server, and the problem must be diagnosed in the Water code being executed.
| We're sorry, but an unusual status message was received from the server. This may be unimportant and everything may be fine, or it may indicate that there has been a problem which may require you to try again later. Should the web site administrators request further detail, they may wish to know the following information: |
The above message indicates that a status code other than the usual 200 was received by the browser in response to its AJAX request to the browser as part of its biz functionality. This would normally be caused by a problem not directly related to the Water server or the browser itself, such as an error generated by the web host on which the Water server resides or a communication error. Should this type of error occur, the preceding error text would be followed by the response code number received from the server, the response name text received from the server, and finally any additional response text received from the server.
When you get this message, a good first step is to insert an echo at the beginning of your controller method to ensure that it's actually getting called. If it is, if you can't see the problem in it immediately, then you may wish to try commenting out sections of it to see what part of it the error is taking place in. If it is not, you should examine the call to h2o to ensure nothing is misspelled, that variable names are all correct, and that you're serving the page in a manner appropriate to meet the page requirements outlined above.
If you get an error with error code 404, a good quick first test is to look in the make method of your biz subclass, if you
wrote a make method. If you did write a make method but forgot to call .<next_method/> , that would cause this
problem.
© Copyright 2007 Clear Methods, Inc.