Thursday, May 18, 2006

ลอง GWT

เพื่อให้เข้ากับยุคสมัย ผมก็เลย download GWT มาลองเล่นดู
เป้าหมายเพื่อให้รู้ว่า Architecture ของมันเป็นอย่างไร

เริ่มที่ขั้นแรกสุด ก็คือ ลองสร้าง project ง่ายๆพวก HelloWorld ขึ้นมา
(ในที่นี้ผมจะลองสร้างแต่ project ที่ใช้ eclipse เป็น IDE)

projectCreator -eclipse Hello

ผลลัพท์ของคำสั่งนี้ จะได้ file structure ออกมาดังนี้

+ src # empty directory
.classpath # eclipse build configuration file
.project # eclipse project file

จากนั้นก็ทำการ generate project files

applicationCreator -eclipse Hello pok.client.Hello

คำสั่ง applicationCreator จะสร้าง file ให้เราดังนี้

+ src
+ pok
+ client
Hello.java
+ public
Hello.html
Hello.gwt.xml
Hello.launch
Hello-shell
Hello-compile

  • Hello.launch
    เป็น configuration file ที่ Eclipse JDT ใช้
    ในการ run ใน Hosted Mode (mode ที่ใช้ java เป็นหลัก)
  • Hello-shell
    ใช้ run ใน Hosted Mode เหมือนกัน แต่เป็นการ
    start จาก command-line (แทนที่จะ run จากใน eclipse)
  • Hello-compile
    ใช้ในการ compile java file ให้เป็น javascript
    เพื่อที่จะได้นำไป deploy
  • Hello.java
    file หลัก ที่กำหนด Dynamic Behavior
  • Hello.html
    Template File
  • Hello.gwt.xml
    Configuration File


ลองดูหน้าตาของ Hello.java
public class Hello implements EntryPoint {

/**
* This is the entry point method.
*/

public void onModuleLoad() {
final Button button = new Button("Click me");
final Label label = new Label();

button.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
if (label.getText().equals(""))
label.setText("Hello World!");
else
label.setText("");
}
});

RootPanel.get("slot1").add(button);
RootPanel.get("slot2").add(label);
}
}

จะเห็นว่าการเขียนมีลักษณะเดียวกับพวก Swing, AWT
ซึ่งในทาง Web Application เราจะเรียก Style การเขียนแบบนี้ว่า
"Component Centric" (Struts เป็นพวก Action Centric)

ลองดู Template บ้าง
<html>
<head>

