0
Ajax on Struts:
Coding an Ajax Application with Struts 2
 Wednesday, October 1st, 1:50p-2:50p
             Ted Husted
 In ...
Ajax on Struts:
Coding an Ajax Application with Struts 2




                       Square One University Series
Ajax on Struts

For the latest version of this presentation,
visit http://slideshare.com/ted.husted
For the latest version...
Abstract

 Ajax is the web's hottest user interface.
Struts is Java's most popular web
framework. What happens when we put...
Ajax on Struts
What are Ajax widgets?
What are we coding?
Is there an Ajax architecture?
How can we switch to server-side ...
Ajax on Struts
What are Ajax widgets?
What are we coding?
Is there an Ajax architecture?
How can we switch to server-side ...
What are Ajax widgets?
Ajax lets scripts make requests and
update content without a page refresh
Widgets are “black-box” u...
What are Ajax widgets?
Widgets can be used with any server platform
  PHP, Java, .NET, Ruby, Python

Client-side widget p...
What is Apache Struts?
Free open-source framework for creating
Java web applications
Provides three major components
 Req...
Can we use Ajax with a Struts
        application?
XHR is just another request/response
Struts can stream data as a respon...
Why use Apache Struts?
Mature, well-supported, well-understood
Provides input validation and data conversion
Interacts wel...
Where do we start?
Case study
 Pure Ajax prototype

 Test data encapsulated as JSON

 Later, replace test data with JSO...
How do we select an Ajax library?
 Submit before you commit
 Code your own sample application
 Pick the one that works for...
http://dojotoolkit.org/
http://developer.yahoo.com/yui/
Why Yahoo User Interface
      (YUI) Library?
Well documented and supported
Lots of working examples
New BSD license
Easy ...
Ajax on Struts
What are Ajax widgets?
What are we coding?
Is there an Ajax architecture?
How can we switch to server-side ...
What did we code?
1 Provide a list of employees
2 Record employee's first name, last name,
extension, username, hire date,...
What did we code?
Use YUI DataTable to display list
Share DataTable record with data-entry
form
 Use TabView to separate l...
What are we going to code?
Prototype with static data
Request data from Struts 2 Action
   Use Struts 2 Plugin to return ...
