Banyak library Ajax yang sudah gede dan banyak fiturnya seperti ASP.NET AJAX, Anthem.NET, AjaxAnywhere, ajaxCFC, AJS, Dojo, Rico dan beberapa library lainnya. Dalam tulisan ini akan saya jelaskan konsep invoke Web Service langsung menggunakan objek XMLHttpRequest (XHR). Saya asumsikan bahwa pembaca sudah memahami konsep dasar XMLHttpRequest, jika belum silakan baca tulisan saya sebelumnya di Pengenalan AJAX.
Pertama buat root object dengan nama Masykur, root object ini bisa disamakan sebagai namespace pada OOP.
if (!window.Masykur) {
window.Masykur = {};
}
Setelah root object dibuat kemudian buat member yang berisi DOM dan Ajax. DOM digunakan untuk mendapatkan informasi DOM dan browser sedangkan Ajax adalah object yang menangani komunikasi singkron dengan Web Service.
if (!Masykur.DOM) {
Masykur.DOM = function() {}
}
if (!Masykur.Ajax) {
Masykur.Ajax = function() {}
}
Pada objek Masykur.DOM, tambahkan property untuk pengecekan browser. Pengecekan browser ini digunakan untuk mengecek kemampuan dari masing-masing browser karena masing-masing browser menggunakan objek yang berbeda untuk menangani XHR maupun dokumen XML.
Masykur.DOM.isInternetExplorer = (navigator.userAgent.indexOf("MSIE") >= 0);
Masykur.DOM.isMozilla = (navigator.userAgent.indexOf("Gecko") >= 0);
Masykur.DOM.isOpera = (navigator.userAgent.indexOf("Opera") >= 0);
Pada objek Masykur.Ajax, buat variabel nameSpace yang digunakan sebagai namespace pada Envelope SOAP. Selain variabel juga tambahkan fungsi set dan get namespace.
Masykur.Ajax = function() {
var nameSpace = "http://tempuri.org/";
this.setNameSpace = function(ns){
nameSpace = ns;
}//end setNameSpace()
this.getNameSpace = function(){
return nameSpace;
}//end getNameSpace()
}
Buat objek XHR lintas browser dengan mencoba semua metode dari create XMLHttpRequest dari JavaScript object, ActiveX dan SOAPCall object. Ini dimaksudkan supaya semua library tidak tergantung dengan browser yang digunakan, dapat berjalan baik IE, Firefox maupun Opera.
Masykur.Ajax = function(){
// ...
var ajaxObject = function(){
try{return new XMLHttpRequest();}catch(ex){};
try{return new ActiveXObject("Microsoft.XMLHTTP");}catch(ex){};
try{return new SOAPCall();}catch(ex){};
}//end ajaxObject()
}
Buat fungsi penanganan error yang kemudian dapat di-override dengan fungsi sendiri saat digunakan.
Masykur.Ajax = function(){
// ...
this.onError = function(error){
alert(error);
}//end onError()
}
Setelah semua persiapan objek XHR selesai, sekarang tinggal buat fungsi callService untuk invoke ke Web Service.
Masykur.Ajax = function(){
// ...
this.callService = function(serviceUrl, soapMethod, callbackFunction /*, unlimited params */){
// code
}
}
Pertama yang perlu disiapkan adalah URL web service. Tambahkan "?WSDL" dibelakang URL.
if(serviceUrl.indexOf("http://") < 0)
serviceUrl = "http://" + serviceUrl;
serviceUrl += "?WSDL";
Hal penting yang harus disiapkan adalah envelope untuk membungkus data yang akan dikirimkan ke server. Envelope berisi semua parameter yang dibutuhkan oleh web service method. Parameter dibungkus dalam tag <soap:Body>, nama parameter merupakan sebuah elemen child <soap:Body> dan valuenya dimasukkan sebagai text element parameter.
var soapEnvelope = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
soapEnvelope += "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ";
soapEnvelope += "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" ";
soapEnvelope += "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">";
soapEnvelope += "<soap:Body>";
soapEnvelope += "<" + soapMethod + " xmlns=\"" + nameSpace + "\">";
if(arguments.length > 3){
for (var i = 3; i < arguments.length; i++)
{
var params = [];
var arg = arguments[i];
var ieq = arg.indexOf('=');
params[0] = arg.substr(0, ieq);
params[1] = arg.substr(ieq + 1);
soapEnvelope += "<" + params[0] + ">";
soapEnvelope += params[1];
soapEnvelope += "</" + params[0] + ">";
}//end for
}//end if
soapEnvelope += "</" + soapMethod + ">";
soapEnvelope += "</soap:Body>";
soapEnvelope += "</soap:Envelope>";
Set event onreadystate pada objek XHR dan kembalikan hasil respon web service dalam format string setelah semua data lengkap diterima.
Terakhir buka sambungan dengan server menggunakan mode POST dan set header content-type menjadi "text/xml" dan tambahkan juga header "soapaction" dengan data namespace diikuti dengan soap method. Kirimkan soap envelope pada request body seperti pada kode berikut.
ao.open("POST", serviceUrl, true);
ao.setRequestHeader("Content-Type", "text/xml");
ao.setRequestHeader("soapaction", nameSpace + soapMethod);
ao.send(soapEnvelope);
Script lengkap dapat dilihat pada kode berikut.
if (!window.Masykur) {
window.Masykur = {};
}
if (!Masykur.DOM) {
Masykur.DOM = function() {}
}
if (!Masykur.Ajax) {
Masykur.Ajax = function() {}
}
Masykur.DOM.isInternetExplorer = (navigator.userAgent.indexOf("MSIE") >= 0);
Masykur.DOM.isMozilla = (navigator.userAgent.indexOf("Gecko") >= 0);
Masykur.DOM.isOpera = (navigator.userAgent.indexOf("Opera") >= 0);
Masykur.Ajax = function(){
var nameSpace = "http://tempuri.org/";
//private method for returning an ajax enabled
//object specific to a browser
var ajaxObject = function(){
try{return new XMLHttpRequest();}catch(ex){};
try{return new ActiveXObject("Microsoft.XMLHTTP");}catch(ex){};
try{return new SOAPCall();}catch(ex){};
}//end ajaxObject()
this.onError = function(error){
alert(error);
}//end onError()
this.callService = function(serviceUrl, soapMethod, callbackFunction /*, unlimited params */){
var callServiceError = this.onError;
var ao = ajaxObject();
if(!ao.encode){
if(serviceUrl.indexOf("http://") < 0)
serviceUrl = "http://" + serviceUrl;
serviceUrl += "?WSDL";
var soapEnvelope = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
soapEnvelope += "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ";
soapEnvelope += "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" ";
soapEnvelope += "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">";
soapEnvelope += "<soap:Body>";
soapEnvelope += "<" + soapMethod + " xmlns=\"" + nameSpace + "\">";
if(arguments.length > 3){
for (var i = 3; i < arguments.length; i++)
{
var params = [];
var arg = arguments[i];
var ieq = arg.indexOf('=');
params[0] = arg.substr(0, ieq);
params[1] = arg.substr(ieq + 1);
soapEnvelope += "<" + params[0] + ">";
soapEnvelope += params[1];
soapEnvelope += "</" + params[0] + ">";
}//end for
}//end if
soapEnvelope += "</" + soapMethod + ">";
soapEnvelope += "</soap:Body>";
soapEnvelope += "</soap:Envelope>";
ao.onreadystatechange = function(){
if(ao.readyState == 4){
if(Masykur.DOM.isOpera){
//opera
var response = ao.responseXML.getElementsByTagName(soapMethod + "Result")[0];
if(!response)
response = ao.responseXML.getElementsByTagName(soapMethod + "Response")[0];
if(!response){
callServiceError("WebService does not contain a Result/Response node");
return;
}//end if
ao.callbackFunction(ao.responseXML.getElementsByTagName(soapMethod + "Result")[0].innerHTML);
}
else if(Masykur.DOM.isInternetExplorer){
//IE
var responseXml = new ActiveXObject('Microsoft.XMLDOM');
responseXml.loadXML(ao.responseText);
var responseNode = responseXml.selectSingleNode("//" + soapMethod + "Result");
if(!responseNode)
responseNode = responseXml.selectSingleNode("//" + soapMethod + "Response");
if(!responseNode) {
callServiceError("Response/Result node not found.\n\nResponse:\n" + ao.responseText);
return;
}//end if
var resultNode = responseNode.firstChild;
if (resultNode != null){
try{
callbackFunction(resultNode.xml);
}
catch(ex){
callServiceError(ex);
}//end tc
}
else{
try{
callbackFunction();
}
catch(ex){
callServiceError(ex);
}//end tc
}//end if
}
else if(Masykur.DOM.isMozilla){
//Mozilla
var xmlDocument = new DOMParser().parseFromString(ao.responseText, "text/xml");
var xr = xmlDocument.evaluate("//" + soapMethod + "Result",
xmlDocument.childNodes[xmlDocument.childNodes.length-1],
null,
XPathResult.ANY_TYPE, null);
var responseNode = xr.iterateNext();
if(!responseNode)
callServiceError("Response/Result node not found.\n\nResponse:\n" + ao.responseText);
var resultNode = responseNode.firstChild;
if (resultNode != null){
try{
callbackFunction(resultNode.textContent);
}
catch(ex){
callServiceError(ex);
}//end tc
}
else{
try{
callbackFunction();
}
catch(ex){
callServiceError(ex);
}//end tc
}//end if
}//end if
}//end if
};
ao.open("POST", serviceUrl, true);
ao.setRequestHeader("Content-Type", "text/xml");
ao.setRequestHeader("soapaction", nameSpace + soapMethod);
try{
ao.send(soapEnvelope);
}
catch(ex){
serviceCallError(ex);
}//end tc
}
else{
var soapParams = new Array();
var headers = new Array();
var soapVersion = 0;
var object = nameSpace;
if(serviceUrl.indexOf("http://") < 0)
serviceUrl = document.location + serviceUrl;
ao.transportURI = serviceUrl;
ao.actionURI = nameSpace + soapMethod;
for(var i=3; i<arguments.length; i++){
var params = arguments[i].split("=");
soapParams.push( new SOAPParameter(params[1],params[0]) );
}//end for
try{
ao.encode(soapVersion, soapMethod, object, headers.length, headers, soapParams.length, soapParams);
}
catch(ex){
serviceCallError(ex);
}//end tc
try{
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
}
catch(ex){
return false;
}//end tc
try{
ao.asyncInvoke(
function(resp,call,status){
if(resp.fault)
return callServiceError(resp.fault);
if(!resp.body){
callServiceError("Service " + call.transportURI + " not found.");
}
else{
try{
callbackFunction(resp.body.firstChild.firstChild.firstChild.data);
}
catch(ex){
callServiceError(ex);
}//end tc
}//end if
}
);
}
catch(ex){
serviceCallError(ex);
}//end tc
}//end if
}//end callService()
this.setNameSpace = function(ns){
nameSpace = ns;
}//end setNameSpace()
this.getNameSpace = function(){
return ns;
}//end getNameSpace()
}//end Masykur.Ajax()