Menulis TodoMVC di dap. Bagian 2

Ini adalah bagian kedua, terakhir, dari tutorial di mana kami menulis klien TodoMVC menggunakan kerangka kerja reaktif ds minimalis.

Ringkasan bagian pertama : kami menerima daftar tugas dari server dalam format JSON, membuat daftar HTML darinya, menambahkan kemampuan untuk mengedit nama dan tanda penyelesaian untuk setiap kasus, dan menerapkan pemberitahuan server tentang pengeditan ini.

Itu masih harus direalisasikan: menghapus kasus sewenang-wenang, menambahkan kasus baru, pemasangan / reset massal dan penyaringan kasus berdasarkan penyelesaian dan fungsi menghapus semua kasus selesai. Ini yang akan kita lakukan. Versi final klien, yang akan kita bahas dalam artikel ini, dapat dilihat di sini .



Opsi yang kami tentukan terakhir kali dapat disegarkan di sini .

Ini kodenya:

'#todoapp'.d("" ,'#header'.d("" ,'H1'.d("") ,'INPUT#new-todo placeholder="What needs to be done?" autofocus'.d("") ) ,'#main'.d("" ,'#toggle-all type=checkbox'.d("") ,'UL#todo-list'.d("*@ todos:query" ,'LI'.d("$completed=.completed $editing= $patch=; a!" ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=($completed=#.checked)") ,'LABEL.view' .d("? $editing:!; ! .title") .e("dblclick","$editing=`yes") ,'INPUT.edit' .d("? $editing; !! .title@value") .ui("$patch=(.title=#.value)") .e("blur","$editing=") ,'BUTTON.destroy'.d("") ) .a("!? $completed $editing") .u("? $patch; (@method`PATCH .url:dehttp headers $patch@):query $patch=") ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) .DICT({ todos : "//todo-backend-express.herokuapp.com/", headers: {"Content-type":"application/json"} }) .FUNC({ convert:{ dehttp: url=>url.replace(/^https?\:/,'') } }) .RENDER() 


Sekarang hanya ada lima puluh baris di sini, tetapi pada akhir artikel akan ada dua kali lebih banyak - sebanyak 100. Akan ada banyak permintaan HTTP ke server, jadi silakan buka alat pengembang (di Chrome, seperti yang Anda ingat, Ctrl + Shift + I) - akan ada Pertama-tama, tab Jaringan menarik, dan kedua, Konsol. Juga, jangan lupa untuk melihat kode untuk setiap versi halaman kami - di Chrome itu adalah Ctrl + U.

Di sini saya harus melakukan sedikit penyimpangan. Jika Anda tidak membaca bagian pertama tutorial, saya tetap merekomendasikan untuk memulainya. Jika Anda membacanya, tetapi tidak mengerti apa-apa, lebih baik membacanya lagi. Seperti yang ditunjukkan oleh komentar pada dua artikel saya sebelumnya, sintaks dan prinsip dap tidak selalu langsung dipahami oleh pembaca yang tidak siap. Artikel lain tidak disarankan untuk dibaca oleh orang yang merasa tidak nyaman dengan tampilan sintaksis tidak seperti si.



Bagian kedua tutorial ini akan sedikit lebih rumit dan menarik daripada yang pertama. [TODO: minta token untuk menemukan gambar otak anak sekolah yang meledak di internet] .

Dengan izin Anda, saya akan terus memberi nomor bab dengan bagian 1. Di sana kami menghitung sampai 7. Jadi,

8. Membuat daftar to-do dari variabel status


Untuk menghapus BUTTON.destroy dari daftar ada tombol BUTTON.destroy . Penghapusan terdiri dari pengiriman permintaan DELETE ke server dan sebenarnya menghapus elemen UL#todo-list > LI dengan semua konten. Dengan mengirimkan permintaan DELETE, semuanya jelas:

  ,'BUTTON.destroy'.ui("(@method`DELETE .url:dehttp):query") 


Tetapi dengan penghapusan elemen dari layar, opsi dimungkinkan. Seseorang bisa dengan mudah memperkenalkan variabel keadaan lain, katakanlah $deleted , dan sembunyikan elemen dengan CSS, termasuk kelas CSS yang deleted

  ,'LI'.d("$completed=.completed $editing= $patch= $deleted=; a!" //  $deleted   "" ... ,'BUTTON.destroy'.d("(@method`DELETE .url:dehttp):query $deleted=`yes") //  $deleted -     ) .a("!? $completed $editing $deleted") //   CSS  .deleted{display:none} 


Dan itu akan berhasil. Tapi itu akan curang. Selain itu, lebih jauh ke bawah kita akan memiliki filter dan penghitung kasus aktif dan selesai (apa yang ada di #footer ). Oleh karena itu, lebih baik untuk segera menghapus objek dari daftar yang harus dilakukan dengan jujur, “secara fisik”. Artinya, kita perlu kemampuan untuk memodifikasi array itu sendiri, yang awalnya kita terima dari server - yang berarti bahwa array ini juga harus menjadi variabel keadaan. Sebut saja dia $todos .

Cakupan variabel $todos adalah untuk memilih leluhur umum dari semua elemen yang akan mengakses variabel ini. Dan itu akan diakses oleh INPUT#new-todo dari #header , dan counter dari #footer , dan sebenarnya UL#todo-list . Nenek moyang yang sama dari mereka semua adalah elemen root dari template, #todoapp . Oleh karena itu, dalam aturan-d kita akan mendefinisikan variabel $todos . Di tempat yang sama, kami segera mengunggah data dari server ke sana. Dan untuk membangun daftar UL#todo-list , kita sekarang juga akan berasal dari itu:

 '#todoapp'.d("$todos=todos:query" //   $todos      ... ,'UL#todo-list'.d("*@ $todos" //     $todos 


Itu penting. Jika selama pengujian tiba-tiba daftar agenda tidak dimuat - sangat mungkin seseorang menghapus semuanya (ini adalah server publik, dan apa pun bisa terjadi di sana).
Dalam hal ini, harap buka contoh fitur lengkap dan buat beberapa kasus untuk percobaan.

Kami melihat . Di sini $todos dideklarasikan dalam aturan-d elemen #todoapp dan segera diinisialisasi dengan data yang diperlukan. Segalanya tampak berfungsi, tetapi satu fitur yang tidak menyenangkan telah muncul. Jika server merespons permintaan untuk waktu yang lama (Chrome memungkinkan Anda untuk mensimulasikan situasi ini: pada tab Jaringan pada alat pengembang, Anda dapat memilih berbagai mode untuk mensimulasikan jaringan lambat), maka versi aplikasi kami yang baru hingga permintaan selesai terlihat agak sedih - tidak ada yang lain selain beberapa CSS artefak. Gambar seperti itu pasti tidak akan menambah antusiasme bagi pengguna. Meskipun versi sebelumnya tidak menderita karena hal ini - sampai data diterima pada halaman, hanya daftar itu sendiri yang hilang, tetapi elemen lain segera muncul, tanpa menunggu data.

Ini masalahnya. Seperti yang Anda ingat, konverter :query tidak sinkron. Sinkronisasi ini dinyatakan dalam fakta bahwa sampai permintaan selesai, hanya eksekusi aturan saat ini yang diblokir, yaitu generasi elemen, yang, pada kenyataannya, membutuhkan data yang diminta (yang logis). Generasi elemen lain tidak diblokir. Oleh karena itu, ketika UL#todo-list mengakses server, hanya saja server itu diblokir, tetapi bukan #header dan bukan #footer , yang langsung ditarik. Sekarang seluruh #todoapp sedang menunggu permintaan untuk diselesaikan.

9. Pemuatan data tertunda


Untuk memperbaiki situasi dan menghindari pemblokiran elemen-elemen yang tidak terlibat, kami akan menunda pemuatan awal data sampai semuanya sudah diambil. Untuk melakukan ini, kami tidak akan segera memuat data ke variabel $todos , tetapi pertama-tama kami hanya menginisialisasi dengan "tidak ada"

 '#todoapp'.d("$todos=" //   $todos    "" 


Jadi dia tidak akan memblokir apa pun dan seluruh templat akan berfungsi - meskipun untuk saat ini dengan "daftar tugas" yang kosong. Tetapi sekarang, dengan layar awal yang membosankan, Anda dapat dengan aman memodifikasi $todos dengan mengunggah daftar tugas untuk itu. Untuk melakukan ini, tambahkan keturunan ini ke #todoapp

  ,'loader' .u("$todos=todos:query") //  $todos,       .d("u") //   (u-)    


Elemen ini memiliki aturan-u yang terlihat persis sama dengan aturan pemblokiran yang kami tolak, tetapi ada satu perbedaan mendasar.
Biarkan saya mengingatkan Anda bahwa aturan-d (dari bawah ) adalah aturan pembuatan elemen yang dieksekusi ketika templat dibuat dari atas ke bawah , dari induk ke turunan; dan aturan-u (dari atas ) adalah aturan reaksi yang dijalankan sebagai respons terhadap peristiwa yang muncul dari bawah ke atas , dari anak ke orang tua.
Jadi, jika sesuatu (termasuk "tidak ada") ditugaskan ke variabel dalam aturan-d , ini berarti deklarasi dan inisialisasi dalam lingkup elemen ini dan turunannya (lingkup bersarang diimplementasikan dalam dap, seperti pada JS ) Penugasan dalam aturan-up berarti modifikasi variabel yang dideklarasikan sebelumnya dalam cakupan. Deklarasi dan inisialisasi variabel dalam aturan-d memungkinkan orang tua untuk meneruskan kepada keturunan menuruni hierarki informasi yang diperlukan untuk konstruksi, dan modifikasi memungkinkan orang tua untuk memberikan pembaruan pada informasi ini dan dengan demikian memulai restrukturisasi yang tepat dari semua elemen yang bergantung padanya.

Elemen loader , yang merupakan turunan #todoapp , dalam aturan-u-nya memodifikasi variabel $todos , memuat data dari server ke dalamnya, yang menyebabkan regenerasi otomatis semua elemen konsumen dari variabel ini (dan hanya mereka, yang penting!). Konsumen suatu variabel adalah elemen yang aturan-dnya mengandung variabel ini sebagai nilai, yaitu Mereka yang membaca variabel ini (dengan mempertimbangkan ruang lingkup) saat membangun.

Kami sekarang memiliki satu konsumen variabel $todos - UL#todo-list sangat, yang kemudian akan dibangun kembali setelah memuat data.

  ,'UL#todo-list'.d("*@ $todos" //  ,   $todos 


Jadi, sekarang kita memiliki daftar yang harus dilakukan adalah variabel status di #todoapp , sambil tidak memblokir rendering awal templat.

10. Menghapus dan menambahkan to-do


Sekarang kita dapat memodifikasi $todos segala cara. Mari kita mulai dengan menghapus item. Kami sudah memiliki tombol-lintas BUTTON.destroy , yang sejauh ini hanya mengirim permintaan penghapusan server

  ,'BUTTON.destroy'.ui("(@method`DELETE .url:dehttp):query") 


Penting untuk memastikan bahwa objek yang sesuai juga dihapus dari variabel $todos - dan karena ini akan menjadi modifikasi, UL#todo-list , sebagai konsumen variabel ini, akan secara otomatis membangun kembali, tetapi tanpa elemen yang dihapus.

Dengan sendirinya, dap tidak menyediakan sarana khusus untuk memanipulasi data. Manipulasi dapat ditulis dengan sempurna dalam fungsi di JS, dan aturan dap hanya mengirimkan data kepada mereka dan mengambil hasilnya. Kami menulis fungsi JS untuk menghapus objek dari array tanpa mengetahui jumlahnya. Sebagai contoh, ini:

 const remove = (arr,tgt)=> arr.filter( obj => obj!=tgt ); 


Anda mungkin dapat menulis sesuatu yang lebih efektif, tetapi ini bukan tentang itu sekarang. Kecil kemungkinan bahwa aplikasi kita harus bekerja dengan daftar yang harus dilakukan dari jutaan item. Yang penting adalah bahwa fungsi mengembalikan objek array baru, dan bukan hanya menghilangkan elemen dari apa adanya.

Untuk membuat fungsi ini dapat diakses dari aturan dap, Anda perlu menambahkannya ke bagian .FUNC , tetapi sebelum itu memutuskan bagaimana kami ingin menyebutnya. Opsi paling sederhana dalam kasus ini adalah, mungkin, untuk memanggilnya dari konverter, yang menerima objek { todos, tgt } dan mengembalikan array yang difilter

 .FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''), //        remove: x => remove(x.todos,x.tgt) //     } }) 


tetapi tidak ada yang menghentikan Anda dari mendefinisikan fungsi ini tepat di dalam .FUNC (Saya sudah mengatakan bahwa .FUNC sebenarnya adalah metode JS biasa, dan argumennya adalah objek JS biasa?)

 .FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''), remove: x => x.todos.filter( todo => todo!=x.tgt ) } }) 


Sekarang kita dapat mengakses konverter ini dari aturan dap

  ,'BUTTON.destroy' .ui("$todos=($todos $@tgt):remove (@method`DELETE .url:dehttp):query") 


Di sini, pertama-tama kita membentuk objek yang dalam notasi JS cocok dengan { todos, tgt:$ } , meneruskannya ke :remove konverter yang dijelaskan dalam .FUNC , dan kembalikan hasil yang difilter ke $todos , dengan demikian mengubahnya. Di sini $ adalah konteks data elemen, objek bisnis dari array $todos tempat templat dibuat. Setelah simbol @ , alias argumen ditunjukkan. Jika @ ada, maka nama argumen sendiri digunakan. Ini mirip dengan inovasi ES6 baru - singkatan properti .

Demikian pula, kami menambahkan kasus baru ke daftar menggunakan elemen INPUT#new-todo dan permintaan POST

  ,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$=(#.value@title) (@method`POST todos@url headers $):query $todos=($todos $@tgt):insert #.value=") ... .FUNC({ convert:{ dehttp: url => url.replace(/^https?\:/,''), remove: x => x.todos.filter( todo => todo!=x.tgt ), //     insert: x => x.todos.concat( [x.tgt] ) //     } }) 


Aturan reaksi elemen INPUT#new-todo ke acara UI standar (untuk elemen INPUT , peristiwa change dianggap sebagai standar dap) termasuk: membaca input pengguna dari properti value elemen ini, membentuk konteks $ lokal dengan nilai ini sebagai bidang .title , mengirimkan $ context ke server menggunakan metode POST, memodifikasi array $todos dengan menambahkan konteks $ sebagai elemen baru dan akhirnya membersihkan properti value elemen INPUT .

Di sini, pembaca muda mungkin bertanya: mengapa menggunakan concat() saat menambahkan elemen ke array, jika ini dapat dilakukan dengan menggunakan push() biasa push() ? Pembaca yang berpengalaman akan segera memahami apa masalahnya, dan menulis jawabannya di komentar.

Kami melihat apa yang terjadi. Kasing ditambahkan dan dihapus secara normal, permintaan yang sesuai dikirim ke server dengan benar (Anda membiarkan tab Jaringan terbuka selama ini, kan?). Tetapi bagaimana jika kita ingin mengubah nama atau status dari kasus yang baru ditambahkan? Masalahnya adalah untuk memberi tahu server tentang perubahan ini, kita perlu .url , yang menetapkan server untuk bisnis ini. Ketika kami menciptakan bisnis, .url tidak tahu masing-masing .url , kami tidak dapat membentuk permintaan PATCH yang benar untuk perubahan.

Faktanya, semua informasi yang diperlukan tentang kasing terdapat dalam respons server terhadap permintaan POST, dan akan lebih tepat untuk membuat objek bisnis baru tidak hanya dari input pengguna, tetapi dari respons server, dan menambahkan objek ini ke $todos - dengan semua informasi yang disediakan informasi server, termasuk bidang .url

  ,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$todos=($todos (@method`POST todos@url headers (#.value@title)):query@tgt ):insert #.value=") 


Kami melihat - oke, sekarang semuanya berjalan dengan benar. Pemberitahuan ke server tentang pengeditan kasus yang baru dibuat berjalan dengan benar.

Orang bisa berhenti pada ini, tapi ... Tapi jika Anda melihat dekat, Anda masih bisa melihat sedikit keterlambatan antara memasukkan nama kasing baru dan saat itu muncul dalam daftar. Penundaan ini jelas terlihat jika Anda mengaktifkan jaringan lambat yang disimulasikan. Seperti yang sudah Anda duga, masalahnya adalah permintaan ke server: pertama, kami meminta data untuk kasus baru dari server, dan hanya setelah menerimanya kami memodifikasi $todos . Langkah selanjutnya, kami akan mencoba memperbaiki situasi ini, tetapi pertama-tama saya akan mengalihkan perhatian Anda ke poin lain yang menarik. Jika kita kembali sedikit ke versi sebelumnya , kita perhatikan: meskipun permintaan juga ada, case baru ditambahkan ke daftar secara instan, tanpa menunggu permintaan untuk mengakhiri

  //    , :query   .ui("$=(#.value@title) (@method`POST todos@url headers $):query $todos=($todos $@tgt):insert #.value=") 


Ini adalah fitur lain untuk mengerjakan konverter asinkron di dap: jika hasil konverter asinkron tidak digunakan (yaitu, ia tidak ditugaskan untuk apa pun), maka Anda tidak bisa menunggu penyelesaiannya - dan pelaksanaan aturan tidak diblokir. Ini sering berguna: Anda mungkin memperhatikan bahwa ketika menghapus case dari daftar, mereka menghilang dari layar secara instan, tanpa menunggu hasil dari permintaan DELETE. Ini terutama terlihat jika Anda dengan cepat menghapus beberapa kasus berturut-turut dan melacak permintaan di panel Jaringan.

Tetapi, karena kita menggunakan hasil dari permintaan POST - menugaskannya ke $ context - kita harus menunggu sampai selesai. Oleh karena itu, Anda perlu menemukan cara lain untuk memodifikasi $todos sebelum menjalankan permintaan POST. Solusi: masih, pertama buat objek bisnis baru dan segera tambahkan ke $todos , biarkan daftar gambar, dan hanya kemudian, setelah rendering, jika bisnis tidak memiliki .url (yaitu, bisnis baru saja dibuat), jalankan permintaan POST, dan memaksakan hasilnya pada konteks data dari kasus ini.

Jadi, pertama-tama kita hanya menambahkan sebuah blank yang hanya berisi .title ke dalam .title

  ,'INPUT#new-todo placeholder="What needs to be done?" autofocus' .ui("$todos=($todos (#.value@title)@tgt):insert #.value=") 


UL#todo-list > LI Aturan UL#todo-list > LI Elemen UL#todo-list > LI Sudah Berisi a! memulai aturan-a setelah elemen pertama kali ditarik. Di sana kita dapat menambahkan peluncuran permintaan POST tanpa adanya .url . Untuk menyuntikkan bidang tambahan ke dalam konteks, dap memiliki & operator

  .a("!? $completed $editing; ? .url:!; & (@method`POST todos@url headers $):query") 


Kami melihat . Hal lain! Bahkan dengan jaringan yang lambat, daftar yang harus dilakukan diperbarui secara instan, dan pemberitahuan server dan pemuatan data yang hilang terjadi di latar belakang, setelah menggambar daftar yang diperbarui.

11. Kawan semuanya!


Di elemen #header ada tombol untuk instalasi massal / reset tanda penyelesaian untuk semua kasus dalam daftar. Untuk penugasan massal nilai ke bidang elemen array, kami hanya menulis pengonversi lain,: :assign , dan menerapkannya ke $todos dengan mengeklik INPUT#toggle-all

  ,'INPUT#toggle-all type=checkbox' .ui("$todos=($todos (#.checked@completed)@src):assign") ... assign: x => x.todos && x.todos.map(todo => Object.assign(todo,x.src)) 


Dalam hal ini, kami hanya tertarik pada bidang .completed , tetapi mudah untuk melihat bahwa dengan konverter seperti itu, Anda dapat secara besar-besaran mengubah nilai bidang apa pun dari elemen array.
Oke, dalam array $todos diaktifkan, sekarang kita perlu memberi tahu server tentang perubahan yang dilakukan. Dalam contoh asli, ini dilakukan dengan mengirimkan permintaan PATCH untuk setiap kasus - bukan strategi yang sangat efektif, tetapi tidak lagi tergantung pada kami. Oke, untuk setiap kasus kami mengirim permintaan PATCH

  .ui("*@ $todos=($todos (#.checked@completed)@src):assign; (@method`PATCH .url:dehttp headers (.completed)):query") 


Kami melihat : Sebuah klik pada common daw meluruskan semua daw individual, dan server diberitahu oleh permintaan PATCH yang sesuai. Norma

12. Menyaring kasus berdasarkan penyelesaian


Selain daftar hal yang harus dilakukan, aplikasi juga harus dapat memfilter kasus dengan tanda penyelesaian dan menunjukkan penghitung tugas yang telah selesai dan belum selesai. Tentu saja, untuk pemfilteran kita akan sepele untuk menggunakan metode filter() sama filter() yang disediakan oleh JS itu sendiri.

Tapi pertama-tama Anda harus memastikan bahwa bidang .completed dari setiap kasus selalu benar, dan ketika Anda mengklik masing-masing kasus diperbarui dengan variabel $completed . Sebelumnya, ini tidak penting bagi kami, tetapi sekarang akan menjadi.

  ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=(.completed=$completed=#.checked) $recount=()") //  .completed        


Poin penting di sini adalah bahwa konteks data setiap kasus adalah objek kasus itu sendiri, yang terletak pada array $todos . Bukan satu salinan, atau konstruksi terkait, tetapi objek itu sendiri. Dan semua panggilan ke bidang .title , .completed , .completed url - baca dan tulis - berlaku langsung ke objek ini. Oleh karena itu, agar pemfilteran array $todos berfungsi dengan benar, kita perlu penyelesaian case agar tidak hanya tercermin di layar, tetapi juga di bidang .completed objek .completed .

Untuk memperlihatkan hanya case dengan tanda kelengkapan yang diperlukan .completed dalam daftar, kami hanya akan memfilter $todos sesuai dengan filter yang dipilih. Filter yang dipilih adalah, Anda dapat menebaknya, variabel keadaan lain dari aplikasi kami, dan kami akan menyebutnya: $filter . Untuk memfilter $todos sesuai dengan $filter dipilih $filter mari kita pergi sepanjang thumbnail dan hanya menambahkan konverter lain, dari form {list, filter} => daftar yang difilter , dan kita akan mengambil nama dan fungsi penyaringan dari "array asosiatif" (yaitu, JS biasa objek) todoFilters

 const todoFilters={ "All": null, "Active": todo => !todo.completed, "Completed": todo => !!todo.completed }; '#todoapp'.d("$todos= $filter=" //   $filter ... ,'UL#todo-list'.d("* ($todos $filter):filter" ... ,'UL#filters'.d("* filter" //  filter      .DICT ,'LI' .d("! .filter") .ui("$filter=.") //    "$filter=.filter" ) ... .DICT({ ... filter: Object.keys(todoFilters) //["All","Active","Completed"] }) .FUNC({ convert:{ ... filter: x =>{ const a = x.todos, f = x.filter && todoFilters[x.filter]; return a&&f ? a.filter(f) : a; } } }) 


Kami periksa . Filter berfungsi dengan baik. Ada nuansa di mana nama-nama filter ditampilkan bersama, karena di sini kami melangkah sedikit dari struktur DOM yang asli dan keluar dari CSS. Tapi kita akan kembali ke sini nanti.

13. Penghitung kasus yang selesai dan aktif.


Untuk menampilkan penghitung kasing yang aktif dan case, cukup filter $todos dengan filter yang sesuai dan perlihatkan panjang array yang dihasilkan

  ,'#footer'.d("$active=($todos @filter`Active):filter $completed=($todos @filter`Completed):filter" ,'#todo-count'.d("! (active $active.length)format") //  length    active ... ,'#clear-completed'.d("! (completed $completed.length)format") ) ... .DICT({ ... active: "{length} items left", completed: "Clear completed items ({length})" }) 


Dalam formulir ini, penghitung menunjukkan nilai yang benar pada saat boot, tetapi tidak menanggapi perubahan selanjutnya dalam penyelesaian urusan (saat mengklik pada daw). Faktanya adalah bahwa klik pada gagak, mengubah keadaan setiap kasus individu, tidak mengubah keadaan $todos - modifikasi elemen array bukanlah modifikasi dari array itu sendiri. Oleh karena itu, kami memerlukan sinyal tambahan tentang perlunya mendaftar ulang kasus. Sinyal semacam itu bisa menjadi variabel status tambahan, yang dimodifikasi setiap kali penghitungan ulang diperlukan. Sebut saja $recount .Kami akan mendeklarasikan leluhur bersama dalam d-rule, kami akan memperbaruinya ketika kami mengklik daw, dan kami #footerakan membuat elemen menjadi konsumen - untuk ini, cukup sebutkan variabel ini dalam d-rule-nya

 '#todoapp'.d("$todos= $filter= $recount=" //  $recount     ... ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=(.completed=$completed=#.checked) $recount=()") //  $recount    ... ,'#footer'.d("$active=($todos @filter`Active):filter $completed=($todos @filter`Completed):filter $recount" //  $recount 


Sekarang semuanya berfungsi sebagaimana mestinya, penghitung diperbarui dengan benar.

14. Penghapusan semua kasus yang diselesaikan.


Penghapusan banyak kasus di TodoMVC diimplementasikan sebagai non-halal sebagai modifikasi batch - oleh beberapa permintaan. Baiklah, mari kita menghela nafas, mengangkat bahu, dan mengeksekusi sesuai dengan DELETE-request untuk setiap case yang sudah selesai - dan kita sudah memiliki semuanya $completed. Dengan demikian, $todossetelah penghapusan kasus yang diselesaikan, apa yang seharusnya sudah masuk$active

  ,'#clear-completed' .d("! (completed $completed.length)format") .ui("$todos=$active; *@ $completed; (@method`DELETE .url:dehttp):query") 


Kami melihat : kami membuat beberapa urusan yang tidak perlu, menandainya dengan daw dan menghapus. Tab Network akan menunjukkan horor dari pendekatan ini ke operasi batch.

15. Status di bilah alamat


Kembali ke pemilihan filter. Dalam contoh asli, filter yang dipilih tercermin di bilah alamat setelah #. Saat mengubah # -fragment di bilah alamat secara manual atau selama navigasi, filter yang dipilih juga berubah. Ini memungkinkan Anda untuk pergi ke halaman aplikasi dengan URL dengan filter tugas yang sudah dipilih.

Anda dapat menulis dengan location.hashoperator urlhash, misalnya, di aturan-a elemen #todoapp(atau keturunannya), yang akan dieksekusi dengan setiap pembaruan$filter

 .a("urlhash $filter") 


Dan Anda dapat menginisialisasi variabel dengan $filternilai dari bilah alamat dan kemudian memperbarui oleh acara hashchange menggunakan pseudo-converter :urlhashyang mengembalikan keadaan saat ini location.hash(tanpa #)

 .d("$todos= $filter=:urlhash $recount=" .e("hashchange","$filter=:urlhash") 


Acara hashchange dimunculkan oleh browser ketika # -fragment di bilah alamat berubah. Namun, untuk beberapa alasan hanya window, dan document.bodydapat mendengarkan acara ini. Untuk melacak peristiwa ini dari suatu elemen #todoapp, Anda harus menambahkan operator ke aturan-d listenyang menandatangani elemen untuk menyampaikan peristiwa dari objekwindow

 '#todoapp' .a("urlhash $filter") .e("hashchange","$filter=:urlhash") .d("$todos= $filter=:urlhash $recount=; listen @hashchange" 


Kami melihat : beralih filter, melacak perubahan di bilah alamat, ikuti tautan dengan #Active , #All , #Completed . Semuanya berfungsi. Tapi kembali ke aslinya. Di sana, tampaknya, pilihan filter diterapkan - dengan mengklik tautan. Meski tidak terlalu praktis, kami akan melakukan hal yang sama demi kelengkapan.

  ,'UL#filters'.d("* filter" ,'LI'.d("" ,'A'.d("!! (`# .filter)concat@href .filter@") ) ) 


Dan untuk membuat filter yang dipilih menonjol, tambahkan operator stylization bersyarat !?yang akan menambahkan kelas CSS ke elemen selectedjika nilai dalam bidang .filterkonteks sama dengan nilai variabel$filter

  ,'A'.d("!! (`# .filter)concat@href .filter@; !? (.filter $filter)eq@selected") 


Dalam formulir ini, fungsi aplikasi dap kami sudah sepenuhnya (sejauh yang saya tahu) konsisten dengan apa yang dilakukan oleh aslinya .

16. Beberapa sentuhan akhir


Saya tidak begitu suka dengan aslinya, bentuk kursor tidak berubah pada elemen yang aktif, jadi kami akan menambahkan headgaya ini ke dokumen HTML kami

  [ui=click]{cursor:pointer} 


Jadi setidaknya kita akan melihat di mana Anda bisa mengklik.

Oh ya! Masih menulis kata "todos" dalam huruf kapital. Tapi di sini saya, mungkin, akan membiarkan diri saya akhirnya menunjukkan sedikit imajinasi dan kreativitas, dan alih-alih hanya "todos" saya akan menulis "dap todos"

  ,'H1'.d("","dap todos") 


Wow Sekarang aplikasi kita dapat dianggap lengkap, dan tutorialnya diadakan (jika Anda dengan jujur ​​membaca baris-baris ini).

Kesimpulannya


Mungkin, saat membaca, Anda mendapat kesan bahwa program dap ditulis dengan coba-coba - ini semua "mari kita lihat apa yang terjadi", "tampaknya berhasil, tetapi ada nuansa", dll. Ini sebenarnya tidak demikian.Semua nuansa ini cukup jelas dan dapat diprediksi saat menulis kode. Tapi saya pikir akan bermanfaat untuk menunjukkan dengan contoh nuansa ini mengapa keputusan ini atau itu ada dalam aturan dan mengapa itu dilakukan dengan cara ini dan bukan sebaliknya.

Tanyakan, seperti kata mereka, pertanyaan.

Source: https://habr.com/ru/post/id481680/


All Articles