How does the Struts 2
          JSON plugin work?
    Exposes action properties as fields in a
    JSON record.
{
    stri...
public class ExampleOutputAction {

   private String stringValue = "A string value";
   public String getStringValue() {
...
private String nextRecord = "start a named record";
    @JSON(name="record")
    public String getNextRecord() {
        r...
{
 "intArray":[10,20],
  "map": { "Arthur": "Monkey-Boy",
           "Zaphod":"Just this guy, you know"},
  "record":"star...
package actions;

import   java.util.Map;
import   java.util.HashMap;
import   com.googlecode.jsonplugin.annotations.JSON;...
How did we code a UI using
       static data?
Entry list
Entry form
List shares data with form
Ajax on Struts
What are Ajax widgets?
What are we coding?
Is there an Ajax architecture?
How can we switch to server-side ...
Is there an Ajax architecture?
<script src="my.js" type="text/javascript">
</script>

if (typeof parent.MY != "undefined")...
Is there an Ajax architecture?
<script src="contacts.js" type="text/javascript">
</script>



MY.Contacts = function(){
  ...
What's a FLEV widget?
Common business workflow is
 Find / List / Edit / View
 Or "FLEV"

The FLEV widget defines one col...
MY.Contact.prototype.oColumnHeaders = [
{key:"first_name", text:"First Name",
sortable:true, resizeable:true,
editor:"text...
MY.Contact.prototype.oResponseSchema = {
fields:["id", "last_name", "first_name",
"extension", "username", "hired", "hours...
my.oEvent.createEvent("contactLoad")

my.oEvent.onContactLoadReturn = function(oData)
{
  my.info("Firing contactLoad Even...
<script type="text/javascript">
 var onContentReady = function() {
  _Contact = new MY.Contact();
  my.oEvent.subscribe("c...
Ajax on Struts
What are Ajax widgets?
What are we coding?
Is there an Ajax architecture?
How can we switch to server-side ...
How can we switch over to
    server-side data?
Create an example Action that returns
example data
Create a database Actio...
public class ContactLoadAction {
private List contactLoad = new ArrayList();
@JSON(name="result")
public List getContactLo...
public class Contact {

 public Contact() {};

 public Contact(String id, String last_name, String first_name,
 String ext...
How can we switch over to
      server-side data?
Action data:
{"result":[{"editor":"1","extension":"555-123-
4565","first...
http://developer.yahoo.com/yui/connection/
<script>
var transaction = YAHOO.util.Connect.asyncRequest(
    'POST', sUrl, callback, null);
</script>

var callback =
{...
// my.oEvents.onContactLoadReturn(_Self.LOCAL_DATA);
YAHOO.util.Connect.asyncRequest('POST', "contact-
load.do", callback,...
{"result":[
    {"editor":"1","extension":"555-123-
4565","first_name":"Zaphod ", ...
]}

MY.Contact.prototype.LOCAL_DATA ...
// my.oEvents.onContactLoadReturn(_Self.LOCAL_DATA);
YAHOO.util.Connect.asyncRequest('GET', "contact-load.do",
callback, n...
What about JavaScript Hijacking?
/* {"result":[{"editor":"1","extension":"555-123-4565",
...
}]} */

var data = o.response...
What about JavaScript Hijacking?
<package name="my-default" extends="json-default">
 <result-types>
  <result-type name="j...
Ajax on Struts
What are Ajax widgets?
What are we coding?
Is there an Ajax architecture?
How can we switch to server-side ...
What about error handling?
var callback = {
   success : function(o) {
     var payload = eval("(" + o.responseText + ")")...
What about error handling?
var callback =
{
  success: function(o) {/*success handler code*/},
  failure: function(o) {/*f...
How does JSON-RPC handle errors?
{
    "version" : "1.1",
    "error" : {
      "name" : "JSONRPCError",
      "code" : 12...
How does Struts handle exceptions?
<global-results>
  <result name="error"/>
</global-results>

<global-exception-mappings...
How does Struts handle exceptions?
<h2>An unexpected error has occurred</h2>
  <p>Please report this error to your system
...
How does Struts handle exceptions?
public String getExceptionMessage() {
  ActionContext context = ActionContext.getContex...
How does Struts handle exceptions?
public String execute() throws Exception {
  throw new Exception("Whoops!");
// return ...
How do widgets handle errors?
<!-- ... -->

 </div>
 <div id="elError" class="error"></div>
 <div

<!-- ... -->
How do widgets handle errors?
my.asyncRequestException = function (o) {
  alert("Error Communicating with Server! See
mess...
How do widgets handle errors?
What about error handling?
var onContentReady = function() {
 _Contact = new MY.Contact();
 my.oEvent.subscribe("contactLo...
my.asyncRequest = function (sAction, fnCallback) {
 return YAHOO.util.Connect.asyncRequest("POST", sAction,
   {
     succ...
my.asyncRequest = function (sAction, fnCallback) {
 return YAHOO.util.Connect.asyncRequest("POST", sAction,
   {
     succ...
my.asyncRequestException = function (oPayload) {
 my.errorMessage(oPayload.exceptionMessage,
   oPayload.exceptionStack);
...
Is that all there is?
During this session, we covered
 Integrating an Ajax UI with Struts 2
 Using Yahoo User Interface ...
Struts University Series
Whats up with Planet Yazaar?
Development team Yahoo! Employees
  Something like Spring, Hibernate
  Unlike Apache projec...
Whats up with Planet Yazaar?
Development team Yahoo! Employees
  Something like Spring, Hibernate
  Unlike Apache projec...
What's up with Planet Yazaar?
Yazaar - (Yahoo + Bazaar = Yazaar)
Accepts and maintains contributor
extensions and document...
What's up with Planet Yazaar?
 Just as an aside ...
  Yahoo GeoCities for public web site
  Uses GoogleCode for reposito...
Square One University Series
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Coding an Ajax Application with Struts 2
Upcoming SlideShare
Loading in...5
×

Coding an Ajax Application with Struts 2

2,028

Published on

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,028
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
21
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Transcript of "Coding an Ajax Application with Struts 2"

  1. 1. Ajax on Struts: Coding an Ajax Application with Struts 2 Wednesday, October 1st, 1:50p-2:50p Ted Husted In this session, we explore  How to integrate an Ajax UI framework with a Struts 2 business framework.  Business services Struts can provide to an Ajax UI,  Basics of the Struts 2 web application framework.  Basics of the Yahoo User Interface Library (YUI).
  2. 2. Ajax on Struts: Coding an Ajax Application with Struts 2 Square One University Series
  3. 3. Ajax on Struts For the latest version of this presentation, visit http://slideshare.com/ted.husted For the latest version of source code, visit http://code.google.com/p/yazaar/
  4. 4. Abstract Ajax is the web's hottest user interface. Struts is Java's most popular web framework. What happens when we put Ajax on Struts? During the session, we will cover  Integrating an Ajax UI with Struts 2  Using Yahoo User Interface (YUI) Library  Using Struts to provide services to Ajax UI
  5. 5. Ajax on Struts What are Ajax widgets? What are we coding? Is there an Ajax architecture? How can we switch to server-side data? What about error-handling?
  6. 6. Ajax on Struts What are Ajax widgets? What are we coding? Is there an Ajax architecture? How can we switch to server-side data? What about error-handling?
  7. 7. What are Ajax widgets? Ajax lets scripts make requests and update content without a page refresh Widgets are “black-box” user interfrace (UI) components Typically, widgets are configured without touching the component's internals DataGrids, Calendars, Carousels, even TextBoxes
  8. 8. What are Ajax widgets? Widgets can be used with any server platform  PHP, Java, .NET, Ruby, Python Client-side widget provides UI Server-side technology provides data access and business logic
  9. 9. What is Apache Struts? Free open-source framework for creating Java web applications Provides three major components  Request handler  Response handler  Tag libraries for JSP, as well as Freemarker, and Velocity
  10. 10. Can we use Ajax with a Struts application? XHR is just another request/response Struts can stream data as a response Use JSP scriptlets Use Ajax JSP tag libraries Use plain-vanilla Ajax libraries
  11. 11. Why use Apache Struts? Mature, well-supported, well-understood Provides input validation and data conversion Interacts well with Spring, Hibernate, et al Defacto standard for Java web applications
  12. 12. Where do we start? Case study  Pure Ajax prototype  Test data encapsulated as JSON  Later, replace test data with JSON via XHR Use “spikes” to separates concerns
  13. 13. How do we select an Ajax library? Submit before you commit Code your own sample application Pick the one that works for you and yours We tried Dojo and YUI
  14. 14. http://dojotoolkit.org/
  15. 15. http://developer.yahoo.com/yui/
  16. 16. Why Yahoo User Interface (YUI) Library? Well documented and supported Lots of working examples New BSD license Easy to read code Easy to hack code
  17. 17. Ajax on Struts What are Ajax widgets? What are we coding? Is there an Ajax architecture? How can we switch to server-side daa? What about error-handling?
  18. 18. What did we code? 1 Provide a list of employees 2 Record employee's first name, last name, extension, username, hire date, and hours worked per week. 3 Record to be updated by authorized users 4 Allow list to be filtered by first name, last name, or username. 5 Allow full or filtered list to be sorted by any field
  19. 19. What did we code? Use YUI DataTable to display list Share DataTable record with data-entry form Use TabView to separate list and edit
  20. 20. What are we going to code? Prototype with static data Request data from Struts 2 Action  Use Struts 2 Plugin to return JSON
  21. 21. How does the Struts 2 JSON plugin work? Exposes action properties as fields in a JSON record. { stringValue : "A string value", intArray: [10, 20], map: { Zaphod : "Just this guy, you know", Arthur : "Monkey-boy" }, record: "start a named record" }
  22. 22. public class ExampleOutputAction { private String stringValue = "A string value"; public String getStringValue() { return stringValue; } public void setStringValue(String value) { stringValue = value; } private int[] intArray = {10, 20}; public int[] getIntArray() { return intArray; } public void setIntArray(int[] value) { intArray = value; } private Map map = new HashMap(); public Map getMap() { return map; } public void setMap(Map value) { map = value; }
  23. 23. private String nextRecord = "start a named record"; @JSON(name="record") public String getNextRecord() { return nextRecord; } //'transient' fields are not serialized @SuppressWarnings("unused") private transient String stayHome; //fields without getter method are not serialized @SuppressWarnings("unused") private String noGetterForMe; public String execute() { map.put("Zaphod", "Just this guy, you know"); map.put("Arthur", "Monkey-Boy"); return "success"; } }
  24. 24. { "intArray":[10,20], "map": { "Arthur": "Monkey-Boy", "Zaphod":"Just this guy, you know"}, "record":"start a named record", "stringValue":"A string value" } { stringValue : "A string value", intArray: [10, 20], map: { Zaphod : "Just this guy, you know", Arthur : "Monkey-boy" }, record: "start a named record" }
  25. 25. package actions; import java.util.Map; import java.util.HashMap; import com.googlecode.jsonplugin.annotations.JSON; import org.texturemedia.smarturls.ParentPackage; import org.texturemedia.smarturls.Result; @ParentPackage("json-default") @Result(name="success", type="json", location="") public class ExampleOutputAction { private String stringValue = "A string value"; // ...
  26. 26. How did we code a UI using static data? Entry list Entry form List shares data with form
  27. 27. Ajax on Struts What are Ajax widgets? What are we coding? Is there an Ajax architecture? How can we switch to server-side data? What about error-handling?
  28. 28. Is there an Ajax architecture? <script src="my.js" type="text/javascript"> </script> if (typeof parent.MY != "undefined") { var MY = parent.MY; // Prototype namespace var my = parent.my; // Variable namespace } else var var MY = {}; var my = {}; var MY.Event = { // ... } var my.oEvent = { // ... instantiate rest of my and MY
  29. 29. Is there an Ajax architecture? <script src="contacts.js" type="text/javascript"> </script> MY.Contacts = function(){ MY.Contacts.superclass.constructor.call(this); }; YAHOO.lang.extend(MY.Contacts,YAHOO.yazaar.flev-base);
  30. 30. What's a FLEV widget? Common business workflow is  Find / List / Edit / View  Or "FLEV" The FLEV widget defines one columnset and datasource to use with all four presentations
  31. 31. MY.Contact.prototype.oColumnHeaders = [ {key:"first_name", text:"First Name", sortable:true, resizeable:true, editor:"textbox", formClassName: "required", formTitle: "Enter employee's first name"}, // more entries ]; key and text are shared attributes sortable, resizable are List attribute formClassName, formtitle are Edit attributes (validation)
  32. 32. MY.Contact.prototype.oResponseSchema = { fields:["id", "last_name", "first_name", "extension", "username", "hired", "hours", "editor"] }; MY.Contact.prototype.LOCAL_DATA = { result : [{id: 'c5b6bbb1-66d6-49cb-9db6- 743af6627828', last_name: 'Beeblebrox ', first_name: 'Zaphod ', extension: '555-123- 4565', username: 'zaphie ', hired: '04/01/1978', hours: -1, editor: '1'}, // ... ];
  33. 33. my.oEvent.createEvent("contactLoad") my.oEvent.onContactLoadReturn = function(oData) { my.info("Firing contactLoad Event"); my.oEvent.fireEvent("contactLoad", oData); };
  34. 34. <script type="text/javascript"> var onContentReady = function() { _Contact = new MY.Contact(); my.oEvent.subscribe("contactLoad",_Contact.load, _Contact); my.oEvent.onContactLoadReturn(_Contact.LOCAL_DATA); }; YAHOO.util.Event.onContentReady("elBody", onContentReady); </script>
  35. 35. Ajax on Struts What are Ajax widgets? What are we coding? Is there an Ajax architecture? How can we switch to server-side data? What about error-handling?
  36. 36. How can we switch over to server-side data? Create an example Action that returns example data Create a database Action that returns persistent data
  37. 37. public class ContactLoadAction { private List contactLoad = new ArrayList(); @JSON(name="result") public List getContactLoad() { contactLoad.add(new Contact( "c5b6bbb1-66d6-49cb-9db6-743af6627828", "Beeblebrox", "Zaphod", "555-123-4565", "zaphie", "04/01/1978", "-1", "1" )); // ... return contacts; }
  38. 38. public class Contact { public Contact() {}; public Contact(String id, String last_name, String first_name, String extension, String username, String hired, String hours, String editor) { setId(id); setLast_name(last_name); setFirst_name(first_name); setExtension(extension); setUsername(username); setHired(new Date(hired)); setHours(new Double(hours)); setEditor(editor); } private String id; public void setId(String value) { id = value; } public String getId() { return id; } // more properties ...
  39. 39. How can we switch over to server-side data? Action data: {"result":[{"editor":"1","extension":"555-123- 4565","first_name":"Zaphod ", ... ]} JavaScript data: MY.Contact.prototype.LOCAL_DATA = {result:[{id: 'c5b6bbb1-66d6-49cb-9db6- 743af6627828', last_name: 'Beeblebrox ', first_name: 'Zaphod ' ...
  40. 40. http://developer.yahoo.com/yui/connection/
  41. 41. <script> var transaction = YAHOO.util.Connect.asyncRequest( 'POST', sUrl, callback, null); </script> var callback = { success: function(o) {/*success handler code*/}, failure: function(o) {/*failure handler code*/}, argument: [argument1, argument2, argument3] } var responseSuccess = function(o){ /* Please see the Success Case section for more * details on the response object's properties. * o.tId * o.status * o.statusText * o.getResponseHeader[ ] * o.getAllResponseHeaders * o.responseText * o.responseXML * o.argument */ };
  42. 42. // my.oEvents.onContactLoadReturn(_Self.LOCAL_DATA); YAHOO.util.Connect.asyncRequest('POST', "contact- load.do", callback, null); var callback = { success : function(o) { my.oEvent.onContactLoadReturn(o.responseText); } };
  43. 43. {"result":[ {"editor":"1","extension":"555-123- 4565","first_name":"Zaphod ", ... ]} MY.Contact.prototype.LOCAL_DATA = {result:[
  44. 44. // my.oEvents.onContactLoadReturn(_Self.LOCAL_DATA); YAHOO.util.Connect.asyncRequest('GET', "contact-load.do", callback, null); var callback = { success : function(o) { var payload = eval("(" + o.responseText + ")"); my.oEvent.onContactLoadReturn(payload); } }; - var payload = eval("(" + o.responseText + ")") ; + var payload = jcontext.parseJSON(o.responseText); http://json.org/json.js
  45. 45. What about JavaScript Hijacking? /* {"result":[{"editor":"1","extension":"555-123-4565", ... }]} */ var data = o.responseText; var payload = eval("("+data.substring(data.indexOf("/*")+2, data.lastIndexOf("*/"))+")"); my.oEvent.onContactLoadReturn(payload);
  46. 46. What about JavaScript Hijacking? <package name="my-default" extends="json-default"> <result-types> <result-type name="json" class="com.googlecode.jsonplugin.JSONResult" default="true"> <param name="wrapWithComments">true</param> </result-type> </result-types> <action name="contact-load" class="actions.ContactLoadAction"> <result /> </action> </package>
  47. 47. Ajax on Struts What are Ajax widgets? What are we coding? Is there an Ajax architecture? How can we switch to server-side data? What about error-handling?
  48. 48. What about error handling? var callback = { success : function(o) { var payload = eval("(" + o.responseText + ")"); my.oEvent.onContactLoadReturn(payload); } }; YAHOO.util.Connect.asyncRequest('POST', "contact- load.do", callback, null);
  49. 49. What about error handling? var callback = { success: function(o) {/*success handler code*/}, failure: function(o) {/*failure handler code*/}, argument: [argument1, argument2, argument3] }
  50. 50. How does JSON-RPC handle errors? { "version" : "1.1", "error" : { "name" : "JSONRPCError", "code" : 123, "message" : "An error occurred parsing the request object.", "error" : { "name" : "JSONError", "message" : "Bad array", "at" : 42, "text" : "{"id":1,"method":"sum","params":[1,2,3,4,5}"} } }
  51. 51. How does Struts handle exceptions? <global-results> <result name="error"/> </global-results> <global-exception-mappings> <exception-mapping exception="java.lang.Exception" result="error"/> </global-exception-mappings>
  52. 52. How does Struts handle exceptions? <h2>An unexpected error has occurred</h2> <p>Please report this error to your system administrator or appropriate technical support personnel. Thank you for your cooperation.</p> <hr/> <h3>Error Message</h3> <p> <s:property value="%{exception.message}"/> </p> <hr/> <h3>Technical Details</h3> <p> <s:property value="%{exceptionStack}"/> </p>
  53. 53. How does Struts handle exceptions? public String getExceptionMessage() { ActionContext context = ActionContext.getContext(); Object value = context.getValueStack(). findValue("exception.message"); if (value==null) return null; return value.toString(); } public String getExceptionStack() { ActionContext context = ActionContext.getContext(); Object value = context.getValueStack(). findValue("exceptionStack"); if (value==null) return null; return value.toString(); }
  54. 54. How does Struts handle exceptions? public String execute() throws Exception { throw new Exception("Whoops!"); // return "success"; } getExceptionMessage :” “Whoops!” getExceptionStack: “java.lang.exception Whoops! at actions.ContactLoadAction ...
  55. 55. How do widgets handle errors? <!-- ... --> </div> <div id="elError" class="error"></div> <div <!-- ... -->
  56. 56. How do widgets handle errors? my.asyncRequestException = function (o) { alert("Error Communicating with Server! See message area for details."); var sTemplate = "<table> <tr><th>Message:&nbsp;</th> <td>{exceptionMessage}</td></tr> <tr><th>Location:</th> <td>{exceptionStack}</td></tr> </table>"; var oPayload = eval("(" + o + ")") ; document.getElementById("elError").innerHTML = sTemplate.supplant(oPayload);
  57. 57. How do widgets handle errors?
  58. 58. What about error handling? var onContentReady = function() { _Contact = new MY.Contact(); my.oEvent.subscribe("contactLoad",_Contact.load, _Contact); // YAHOO.util.Connect.asyncRequest('POST', "contact- load.do", callback, null); my.asyncRequest("contact-load.do", my.oEvent.onContactLoadReturn); };
  59. 59. my.asyncRequest = function (sAction, fnCallback) { return YAHOO.util.Connect.asyncRequest("POST", sAction, { success : function(o) { var oPayload = eval("(" + o.responseText + ")") ; fnCallback(oPayload); } }); };
  60. 60. my.asyncRequest = function (sAction, fnCallback) { return YAHOO.util.Connect.asyncRequest("POST", sAction, { success : function(o) { var oPayload = eval("(" + o.responseText + ")") ; if (oPayload.exceptionMessage) { my.asyncRequestException(oPayload); } fnCallback(oPayload); } }); };
  61. 61. my.asyncRequestException = function (oPayload) { my.errorMessage(oPayload.exceptionMessage, oPayload.exceptionStack); }; my.errorMessage = function (sMessage, sStackTrace) { alert("Error Communicating with Server! // ... var sTemplate = "<table><tr> // ... var oContext = {message: sMessage, stackTrace: sStackTrace}; document.getElementById("elError").innerHTML = sTemplate.supplant(oContext); my.isMessage(true); my.error(oContext); };
  62. 62. Is that all there is? During this session, we covered  Integrating an Ajax UI with Struts 2  Using Yahoo User Interface (YUI) Library  Using Struts to provide services to Ajax U
  63. 63. Struts University Series
  64. 64. Whats up with Planet Yazaar? Development team Yahoo! Employees  Something like Spring, Hibernate  Unlike Apache projects No formal mechanism for contributions
  65. 65. Whats up with Planet Yazaar? Development team Yahoo! Employees  Something like Spring, Hibernate  Unlike Apache projects No formal mechanism for contributions A cathderal, rather than a bazaar
  66. 66. What's up with Planet Yazaar? Yazaar - (Yahoo + Bazaar = Yazaar) Accepts and maintains contributor extensions and documentation Working toward "soup to nuts" project documentation Public repository with version-to-version change logs
  67. 67. What's up with Planet Yazaar? Just as an aside ...  Yahoo GeoCities for public web site  Uses GoogleCode for repository  Google Group for mailing list and change logs  Yahoo 360 for Blog
  68. 68. Square One University Series
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×