Panduan untuk Membangun Aplikasi Ember.js Pertama Anda

Diterbitkan: 2022-03-11

Karena 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:

Versi final aplikasi dengan ember.js

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:

  1. 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).
  2. Mengosongkan bidang 'Artis Baru' akan menonaktifkan tombol 'Tambah' (hal yang sama berlaku untuk lagu untuk artis tertentu).
  3. Mengklik nama artis akan mencantumkan lagu mereka di sebelah kanan.
  4. 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:

Rute

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:

  1. Ada rute application implisit.

    Ini diaktifkan untuk semua permintaan (transisi).

  2. Ada rute index implisit.

    Ini akan dimasukkan ketika pengguna menavigasi ke root aplikasi.

  3. 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 .

  4. Sederhana (non-sumber daya), rute bersarang akan memiliki nama rute induknya sebagai awalan.

    Rute yang kita definisikan sebagai this.route('songs', ...) akan memiliki nama artists.songs . Itu dipicu ketika pengguna menavigasi ke /artists/pearl-jam atau /artists/radiohead .

  5. Jika jalur tidak diberikan, diasumsikan sama dengan nama rute.

  6. 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. Segmen slug di atas akan memiliki nilai pearl-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/ :

artis

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.

Terkait: 8 Pertanyaan Wawancara Ember.js Penting

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:

  1. Tambahkan artis baru ke model template (semua artis) sehingga dirender ulang dan artis baru muncul sebagai item terakhir dari daftar.
  2. Kosongkan bidang input melalui pengikatan newName , sehingga kita tidak perlu memanipulasi DOM secara langsung.
  3. 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:

  1. Pertama, each helper menunjuk bahwa ia menggunakan properti view (sebagai lawan dari properti di controller) dengan mengawali nama properti dengan view .
  2. Kedua, atribut class dari tag span memiliki kelas dinamis dan statis campuran yang ditetapkan. Apa pun yang diawali dengan a : menjadi kelas statis, sedangkan full:glyphicon-star:glyphicon-star-empty seperti operator ternary dalam JavaScript: jika properti lengkap adalah benar, kelas pertama harus ditetapkan; jika tidak, yang kedua.
  3. 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!

Terkait: Ember.js dan 8 Kesalahan Paling Umum yang Dilakukan Pengembang