Lanjutan dari Bagian 6
Semua aplikasi yang punya banyak konten terstruktur pasti punya yang namanya dashboard. Untuk Dooitkoo, saya ingin dashboard bisa nampilin sekilas info tentang masing-masing subjek. Jadi sekali lirik, user bisa liat status terakhir semua subjek dalam bulan yang bersangkutan. Karena tujuannya “sekilas info”, ga ada pilihan bulan & tahun. Kalo misalnya user buka aplikasi pada bulan Mei 2013, info yang ditampilin adalah data bulan Mei 2013. Kalo buka di bulan April 2014, data yg ditampilin juga data bulan April 2014. Bulan & tahun diambil dari seting komputer user (local date & time).
Flow di Dashboard juga sederhana. Awalnya user lihat daftar subjek. Dia punya pilihan lihat ringkasan salah satu subjek atau bikin subjek baru. Kalo pilih liat ringkasan, dia punya pilihan untuk edit, hapus, atau buka subjek yang bersangkutan.
Wireframe
Sebelum mulai koding bikin desain dulu. Ga perlu bagus, sket wireframe aja udah cukup. Yg penting bisa jadi referensi waktu koding. Layout dashboard punya 5 bagian utama :
- Header. Isinya logo, user name (email) & link untuk logout.
- Sidebar. Panel di samping kiri untuk tampilin menu.
- Content. Untuk nampilin … konten
- Breadcrumb. Menu kecil di atas area konten buat tempat shortcut utk kembali ke dashboard & titel konten yang sedang ditampilin
- Footer.
Karena Dashboard punya 3 state yaitu add/edit, ringkasan, & daftar subjek, sekalian bikin wireframe-nya.
Page View
Selain berisi layout skeleton & bagian-bagian yg statis, Dashboard View juga berisi sedikit skrip, sekedar untuk booting aplikasi. Skrip yg utama ada 2 yaitu:
- dashboardpage.js
- yepnope.js
dashboardpage.js
adalah startup script yang tugasnya:
- Loading file JavaScript & CSS untuk plugin jquery yg dibutuhkan pake Yepnope
- Inisialisasi KO binding
Pengennya sih pake RequireJS untuk atur dependency & loading library, tapi nyoba bolak-balik selalu ada aja yg ga jalan. Daripada pusing kelamaan utak-atik, cari bulk loader aja. Untuk Dooitkoo saya pake library yang namanya Yepnope (bisa dipake sendirian atau sebagai bagian dari modernizr). Library ini bisa dipake untuk download resource (.js, .css) secara paralel, tapi nanti injeksi resource & eksekusi file javascript (kalo pake IIFE) sesuai urutan.
Kalo belum tau apa itu IIFE, baca ebook saya yg judulnya Mengenal JavaScript.
Seperti yg saya bilang di bagian sebelumnya, untuk JavaScript & CSS saya pake 2 versi yaitu versi concatenated+minified & versi non-minified. Versi minified dipake untuk production sedangkan versi non-minified dipake selama development di server lokal. Untuk itu saya pake kondisional (baris 7 – 15 di potongan kode di bawah) untuk nentuin versi mana yang harus dipake. Skrip di bawah ini saya tulis di master layout.
var vendorPath = '/vendor/'; var jqueryPath = '/vendor/jquery/'; var koPath = '/vendor/ko/'; var appPath = '/js/app/'; <?php if(Config::get('dooitkoo.asset_suffix') == '.min'): ?> var appLibs = [appPath+'app_libs.min.js']; <?php else: ?> var appLibs = [ appPath+'namespace.js',appPath+'binding-handlers.js' ,appPath+'validators.js',appPath+'vm/bootstrapmodal-vm.js' ,appPath+'dooitkoo-utils.js' ] <?php endif; ?> var libs = [ //jquery '/js/corelibs.min.js', jqueryPath + 'jquery-ui/jquery-ui-1.10.0.custom' + window.asset_suffix + '.css', jqueryPath + 'plugins/toastr/toastr' + window.asset_suffix + '.css', //knockout koPath + 'ko_all.min.js', //amplify vendorPath + 'amplify/amplify_all.min.js' ].concat(appLibs);
Di potongan skrip di atas, saya bikin daftar resource apa aja yang harus dipake, jadiin dalam satu global array yg namanya libs
& nantinya dikirim ke Yepnope sama dashboardpage.js
. Di bawah blok skrip di atas, baru saya pasang dashboardpage.js
.
{{HTML::script('/js/app/dashboard/dashboardpage'.Config::get('dooitkoo.asset_suffix').'.js')}}
Dari potongan skrip di atas ada beberapa catatan (mungkin pertanyaan dari yang baca artikel ini):
- Kenapa KO & Amplify dimuat terpisah dari
corelibs.min.js
? - Karena ada beberapa halaman yang ga butuh library ini.
- Kenapa ga langsung aja pasang skrip secara berurutan terus pake
$(document).ready()
, kan ga usah pake Yepnope? $(document).ready()
cuman menjamin DOM udah dimuat. Ga ada jaminan semua skrip dimuat dalam urutan yg bener. Semua skrip yang dipake Dooitkoo saya buat modular, jadi urutan skrip sangat penting.
dashboardpage.js
Dalam skrip ini, array libs
di master layout ditambah sama library lain yang spesifik untuk halaman dashboard aja (baris 5-12). Setelah semua skrip selesai dimuat, KO binding diinisialisasi.
;(function (suffix,libs,appPath,token) { yepnope({ load : libs.concat([ appPath + 'subject/vm/subject-vm' + suffix + '.js', appPath + 'dashboard/vm/subjectform-vm' + suffix + '.js', appPath + 'dashboard/vm/sidebar-vm' + suffix + '.js', appPath + 'dashboard/vm/summary-vm' + suffix + '.js', appPath + 'dashboard/vm/dashboardpage-vm'+suffix+'.js', //plus library lain2 yang ga masuk master libs ]), complete: function () { //default properti untuk ko external template engine infuser.defaults.templateSuffix = ".tmpl.html"; infuser.defaults.templatePrefix = "_"; infuser.defaults.templateUrl = '/js/app/dashboard/templates' var pageVM = new dooitkoo.DashboardPageVM(token); ko.applyBindings(pageVM); } }) })(window.asset_suffix,window.libs,window.appPath,window.token);
Page VM
DashboardPageVM
adalah root VM untuk halaman dashboard. Di dalamnya, saya bikin instance viewmodel yang lain jadi saya ga perlu panggil ko.applyBindings
lebih dari satu kali. Sesuai saran dari pembuat KO, semakin sedikit eksekusi ko.applyBindings
, semakin baik dari segi performa karena setiap kali function ini dipanggil, KO akan melakukan scanning seluruh elemen DOM untuk menentukan elemen mana yang musti di-bind. Untuk info mengenai hirarki viewmodel, baca binding-context di dokumentasi KO.
;(function (dooitkoo) { function DashboardPageVM(){ var self = this; self.newSubject = ko.observable(); self.bootstrapVM = new dooitkoo.BootstrapModalVM(); self.sidebarVM = new dooitkoo.SidebarVM(); self.formVM = new dooitkoo.SubjectFormVM(); self.subjectVM = new dooitkoo.SubjectVM(); self.summaryVM = new dooitkoo.SummaryVM(); } dooitkoo.DashboardPageVM = DashboardPageVM; })(window.dooitkoo);
External Template
Dashboard bisa nampilin 3 macam konten: add/edit form, daftar subjek atau ringkasan subjek. Di sini saya punya beberapa alternatif untuk implementasinya:
- Bikin 3 Laravel view, untuk form, daftar subjek, & ringkasan.
- Bikin 1 view, tapi di dalamnya ada 3 Knockout template (embedded).
- Bikin 1 view, plus 3 template eksternal. Isinya fragmen HTML & nanti dimuat waktu aplikasi berjalan, tergantung menu apa yg diklik user.
Pilihan no 3 yg paling bagus karena :
- Hemat bandwidth. File HTML utama yg dikirim dari server isinya cuman skeleton, atau bagian-bagian utama aja.
- Kontennya baru dikirim pada waktu dibutuhkan. Kalo user ga perlu bikin subjek, ga usah kirim form.
- File template eksternal bakal masuk browser cache jadi ga perlu berulang kali diunduh.
- Template eksternal bisa dipake bersama oleh beberapa view.
Library JavaScript utk templating ada macem-macem, ada JsRender, Jquery Template, Handlebars, dll. Loading template sih gampang. Masalahnya, apa bisa begitu template di-load otomatis data-binding elemen DOM di template itu langsung jalan. Kayanya perlu eksperimen dulu. Untungnya pas tanya Om Google ketemu plugin buat Knockout yg suport data-binding di template eksternal … maknyus! :-).
Saya bikin 3 file eksternal, penamaannya ngikutin “standar” yang biasa dipake untuk template, diawali underscore, diakhiri “.tmpl.html”. Jadi saya punya file : _subject_table.tmpl.html
, _subject_form.tmpl.html
, & _summary.tmpl.html
.
Gimana cara nentuin template mana yang harus dimuat, cara memuatnya, & kapan?
Saya tambahin sebuah observable bernama displayMode
di dalam DashboardPageVM
. Observable ini berisi data bertipe string
yang nilainya bisa all
,new
, atau summary
. Nilai dari observable ini kemudian saya pake untuk memuat eksternal template pake deklarasi virtual element seperti berikut:
Sidebar
SidebarVM
punya properti subjects
yang bertipe observableArray
. Isi array ini adalah SubjectVM
, viewmodel yang merepresentasikan sebuah subjek. Nilai dari setiap properti diisi dgn JSON data dari server.
;(function (dooitkoo,ko) { function SubjectVM() { var self = this; self.id = ko.observable(); self.name = ko.observable(); self.user_id = ko.observable(); self.currency = ko.observable(); self.note = ko.observable(); self.total_expense = ko.numericObservable(0); self.total_income = ko.numericObservable(0); //array of js object self.categories = ko.observableArray([]); self.selected = ko.observable(false); self.summary = ko.observable(); }; dooitkoo.SubjectVM = SubjectVM; })(window.dooitkoo,window.ko);
numericObservable
adalah custom observable dan bukan bagian dari paket standar KO.
Sidebar saya bind ke sidebarVM
pake deklarasi with:sidebarVM
. Untuk daftar subjek yang ditampilin di sidebar, saya pake foreach:subjects
yang berarti elemen ul
pake properti subjects
yang ada di sidebarVM
. Untuk setiap subjek, saya ingin KO bikin elemen li
. Kalo saya punya 10 subjek, KO akan bikin 10 elemen li
yang masing-masing berisi teks nama subjek & struktur HTML-nya sama dengan li
(baris 7 – 11) di potongan kode di bawah ini.
Di bagian bawah sidebar saya bikin link untuk bikin subjek baru. Link ini saya bind dengan function add()
di sidebarVM
.
Form VM
FormVM
adalah viewmodel untuk form yang dipake buat bikin atau edit subjek. Formnya sendiri ada di template eksternal dan saya bind ke properti formVM
dari pageVM
. Setiap input di form saya bind ke observable properti dari formVM
.
nice banget mas.. untuk code editornya mas menggunakan apa?
Pake PHPStorm. JavaScript debugger & XDebug supportnya mantap.
bagus tulisannya. Pada saya lebih selesa menggunakan requireJS heheh