<title>Wrapper HTML for Hello</title>
<style>
body,td,a,div,.p{font-family:arial,sans-serif}
div,td{color:#000000}
a:link,.w,.w a:link{color:#0000cc}
a:visited{color:#551a8b}
a:active{color:#ff0000}
</style>

<!-- -->
<!-- The module reference below is the link -->
<!-- between html and your Web Toolkit module -->
<!-- -->
<meta name='gwt:module' content='pok.Hello'>

</head>
<body>

<!-- -->
<!-- This script is required bootstrap stuff. -->
<!-- You can put it in the HEAD, but startup -->
<!-- is slightly faster if you include it here. -->
<!-- -->
<script language="javascript" src="gwt.js"></script>

<!-- OPTIONAL: include this if you want history support -->
<iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>

<h1>Hello</h1>

<table align=center>
<tr>
<td id="slot1"></td><td id="slot2"></td>
</tr>
</table>
</body>
</html>


จะเห็นว่าแนวทางจะเหมือน WebObject ของ Apple,
หรือถ้าพวก java ก็คือ Tapestry (Tapestry ได้แรงบันดาลใจจาก WebObject)
โดยมีการ map tag ใน Template เข้ากับ java component

ส่วนที่น่าสนใจ สำหรับ Hosted Mode ก็คือ
โปรแกรมส่วน Browser พัฒนาโดยใช้ Eclipse SWT
โดยมี Mozilla เป็น embed browser

เมื่อเราต้องการที่จะ run ทดสอบ
เราก็จะ start Hosted mode โดยใช้ script Hello-shell
ซึ่งการทำงานภายใน จะมีการเรียกใช้ tomcat เป็น server
และใช้ Servlet เป็นตัว provide Dynamic generate Javascript

ตอนพัฒนาใน mode ของ Java นี่ไม่ค่อยตื่นเต้นเท่าไร
ลองไปดู ผลลัพท์ที่ได้จากการ compile ดูบ้าง

structure ที่เกิดจากการ compile มีหน้าตาดังนี้
 + pok.Hello
6C111FAEE0653F4BBB3843A644BBC3B9.cache.html
6C111FAEE0653F4BBB3843A644BBC3B9.cache.xml
716702EAD597E14C305C093A453D09E0.cache.html
716702EAD597E14C305C093A453D09E0.cache.xml
BCF8E41DC1EA80A1D898F3F24A8B0E79.cache.html
BCF8E41DC1EA80A1D898F3F24A8B0E79.cache.xml
C23A120D5B9051FB13E2470D6906FE0B.cache.html
C23A120D5B9051FB13E2470D6906FE0B.cache.xml
gwt.js
hello.html
history.html
pok.Hello.nocache.html
tree_closed.gif
tree-open.gif
tree_white.gif

file ที่เราเรียกใช้งาน ก็คือ hello.html
ซึ่งภายในจะไป load gwt.js ขึ้นมาทำงาน
หน้าที่ของ gwt.js ก็คือมันจะ inject web framework เข้ามาทาง iframe
ดูได้จากคำสั่ง
function __gwt_injectWebModeFrame(name) {
if (document.body) {
var parts = __gwt_splitModuleNameRef(name);

// Insert an IFRAME
var iframe = document.createElement("iframe");
var selectorURL = parts[0] + parts[1] + ".nocache.html";
iframe.src = selectorURL;
iframe.style.border = '0px';
iframe.style.width = '0px';
iframe.style.height = '0px';
if (document.body.firstChild) {
document.body.insertBefore(iframe, document.body.firstChild);
} else {
document.body.appendChild(iframe);
}
} else {
// Try again in a moment.
//
window.setTimeout(function() { __gwt_injectWebModeFrame(name); }, __gwt_retryWaitMs);
}
}

ถ้าดูจาก source code จะเห็นว่า มัันไป load เอา pok.Hello.nocache.html ขึ้นมาทำงาน
ตามไปดูใน pok.Hello.nocache.html
ข้างในจะเป็น javascript ทีทำหน้าที่แค่เป็น Loader
โดยจะทำการตรวจสอบสภาพแวดล้อม ว่าเป็น browser แบบไหน
แล้วก็เรียกใช้ javascript ให้ถูกตัว
ซึ่งถ้าดูจาก source code จะเห็นว่า support browser อยู่ 4 พวก
ก็คือ Mozilla, IE, Safari, Opera
function selectScript() {
try {
var F;
var I = ["true", (F=window["prop$user.agent"],F())];

O(["true","oldmoz"],"6C111FAEE0653F4BBB3843A644BBC3B9");
O(["true","moz"],"6C111FAEE0653F4BBB3843A644BBC3B9");
O(["true","ie6"],"716702EAD597E14C305C093A453D09E0");
O(["true","safari"],"BCF8E41DC1EA80A1D898F3F24A8B0E79");
O(["true","opera"],"C23A120D5B9051FB13E2470D6906FE0B");

var strongName = O.answers[I[0]][I[1]];
location.replace(strongName + '.cache.html');
} catch (e) {
// intentionally silent on property failure
}
}

รหัส 6C111FAEE0653F4BBB3843A644BBC3B9 ก็คือ
ชื่อ file 6C111FAEE0653F4BBB3843A644BBC3B9.cache.html นั่นเอง

ภายใน file นี้ จะเป็น framework ที่ google เขียนขึ้นมาแล้ว
แต่เนื่องจากถูกลดรูปไว้แล้ว ก็เลยหมดปัญญาที่จะแกะ

ดูจากวิธีการของ framework นี้แล้ว ข้อดี ก็คือ
สำหรับคนที่ใช้ java อยู่แล้ว จะพัฒนาได้เร็ว
แถมยัง debug ได้ง่าย เนื่องจากยัง debug อยู่ใน java อยู่
เมื่อจะ deploy ก็ค่อยแปลงเป็น javascript

ข้อสงสัย สำหรับบางคน ก็คือแล้ว มันมีส่วน server ด้วยหรือเปล่า
ตัว GWT provide RPC API มาให้เราด้วย
วิธีใช้ ก็แค่เขียน Service โดย extend RemoteServiceServlet
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class SchoolCalendarServiceImpl extends RemoteServiceServlet

โดยใน file gwt.xml ก็ระบุด้วยว่า
<module>
<inherits name='com.google.gwt.user.User'/>
<entry-point class='com.google.gwt.sample.dynatable.client.DynaTable'/>
<servlet path='/calendar' class='com.google.gwt.sample.dynatable.server.SchoolCalendarServiceImpl'/>
</module>

พอจะใช้ ก็แค่
// Initialize the service.
//
calService = (SchoolCalendarServiceAsync) GWT
.create(SchoolCalendarService.class);

// By default, we assume we'll make RPCs to a servlet, but see
// updateRowData(). There is special support for canned RPC responses.
// (Which is a totally demo hack, by the way :-)
//
ServiceDefTarget target = (ServiceDefTarget) calService;
target.setServiceEntryPoint("/calendar");


calService.getPeople(startRow, maxRows, new AsyncCallback() {
public void onFailure(Throwable caught) {
acceptor.failed(caught);
}

public void onSuccess(Object result) {
Person[] people = (Person[]) result;
lastStartRow = startRow;
lastMaxRows = maxRows;
lastPeople = people;
pushResults(acceptor, startRow, people);
}

});


ดูแล้ว ก็ไม่ยากอะไร
สิ่งที่ GWT ไม่ได้เตรียมมาให้ สำหรับส่วน server ก็คือ
script ที่ใช้ในการ pack war เพื่อไป deploy remote service
บน Application Server ที่เราต้องการ

Related link from Roti

1 comment:

Anonymous said...

Thank you for good article krub.