Dap beraksi. Menulis TodoMVC. Bagian 1

Artikel pertama tentang dap , jelas, tidak menjadi kesuksesan tulisan saya: sebagian besar komentar mengenai hal itu bermuara pada "niasilil" dan "niasilil, tetapi saya mengutuk." Dan hadiah untuk satu - satunya komentar tingkat atas yang konstruktif diberikan kepada OldVitus , untuk saran untuk menunjukkan yang dap menggunakan TodoMVC sebagai contoh, sehingga ada sesuatu yang bisa dibandingkan. Apa yang akan saya lakukan di artikel ini.

TodoMVC , jika ada yang tidak tahu, ini adalah dunia panggilan-UI standar, yang memungkinkan Anda untuk membandingkan solusi untuk masalah yang sama - "daftar tugas" bersyarat - menggunakan kerangka kerja yang berbeda. Tugas, dengan semua kesederhanaannya ( solusi pada dap break "dalam satu layar"), sangat ilustratif. Oleh karena itu, dengan menggunakan contohnya, saya akan mencoba menunjukkan bagaimana tugas-tugas khas untuk web front-end diimplementasikan menggunakan dap.

Saya tidak mencari dan mempelajari deskripsi formal masalah, tetapi memutuskan untuk membalik salah satu contoh. Backend untuk artikel ini tidak menarik bagi kami, jadi kami tidak akan menulisnya sendiri, tetapi gunakan salah satu yang sudah jadi dari situs www.todobackend.com , dan dari sana kami akan mengambil contoh klien dan file CSS standar.

Untuk menggunakan dap, Anda tidak perlu mengunduh dan menginstal apa pun. Tidak ada npm install dan itu saja. Tidak diperlukan untuk membuat proyek apa pun dengan struktur direktori tertentu, manifesto, dan atribut keberhasilan TI lainnya. Editor dan peramban teks yang cukup. Untuk men-debug permintaan XHR, Anda juga mungkin memerlukan server web - yang cukup sederhana, seperti ekstensi untuk Chrome ini. Seluruh frontend kami akan terdiri dari satu file .html (tentu saja, mengacu pada skrip engine-dap dan ke file CSS TodoMVC standar)

Jadi, dari awal.

1. Buat file .html


 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo -- dap sample</title> <link rel="stylesheet" href="https://www.todobackend.com/client/css/vendor/todomvc-common.css"/> <script src="https://dap.js.org/0.4.js"></script> </head> <body> <script> //   dap </script> </body> </html> 

Persiapan html biasa di mana kami menyertakan file-CSS, silakan disediakan oleh situs www.todobackend.com dan mesin-dap, tidak kalah ramah disediakan oleh situs dap.js.org

2. Salin struktur DOM dari contoh aslinya


Untuk menggunakan file CSS standar tanpa perubahan, kami akan mematuhi struktur DOM yang sama dengan contoh aslinya . Buka di browser Chrome, tekan Ctr + Shift + I, pilih tab Elements dan lihat bahwa aplikasi itu sendiri ada di bagian elemen section id="todo-app">



Dengan membuka subtree ini satu per satu, kami menulis ulang strukturnya menjadi file .html kami. Sekarang kita hanya membuat sketsa dengan cepat, dan tidak menulis kode, jadi kita hanya menulis tanda tangan elemen dalam 'tanda kutip tunggal', dan dalam tanda kurung anak-anak mereka. Jika tidak ada anak, kami menggambar tanda kurung kosong. Kami memantau indeks dan keseimbangan kurung.

 //   dap '#todoapp'( '#header'( 'H1'() 'INPUT#new-todo placeholder="What needs to be done?" autofocus'() ) '#main'( '#toggle-all type=checkbox'() 'UL#todo-list'( 'LI'( 'INPUT.toggle type=checkbox'() 'LABEL'() 'BUTTON.destroy'() ) ) ) '#footer'( '#todo-count'() 'UL#filters'( 'LI'() ) '#clear-completed'() ) ) 

