Panduan untuk Membangun Aplikasi Ember.js Pertama Anda
Diterbitkan: 2022-03-11Karena aplikasi web modern melakukan lebih banyak dan lebih banyak di sisi klien (faktanya sendiri bahwa kita sekarang menyebutnya sebagai "aplikasi web" sebagai lawan dari "situs web" cukup jitu), telah ada peningkatan minat pada kerangka kerja sisi klien . Ada banyak pemain di bidang ini, tetapi untuk aplikasi dengan banyak fungsi dan banyak bagian yang bergerak, dua di antaranya menonjol: Angular.js dan Ember.js.
Kami telah menerbitkan [tutorial Angular.js] yang komprehensif][https://www.toptal.com/angular-js/a-step-by-step-guide-to-your-first-angularjs-app], jadi kami kembali fokus pada Ember.js dalam posting ini, di mana kami akan membangun aplikasi Ember sederhana untuk membuat katalog koleksi musik Anda. Anda akan diperkenalkan dengan blok bangunan utama kerangka kerja dan melihat sekilas prinsip-prinsip desainnya. Jika Anda ingin melihat kode sumber saat membaca, itu tersedia sebagai rock-and-roll di Github.
Apa yang akan kita bangun?
Inilah yang akan terlihat seperti aplikasi Rock & Roll kami dalam versi finalnya:
Di sebelah kiri, Anda akan melihat bahwa kami memiliki daftar artis dan, di sebelah kanan, daftar lagu dari artis yang dipilih (Anda juga dapat melihat bahwa saya memiliki selera musik yang bagus, tetapi saya ngelantur). Artis dan lagu baru dapat ditambahkan hanya dengan mengetik di kotak teks dan menekan tombol yang berdekatan. Bintang di samping setiap lagu berfungsi untuk menilainya, ala iTunes.
Kami dapat memecah fungsionalitas dasar aplikasi menjadi langkah-langkah berikut:
- Mengklik 'Tambah' akan menambahkan artis baru ke daftar, dengan nama yang ditentukan oleh bidang 'Artis Baru' (hal yang sama berlaku untuk lagu untuk artis tertentu).
- Mengosongkan bidang 'Artis Baru' akan menonaktifkan tombol 'Tambah' (hal yang sama berlaku untuk lagu untuk artis tertentu).
- Mengklik nama artis akan mencantumkan lagu mereka di sebelah kanan.
- Mengklik bintang menilai lagu tertentu.
Jalan kita masih panjang untuk membuatnya bekerja, jadi mari kita mulai.
Rute: kunci ke aplikasi Ember.js
Salah satu fitur yang membedakan Ember adalah penekanannya yang berat pada URL. Dalam banyak kerangka kerja lain, memiliki URL terpisah untuk layar terpisah tidak ada atau ditempelkan sebagai renungan. Di Ember, router—komponen yang mengelola url dan transisi di antaranya—adalah bagian utama yang mengoordinasikan pekerjaan di antara blok penyusun. Akibatnya, ini juga merupakan kunci untuk memahami cara kerja aplikasi Ember.
Berikut adalah rute untuk aplikasi kami:
App.Router.map(function() { this.resource('artists', function() { this.route('songs', { path: ':slug' }); }); });
Kami mendefinisikan rute sumber daya, artists
, dan rute songs
yang bersarang di dalamnya. Definisi itu akan memberi kita rute berikut:
Saya menggunakan plugin Ember Inspector yang hebat (ada untuk Chrome dan Firefox) untuk menunjukkan kepada Anda rute yang dihasilkan dengan cara yang mudah dibaca. Berikut adalah aturan dasar untuk rute Ember, yang dapat Anda verifikasi untuk kasus khusus kami dengan bantuan tabel di atas:
Ada rute
application
implisit.Ini diaktifkan untuk semua permintaan (transisi).
Ada rute
index
implisit.Ini akan dimasukkan ketika pengguna menavigasi ke root aplikasi.
Setiap rute sumber daya membuat rute dengan nama yang sama dan secara implisit membuat rute indeks di bawahnya.
Rute indeks ini diaktifkan saat pengguna menavigasi ke rute. Dalam kasus kami,
artists.index
terpicu saat pengguna menavigasi ke/artists
.Sederhana (non-sumber daya), rute bersarang akan memiliki nama rute induknya sebagai awalan.
Rute yang kita definisikan sebagai
this.route('songs', ...)
akan memiliki namaartists.songs
. Itu dipicu ketika pengguna menavigasi ke/artists/pearl-jam
atau/artists/radiohead
.Jika jalur tidak diberikan, diasumsikan sama dengan nama rute.
Jika jalur berisi
:
, itu dianggap sebagai segmen dinamis .Nama yang ditetapkan untuknya (dalam kasus kami,
slug
) akan cocok dengan nilai di segmen url yang sesuai. Segmenslug
di atas akan memiliki nilaipearl-jam
,radiohead
atau nilai lainnya yang diekstrak dari URL.
Tampilkan daftar artis
Sebagai langkah pertama, kita akan membuat layar yang menampilkan daftar artis di sebelah kiri. Layar ini harus ditampilkan kepada pengguna saat mereka menavigasi ke /artists/
:
Untuk memahami bagaimana layar itu dirender, inilah saatnya untuk memperkenalkan prinsip desain Ember menyeluruh lainnya: convention over configuration . Pada bagian di atas, kita melihat bahwa /artists
mengaktifkan rute artists
. Berdasarkan konvensi, nama objek rute tersebut adalah ArtistsRoute
. Objek rute ini bertanggung jawab untuk mengambil data yang akan dirender oleh aplikasi. Itu terjadi di kait model rute:
App.ArtistsRoute = Ember.Route.extend({ model: function() { var artistObjects = []; Ember.$.getJSON('http://localhost:9393/artists', function(artists) { artists.forEach(function(data) { artistObjects.pushObject(App.Artist.createRecord(data)); }); }); return artistObjects; } });
Dalam cuplikan ini, data diambil melalui panggilan XHR dari back-end dan—setelah konversi ke objek model—didorong ke larik yang selanjutnya dapat kita tampilkan. Namun, tanggung jawab rute tidak mencakup penyediaan logika tampilan, yang ditangani oleh pengontrol. Mari lihat.
Hmmm—sebenarnya, kita tidak perlu mendefinisikan controller pada saat ini! Ember cukup pintar untuk membuat pengontrol saat dibutuhkan dan menyetel atribut M.odel
ke nilai kembalian dari kait model itu sendiri, yaitu daftar artis. (Sekali lagi, ini adalah hasil dari paradigma 'konvensi atas konfigurasi'.) Kita dapat menurunkan satu lapisan dan membuat template untuk menampilkan daftar:
<script type="text/x-handlebars" data-template-name="artists"> <div class="col-md-4"> <div class="list-group"> {{#each model}} {{#link-to "artists.songs" this class="list-group-item artist-link"}} {{name}} <span class="pointer glyphicon glyphicon-chevron-right"></span> {{/link-to}} {{/each}} </div> </div> <div class="col-md-8"> <div class="list-group"> {{outlet}} </div> </div> </script>
Jika ini terlihat familier, itu karena Ember.js menggunakan template Handlebars, yang memiliki sintaks dan helper yang sangat sederhana tetapi tidak mengizinkan logika non-sepele (misalnya, istilah ORing atau ANDing dalam kondisi).
Dalam template di atas, kami mengulangi model (diatur sebelumnya dengan rute ke array yang berisi semua artis) dan untuk setiap item di dalamnya, kami membuat tautan yang membawa kami ke rute artists.songs
untuk artis itu. Tautan berisi nama artis. #each
helper di Handlebars mengubah cakupan di dalamnya ke item saat ini, jadi {{name}}
akan selalu merujuk ke nama artis yang saat ini sedang dalam iterasi.
Rute bertingkat untuk tampilan bertingkat
Hal menarik lainnya dalam cuplikan di atas: {{outlet}}
, yang menentukan slot di template tempat konten dapat dirender. Saat menyarangkan rute, template untuk luar, rute sumber daya dirender terlebih dahulu, diikuti oleh rute dalam, yang merender konten template ke {{outlet}}
yang ditentukan oleh rute luar. Inilah yang terjadi di sini.
Dengan konvensi, semua rute merender konten mereka ke dalam template dengan nama yang sama. Di atas, atribut data-template-name
dari template di atas adalah artists
yang berarti akan dirender untuk rute luar, artists
. Ini menentukan outlet untuk konten panel kanan, di mana rute dalam, artists.index
membuat kontennya:
<script type="text/x-handlebars" data-template-name="artists/index"> <div class="list-group-item empty-list"> <div class="empty-message"> Select an artist. </div> </div> </script>
Singkatnya, satu rute ( artists
) merender kontennya di bilah sisi kiri, modelnya adalah daftar artis. Rute lain, artists.index
merender kontennya sendiri ke dalam slot yang disediakan oleh template artists
. Itu bisa mengambil beberapa data untuk dijadikan modelnya tetapi dalam kasus ini yang ingin kita tampilkan hanyalah teks statis, jadi kita tidak perlu melakukannya.
Buat artis
Bagian 1: Pengikatan data
Selanjutnya, kami ingin bisa membuat artis, tidak hanya melihat daftar yang membosankan.
Ketika saya menunjukkan template artists
yang membuat daftar artis, saya sedikit curang. Saya memotong bagian atas untuk fokus pada apa yang penting. Sekarang, saya akan menambahkannya kembali:
<script type="text/x-handlebars" data-template-name="artists"> <div class="col-md-4"> <div class="list-group"> <div class="list-group-item"> {{input type="text" class="new-artist" placeholder="New Artist" value=newName}} <button class="btn btn-primary btn-sm new-artist-button" {{action "createArtist"}} {{bind-attr disabled=disabled}}>Add</button> </div> < this is where the list of artists is rendered > ... </script>
Kami menggunakan pembantu Ember, input
, dengan jenis teks untuk membuat input teks sederhana. Di dalamnya, kita mengikat nilai input teks ke properti newName
dari controller yang mencadangkan template ini, ArtistsController
. Akibatnya, ketika properti value dari input berubah (dengan kata lain, ketika pengguna mengetik teks ke dalamnya), properti newName
pada controller akan tetap sinkron.
Kami juga memberitahukan bahwa tindakan createArtist
harus diaktifkan saat tombol diklik. Terakhir, kami mengikat properti tombol yang disabled
ke properti pengontrol yang dinonaktifkan. Lalu seperti apa controllernya?
App.ArtistsController = Ember.ArrayController.extend({ newName: '', disabled: function() { return Ember.isEmpty(this.get('newName')); }.property('newName') });
newName
disetel ke kosong di awal yang berarti input teks akan kosong. (Ingat apa yang saya katakan tentang binding? Coba ubah newName
dan lihat itu tercermin sebagai teks di bidang input.)
disabled
diimplementasikan sedemikian rupa sehingga ketika tidak ada teks di kotak input, itu akan kembali true
dan dengan demikian tombol akan dinonaktifkan. Panggilan .property
di akhir membuat ini menjadi "properti yang dihitung", sepotong kue Ember yang nikmat.

Properti yang dihitung adalah properti yang bergantung pada properti lain, yang dapat dengan sendirinya menjadi "normal" atau dihitung. Ember menyimpan nilai ini hingga salah satu properti dependen berubah. Itu kemudian menghitung ulang nilai properti yang dihitung dan menyimpannya lagi di cache.
Berikut adalah representasi visual dari proses di atas. Untuk meringkas: ketika pengguna memasukkan nama artis, properti newName
diperbarui, diikuti oleh properti yang disabled
dan, akhirnya, nama artis ditambahkan ke daftar.
Jalan memutar: Satu sumber kebenaran
Pikirkan tentang itu sejenak. Dengan bantuan binding dan properti yang dihitung, kita dapat menetapkan (memodelkan) data sebagai satu-satunya sumber kebenaran . Di atas, perubahan nama artis baru memicu perubahan pada properti pengontrol, yang pada gilirannya memicu perubahan pada properti yang dinonaktifkan. Saat pengguna mulai mengetikkan nama artis baru, tombol menjadi aktif, seperti sulap.
Semakin besar sistemnya, semakin besar pengaruh yang kita peroleh dari prinsip 'sumber kebenaran tunggal'. Itu membuat kode kita tetap bersih dan kuat, dan definisi properti kita, lebih deklaratif.
Beberapa kerangka kerja lain juga menekankan agar data model menjadi satu-satunya sumber kebenaran tetapi tidak sampai sejauh Ember atau gagal melakukan pekerjaan yang menyeluruh. Angular, misalnya, memiliki ikatan dua arah—tetapi tidak memiliki properti yang dihitung. Itu dapat "meniru" properti yang dihitung melalui fungsi sederhana; masalahnya di sini adalah bahwa ia tidak memiliki cara untuk mengetahui kapan harus menyegarkan "properti yang dihitung" dan dengan demikian menggunakan pemeriksaan kotor dan, pada gilirannya, menyebabkan hilangnya kinerja, terutama yang menonjol dalam aplikasi yang lebih besar.
Jika Anda ingin mempelajari lebih lanjut tentang topik tersebut, saya sarankan Anda membaca posting blog eviltrout untuk versi yang lebih pendek atau pertanyaan Quora ini untuk diskusi yang lebih panjang yang melibatkan pengembang inti dari kedua belah pihak.
Bagian 2: Penangan tindakan
Mari kembali untuk melihat bagaimana tindakan createArtist
dibuat setelah diaktifkan (setelah menekan tombol):
App.ArtistsRoute = Ember.Route.extend({ ... actions: { createArtist: function() { var name = this.get('controller').get('newName'); Ember.$.ajax('http://localhost:9393/artists', { type: 'POST', dataType: 'json', data: { name: name }, context: this, success: function(data) { var artist = App.Artist.createRecord(data); this.modelFor('artists').pushObject(artist); this.get('controller').set('newName', ''); this.transitionTo('artists.songs', artist); }, error: function() { alert('Failed to save artist'); } }); } } });
Penangan tindakan perlu dibungkus dalam objek actions
dan dapat ditentukan pada rute, pengontrol, atau tampilan. Saya memilih untuk mendefinisikannya pada rute di sini karena hasil tindakan tidak terbatas pada pengontrol melainkan, "global".
Tidak ada yang mewah terjadi di sini. Setelah back-end memberi tahu kami bahwa operasi penyimpanan selesai dengan sukses, kami melakukan tiga hal, secara berurutan:
- Tambahkan artis baru ke model template (semua artis) sehingga dirender ulang dan artis baru muncul sebagai item terakhir dari daftar.
- Kosongkan bidang input melalui pengikatan
newName
, sehingga kita tidak perlu memanipulasi DOM secara langsung. - Transisi ke rute baru (
artists.songs
), melewati artis yang baru dibuat sebagai model untuk rute itu.transitionTo
adalah cara untuk berpindah antar rute secara internal. (link-to
pembantu berfungsi untuk melakukan itu melalui tindakan pengguna.)
Tampilkan lagu untuk artis
Kita bisa menampilkan lagu-lagu untuk artis baik dengan mengklik nama artis. Kami juga memasukkan artis yang akan menjadi model rute baru. Jika objek model dilewatkan demikian, kait model
dari rute tidak akan dipanggil karena tidak perlu menyelesaikan model.
Rute aktif di sini adalah artists.songs
dan dengan demikian controller dan template masing-masing akan menjadi ArtistsSongsController
dan artists/songs
. Kami telah melihat bagaimana template dirender ke outlet yang disediakan oleh template artists
sehingga kami dapat fokus hanya pada template yang ada:
<script type="text/x-handlebars" data-template-name="artists/songs"> (...) {{#each songs}} <div class="list-group-item"> {{title}} {{view App.StarRating maxRating=5}} </div> {{/each}} </script>
Perhatikan bahwa saya menghapus kode untuk membuat lagu baru karena akan sama persis dengan untuk membuat artis baru.
Properti songs
diatur di semua objek artis dari data yang dikembalikan oleh server. Mekanisme yang tepat yang dilakukan tidak banyak menarik untuk diskusi saat ini. Untuk saat ini, cukup bagi kita untuk mengetahui bahwa setiap lagu memiliki judul dan peringkat.
Judul ditampilkan langsung di template dan peringkat diwakili oleh bintang, melalui tampilan StarRating
. Mari kita lihat itu sekarang.
Widget peringkat bintang
Peringkat lagu turun antara 1 dan 5 dan ditampilkan kepada pengguna melalui tampilan, App.StarRating
. Tampilan memiliki akses ke konteksnya (dalam hal ini, lagu) dan pengontrolnya. Ini berarti mereka dapat membaca dan memodifikasi propertinya. Ini berbeda dengan blok bangunan Ember lainnya, komponen, yang terisolasi, kontrol yang dapat digunakan kembali dengan akses hanya ke apa yang telah diteruskan ke dalamnya. (Kita juga dapat menggunakan komponen peringkat bintang dalam contoh ini.)
Mari kita lihat bagaimana tampilan menampilkan jumlah bintang dan menetapkan peringkat lagu ketika pengguna mengklik salah satu bintang:
App.StarRating = Ember.View.extend({ classNames: ['rating-panel'], templateName: 'star-rating', rating: Ember.computed.alias('context.rating'), fullStars: Ember.computed.alias('rating'), numStars: Ember.computed.alias('maxRating'), stars: function() { var ratings = []; var fullStars = this.starRange(1, this.get('fullStars'), 'full'); var emptyStars = this.starRange(this.get('fullStars') + 1, this.get('numStars'), 'empty'); Array.prototype.push.apply(ratings, fullStars); Array.prototype.push.apply(ratings, emptyStars); return ratings; }.property('fullStars', 'numStars'), starRange: function(start, end, type) { var starsData = []; for (i = start; i <= end; i++) { starsData.push({ rating: i, full: type === 'full' }); }; return starsData; }, (...) });
rating
, fullStars
dan numStars
adalah properti yang dihitung yang telah kita diskusikan sebelumnya dengan properti yang disabled
dari ArtistsController
. Di atas, saya menggunakan apa yang disebut makro properti terkomputasi, sekitar selusin di antaranya didefinisikan dalam Ember. Mereka membuat properti terkomputasi yang khas lebih ringkas dan kurang rawan kesalahan (untuk menulis). Saya menetapkan rating
menjadi peringkat konteks (dan dengan demikian lagu), sementara saya mendefinisikan properti fullStars
dan numStars
sehingga mereka membaca lebih baik dalam konteks widget peringkat bintang.
Metode stars
adalah daya tarik utama. Ini mengembalikan array data untuk bintang-bintang di mana setiap item berisi properti rating
(dari 1 hingga 5) dan bendera ( full
) untuk menunjukkan apakah bintang itu penuh. Ini membuatnya sangat mudah untuk menelusurinya di template:
<script type="text/x-handlebars" data-template-name="star-rating"> {{#each view.stars}} <span {{bind-attr data-rating=rating}} {{bind-attr class=":star-rating :glyphicon full:glyphicon-star:glyphicon-star-empty"}} {{action "setRating" target=view}}> </span> {{/each}} </script>
Cuplikan ini berisi beberapa poin catatan:
- Pertama,
each
helper menunjuk bahwa ia menggunakan properti view (sebagai lawan dari properti di controller) dengan mengawali nama properti denganview
. - Kedua, atribut
class
dari tag span memiliki kelas dinamis dan statis campuran yang ditetapkan. Apa pun yang diawali dengan a:
menjadi kelas statis, sedangkanfull:glyphicon-star:glyphicon-star-empty
seperti operator ternary dalam JavaScript: jika properti lengkap adalah benar, kelas pertama harus ditetapkan; jika tidak, yang kedua. - Terakhir, saat tag diklik, tindakan
setRating
harus diaktifkan—tetapi Ember akan mencarinya di tampilan, bukan rute atau pengontrol, seperti dalam kasus membuat artis baru.
Tindakan demikian didefinisikan pada tampilan:
App.StarRating = Ember.View.extend({ (...) actions: { setRating: function() { var newRating = $(event.target).data('rating'); this.set('rating', newRating); } } });
Kami mendapatkan peringkat dari atribut data rating
yang kami tetapkan di template dan kemudian menetapkannya sebagai rating
untuk lagu tersebut. Perhatikan bahwa peringkat baru tidak bertahan di back-end. Tidak akan sulit untuk menerapkan fungsi ini berdasarkan bagaimana kami menciptakan seorang seniman dan dibiarkan sebagai latihan untuk pembaca yang termotivasi.
Membungkus semuanya
Kami telah mencicipi beberapa bahan dari kue Ember tersebut di atas:
- Kami telah melihat bagaimana rute merupakan inti dari aplikasi Ember dan bagaimana mereka berfungsi sebagai dasar konvensi penamaan.
- Kita telah melihat bagaimana pengikatan data dua arah dan properti yang dihitung membuat data model kita menjadi satu-satunya sumber kebenaran dan memungkinkan kita untuk menghindari manipulasi DOM langsung.
- Dan kami telah melihat cara mengaktifkan dan menangani tindakan dalam beberapa cara dan membangun tampilan kustom untuk membuat kontrol yang bukan bagian dari HTML kami.
Indah, bukan?
Bacaan lebih lanjut (dan menonton)
Ada jauh lebih banyak untuk Ember daripada yang bisa saya muat di posting ini saja. Jika Anda ingin melihat seri screencast tentang bagaimana saya membangun versi aplikasi di atas yang agak lebih berkembang dan/atau mempelajari lebih lanjut tentang Ember, Anda dapat mendaftar ke milis saya untuk mendapatkan artikel atau tip setiap minggu.
Saya harap saya telah membangkitkan selera Anda untuk mempelajari lebih lanjut tentang Ember.js dan bahwa Anda melampaui contoh aplikasi yang saya gunakan dalam posting ini. Saat Anda terus mempelajari tentang Ember.js, pastikan untuk melihat artikel kami tentang Data Ember untuk mempelajari cara menggunakan pustaka ember-data. Selamat membangun!