Java Script Lanjutan
Java Script Lanjutan
[1]
8. Jalankan perintah node --version pada CMD. Jika NodeJS terpasang dengan
benar, sistem akan mengembalikan nomor versi.
Asumsikan kita ingin menggabungkan semua file Javascript yang ada dalam
direktori scripts serta file CSS yang ada dalam direktori styles menjadi
satu. File Javascript akan kita gabungkan menjadi file scripts.js dan
disimpan dalam direktori js. File CSS akan digabung menjadi
file styles.css dan disimpan dalam direktori css.
Untuk memberitahukan Gulp apa yang akan kita lakukan, kita harus membuat
sebuah file khusus yang dikenali Gulp terlebih dahulu. File ini harus
bernama gulpfile.js, dan harus diletakkan di direktori utama proyek. Dalam
kasus ini, kita harus membuat gulpfile.js di dalam direktori
tempat index.html berada. Isi dari gulpfile.js sangat sederhana, hanya
melakukan inisialisasi data:
2
3
gulp.task('default', function () {
4
5
Terdapat lima perintah utama yang dapat kita berikan kepada Gulp, yaitu:
1. gulp.task(name, fn): yang telah kita gunakan pada kode di atas. Membuat
perintah baru untuk Gulp ketika dipanggil melalui command
line nantinya. Parameter name merupakan nama dari task yang dibuat,
sementara fnadalah fungsi yang dijalankan ketika task dipanggil.
2. gulp.run(tasks...): menjalankan satu atau lebih task yang dibuat
menggunakan gulp.task melalui kode (programmatically).
3. gulp.watch(glob, fn): melakukan eksekusi fungsi fn ketika file yang
dispesifikasikan oleh glob berubah isinya.
4. gulp.src(glob): membaca file yang memenuhi syarat sesuai
spesifikasi glob dan kemudian mengembalikan file tersebut satu per satu
untuk kemudian diproses.
5. gulp.dest(folder): file yang dikirimkan ke perintah ini akan disimpan
di dalam folder.
Selain kelima perintah di atas, Gulp menyediakan satu perintah pendukung
lagi, yaitu pipe(fn). Perintah pipemerupakan saluran penghubung antar dua
perintah, yang pada dasarnya memberikan fasilitas untuk menjalankan satu
perintah setelah perintah sebelumnya dijalankan.
Agar lebih mudah pahami, kita akan langsung melihat contoh penggunaan
beberapa perintah di atas. Sebagai langkah pertama, katakan lah kita akan
melakukan kopi seluruh file Javascript dari scripts ke js. Ganti isi
file glupfile.jsmenjadi seperti berikut:
1
2
3
4
5
6
gulp.task('default', function () {
gulp.src(scriptFiles)
7
8
.pipe(gulp.dest('./js/'));
});
Pada kode di atas, kita menggunakan perintah gulp.src dan gulp.dest secara
berurutan. Kedua perintah ini dihubungkan oleh pipe. Untuk melakukan
eksekusi task di atas, jalankan perintah glup dari command line.
Bagaimana jika kita ingin menggabungkan file atau meminimalkan file? Untuk
berbagai tugas tambahan seperti itu, kita dapat menggunakan tambahan perintah
dari pihak ketiga. Jalankan perintah berikut untuk memasang perintah baru
dari pihak ketiga:
1
var gulp
= require('gulp');
4
5
6
7
gulp.task('default', function () {
gulp.src(scriptFiles)
.pipe(concat('scripts.js'))
10
.pipe(uglify())
11
.pipe(gulp.dest('./js/'));
12
});
Cukup gamblang dan sederhana. Kita hanya melakukan impor modul, dan kemudian
menggunakan modul tersebut (fungsiconcat() dan uglify()) melalui pipe.
Kesederhanaan ini merupakan daya tarik utama dari Gulp, sebagai sebuah build
tools yang sederhana sekaligus sangat bermanfaat. Penggabungan dan minifikasi
file CSS diserahkan kepada pembaca sebagai latihan.
Penguji HTTP Request
Ketika kita mengembangkan aplikasi web dinamis, seringkali kita akan
berhubungan dengan server. Hubungan dengan server ini biasanya dilakukan
secara dinamis, dengan menggunakan teknologi seperti XMLHttpRequest atau
WebSocket. Untuk memudahkan kita dalam menguji apakah server memberikan data
yang tepat atau sesuai dengan yang kita inginkan, kita dapat
menggunakan tools yang dapat mengirimkan permintaan ke server, dengan format
yang kita inginkan.
Beberapa tools yang dapat digunakan untuk kepentingan ini yaitu:
1. Advanced REST Client (Chrome)
2. REST Client (Firefox)
Kedua tools di atas cukup sederhana dan mudah digunakan. Kita hanya perlu
memasukkan URL yang akan kita uji, beserta dengan method HTTP-nya. Program
kemudian akan menampilkan data yang diberikan dari server, dan kita dapat
langsung memastikan apakah format yang diberikan sudah benar atau tidak. Hal
ini akan sangat memudahkan kita ketika ingin berbicara degnan server.
Catatan Kaki
2. Menerima data dari server dan kemudian mengolahnya. Data hanya akan
dapat diberikan server setelah kita mengirimkan perintah.
Setelah kita menerima dan mengolah data dari server, kita kemudian dapat
memperbaharui halaman sesuai dengan kebutuhan. Ingat juga bahwa komunikasi
ke server pada teknologi AJAX berjalan satu arah: kita mengirimkan perintah
ke server, dan hanya setelah itu server dapat mengirimkan data kembali ke
klien. Jika tidak ada pengiriman perintah dari klien, maka server tidak dapat
langsung memberikan data ke klien. Hal ini disebabkan oleh sifat dasar dari
web yang*stateless*, di mana server tidak menyimpan daftar klien yang sedang
terkoneksi dengannya. Tanpa server mengetahi siapa saja yang sedang
terkoneksi dengannya, maka ia tidak dapat mengirimkan data kepada klien
tersebut.
Keseluruhan fungsi dari AJAX ini dapat dicapai dengan menggunakan
objek XMLHttpRequest yang telah disediakan oleh browser. Langsung saja, kita
akan melihat bagaimana menggunakan objek ini.
Pembuatan XMLHttpRequest
Objek XMLHttpRequest (selanjutnya dirujuk sebagai xhr saja dalam kode dan
nama method) merupakan objek inti untuk melakukan operasi AJAX. Pembuatan
objek ini pada browser modern cukup mudah dan jelas:
1
var xhr;
if (window.XMLHttpRequest) {
3
4
5
6
var xhr;
} else {
Cara kedua yang mendukung browser lama ini tidak disarankan untuk digunakan,
karena dapat menambah beban pengujian. Anda lebih disarankan untuk
memperbaharui browser anda dibandingkan dengan menggunakan kode di atas.
Menggunakan browser lama yang tidak didukung lagi membuka celah kemanan yang
sangat besar, ditambah lagiActiveXObject merupakan objek yang tidak didukung
oleh standar Javascript secara resmi.
Mengirimkan Perintah ke Server
Mengirimkan perintah ke server dilakukan melalui protokol HTTP, yang dapat
dipanggil menggunakan method xhr.opendan xhr.send dari XMLHttpRequest:
1
xhr.send(null);
Parameter kedua dari xhr.open berisi alamat URL server yang dapat
menerima dan mengerti perintah yang kita kirimkan. Untuk menjaga
keamanan, kita tidak dapat mengirimkan perintah ke domain yang berbeda
http://example.com/path?param1=value1¶m2=value2...
Karena bentuk format data yang sederhana ini kita dapat langsung mengirimkan
data melalui URL pada server, pada method xhr.open seperti berikut:
1
xhr.send(null);
Sayangnya, hal ini tidak dapat kita gunakan pada pengiriman data
menggunakan method POST. Jika ingin mengirimkan data menggunakan method POST,
kita harus terlebih dahulu menyimpan data yang ada di dalam objek yang dapat
diterima oleh xhr.send dan mengirimkannya melalui xhr.send. Berikut adalah
contoh penggunaan objek FormDatauntuk mengambil data dalam form dan
mengirimkannya melalui POST:
var form
= document.querySelector("#form-login");
3
4
xhr.open("POST", "http://example.com/login");
xhr.send(formData);
Kita bahkan dapat menambahkan data baru ke FormData jika diperlukan (misalnya
untuk data yang tidak perlu diisikan oleh pengguna seperti nomor
pendaftaran). Kita dapat menggunakan method FormData.append untuk ini:
1
var form
= document.querySelector("#form-daftar");
3
4
formData.append("NomorPendaftaran", noDaftar++);
5
6
xhr.open("PSOT", "http://example.com/register");
xhr.send(formData);
2
3
// kode...
};
4
5
xhr.onreadystatechange = fungsiRespon;
State
Deskripsi
UNSENT
OPENED
HEADERS_RECEIVED
LOADING
DONE
if (xhr.readystate === 4) {
4
5
}
}
Setelah memastikan server memberikan respon sepenuhnya, kita lalu masih harus
memastikan server tidak mengembalikan pesan kesalahan. Status keberhasilan
atau kesalahan pada server HTTP dikomunikasikan kepada client melalui HTTP
Status Code. Contohnya, jika server sukses menjalankan perintah tanpa masalah
kita akan diberi kode 200. Jika halaman tidak ditemukan maka kita akan
diberikan status code 404. Dengan pengetahuan ini, kita dapat melakukan
pengecekan tambahan, untuk memastikan server menjalankan perintah dengan
benar. HTTP Status Code dapat diakses melalui xhr.status:
1
if (xhr.readystate === 4) {
// proses data
alert("Server error!");
10
11
}
}
Sampai titik ini kita sudah memastikan server mengembalikan data yang
lengkap, tanpa pesan kesalahan. Selanjutnya kita dapat langsung mengakses
data yang diberikan oleh server, untuk diolah lebih lanjut. Data yang
diberikan oleh server dapat diakses melalui dua buah properti khusus:
Setelah mendapatkan data dalam format sesuai kebutuhan kita, kemudian kita
dapat mengolah data tersebut sesuai keinginan. Jika data kembalian yang
diberikan dibuat dalam format JSON, kita harus terlebih dahulu mengakses data
melalui xhr.responseText. Pembahasan lebih lanjut mengenai hal ini akan
dilakukan pada bagian berikutnya.
Note
Cara pengambilan respon yang dibahas pada bagian ini adalah cara pengambilan
respon untuk perintah asinkronus. Ketika menggunakan perintah sinkronus, kita
cukup memasukkan kode pengolahan data setelah xhr.send karena kode akan
berhenti dieksekusi sampai xhr.send selesai dijalankan.
var response = "{\"health\": 3000, \"mana\": 400, \"str\": 86, \"agi\": 63, \"int\":
var ResObj
= JSON.parse(response);
3
4
ResObj["health"]; // 3000
ResObj.mana;
// 400
Parameter kedua dari JSON.parse dapat kita gunakan untuk melakukan sesuatu
terhadap data pada string, sebelum data tersebut dimasukkan ke dalam objek.
Parameter kedua ini menerima sebuah fungsi dengan dua parameter yang masingmasing adalah nama atribut dan nilai atribut dari string JSON yang sedang
dibaca. Fungsi akan dijalankan terhadap semua atribut JSON yang ada, sehingga
kita dapat mengubah nilai dari atribut jika diinginkan. Nama umum dari
parameter kedua ini adalah reviver. Perhatikan kode berikut:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
JSON.parse(data, reviver);
// Keluaran:
// satu: 1
// dua: 2
// empat: 4
// lima: 5
// tiga: [object Object]
// : [object Object]
Terdapat satu atribut tambahan yang tidak ada dalam data, yaitu cetakan
terakhir (: [object: Object]). Atribut ini mewakili keseluruhan JSON
yang kita kirimkan, dan akan selalu dibaca paling terkhir. Hal ini
dibuat agar kita juga dapat mengubah objek utama (misal:
menambahkan method) jika diperlukan. Kita tetap harus ingat untuk
menangani kasus ini bahkan ketika kita tidak mengubah objek utama,
biasanya dengan mengembalikan nilai awal (v pada kode di atas)
agar JSON.parse tidak mengembalikan undefined.
Untuk memperjelas masalah penanganan objek utama pada poin ketiga, perhatikan
kode berikut:
2
3
return v * 2;
});
4
5
8
9
10
return v * 2;
});
11
12
kosong; // NaN
13
isi;
Pada kode di atas, kita dapat melihat bagaimana jika kita tidak melakukan
pengecekan nilai k kosong maka objek hasil tidak akan dikembalikan. Sebagai
latihan, coba ganti baris if pada fungsi milik isi dengan yang dikomentari
dan lihat hasilnya. Kegunaan dari cek typeof v === "object diberikan sebagai
latihan kepada pembaca.
Memanfaatkan pengetahuan ini, kita dapat mengembangkan fungsi reviver yang
membuat objek penuh beserta denganmethod baru seperti berikut:
1
2
3
4
var response = "{\"health\": 3000, \"mana\": 400, \"str\": 86, \"agi\": 63, \"int\":
var reviver = function (k, v) {
if (k === "") {
v.getStr = function () {
return v.str;
7
8
9
10
11
return v;
}
12
13
14
15
16
17
18
return v * 2;
};
TransformedObj["health"]; // 6000
TransformedObj.mana;
// 800
TransformedObj.getStr();
// 172
Selanjutnya, mari kita lihat bagaimana cara mengubah objek menjadi string
JSON.
Mengubah Objek Menjadi String
Selain menerima string JSON dan mengubahnya ke dalam objek Javascript,
seringkali kita juga perlu mengirimkan data baru ke server, yang berasal dari
objek Javascript. Untuk kasus ini kita memerlukan mekanisme untuk mengubah
objek menjadi string JSON. Javascript menyediakan method JSON.stringify untuk
melakukan hal ini. Penggunaan sederhanamethod ini sangat mudah:
1
var obj = {
model: "L925",
size: 3.5,
age: 18,
conn: "4G"
};
7
8
9
10
jsonString; // '{"model":"L925","size":3.5,"age":18,"conn":"4G"}'
untuk melakukan proses data dalam objek sebelum objek tersebut dijadikan
string. Berikut adalah contoh penggunaannya:
1
return v;
});
5
6
filteredJSON; // '{"size":3.5,"age":18}'
2
3
altJSON; // '{"model":"L925","conn":"4G"}'
"model": "L925",
//
"conn": "4G"
// }"
10
// "{
11
// aaaa"model": "L925",
12
// aaaa"conn": "4G"
13
// }"
14
15
16
// keluaran:
17
// "{
18
//
"model": "L925",
19
//
"conn": "4G"
20
// }"
WebSocket
WebSocket merupakan sebuah protokol komunikasi dua arah yang dapat digunakan
oleh browser. Jika pada AJAX kita hanya dapat melakukan komunikasi satu arah
dengan mengirimkan request kepada server dan menunggu balasannya, maka
menggunakan WebSocket kita tidak hanya dapat mengirimkan request kepada
server, tetapi juga menerima data dari server tanpa harus
mengirimkan request terlebih dahulu. Hal ini berarti ketika menggunakan
WebSocket pengguna harus terus menerus terkoneksi dengan server, dan kita
memerlukan sebuah server khusus untuk dapat menjalankan aplikasi WebSocket
dengan benar. Sederhananya, perhatikan gambar berikut:
Dari gambar di atas, kita dapat melihat bagaimana dalam kasus AJAX, setiap
request dari pengguna maupun respon dari server dilakukan secara terpisah.
Jika ingin mengirimkan request ke server, kita perlu membuka koneksi baru
yang kemudian akan dibalas oleh server seperti ia membalas permintaan halaman
HTML biasa. Server juga tidak dapat langsung melakukan pengiriman data ke
klien tanpa ada permintaan terlebih dahulu. Hal ini berbeda dengan pola
komunikasi WebSocket, di mana koneksi berjalan tanpa terputus dan server
dapat mengirimkan data atau perintah tanpa harus ada permintaan dari pengguna
terlebih dahulu. Dengan begitu, WebSocket bahkan juga memungkinkan server
mengirimkan data atau perintah ke semua klien yang terkoneksi, misalkan untuk
memberikan notifikasi global.
Untuk dapat melihat langsung bagaimana cara kerja WebSocket, kita akan
langsung melakukan eksperimen. Objek yang diperlukan untuk menggunakan
WebSocket dari browser adalah WebSocket. Tetapi sebelumnya, kita memerlukan
sedikit persiapan terlebih dahulu, yaitu komponen server dari WebSocket.
Komponen Server WebSocket
Kita akan membangun klien dari index.js dari awal, sementara klien
untuk zero.js akan dimodifikasi dari contoh permainan sederhana yang sudah
dipersiapkan sebelumnya. Silahkan download juga kode untuk permainan
serderhana tersebut, dan coba pelajari cara kerjanya.
Menjalankan server WebSocket dari kedua kode tersebut juga sangat mudah,
cukup eksekusi kode dengan NodeJS:
1
$ node index.js
$ node zero.js
Setelah memilki semua komponen dasar yang diperlukan, kita akan mulai
mempelajari WebSocket dengan menelaah objek WebSocket terlebih dahulu.
Membuat Objek WebSocket
Pembuatan objek WebSocket (selanjutnya dirujuk sebagai ws saja) sangat mudah.
Kita dapat langsung memanggilconstructor dari objek tanpa perlu melakukan
pengecekan tambahan. Berikut adalah contoh pembuatan objek WebSocket baru:
1
Dan kita telah membuat sebuah objek WebSocket, sekaligus mencoba melakukan
koneksi ke alamat yang diberikan padaconstructor. Constructor dari ws juga
memiliki parameter kedua yang bersifat opsional. Parameter kedua ini dapat
berupa sebuah string ataupun array dari string, yang berisi nama dari
subprotokol WebSocket yang akan kita gunakan. Subprotokol merupakan pesan
tambahan yang dapat kita kirimkan ke server agar server mendapatkan informasi
tambahan yang dibutuhkan untuk memberikan respon. Subprotokol ini dibuat oleh
pengembang sendiri, dan baik klien maupun server harus dapat mengerti
subprotokol yang dikirimkan. Kegunaan utama dari subprotokol adalah agar baik
klien maupun server dapat yakin bahwa lawan bicaranya benar-benar mengerti
pesan yang dikirimkan maupun diterima. Kita tidak ingin server Dota2 versi 1
berkomunikasi dengan klien Dota2 versi 2 misalnya.
Penambahan subprotokol pada constructor WebSocket tidak rumit sama sekali:
1
2
3
ws.onopen = function() {
4
5
console.log("Koneksi berhasil!");
};
6
7
ws.onerror = function () {
8
9
console.log("Koneksi gagal.");
};
10
11
12
13
14
15
16
17
18
19
// atau bahkan
var obj = {
key: "F5-A1-00-32-E4",
command: "Run"
};
9
10
ws.send(JSON.stringify(obj));
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
5
6
7
8
9
<title>Websocket test</title>
</head>
<body>
<ul id="chat"></ul>
10
11
12
13
var chat
= document.querySelector("#chat"),
chatinput = document.querySelector("#chatinput"),
addChat
newText = document.createTextNode(data);
7
8
newChat.appendChild(newText);
chat.appendChild(newChat);
10
};
11
13
14
15
16
17
18
19
20
21
22
23
24
// #2
12
= function (data) {
// #3
websocket.onmessage = function (event) {
addChat(event.data);
}
// #4
chatinput.addEventListener("keydown", function (evt) {
if (evt.keyCode === 13) {
addChat(chatinput.value);
websocket.send(chatinput.value); // #5
chatinput.value = "";
return false;
25
26
}
});
Pemrograman Asinkron
Pada bagian sebelumnya, kita telah melihat dua model pemrograman asinkron,
yaitu AJAX dan WebSocket. Kedua model pemrograman ini memiliki pola
pemrograman yang sama, yaitu:
1. Kirimkan data atau perintah ke server secara asinkron.
2. Buat kode yang akan menangani respon dari server.
3. Lanjutkan kode aplikasi.
4. Ketika server telah mengembalikan data, jalankan kode pada no 2.
Model pemrograman yang menunggu sesuatu terjadi untuk kemudian menjalankan
kode tertentu seperti ini kita kenal dengan nama Event-Based
Programming (EBP). Meskipun dapat bekerja dengan sangat baik, model EBP
seperti ini cukup kompleks dan dapat menyebabkan kode program menjadi sulit
untuk dirawat. Misalkan kode seperti berikut:
1
$.ajax("/feature/", function(html) {
3
4
$.ajax("/feature/subfeature", function(css) {
6
7
$.ajax("/feature/subfeature2", function() {
});
10
11
12
});
13
14
15
trace*) dari kode di atas. Kesulitan ini disebabkan berlapisnya fungsi anonim
yang digunakan, dan terkadang pesan kesalahan atau Exception yang dilemparkan
oleh fungsi anonim di dalam belum tentu dapat dibaca oleh fungsi luarnya.
Selain masalah di atas, umumnya juga sering terjadi kesulitan dalam mengikuti
alur eksekusi kode program seperti di atas. Contohnya, ketika #2 berjalan,
fungsi yang menampung #3 dapat saja dijalankan sebelum atau sesudah #4,
tergantung dari banyak faktor. Hal yang sama juga ditemui untuk
eksekusi #2 dan #5 (otomatis diikuti oleh eksekusi #3 serta #4). Masalah lain
lagi yaitu ketika kode di bagian #4 bergantung pada hasil yang ada pada #3.
Kita harus menggunakan mekanisme event yang lebih kompleks lagi,
yaitu callback, untuk memastikan #4 berjalan setelah #3.
Permsalahan-permasalahan yang dikemukakan di atas kemudian melahirkan
konstruk pemrograman baru
[1]
resolve(data);
} else {
reject(Error("Terjadi kesalahan."));
});
Kita hanya cukup memanggil constructor dari objek promise, yang menerima satu
fungsi. Fungsi yang diberikan kepadaPromise dapat diisikan dengan dua
parameter, yaitu:
1. Parameter pertama yang adalah sebuah fungsi yang dijalankan jika kode
asinkron telah selesai berjalan, dan berjalan tanpa masalah.
2. Parameter kedua sifatnya opsional, berisi fungsi yang dijalankan ketika
terjadi kesalahan dalam eksekusi kode asinkron.
Isi dari badan fungsi yang dikirimkan ke Promise sendiri tidak dibatasi.
Misalnya, kita dapat mengisikan fungsi tersebut dengan pemanggilan AJAX
seperti berikut:
1
2
3
4
5
6
7
8
9
try {
xhr = new XMLHttpRequest();
} catch (e) { reject(Error("XHR tidak didukung browser.")); }
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
10
resolve(xhr.responseText);
11
12
13
14
}
};
xhr.open('GET', 'http://example.com/testajax/');
15
xhr.send();
16
17
});
Seperti yang dilihat dari kode di atas, kita pada dasarnya melakukan
pemanggilan AJAX di dalam fungsi yang dikirimkan kePromise. Perbedaan dari
kode yang kita tulis di atas adalah pemanggilan
fungsi resolve dan reject ketika kode AJAX berhasil ataupun gagal berjalan.
Sebuah Promise yang telah kita buat dapat memiliki tiga status keadaan:
1. Pending, yaitu jika nilai hasil eksekusi asinkron Promise belum dapat
diakses.
2. Fulfilled, yaitu ketika nilai sudah dapat diakses. Nilai ini nantinya
akan menjadi nilai permanen dari Promise.
3. Rejected, yaitu nilai yang didapatkan kalau eksekusi asinkron gagal
berjalan. Sama seperti fulfilled, nilai dari rejectedakan diasosiasikan
kepada Promise secara permanen. Nilai rejected biasanya berupa
objek Error, meskipun tidak terdapat batasan khusus apa yang harus kita
isikan di sini.
Ketika Promise dibuat, secara otomatis Promise akan berada pada
status pending. Kita kemudian dapat menggunakanPromise selayaknya variabel
biasa. Utilisasi dari hasil eksekusi asinkron Promise (setelah status berubah
menjadi fulfilledatau rejected) sendiri harus dilakukan melalui
pemanggilan method khusus.
Mengambil Hasil Eksekusi Promise
Meskipun Promise dapat digunakan layaknya variabel sembari menjalankan
eksekusi asinkron di balik layar, sampai satu titik tertentu kita tetap akan
harus mendapatkan hasil eksekusi tersebut. Ketika ingin mengakses nilai hasil
eksekusi asinkron dari Promise kita dapat memanggil method then, seperti
berikut:
1
2
3
promise.then(
function (data) {
},
function (error) {
7
8
}
);
Kita dapat menggunakan dua cara untuk melakukan pemanggilan Promise secara
berantai seperti yang dijelaskan sebelumnya, yaitu:
1. Mengembalikan objek Promise pada fungsi yang dikirimkan ke then, dan
kemudian memanggil then dari fungsi tersebut lagi.
2. Menggunakan method Promise.all yang dirancang untuk memanggil
beberapa Promise sekaligus.
Mari kita langsung lihat metode yang pertama terlebih dahulu. Asumsikan kita
memiliki sebuah fungsi yang mengembalikan Promise seperti berikut:
1
MyLib.AJAX({
onSuccess: success,
onFailed: fail,
url: url,
method: "GET"
});
10
},
11
12
13
14
15
});
};
promise.then(function (data) {
// lakukan sesuatu
});
// atau langsung:
getPromised("https://example.com/api/testing").then(function (data) {
9
10
// lakukan sesuatu
});
Ketika kita mengembalikan nilai dari fungsi yang diberikan pada then, kita
dapat menyambung pemanggilan then untuk melakukan operasi lebih lanjut kepada
nilai tersebut:
1
getPromised("https://example.com/api/testing").then(function (data) {
// lakukan sesuatu
3
4
return data + 1;
}).then(function (val) {
7
8
console.log(val); // 11 (data + 1)
});
Dengan prinsip yang sama, ketika kita mengembalikan objek Promise di dalam
fungsi then, maka operasi Promise pada fungsi kedua hanya dapat
dijalankan setelah operasi pertama selesai. Perhatikan kode berikut:
1
2
getPromised("https://example.com/api/testing")
.then(function (data) {
// lakukan sesuatu
4
5
// ...
6
7
return getPromised("https://example.com/api/percobaan");
})
.then(function (data) {
10
11
12
});
sebuah Promise baru kita akan memastikan then kedua hanya berjalan
setelah Promiseselesai berjalan.
Cara lain untuk menjalankan beberapa Promise sekaligus adalah dengan
menggunakan method Promise.all. MethodPromise.all menerima sebuah array
dari Promise, yang akan dijalankan dan dikembalikan hasilnya kepada kita pada
fungsi then:
1
var p1
= getPromised("https://example.com/api/testing");
var p2
= getPromised("https://example.com/api/percobaan");
4
5
Promise.all(allP).then(function (results) {
});
3
4
5
6
Promise.race([p1, p2]).then(function(value) {
// value === "two"
});
3
4
Promise.race([p3, p4]).then(
function(value) {
},
function(reason) {
// tidak dieksekusi
10
11
}
);
Bahkan ketika salah Promise terlebih dahulu gagal, Promise lain yang mungkin
memberikan hasil juga tidak lagi diperhitungkan hasilnya:
1
2
3
4
5
Promise.race([p5, p6]).then(
function(value) {
// tidak dieksekusi
}, function(reason) {
9
10
}
);
[1]
. Sebagai elemen
terpopuler, tentunya sangat banyak pengguna yang bergantung pada kedua elemen
ini dalam melakukan navigasi website. Ketika ingin mengunjungi sebuah web,
kita memasukkan alamat web tersebut ke dalam Location Bar. Jika kita
berpindah-pindah halaman dalam satu jendela browser, kita lalu dapat kembali
ke halaman sebelumnya melalui tombol Back. Model navigasi seperti ini
merupakan model yang paling umum digunakan oleh pengguna awam.
Di dalam sebuah website sendiri biasanya terdapat elemen-elemen yang tidak
berubah antar halaman web, misalnya menu atau logo dari website. Ketika
pengguna berpindah dari satu halaman ke halaman lain maka pengguna harus
mengambil kembali elemen-elemen yang sama tersebut. Pengembang web modern
menghindari hal ini dengan menggunakan teknik seperti AJAX yang dapat
memperbaharui halaman web dengan menambah atau mengubah bagian-bagian yang
perlu diganti tanpa pengguna harus berpindah halaman. Penggunaan AJAX untuk
navigasi web seperti ini, sayangnya membuat tombol Back tidak lagi dapat
digunakan. AJAX memperbaharui halaman web tanpa berpindah halaman ini
maka browser tidak dapat mencatat sejarah browsing, yang menyebabkan tombol
Back tidak dapat digunakan. Hal ini seringkali mengecewakan para pengguna.
History API dari Javascript kemudian dikembangkan untuk menyelesaikan
permasalahan tombol Back yang tidak dapat digunakan pada website dengan fitur
AJAX ini. Fitur utama yang ditawarkan oleh History API adalah penelusuran dan
manipulasi sejarah browser. Adapun mayoritas dari History API dapat diakses
melalui objek window.history.
Langsung saja, mari kita lihat beberapa fitur yang ditawarkan.
Penelusuran Sejarah Browser
Penelusuran sejarah browser dapat dilakukan dengan sangat mudah,
melalui method khusus yang sudah disediakan
yaituhistory.back dan history.forward:
1
window.history.back();
history.back();
4
5
history.forward();
3
4
history.go(1);
history.go(5);
Jika kita tidak mengetahui dengan pasti ada berapa halaman ke belakang maupun
ke depan, kita dapat menggunakan properti history.length untuk melihat ada
berapa banyak total sejarah yang sudah tercatat.
1
Fungsi history.pushState
Seperti yang dijelaskan sebelumnya, history.pushState menambahkan sejarah
baru ke dalam browser. Hal ini berarti setiap kali kita
memanggil history.pushState browser akan memindahkan halaman yang sekarang
dengan data baru ke dalam history stack. Hal ini sedikit berbeda dengan
navigasi biasa yang mana browser memasukkan URL halaman baru ke dalam history
stack dan kemudian melakukan download kepada halaman baru tersebut.
history.pushState menerima tiga buah parameter, yaitu:
1. Sebuah objek yang menyimpan data sejarah. Objek ini dapat diisikan
dengan data apapun yang kita inginkan, dan idealnya berisi data yang
digunakan untuk menandai status halaman yang dibuka. Objek ini disebut
dengan objekstate. Objek state disimpan di dalam komputer lokal
pengguna, dengan batas maksimal 640k karakter ketika objek diserialkan
(misal dengan memanggil JSON.stringify). Jika memerlukan objek yang
lebih besar dari itu, gunakansessionStorage dan localStorage.
Objek state ini nantinya akan diberikan ke halaman yang sedang aktif
ketika pengguna menekan tombol Back (atau kembali ke halaman sebelumya
denganhistory.back maupun history.go). Objek diberikan
melalui event popstate.
2. Parameter kedua merupakan sebuah string, yang memberikan nama dari
sejarah yang akan dikunjungi. Sayangnya, parameter kedua ini belum
didukung mayoritas browser pada masa penulisan (September 2014)
sehingga parameter ini tidak melakukan apa-apa.
3. Sebuah string yang berisi URL baru. Browser tidak akan pindah ke URL
baru ini ketika kita memanggil pushState, melainkan hanya menggantikan
URL yang ada dengan URL baru ini. Begitupun, terdapat kasus di
mana browser akan membuka URL ini, misalnya ketika kita mematikan dan
menjalankan kembali browser. Parameter ketiga ini dapat diisikan dengan
URL relatif, tetapi harus dari domain yang sama. Jika URL yang
diberikan memiliki domain berbeda maka sebuah exception akan
dilemparkan. Parameter ketiga bersifat opsional. Jika tidak diberikan,
parameter ketiga diasumsikan adalah alamat URL sekarang.
Ketika menggunakan history.pushState, hal yang paling perlu diperhatikan
adalah data yang kita berikan pada parameter pertama. Sebagai data yang
menandakan keadaan (state) dari sebuah sejarah yang baru dibuat, biasanya
data ini lah yang menentukan tampilan atau bentuk halaman yang akan kita
berikan ke pengguna. Misalnya pada sebuah daftar artikel yang disimpan dalam
beberapa halaman, kita dapat mengirimkan nomor halaman selanjutnya seperti
berikut:
1
var data = {
2
3
hal: halSelanjutnya
};
4
5
Perhatikan bahwa data yang kita kirimkan berisi halaman selanjutnya, yakni
halaman yang akan kita tampilkan bukan halaman tempat sekarang pengguna
berada. Data yang kita kirimkan ini akan dapat diakses ketika pengguna
menekan tombol Back dari halaman berikutnya. Data kita akses
melalui event popstate.
Fungsi history.replaceState
history.replaceState merupakan method yang cara kerjanya sama persis
dengan history.pushState. Perbedaan utama method ini
dengan history.pushState adalah history.replaceState tidak menambahkan
catatan sejarah baru, melainkan langsung menggantikan state halaman yang
sedang dibuka sekarang.
Method ini sangat berguna terutama ketika kita ingin menambahkan state baru
ke halaman, berdasarkan apa yang dilakukan oleh pengguna.
Event PopState
Event PopState dijalankan oleh browser setiap kali terjadi pergerakan isi
sejarah dari satu dokumen yang sama. PopState hanya berjalan ketika
pergerakan sejarah dilakukan secara langsung atas permintaan pengguna, dengan
kata lain penekanan tombol Back dan Forward
atau history.back maupun history.go akan menjalankan PopState. PopState tidak
akan dijalankan
ketika history.pushState maupun history.replaceState dijalankan.
Ingat juga bahwa PopState hanya berjalan untuk satu dokumen yang sama. Hal
ini berarti ketika pengguna berpindah dokumen (misal
dari index.html ke about.html), maka PopState tidak akan dijalankan ketika
pengguna menekan tombol Back. Ketika mengembangkan aplikasi dengan History
API, kita hanya akan memiliki satu dokumen web saja, untuk menjaga
keberjalnjutan sejarah ini. Karenanya, aplikasi yang dibangun dengan
memanfaatkan History API sebagai pengendali dokumen dikenal dengan
nama Single Page Application (SPA).
Penggunaan event PopState sendiri tidak berbeda dengan event lainnya:
1
3
4
});
Beberapa hal yang perlu diperhatikan dari contoh penggunaan PopState di atas:
1. PopState diikatkan pada objek window. Hal ini penting, karena PopState
yang sifatnya global dan untuk seluruh halaman ini hanya dimiliki
oleh window saja.
2. Data yang kita akses pada event.state adalah data yang kita berikan
untuk parameter pertama
padahistory.pushState atau history.replaceState. Isi
dari event.state ini akan terus berubah setiap kali pengguna menekan
tombol Back atau Forward.
Hal lain yang juga perlu diingat adalah bahwa PopState tidak dijalankan
ketika kita pertama kali membuka sebuah dokumen HTML. Begitupun, PopState
akan tetap dijalankan ketika kita kembali membuka halaman pertama melalui
tomblback.
Kegunaan Praktis History API
Seperti yang telah dijelaskan pada bagian awal, fungsi utama dari History API
adalah untuk memastikan sejarah dari penelusuran pengguna tersimpan dalam
Javascript Routing
Pada bagian ini kita akan membahas penggunaan History API untuk membangun
sebuah routing library sederhana. Tujuan utama dari pustaka yang ingin kita
kembangkan adalah untuk memetakan URL yang diakses user dengan fungsi atau
objek tertentu dalam kode kita. Misalkan ketika pengguna
mengakses http://contoh.com/profile/bertzzie maka
fungsiContoh.Profile("bertzzie") di dalam kode kita akan dipanggil. Karena
menggunakan History API, tentunya pengguna tidak perlu benar-benar berpindah
halaman (data bisa diambil menggunakan AJAX / WebSocket) dan sejarah
penelusuran dalam browser akan tetap diperbaharui.
Contoh penggunaan
Agar pembaca dapat lebih mudah memahami apa yang akan kita kembangkan, kita
terlebih dahulu akan melihat contoh penggunaan routing library yang akan
dikembangkan. Berikut adalah contoh penggunaan routing library yang akan kita
kembangkan:
1
var root
route
= "/routing/",
3
4
// http://contoh.com/routing/profile/*
});
8
9
10
11
// http://contoh.com/routing/options/*
12
});
13
14
Pada akhirnya, kode yang kita buat di atas cukup sederhana, dan terlihat
tidak jauh berbeda dengan kode frameworkaplikasi pada umumnya.
Dengan menggunakan routing library seperti pada kode di atas, secara otomatis
seluruh request yang datang dari pengguna akan hanya boleh datang dari satu
titik saja. Seluruh request harus masuk ke satu halaman yang sama, dan
kemudian request tersebut ditangani oleh kode router kita di atas. Karena
seluruh request masuk ke satu halaman (biasanyaindex.html), maka aplikasi
yang dibangun dengan cara seperti ini dikenal dengan nama Single Page
Application (SPA)
[1]
Persiapan Awal
Sebelum mulai mengembangkan routing library ini, terdapat satu hal yang perlu
kita persiapkan terlebih dahulu, yaitu memastikan semua request pengguna
masuk ke satu titik pada kode kita saja. Jika kita ingin menggunakan routing
librarydengan optimal, kita harus dapat memastikan request pengguna, dalam
bentuk apapun, kembali ke index.html. Hal ini berarti ketika pengguna
mengakses http://contoh.com/profile/bertzzie (atau URL lainnya) sebenarnya ia
akan dibawa ke http://contoh.com/index.html di balik layar.
Untuk dapat mencapai hal ini, kita perlu melakukan konfigurasi melalui web
server yang digunakan. Silahkan baca dokumentasi pada web server yang anda
gunakan untuk melakukan hal ini
[2]
dapat digunakan jika anda menggunakan web server Apache httpd (yang biasa
dipaketkan dalam XAMPP
[3]
):
RewriteEngine On
Kemudian coba akses halaman secara acak pada URL relatif terhadap aplikasi
web anda. Jika anda menyimpan kode dihtdocs/routing/ maka akses
2
3
4
(function () {
"use strict";
5
6
MyLib.URL = {
},
10
11
var a
= document.createElement("a");
12
a.href = url;
13
14
return a.pathname;
15
16
};
17
})(MyLib);
2
3
4
(function () {
"use strict";
5
6
var root
= r? MyLib.URL.trimSlash(r) : "/",
routes = [],
fin
= {};
10
11
12
13
return fin;
};
})();
3
4
5
6
7
8
return this;
};
len = routes.length,
r;
10
11
12
13
if (r === pattern.toString()) {
14
routes.splice(i, 1);
15
return this;
16
17
18
= 0,
}
}
19
20
return this;
};
21
22
fin.reset = function () {
23
routes = [];
24
root
25
= ''
};
Penambahan rute baru, melalui fin.add cukup sederhana. Method ini menerima
dua argumen: argumen pertama beruparegular expression yang digunakan untuk
mencocokkan pola rute, sementara argumen kedua adalah fungsi yang dipanggil
ketika rute cocok dengan URL. Kita hanya menambahkan objek baru ke dalam
array routes, dan kemudian mengembalikan this agar method dapat dipanggil
secara berantai jika diperlukan.
Penghapusan rute pada fin.remove sedikit lebih panjang, karena kita terlebih
dahulu harus menelusuri seluruh rute yang ada. Setiap rute dicek
apakah pattern-nya sama dengan rute yang akan dihapus, dan jika sama rute
tersebut kita buang dari array routes. Pembuangan array dilakukan
melalui Array.prototype.splice (dokumentasi splice) untuk menyederhanakan
fungsi.
Sebagai tambahan, kita juga membuat fungsi fin.reset yang hanya menghapus
semua rute, beserta dengan rute asal.
Masuk ke Rute Baru
Setelah bisa menambahkan rute baru, tentunya kita ingin memberikan fasilitas
kepada pengguna library untuk berpindah rute. Misalkan, pengguna web dapat
dibawa ke rute baru ketika melakukan klik sebuah tombol. Berikut adalah kode
untuk mencapai perpindahan rute ini:
1
2
3
4
5
6
7
= 0,
len
= routes.length,
match;
path = MyLib.URL.trimSlash(path);
8
9
10
match = path.match(routes[i].pattern);
11
if (match) {
12
match.shift();
13
routes[i].handler.apply({}, match);
14
15
16
17
18
19
}
};
fin.getCurrentPath = function () {
var path = '';
20
21
path = MyLib.URL.trimSlash(location.pathname);
22
23
24
25
26
27
28
29
return MyLib.URL.trimSlash(path);
};
30
Run(path);
31
32
33
Run merupakan sebuah fungsi privat yang melakukan pencocokan pola URL
terhadap regular expression dari rute yang kita berikan pada fin.add. Jika
kita tidak memberikan nilai url kepada Run, maka eksekusi akan dilakukan
terhadap URL yang sedang dibuka pengguna pada saat itu (sehingga pengguna
tidak berpindah halaman). Pada fungsi Run, pada dasarnya kita menelusuri satu
per satu rute, dan mencocokkan polanya dengan
[4]
untuk
memanggil fungsi handleryang kita simpan ketika memanggil fin.add. Tentu saja
pemanggilan apply dilakukan setelah kita membuang elemen pertama dari array
hasil, karena elemen pertama merupakan pola dasar URL yang diberikan
(contohnya"/profile/bertzzie" dengan pola "/profile\/(.*)/" akan
mengembalikan ["profile/bertzzie", "bertzzie"], yang mana kita hanya
memerlukan "bertzzie").
Pengambilan URL pengguna sekarang dilakukan melalui fin.getCurrentPath.
Fungsi ini cukup gamblang, di mana kita hanya mengambil informasi URL
sekarang melalui location.pathname, dan kemudian melakukan pembersihan
terhadap data yang diberikan. Pembersihan yang dimaksud adalah dengan
menghapus garis miring di depan maupun belakang, serta penambahan root.
Fungsi ini dapat juga diletakkan pada MyLib.URL jika diinginkan. Kita
meletakkan fungsi ini padaMyLib.Routes dengan pertimbangan
bahwa currentPath atau pathname yang kita gunakan memiliki aturan spesifik
dari routing library kita (garis miring pembuka dan penutup harus dihapus,
rute asal, dst). Jika diletakkan pada MyLib.URL, kita pada dasarnya dapat
langsung mengembalikan location.pathname.
Terakhir, pengguna library dapat melakukan pemindahan rute melalui fungsi
publik fin.navigate, yang hanya melakukan pemanggilan Run dan kemudian
menyimpan perpindahan ini ke dalam sejarah
penelusuran browser melaluihistory.pushState. fin.navigate perlu dipisahkan
dengan Run terutama karena penyimpanan sejarah ini. Terkadang kita ingin
memanggil Run tanpa memindahkan sejarah, misalnya ketika pengguna menekan
tombol Back. Jika aksi pengguna menekan Back disimpan dalam sejarah, kita
akan memiliki perulangan sejarah sampai tidak terbatas.
Navigasi Link Otomatis
Untuk menambahkan fitur terakhir dari routing library, kita dapat membuat
sebuah fungsi yang akan mengubah semua link (elemen a pada HTML) untuk
menggunakan navigasi dari routing library kita. Beriktu adalah kode yang
digunakan:
1
2
3
4
6
7
8
9
10
};
};
fin.init = function () {
var links = document.querySelectorAll("a"),
11
12
13
14
= 0,
len
= links.length,
that
= this,
link;
15
16
17
link = links[i];
18
19
20
21
22
23
Run(location.href);
24
25
};
};
library. Jika anda tidak menginginkan hal ini, pantau event popstate pada
akhirconstructor.
Penutup
Pada akhir tulisan ini kita telah melihat bagaimana menggunakan History API
dengan praktis dan maksimal. Walaupun masih terdapat banyak kekurangan
dari routing library yang kita kembangkan, kita telah memanfaatkan History
API dengan efektif. Adapun beberapa kekurangan yang belum terimpelemntasi
dengan baik yaitu:
1. HrefClickHandler belum dapat menangani link eksternal. Jika tujuan link
yang diberikan oleh a adalah ke website
lain, HrefClickHandler seharusnya tahu dan tidak menanganinya
dengan MyLib.Routes.
2. Belum terdapat mekanisme atau method yang dapat digunakan ketika kita
ingin mengubah root secara dinamis.
3. MyLib.Routes belum menangani HTTP dengan baik. Misalnya, pengiriman
data form melalui POST atau PUT tidak akan dapat ditangani.
Implementasi perbaikan dari beberapa kekurangan di atas (dan pastinya masih
terdapat banyak kekurangan lain lagi) akan diserahkan sebagai latihan untuk
pembaca.
Kode lengkap dari routing library yang kita kembangkan, beserta contoh
penggunaannya dapat diambil pada halaman Github untuk artikel ini.
Catatan Kaki
[1]
[2]
[4]
Setiap kode modul memiliki peranan spesifik, dan biasanya sebuah modul
terdiri dari beberapa fungsi. Semakin spesifik dan sejenis fungsi yang
terkumpul di dalam sebuah modul semakin baik. Tingkat kecocokan fungsifungsi di dalam modul ini kita kenal dengan istilah cohesion.
2. Setelah kode program dipecah menjadi bagian-bagian kecil, masing-masing
komponen kecil ini kemudian dapat kita hubungkan untuk menjadi sebuah
komponen yang lebih besar atau bahkan program jadi.
Pembuatan modul dari kode program dapat dilakukan dengan banyak cara, seperti
mengumpulkan fungsi-fungsi ke dalam kelas atau objek, dan kmeudian memasukkan
kode tersebut ke dalam sebuah file. Bagian kedua, di mana kita dapat
memanggil modul-modul lain dengan mudah, biasanya didukung secara langsung
oleh bahasa pemrograman yang digunakan. Contohnya, pada bahasa Java kita
memiliki perintah import dan pada C# kita memiliki perintah using.
Javascript, sampai pada saat penulisan (November 2014), sayangnya belum
mengimplemenatsikan fitur ini secara alami di dalam bahasa (meskipun telah
direncanakan untuk ke depannya).
Karena belum diimplementasikan secara langsung oleh bahasa pemrogramannya,
situasi sistem manajemen modul pada Javascript belum terlalu baik. Terdapat
beberapa alternatif implementasi, yang berbeda-beda dan memiliki kelebihan
dan kekurangannya masing-masing. Kita akan mencoba melihat dan membahas
beberapa sistem yang paling populer pada bagian ini, yaitu AMD, CommonJS, dan
terakhir ES6 Harmony untuk melihat bagaimana Javascript akan
mengimplementasikan sistem manajemen modul kedepannya.
Persiapan Module System: Script Loader
Sebelum mulai membahas tentang module system, kita akan terlebih dahulu
membahas sebuah sistem lain yang akan selalu digunakan oleh module
system: script loader. Script loader merupakan sebuah sistem yang membantu
kita dalam melakukan pengambilan file-file kode yang dibutuhkan oleh module
system.
Karena terdapat banyak jenis dari script loader, masing-masing dengan
kelebihan dan kekurangannya sendiri, kita tidak akan membahas hal ini terlalu
dalam. Script loader yang akan kita gunakan adalah RequireJS.
AMD (Asynchronous Module Definition)
Tujuan dari pembuatan AMD adalah untuk menyediakan sebuah solusi dalam
mengembangkan kode modular Javascript. AMD dirancang agar kita dapat
mendefinisikan dan mengunakan modul secara asinkron. Sifat asinkron serta
fleksibilitas dari AMD membuat AMD banyak disukai oleh pengembang, dan
dianggap sebagai standar implementasi module system yang cukup baik sebelum
ES Harmony siap digunakan.
Module System pada AMD
Terdapat dua method utama yang perlu kita mengerti sebelu menggunakan AMD,
yaitu:
1. define digunakan untuk membuat sebuah model baru.
2. require digunakan untuk menggunakan modul yang telah didefinisikan,
menjadikan modul kita sekarang bergantung kepada modul lain.
Fungsi define dapat digunakan dengan cukup sederhana:
1
define(
module_id /*optional*/,
[dependencies] /*optional*/,
);
Seperti yang dapat dilihat dari kode di atas, bagian module_id bersifat
opsional, dan biasanya hanya digunakan ketika kita menggunakan sistem lain
yang tidak mendukung AMD. Jika module_id tidak diisikan, modul yang kita
gunakan disebut dengan modul anonim. Jika modul kita bersifat anonim, maka
nama dari modul akan dikenali sebagai nama file oleh AMD. Hal ini
memungkinkan kita untuk mengubah lokasi modul dengan bebas, tanpa harus
terikat dengan bagaimana kita menyimpan modul tersebut.
Parameter kedua, [dependencies], juga bersifat opsional. Parameter ini
merupakan sebuah array dari modul-modul lain yang akan digunakan di dalam
modul yang akan dibuat. Karena seringkali kita akan membuat modul yang tidak
bergantung dengan modul-modul lain, maka modul ini bersifat opsional.
define('myModule',
['foo', 'bar'],
var myModule = {
10
doStuff:function(){
11
console.log('Yay! Stuff');
12
13
14
15
return myModule;
16
17
}
);
// kode di sini
3
4
5
foo.doSomething();
});
Pada kode di atas, kita dapat menganggap foo dan bar sebagai sebuah fungsi
eksternal. Untuk menggunakan kedua fungsi ini kita memanggilnya melalui
console.log('Hello foo');
};
7
8
this.bar = function(){
console.log('Hello bar');
10
11
12
13
14
};
};
15
Seperti yang dapat dilihat pada kode di atas, pada CommonJS kita cukup hanya
menyimpan fungsi atau objek yang kita ingin gunakan pada modul lain ke
dalam exports. exports sendiri bersifat global, yang artinya dapat kita
panggil kapanpun, di manapun.
Untuk menggunakan fungsi foobar di atas kita dapat memanggil
fungsi require seperti berikut:
1
3
4
Mirip seperti AMD, ketika kita memanggil modul dari CommonJS, pemanggilan
dilakukan melalui file, relatif terhadap modul yang sedang mengakses
sekarang. Jika dianalogikan, require('./foobar') pada kode di atas dapat
dianggap mengakses objek global exports yang ada pada file foobar.js.
Perbandingan AMD dan CommonJS
Sekilas CommonJS memang terlihat lebih sederhana untuk digunakan. Sayangnya,
terdapat banyak pengembang yang berpendapat bahwa CommonJS lebih cocok
digunakan hanya pada sisi server. Pendapat seperti ini didasarkan pada
berbagai modul bawaan pada CommonJS yang dianggap lebih condong ke penggunaan
Javascript pada sisi server misalnya I/O, system (pemanggilan program,
pembuatan proses, dkk). Fitur-fitur seperti ini tentu saja mustahil untuk
diimplementasikan pada sisi browser tanpa membuka lobang keamanan yang besar.
Hal ini kemudian menjadikan CommonJS gagal sebagai stanar de facto untuk
module system JavaScript, meskipun CommonJS digunakan secara luas pada
pemrograman sisi server seperti NodeJS.
AMD sendiri lebih menggunakan pendekatan yang mengutamakan browser dalam
pengembangannya. Fitur-fitur seperti pengambilan modul secara asinkron dan
kompatibilitas terhadap Javascript lama menunjukkan hal ini. Tanpa
menggunakan I/O file, AMD dapat mendukung berbagai jenis modul, dari pustaka
seperti jQuery sampai framework seperti Dojo. Semua jenis modul AMD juga
dapat dipastikan berjalan secara alami (native) dalam browser. Hal ini
membuat AMD menjadi sangat fleksibel.
Pendekatan module system dari ES Harmony sendiri sebenarnya lebih dekat
dengan CommonJS dalam beberapa hal seperti cara impor / ekspor modul
dibandingkan dengan AMD. Hal ini dikarenakan ES Harmony yang mencoba untuk
mengikutsertakan penggunaan Javascript pada server dan klien sebagai kasus
penggunaan module systemnya. Tanpa adanya fungsi standar yang berhubungan
dengan server, ES Harmony juga mendukung berbagai fitur baik dari AMD seperti
penggunaan modul asinkron.
Secara singkat, beberapa kelebihan utama dari AMD yaitu:
ES6 Harmony
module staff{
// spesifikasikan export untuk nilai
8
9
10
11
12
}
}
module skills{
export var specialty = "baking";
13
14
15
16
17
18
module cakeFactory{
19
20
21
// import semuanya
22
23
24
25
26
baker.bake('cupcake', toppings);
27
},
28
29
baker.bake('muffin', size);
30
31
32
Kita bahkan dapat menggunakan modul yang berada pada server lain dengan ES
Harmony, seperti berikut:
1
cakeFactory.oven.makeCupcake('sprinkles');
cakeFactory.oven.makeMuffin('large');
Penutup
Pada bagian ini kita telah melihat beberapa pilihan moduel system pada
Javascript. Module system memiliki kelebihan jika dibandingkan dengan
penggunaan fungsi maupun objek global pada Javascript yang biasa kita
gunakan. Keuntungan utamanya yang didapatkan misalnya kita tidak lagi perlu
mengkhawatirkan urutan pemasukan file pada tag <script>yang digunakan.
Pembaca sangat direkomendasikan untuk setidaknya mencoba salah satu dari
CommonJS atau AMD sebelum melanjutkan ke bab berikutnya, karena pada bagian
framework kita akan menggunakan module system sepenuhnya.
Framework Javascript
Pada bagian ini kita akan melihat apa yang dimaksud dengan Framework secara
umum, serta berbagai contoh framework yang ada pada Javascript. Tujuan utama
kita mempelajari ini adalah karena pada pengembangan aplikasi yang berukuran
signifikan (katakanlah, lebih dari 1.000 baris), umumnya kita akan menemui
banyak kode-kode yang serupa dengan beberapa parameter atau nilai berbeda.
Ketika hal ini terjadi, kita perlu mengembangkan apa yang dikenal dengan
abstraksi. Mengutip Wikipedia:
Pada ilmu komputer, abstraksi merupakan proses memisahkan inti pemikiran
(ide; gagasan) dari contoh (kejadian) spesifik ketika gagasan tersebut
diimplementasikan.
Struktur komputasi dari gagasan didefinisikan melalui makna (semantik)
mereka, dengan menyembunyikan detil dari cara kerja gagasan tersebut.
- Abstraction (Computer Science)
Ketika mengembangkan aplikasi yang relatif besar, pada umumnya kita akan
menemukan bagian dari aplikasi yang berulang. Misalnya, sebuah aplikasi yang
perlu mengakses basis data akan memiliki kode untuk melakukan koneksi dan
pengiriman perintah ke basis data. Meskipun perintah-perintah yang dikirimkan
berbeda, umumnya langkah untuk mengirimkan perintah tidak jauh berbeda:
1. Koneksi ke basis data
2. Kirimkan perintah
3. Proses respon
Ketiga langkah di atas dapat kita anggap sebagai implementasi dari sebuah
gagasan (inti pemikiran) umum. Inti pemikiran yang ada pada langkah di atas
adalah berhubungan dengan basis data. Karena hal ini akan dilakukan berulang
kali dalam sebuah aplikasi, akan sangat baik jika kita memiliki sebuah
gagasan umum yang membungkus ketiga langkah di atas menjadi hanya kirim
Sebelum mulai masuk dan mencoba berbagai abstraksi yang ada, kita akan
melihat jenis-jenis abstraksi yang ditawarkan oleh lingkungan Javascript
terlebih dahulu. Secara umum, di lingkungan Javascript terdapat beberapa
jenis abstraksi yang dapat kita temui:
1. Library
2. Widget Toolkit
3. Framework
Mari kita lihat perbedaan dari masing-masing abstraksi ini.
Library
Sebuah library, atau pustaka kode, didefinisikan sebagai berikut pada tulisan
ini:
Kumpulan implementasi fungsi-fungsi umum dari perangkat lunak yang memiliki
perilaku spesifik
Fungsi yang dimaksudkan pada kumpulan implementasi fungsi-fungsi umum di
atas adalah fungsi dalam arti fungsionalitas, bukan fitur function pada
sebuah bahasa pemrograman. Sebuah library dapat menyediakan kumpulan
implementasi fungsionalitas untuk membaca file PDF atau melakukan
kalkulasi Fast Fourier Transform. Fitur bahasa yang digunakan
oleh library untuk mendapatkan sebuah fungsionalitas tertentu mungkin
saja function, class, atau yang lain. Cara library mengimplementasikan
sesuatu tidak terlalu penting, selama fungsionalitas yang diinginkan dapat
tercapai.
Fungsi-fungsi umum yang ada di dalam sebuah library umumnya memiliki perilaku
spesifik. Perilaku spesifik ini diartikan sebagai sebuah spesifikasi masukan
dan keluaran dari fungsi tersebut. Spesifikasi ini dapat mencakup tipe data
(masukan maupun keluaran), efek samping, paramter fungsi, nilai kembalian,
dan banyak hal lain. Tingkat kelengkapan spesifikasi dari
sebuah library sendiri berbeda-beda, dan tentunya semakin lengkap (spesifik)
detil spesifikasinya semakin baik pulalibrary tersebut.
Untuk memperjelas lagi, mari kita lihat contoh penggunaan sebuah library pada
aplikasi. Diagram berikut menunjukkan alur arus data sebuah aplikasi yang
menggunakan library:
Pada diagram di atas, kita ingin mengembangkan sebuah aplikasi yang dapat
memainkan file musik ogg vorbis. Terdapat dua buah library yang digunakan
oleh aplikasi kita, yaitu libvorbisfile dan libalsa. Alur kerja dari aplikasi
adalah kira-kira seperti berikut:
1. Pengguna memberikan file ogg kepada aplikasi.
2. Aplikasi memberikan file ogg kepada libvorbisfile.
3. libvorbisfile memproses file ogg dan memberikan kembali hasil proses
berupa stream audio ke aplikasi.
4. Aplikasi memberikan stream audio dari libvorbisfile ke libalsa.
$.ajax({
url: "/api/getWeather",
data: {
zipcode: 97201
},
success: function(data) {
8
9
}
});
Widget Toolkit
Setingkat di atas library, kita memiliki Widget Toolkit dalam dunia
Javascript. Definisi sederhananya adalah:
(Kumpulan) library untuk membuat elemen-elemen tertentu untuk mengembangkan
antarmuka (UI)
Widget Toolkit merupakan kumpulan library, atau terkadang
sebuah library besar yang dibangun di atas banyak librarykecil lainnya.
Karena merupakan kumpulan library, Widget Toolkit biasanya menspesifikasikan
bagaimana seluruh libraryyang digunakan saling berinteraksi. Hal ini berarti
terdapat coupling antara semua library yang digunakan oleh Widget Toolkit.
Sebuah Widget Toolkit seringkali juga bukan hanya menggunakan library sebagai
alat untuk membangun antarmuka pengguna, tetapi juga untuk membantu fiturfitur tertentu. Misalnya, sebuah Widget Toolkit dapat menggunakan jQuery
untuk berinteraksi dengan elemen-elemen antarmuka pengguna yang
disediakannya.
Karena sebuah antarmuka biasanya disertai dengan berbagai komponen pendukung
seperti ikon atau gambar latar, sebuah Widget Toolkit seringkali dipaketkan
bersamaan dengan komponen-komponen pendukung tersebut. Dalam lingkungan
Javascript, komponen-komponen pendukung ini dapat berupa CSS, gambar, font,
suara, dst.
Sebagai suatu abstraksi yang mencampurkan berbagai library dan komponen
antarmuka, sebuah Widget Toolkit biasanya memiliki cara kerja dan interaksi
antar komponen yang cukup spesifik. Hal ini menyebabkan Widget Toolkit sulit
digunakan bersamaan dengan Widget Toolkit atau bahkan library lain. Ketika
memutuskan untuk menggunakan sebuah Widget Toolkit, biasanya evaluasi akan
dilakukan dengan lebih hati-hati, karena pergantian Widget Toolkit biasanya
akan diikuti oleh pergantian seluruh komponen antarmuka beserta
dengan library yang digunakan.
Untuk memperjelas perbedaan antar Widget Toolkit ini, mari kita lihat sekilas
bagaimana tiga buah Widget Toolkit yang cukup populer (jQuery UI, Dojo
Toolkit, YUI) menampilkan sebuah widget untuk memilih tanggal (datepicker):
Kode (jQuery UI)
Tampilan
Kode (Dojo)
Kode (YUI)
Tampilan
Tampilan
Dari ketiga Widget Toolkit yang ada di atas, kita dapat melihat bagaimana
baik tampilan hasil antarmuka maupun kode yang digunakan untuk menghasilkan
elemen tersebut sangat berbeda. Perbedaan paling mencolok tampak pada
tampilan yang dihasilkan. Karena kita tidak menambahkan CSS sendiri di sini,
tampilan yang dihasilkan didapatkan dari masing-masing CSS yang telah
dipaketkan oleh Widget Toolkit. Cara pembuatan elemen juga berbeda-beda. Pada
jQuery UI, kita dapat melihat bagaimana elemen dibuat dari pengembangan
React JS (Pengenalan)
ReactJS merupakan sebuah library dari facebook yang dapat digunakan untuk
membangun antarmuka pengguna. Pada bagian ini kita akan mencoba menggunakan
React untuk membangun aplikasi sederhana, untuk melihat konsep-konsep inti
yang ditawarkan oleh React.
Sebelum mulai mencoba menggunakan React, beberapa prasyarat yang diperlukan
yaitu:
1. React Starter Kit untuk berbagai file dari React
2. jQuery, sebagai library pendukung
Jika sudah mengambil kedua kebutuhan di atas, ekstrak React Starter Kit ke
dalam sebuah direktori dan jalankan sebuah web server lokal (misal: httpserver) di dalamnya. Seluruh eksperimen kita akan dijalankan dari direktori
ini.
Hello, React!
Untuk memulai eksperimen, kita akan langsung mencoba membuat sebuah halaman
sederhana dengan menggunakan ReactJS. Buat sebuah file dengan
nama index.html di dalam direktori utama starter kit ReactJS, dan isikan
dengan kode berikut:
1
<!DOCTYPE html>
<html>
<head>
<script src="build/react.js"></script>
<script src="build/JSXTransformer.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/jsx">
10
React.render(
11
<h1>Hello, world!</h1>,
12
document.getElementById('content')
13
);
14
15
16
</script>
</body>
</html>
awal tag <script>. Penjelasan lebih lanjut mengenai JSX akan kita
tambahkan nanti.
2. Parameter kedua menerima elemen DOM tempat kita ingin menampilkan
keluaran dari elemen pertama. Parameter ini cukup gamblang, dan kita
hanya mengirimkan elemen DOM standar yang didapat
daridocument.getElementById di sini.
Bentuk awal dari penggunaan ReactJS memang cukup sederhana. Kita akan melihat
bagaimana hampir keseluruhan pengembangan dari ReactJS berdasar dari model
sederhana ini nantinya.
Kode ReactJS pada File Terpisah
Jika diinginkan, kita juga dapat memisahkan kode JSX kita pada sebuah file
terpisah, layaknya kita memisahkan kode javascript biasa pada file lain.
Misalnya, kita dapat membuat sebuah file src/helloworld.js dengan isi:
1
React.render(
<h1>Hello, world!</h1>,
document.getElementById('content')
);
Dan tentunya kita harus menggantikan tag script pada index.html menjadi:
1
Tentunya karena kita telah memiliki hasil kompilasi JSX, kita tidak lagi
perlu menggunakan JSXTransformer.js pada HTML kita, dan kita juga dapat
langsung memasukkan hasil kompilasi seperti berikut:
1
<!DOCTYPE html>
<html>
<head>
<script src="build/react.js"></script>
</head>
<body>
<div id="content"></div>
<script src="out/helloworld.js"></script>
10
11
</body>
</html>
React.render(
3
4
document.getElementById('content')
);
Dengan hanya perbedaan pada bagian sintaks JSX saja. Tentunya kita juga dapat
langsung menggunakan pemanggilan fungsi seperti yang ada pada file hasil
kompilasi ini jika mau.
Pembangunan Elemen Antarmuka ReactJS
ReactJS merupaakn sebuah library yang berbasis komponen. Hal ini menyebabkan
kita harus mengembangkan sebuah elemen antarmuka komponen demi komponen. Kita
akan langsung melihat bagaimana kita dapat mengembangkan sebuah komponen
ReactJS sederhana dengan menggunakan fungsi-fungsi yang ada.
Aplikasi percobaan yang akan kita bangun adalah sebuah todo list (TDL)
sederhana, yang menyimpan catatan berbagai hal yang akan kita lakukan.
Tampilan aplikasi sangat sederhana:
TDL yang akan kita kembangkan ini akan terdiri dari tiga buah komponen, yaitu
1. komponen yang menerima masukan pengguna untuk menambahkan data baru,
2. komponen yang menampilkan semua tugas pengguna yang telah tercatat
dalam sistem, dan
3. komponen utama TDL yang menampung kedua komponen sebelumnya.
Jika diilustrasikan, pembagian komponen kita dapat dilihat seperti gambar
berikut:
Langsung saja, kita dapat mulai membangun elemen antarmuka TDL ini dari file
HTML yang diperlukan. Buat sebuah filetodo.html dan isikan dengan kode
berikut:
1
2
3
4
5
6
7
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Todolist React</title>
</head>
<body>
<div id="content"></div>
9
10
<script src="build/react.js"></script>
11
<script src="build/JSXtransformer.js"></script>
12
13
14
15
HTML yang dibuat cukup standar. Sekarang kita akan membuat file javascript
yang dibutuhkan, yaitu src/todolist.js:
1
render: function () {
return (
<div className="todoForm">
<label htmlFor="todoInput">
Tambahkan Data:
</label>
</div>
10
);
11
12
}
});
13
14
React.render(
15
<TodoForm />,
16
document.getElementById("content")
17
);
2
3
React.render(
document.getElementById("content")
);
</div>
10
);
11
12
}
});
7
8
9
10
11
12
13
14
15
16
17
18
19
);
});
return (
<ul className="list">
20
{listData}
21
22
23
</ul>
);
24
}
});
Elemen List yang kita kembangkan melakukan sesuatu yang menarik. Data yang
kita peroleh, berupa array dari objek, dipetakan menjadi array dari
elemen <li> JSX pada variabel listData. listData kemudian langsung kita
masukkan sebagai nilai literal di dalam <ul> yang dikembalikan, dan React
secara otomatis mengetahui apa yang harus dilakukan dengan array <li>!
Penggabungan List dan TodoForm dapat dilakukan dengan cukup alami:
1
render: function () {
return (
<div className="TodoList">
<h1>Todo List</h1>
</div>
);
10
11
}
});
12
13
React.render(
14
15
document.getElementById("content")
16
);
Perlu dicatat bahwa sebuah elemen yang dikembalikan oleh komponen React hanya
boleh terdiri dari satu elemen terluar. Hal ini berarti kode berikut:
1
2
3
return (
);
7
8
}
});
<head>
<script src="build/jquery-2.1.1.min.js"></script>
<script src="build/react.js"></script>
<script src="build/JSXTransformer.js"></script>
</head>
Kita kemudian perlu membuat file data.json yang isinya tidak berbeda jauh
dengan variabel data yang kita buat sebelumnya:
1
2
3
[
{
"content": "Beli Telur",
4
5
6
7
"tag": "belanja"
},
{
"tag": "kuliah"
},
10
11
12
"tag": "game"
13
},
14
15
"content": "Doraemon",
16
"tag": "film"
17
18
}
]
Data pada data.json ini nantinya yang akan kita ambil dari sever.
Selanjutnya, kita tidak akan langsung menggunakan variabel data lagi untuk
mengisikan list. Kita akan mengirimkan URL data yang diperlukan saja.
1
2
return (
<div className="TodoList">
<h1>Todo List</h1>
</div>
);
10
11
12
13
14
15
16
}
});
React.render(
<TodoList url="./data.json" />,
document.getElementById("content")
);
url: this.props.url,
dataType: 'json',
this.setState({data: data})
}.bind(this),
10
console.log(xhr);
11
console.log(status);
12
console.log(err);
13
}.bind(this)
14
15
16
17
18
19
20
21
22
23
24
});
},
getInitialState: function () {
return { data: [] }
},
componentDidMount: function () {
this.loadData();
},
render: function () {
var listData = this.state.data.map(function (data) {
return (
25
26
27
28
29
);
});
return (
30
<ul className="list">
31
{listData}
32
</ul>
33
);
34
35
}
});
sendiri. Karena ini, kita dapat dengan mudah mengimplementasikan live update,
dengan teknik polling:
1
componentDidMount: function () {
this.loadData();
setInterval(this.loadData, 2000);
},
Tentu saja kita dapat mengimplementasikan metode live update yang lebih baik,
misalnya dengan menggunakan WebSocket di bagian ini.