Catatan: elemen berulang (misalnya, ini adalah elemen LI ) kami menulis ke struktur sekali, bahkan jika ada beberapa di antaranya dalam aslinya; jelas ini adalah array dari pola yang sama.

Format tanda tangan, saya pikir, dapat dimengerti oleh siapa saja yang menulis HTML dan CSS dengan tangan mereka, jadi saya tidak akan membahasnya secara rinci untuk saat ini. Saya hanya bisa mengatakan bahwa tag ditulis dalam huruf kapital, dan tidak adanya tag setara dengan keberadaan tag DIV. Kelimpahan # -elements (dengan id) di sini disebabkan oleh spesifikasi file CSS yang disertakan, yang terutama menggunakan pemilih-id.

3. Ingat bahwa program dap adalah Javascript


Untuk menyelamatkan kami dari tanda kurung yang tidak perlu dalam kode, mesin dap menyuntikkan beberapa metode langsung ke String.prototype (Saya menyadari bahwa menerapkan metode Anda di objek standar adalah ayahay, tapi ... singkatnya, kami telah lulus), yang mengubah string tanda tangan menjadi dap templat. Salah satu metode tersebut adalah .d(rule, ...children) . Argumen pertama yang dibutuhkan adalah aturan generasi ( d-rule ), dan sisanya dari argumen adalah jumlah anak yang sewenang-wenang.

Berdasarkan pengetahuan baru ini, kami menambahkan kode kami sehingga alih-alih setiap brace pembuka, kami memiliki urutan .d("" , dan sebelum setiap kutipan tunggal dibuka, kecuali yang pertama, ada koma. Life hack: Anda dapat menggunakan penggantian otomatis.

 '#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("" ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("") ,'LABEL'.d("") ,'BUTTON.destroy'.d("") ) ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) 

Voila! Kami mendapat pohon panggilan ke metode .d , yang siap untuk berubah menjadi template dap. String kosong "" adalah benih aturan-d di masa depan, dan anak-anak adalah argumen yang dipisahkan oleh koma. Secara formal, ini adalah program dap yang valid, meski belum sepenuhnya dengan knalpot yang kita butuhkan. Tapi itu sudah bisa diluncurkan! Untuk melakukan ini, setelah braket root penutup, tambahkan metode .RENDER() . Metode ini, seperti namanya, membuat templat yang dihasilkan.

Jadi, pada tahap ini, kami memiliki file .html dengan konten ini:

 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Todo -- dap sample</title> <link rel="stylesheet" href="https://www.todobackend.com/client/css/vendor/todomvc-common.css"/> <script src="https://dap.js.org/0.4.js"></script> </head> <body> <script> '#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("" ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("") ,'LABEL'.d("") ,'BUTTON.destroy'.d("") ) ) ) ,'#footer'.d("" ,'#todo-count'.d("") ,'UL#filters'.d("" ,'LI'.d("") ) ,'#clear-completed'.d("") ) ) .RENDER() //   dap   </script> </body> </html> 

Anda dapat membukanya di browser untuk memastikan bahwa elemen DOM dihasilkan, gaya CSS diterapkan, tetap hanya untuk mengisi templat ini dengan data.

4. Dapatkan data


Kami pergi ke halaman asli , buka tab Network di alat, aktifkan filter XHR, dan lihat dari mana data berasal dan dalam bentuk apa.





Baik, baik. Daftar yang harus dilakukan diambil langsung dari todo-backend-express.herokuapp.com sebagai json array objek. Bagus

Untuk menerima data, dap memiliki konverter bawaan :query yang secara asinkron "mengubah" URL menjadi data yang diterima darinya. Kami tidak akan menulis URL itu sendiri secara langsung dalam aturan, tetapi kami akan menetapkannya dengan todos konstan; maka seluruh desain data mining akan terlihat seperti ini:

 todos:query 

dan tulis konstanta todos itu sendiri di kamus - di bagian .DICT , tepat sebelum .RENDER() :

 '#todoapp'.d("" ... ) .DICT({ todos : "https://todo-backend-express.herokuapp.com/" }) .RENDER() 

Setelah menerima array todos , kami membuat daftar tugas dari itu: untuk setiap kasus kami mengambil nama dari bidang .title dan menulisnya ke elemen LABEL , dan dari bidang .completed mengambil tanda "kelengkapan" dan menulis ke properti yang checked dari checked elemen INPUT.toggle . Ini dilakukan seperti ini:

  ,'UL#todo-list'.d("*@ todos:query" //  *       ,'LI'.d("" ,'INPUT.toggle type=checkbox'.d("#.checked=.completed") // #  " " ,'LABEL'.d("! .title") //  !      ,'BUTTON.destroy'.d("") ) ) 

Kami memperbarui halaman kami di browser dan ... jika Anda memulainya dari sistem file, maka tidak ada yang terjadi. Masalahnya adalah browser modern tidak mengizinkan permintaan XHR lintas-domain dari dokumen lokal.



Saatnya menonton laman kami melalui http - menggunakan server web lokal apa pun. Baik, atau jika Anda tidak siap untuk menulis dap dengan tangan Anda sendiri, lihat versi berurutan halaman menggunakan tautan saya (jangan lupa untuk melihat sumbernya - di Chrome ini dilakukan dengan menggunakan Ctrl + U)

Jadi, kita pergi ke halaman kita di http: // dan melihat bahwa datanya datang, daftar sedang dibangun. Hebat! Anda telah menguasai * dan operator ! , konverter :query , konstanta dan akses ke bidang elemen array saat ini. Lihat lagi kode yang dihasilkan. Apakah Anda masih merasa tidak dapat membaca?

5. Tambahkan status


Mungkin Anda sudah mencoba mengklik tanda centang di daftar yang harus dilakukan. Kotak centang itu sendiri berubah warna, tetapi, tidak seperti aslinya, elemen induk LI tidak mengubah gayanya ("pekerjaan yang diselesaikan" harus menjadi abu-abu dan dicoret, tetapi ini tidak terjadi) - hal-hal tidak mengubah keadaan mereka. Tetapi elemen-elemen ini belum memiliki keadaan dan, karenanya, tidak dapat mengubahnya. Sekarang kita akan memperbaikinya.

Tambahkan status "selesai" ke elemen LI . Untuk melakukan ini, tentukan variabel state $completed completion dalam d-rule-nya. Untuk INPUT.toggle , yang dapat mengubah status ini, kami akan menetapkan aturan reaksi yang sesuai ( ui-rule ), yang akan mengatur variabel $completed sesuai dengan tanda bendernya sendiri yang checked (“daw diaktifkan”). Bergantung pada status $completed elemen LI akan mengaktifkan atau menonaktifkan kelas CSS "selesai".

  ,'UL#todo-list'.d("*@ todos:query" ,'LI'.d("$completed=.completed"//  ,    .completed ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") //       .ui("$completed=#.checked") //    $completed ,'LABEL'.d("! .title") ,'BUTTON.destroy'.d("") ) .a("!? $completed") //     $completed,    css- completed ) 

Manipulasi seperti ini dengan kelas CSS adalah hal yang cukup umum, jadi ada operator khusus untuk mereka !?
Harap dicatat bahwa kami melakukan ini di a-rule (dari kata akumulasi). Kenapa tidak di d-rule? Perbedaan antara kedua jenis aturan ini adalah ketika memperbarui, aturan-d sepenuhnya membangun kembali konten elemen, menghapus yang lama dan yang baru lagi, sedangkan aturan-a tidak menyentuh konten elemen yang ada, tetapi "menambahkan" hasilnya pada apa yang sudah ada. Mengubah atribut tunggal elemen LI tidak memerlukan restrukturisasi sisa isinya, sehingga lebih rasional untuk melakukan ini dalam aturan-a.

Kami melihat hasilnya . Sudah lebih baik: mengklik pada kotak centang mengubah keadaan item yang harus dilakukan, dan sesuai dengan keadaan ini, gaya visual elemen berubah. Tetapi masih ada masalah: jika daftar tugas yang diselesaikan pada awalnya hadir, mereka tidak akan berwarna abu-abu, karena secara default a-rule tidak dieksekusi ketika elemen dihasilkan. Untuk menjalankannya bahkan selama pembangkitan, kita menambahkan operator a! Pada aturan-d dari elemen LI

  ,'LI'.d("$completed=.completed; a!" //      $completed    a- 

Kami melihat . Baiklah Dengan status $completed tahu. Kasing yang telah selesai distilisasi dengan benar, baik pada saat boot awal maupun selama perpindahan manual berikutnya.

6. Mengedit nama kasus


Kembali ke aslinya . Dengan mengklik dua kali pada nama case, mode pengeditan diaktifkan, di mana nama ini dapat diubah. Itu diimplementasikan sedemikian rupa sehingga templat mode tampilan “view” (dengan daw, judul dan tombol hapus) sepenuhnya tersembunyi, dan elemen INPUT class="edit" ditampilkan. Kami akan melakukannya sedikit berbeda - kami hanya akan menyembunyikan elemen LABEL , karena dua elemen lainnya tidak mengganggu pengeditan kami. Cukup tambahkan kelas view ke elemen LABEL .

Untuk kondisi "editing", tentukan variabel $editing dalam elemen LI . Awalnya, ini (keadaan) diatur ulang, dblclick dengan dblclick pada elemen LABEL , dan mati ketika elemen INPUT.edit tidak fokus. Jadi kami menulis:

  ,'LI'.d("$completed=.completed $editing=; a!" //       ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$completed=#.checked") ,'LABEL.view' .d("? $editing:!; ! .title") //  $editing ,     .e("dblclick","$editing=`yes") //  dblclick  $editing ,'INPUT.edit' .d("? $editing; !! .title@value") //  $editing  .ui(".title=#.value") //  .title   change (ui     INPUT) .e("blur","$editing=") //  $editing   blur ,'BUTTON.destroy'.d("") ).a("!? $completed $editing") //   $completed  $editing  css-  'LI' 

Sekarang kita bisa mengedit nama-nama kasing.

7. Mengirim data ke server


Oke, kita sudah bisa mengedit hal-hal di browser, tetapi perubahan ini juga harus ditransfer ke server. Kami melihat bagaimana yang asli melakukannya:



Perubahan dikirim ke server menggunakan metode PATCH dengan URL tertentu dari formulir http://todo-backend-express.herokuapp.com/28185 , yang, jelas, unik untuk setiap kasus. URL ini ditunjukkan oleh server di bidang .url untuk setiap kasus dalam daftar. Artinya, semua yang diperlukan dari kami untuk memperbarui kasus di server adalah mengirim permintaan PATCH ke alamat yang ditentukan dalam bidang .url , dengan data yang diubah dalam format JSON:

  ,'INPUT.edit' .d("? $editing; !! .title@value") .ui(".title=#.value; (@method`PATCH .url (@Content-type`application/json)@headers (.title):json.encode@body):query") .e("blur","$editing=") 

Di sini kami menggunakan konverter yang sama :query , tetapi dalam versi yang lebih rinci. Ketika :query diterapkan ke string sederhana, string ini diperlakukan sebagai URL dan permintaan GET dijalankan. Jika :query menerima objek yang kompleks, seperti dalam kasus ini, :query memperlakukannya sebagai deskripsi terperinci dari permintaan yang berisi bidang .method , .url , .headers , dan .body , dan menjalankan permintaan sesuai dengan mereka. Di sini, segera setelah memperbarui .title mengirim permintaan PATCH ke server dengan .title diperbarui ini

Namun ada nuansa. Kami mendapatkan bidang .url dari server, tampilannya seperti ini: http://todo-backend-express.herokuapp.com/28185 , yaitu protokol http: // hardcode di dalamnya jika klien kami juga membuka melalui http: // maka semuanya baik-baik saja. Tetapi jika klien terbuka melalui https: //, maka muncul masalah: untuk alasan keamanan, browser memblokir http-traffic dari sumber https.

Itu dipecahkan secara sederhana: jika Anda menghapus protokol dari .url , permintaan akan melewati protokol halaman. Jadi mari kita lakukan: tulis konverter yang sesuai - dehttp , dan kita akan melewati .url melaluinya. Konverter khusus (dan fungsi lainnya) .FUNC di bagian .FUNC :

  .ui(".title=#.value; (@method`PATCH .url:dehttp (@Content-type`application/json)@headers (.title):json.encode@body):query") ... .FUNC({ convert:{ //  -         dehhtp: url=>url.replace(/^https?\:/,'')//    URL } }) 

Masuk akal juga untuk menempatkan objek header di kamus sehingga dapat digunakan di pertanyaan lain:

  .ui(".title=#.value; (@method`PATCH .url:dehttp headers (.title):json.encode@body):query") ... .DICT({ todos : "//todo-backend-express.herokuapp.com/", headers: {"Content-type":"application/json"} }) 

Nah, untuk feng shui penuh, kita akan menggunakan properti lain yang berguna dari konverter :query - pengkodean otomatis dari tubuh permintaan di json sesuai dengan Content-type:application/json header. Akibatnya, aturannya akan terlihat seperti ini:

  .ui(".title=#.value; (@method`PATCH .url:dehttp headers (.title)):query") 

Jadi, lihat . Oke, nama case sekarang berubah tidak hanya di browser, tetapi juga di server. Tapi! Tidak hanya nama kasus dapat berubah, tetapi juga status penyelesaiannya - completed . Jadi, itu juga perlu dikirim ke server.
Anda dapat menambahkan permintaan PATCH yang sama ke elemen INPUT.toggle , cukup kirim (.completed) alih-alih (.completed) :

  ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$completed=#.checked; (@method`PATCH .url:dehttp headers (.completed:?)):query") 

Dan Anda dapat menempatkan permintaan PATCH ini "out of brackets":

  ,'LI'.d("$completed=.completed $editing= $patch=; a!" // $patch - ""   ,'INPUT.toggle type=checkbox' .d("#.checked=.completed") .ui("$patch=($completed=#.checked)") //   $patch  completed ,'LABEL.view' .d("? $editing:!; ! .title") .e("dblclick","$editing=`yes") ,'INPUT.edit' .d("? $editing; !! .title@value") .ui("$patch=(.title=#.value)") //   $patch  title .e("blur","$editing=") ,'BUTTON.destroy'.d("") ) .a("!? $completed $editing") //  $patch  ,   ,   .u("? $patch; (@method`PATCH .url:dehttp headers $patch@):query $patch=") 

Ini masalahnya. Aturan reaksi termasuk dalam kelompok "aturan atas", yang dieksekusi "dari bawah ke atas" - dari turunan ke induk, ke akar itu sendiri (urutan ini dapat diinterupsi jika perlu). Ini agak seperti acara pop-up di DOM. Oleh karena itu, beberapa fragmen dari reaksi umum terhadap beberapa keturunan dapat diberikan kepada leluhur yang sama.

Secara khusus, dalam kasus kami, keuntungan dari delegasi tersebut tidak terlalu terlihat, tetapi jika ada lebih banyak bidang yang dapat diedit, maka menempatkan permintaan besar ini (berdasarkan standar, tentu saja) ke dalam satu aturan umum akan sangat membantu menjaga kode tetap sederhana dan mudah dibaca. Jadi saya merekomendasikannya.

Kami melihat : Sekarang perubahan nama dan perubahan status dikirim ke server.

Di artikel selanjutnya, jika Anda tertarik, pertimbangkan untuk menambah, menghapus, dan memfilter kasing. Sementara itu, Anda dapat melihat hasil akhir dan contoh kode dap lainnya di dap.js.org/docs

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


All Articles