Hầu hết các trang web tôi xây dựng đều là các trang web tĩnh với các tệp HTML được tạo bởi trình tạo trang web tĩnh hoặc các trang được phục vụ trên máy chủ bởi CMS như Wordpress hoặc CraftCMS. Tôi chỉ sử dụng JavaScript để nâng cao trải nghiệm của người dùng. Tôi sử dụng JavaScript cho những thứ như tiện ích tiết lộ, accordion, điều hướng bay ra hoặc hộp thoại.
Yêu cầu đối với hầu hết các tính năng này đều đơn giản, vì vậy việc sử dụng thư viện hoặc khung sẽ là quá mức cần thiết. Tuy nhiên, gần đây, tôi thấy mình rơi vào tình huống mà việc viết một thành phần từ đầu trong Vanilla JS mà không có sự trợ giúp của một khung sẽ quá phức tạp và lộn xộn.
Lưu ý: Bạn có thể bỏ qua phần giới thiệu này và đi thẳng đến nội dung chính của bài viết nếu bạn đã quen thuộc với Alpine.js.
Giả sử chúng ta muốn biến một danh sách đơn giản có nhiều mục thành tiện ích tiết lộ. Bạn có thể sử dụng các thành phần HTML gốc: chi tiết và tóm tắt cho mục đích đó, nhưng đối với bài tập này, tôi sẽ sử dụng Alpine.
Theo mặc định, khi JavaScript bị tắt, chúng tôi sẽ hiển thị danh sách, nhưng chúng tôi muốn ẩn nó và cho phép người dùng mở và đóng nó bằng cách nhấn nút nếu JavaScript được bật:
Đầu tiên, chúng tôi bao gồm Alpine bằng cách sử dụng thẻ
Chúng ta có thể sử dụng thuộc tính
Vì chúng ta đặt
Tiếp theo, chúng ta cần một nút để chuyển đổi giá trị của thuộc tính
Khi nhấn nút,
Mặc dù điều này có hiệu quả với người dùng bàn phím và chuột, nhưng lại vô dụng với người dùng trình đọc màn hình vì chúng ta cần truyền đạt trạng thái của tiện ích. Chúng ta có thể thực hiện điều đó bằng cách chuyển đổi giá trị của thuộc tính
Chúng ta cũng có thể tạo kết nối ngữ nghĩa giữa nút và danh sách bằng cách sử dụng
Đây là kết quả cuối cùng:
Xem Bút [Tiện ích tiết lộ đơn giản với Alpine.js](https://codepen.io/smashingmag/pen/xxpdzNz) của Manuel Matuzovic.
Xem Bút Tiện ích tiết lộ đơn giản với Alpine.js của Manuel Matuzovic.
Thật tuyệt! Bạn có thể cải thiện nội dung tĩnh hiện có bằng JavaScript mà không cần phải viết một dòng JS nào. Tất nhiên, bạn có thể cần phải viết một số JavaScript, đặc biệt là nếu bạn đang làm việc trên các thành phần phức tạp hơn.

Sau đó, tạo một tệp
Tiếp theo, tạo một Tệp
Bạn không cần phải thực hiện tất cả các bước này trên dòng lệnh. Bạn cũng có thể tạo thư mục và tệp trong bất kỳ giao diện người dùng nào. Cấu trúc tệp và thư mục cuối cùng trông như thế này:

Cuối cùng, hãy thêm một cấu trúc HTML cơ bản vào tệp
Bằng cách chạy lệnh sau, bạn sẽ có thể truy cập trang web tại


Nếu bạn truy cập lại trang, danh sách chỉ chứa 5 mục. Bạn cũng có thể thấy rằng tôi đã thêm một thông báo trạng thái (bỏ qua phần tử
Tiếp theo, chúng ta sẽ thêm một điều hướng có liên kết đến tất cả các trang được tạo bởi trình tạo trang tĩnh. Đối tượng
Cuối cùng, hãy thêm một số mã CSS cơ bản để cải thiện kiểu dáng:

Bạn có thể xem nó hoạt động trong bản demo trực tiếp và bạn có thể xem mã trên GitHub.
Điều này hoạt động khá tốt với 7 bản ghi. Nó thậm chí có thể hoạt động với 10, 20 hoặc 50, nhưng tôi có hơn 400 bản ghi. Chúng ta có thể duyệt danh sách dễ dàng hơn bằng cách thêm bộ lọc.
Nền tảng của chúng tôi trong ví dụ này là danh sách tĩnh được tạo bằng 11ty và bây giờ chúng tôi thêm một lớp chức năng bằng Alpine.
Đầu tiên, ngay trước thẻ đóng
Lưu ý: Hãy cẩn thận khi sử dụng CDN của bên thứ ba, điều này có thể gây ra nhiều tác động tiêu cực (hiệu suất, quyền riêng tư, bảo mật). Hãy cân nhắc tham chiếu tệp cục bộ hoặc nhập tệp dưới dạng mô-đun.
Trong trường hợp bạn đang tự hỏi tại sao bạn không thấy hàm băm Subresource Integrity trong tài liệu chính thức, thì đó là vì tôi đã tạo và thêm hàm băm này theo cách thủ công.
Vì chúng ta đang chuyển sang thế giới JavaScript, chúng ta cần cung cấp các bản ghi của mình cho Alpine.js. Có lẽ không phải là giải pháp tốt nhất, nhưng giải pháp nhanh nhất là tạo tệp
Điều này đảm bảo rằng eleventy không chỉ tạo tệp HTML mà còn sao chép nội dung của thư mục
Chúng ta không có dữ liệu nào, vì vậy chúng ta cần lấy dữ liệu khi thành phần khởi tạo. Chỉ thị
Nếu chúng ta xuất kết quả trực tiếp, chúng ta sẽ thấy danh sách
Thật đáng kinh ngạc khi chúng tôi có thể truy xuất và xuất dữ liệu nhanh như vậy phải không? Hãy xem bản demo bên dưới để xem Alpine điền kết quả vào danh sách như thế nào.
Gợi ý: Bạn không thấy bất kỳ mã Nunjucks nào trong CodePen này, vì 11ty không chạy trên trình duyệt. Tôi vừa sao chép và dán mã HTML đã kết xuất của trang đầu tiên.
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 1](https://codepen.io/smashingmag/pen/abEWRMY) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 1 của Manuel Matuzovic.
Bạn có thể đạt được nhiều thành tựu bằng cách sử dụng Alpine chỉ thị, nhưng tại một số điểm chỉ dựa vào các thuộc tính có thể trở nên lộn xộn. Đó là lý do tại sao tôi quyết định di chuyển dữ liệu và một số logic vào một đối tượng thành phần Alpine riêng biệt.
Đây là cách thức hoạt động: Thay vì truyền dữ liệu trực tiếp, giờ đây chúng ta tham chiếu đến một thành phần bằng cách sử dụng
Nhìn vào CodePen trước, bạn có thể nhận thấy rằng bây giờ chúng ta có một tập dữ liệu trùng lặp. Đó là vì danh sách tĩnh 11ty của chúng ta vẫn còn đó. Alpine có một lệnh yêu cầu nó bỏ qua một số phần tử DOM nhất định. Tôi không biết điều này có thực sự cần thiết ở đây không, nhưng đây là một cách hay để đánh dấu các phần tử không mong muốn này. Vì vậy, chúng ta thêm lệnh
11ty dữ liệu bị ẩn, kết quả đến từ Alpine, nhưng phân trang không hoạt động tại thời điểm này:
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 2](https://codepen.io/smashingmag/pen/eYyWQOe) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 2 của Manuel Matuzovic.
Số lượng mục trên mỗi trang là một giá trị cố định
Cách dễ nhất để tôi có được các mục trên mỗi trang là sử dụng phương thức
Để chỉ hiển thị các mục cho trang hiện tại, chúng ta phải điều chỉnh vòng lặp
Bây giờ chúng ta có một trang, nhưng không có liên kết nào cho phép chúng ta nhảy từ trang này sang trang khác. Giống như trước đó, chúng ta sử dụng phần tử
Vì chúng tôi không muốn tải lại toàn bộ trang nữa, nên chúng tôi đặt sự kiện
Đây là giao diện trong trình duyệt. (Tôi đã thêm nhiều mục nhập hơn vào tệp JSON. Bạn có thể tải xuống trên GitHub.)
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 3](https://codepen.io/smashingmag/pen/GRymwjg) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 3 của Manuel Matuzovic.
Chúng tôi thêm hai phần tử select được bọc trong
Tất nhiên, chúng ta cũng phải tạo các trường dữ liệu này trong thành phần Alpine của mình:
Nếu chúng ta thay đổi giá trị đã chọn trong mỗi
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 4](https://codepen.io/smashingmag/pen/GRymwEp) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 4 của Manuel Matuzovic.
Bây giờ chúng ta có
Điều này có vẻ hoang đường, và tôi chắc chắn là tôi sẽ sớm quên những gì đang diễn ra ở đây thôi, nhưng đoạn mã này sẽ lấy mảng các đối tượng và biến nó thành một mảng các chuỗi (
Tiếp theo, chúng ta điền thông tin cho các phần tử
Hãy tự mình thử nghiệm trong bản demo 5 trên Codepen.
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 5](https://codepen.io/smashingmag/pen/OJzmaZb) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 5 của Manuel Matuzovic.
Chúng tôi đã điền thành công các phần tử được chọn bằng dữ liệu từ tệp JSON của mình. Để lọc dữ liệu cuối cùng, chúng ta duyệt qua tất cả các bản ghi, chúng ta kiểm tra xem bộ lọc có được đặt hay không. Nếu đúng như vậy, chúng ta kiểm tra xem trường tương ứng của bản ghi có tương ứng với giá trị đã chọn của bộ lọc hay không. Nếu không, chúng ta lọc bản ghi này ra. Chúng ta còn lại một mảng đã lọc khớp với tiêu chí:
Để điều này có hiệu lực, chúng ta phải điều chỉnh các hàm
Xem Bút [Phân trang + Lọc với Bước Alpine.js 6](https://codepen.io/smashingmag/pen/GRymwQZ) của Manuel Matuzovic.
Xem Bút Phân trang + Lọc bằng Alpine.js Bước 6 của Manuel Matuzovic.
Còn ba việc phải làm:
Mỗi khi thuộc tính
Tôi đang sử dụng
Đầu tiên, chúng ta liên kết dữ liệu với phần tử
Tiếp theo, chúng ta muốn truyền đạt cho trình đọc màn hình rằng nội dung trên trang đã thay đổi. Có ít nhất hai cách để thực hiện điều đó:
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 7](https://codepen.io/smashingmag/pen/zYpwMXX) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 7 của Manuel Matuzovic.
Lưu ý: Khi bạn lọc theo nghệ sĩ và thông báo trạng thái hiển thị "1 bản ghi" và bạn lọc lại theo một nghệ sĩ khác, cũng chỉ có một bản ghi, nội dung của phần tử
Tôi khá hài lòng với kết quả, nhưng vẫn còn một vài điều nữa có thể cải thiện:

P.S.S. Xin gửi lời cảm ơn đặc biệt đến Scott, Søren, Thain, David, Saptak và Christian vì phản hồi của họ.
Yêu cầu đối với hầu hết các tính năng này đều đơn giản, vì vậy việc sử dụng thư viện hoặc khung sẽ là quá mức cần thiết. Tuy nhiên, gần đây, tôi thấy mình rơi vào tình huống mà việc viết một thành phần từ đầu trong Vanilla JS mà không có sự trợ giúp của một khung sẽ quá phức tạp và lộn xộn.
Khung nhẹ
Nhiệm vụ của tôi là thêm nhiều bộ lọc, sắp xếp và phân trang vào danh sách các mục hiện có. Tôi không muốn sử dụng một JavaScript Framework như Vue hay React, chỉ vì tôi cần trợ giúp ở một số nơi trên trang web của mình và tôi không muốn thay đổi ngăn xếp của mình. Tôi đã tham khảo Twitter và mọi người đề xuất các framework tối giản như lit, petite-vue, hyperscript, htmx hoặc Alpine.js. Tôi đã chọn Alpine vì nghe có vẻ như đây chính xác là thứ tôi đang tìm kiếm:“Alpine là một công cụ mạnh mẽ, tối giản để soạn thảo hành vi trực tiếp trong mã đánh dấu của bạn. Hãy nghĩ về nó như jQuery cho web hiện đại. Thêm một thẻ script và bắt đầu.”
Alpine.js
Alpine là một bộ sưu tập nhẹ (~7KB) gồm 15 thuộc tính, 6 thuộc tính và 2 phương thức. Tôi sẽ không đi sâu vào những điều cơ bản của nó (hãy xem bài viết này về Alpine của Hugo Di Francesco hoặc đọc tài liệu Alpine), nhưng hãy để tôi giới thiệu nhanh cho bạn về Alpine:Lưu ý: Bạn có thể bỏ qua phần giới thiệu này và đi thẳng đến nội dung chính của bài viết nếu bạn đã quen thuộc với Alpine.js.
Giả sử chúng ta muốn biến một danh sách đơn giản có nhiều mục thành tiện ích tiết lộ. Bạn có thể sử dụng các thành phần HTML gốc: chi tiết và tóm tắt cho mục đích đó, nhưng đối với bài tập này, tôi sẽ sử dụng Alpine.
Theo mặc định, khi JavaScript bị tắt, chúng tôi sẽ hiển thị danh sách, nhưng chúng tôi muốn ẩn nó và cho phép người dùng mở và đóng nó bằng cách nhấn nút nếu JavaScript được bật:
Mã:
[HEADING=2]Beastie Boys Anthology[/HEADING]
The Sounds of Science là album tuyển tập đầu tiên của nhóm nhạc rap rock Mỹ Beastie Boys gồm các bản hit hay nhất, B-side và các bản nhạc chưa phát hành trước đó.
[LIST=1]
[*] Beastie Boys
[*] Slow And Low
[*] Shake Your Rump
[*] Gratitude
[*] Skills To Pay The Bills
[*] Root Down
[*] Believe Me …[/LIST]
script
. Sau đó, chúng tôi gói danh sách trong div
và sử dụng chỉ thị x-data
để truyền dữ liệu vào thành phần. Thuộc tính open
bên trong đối tượng mà chúng ta đã truyền có sẵn cho tất cả các phần tử con của div
:
Mã:
[LIST=1]
[*] Beastie Boys
[*] Slow And Low … [/LIST]
open
cho chỉ thị x-show
, chỉ thị này xác định xem một phần tử có hiển thị hay không:
Mã:
[LIST=1]
[*] Beastie Boys
[*] Slow And Low … [/LIST]
open
thành false
, nên danh sách hiện đã ẩn.Tiếp theo, chúng ta cần một nút để chuyển đổi giá trị của thuộc tính
open
. Chúng ta có thể thêm sự kiện bằng cách sử dụng lệnh x-on:click
hoặc @-Syntax ngắn hơn @click
:
Mã:
Tracklist [LIST=1]
[*] Beastie Boys
[*] Slow And Low … [/LIST]
open
giờ sẽ chuyển đổi giữa false
và true
và x-show
sẽ theo dõi những thay đổi này một cách phản ứng, hiển thị và ẩn danh sách cho phù hợp.Mặc dù điều này có hiệu quả với người dùng bàn phím và chuột, nhưng lại vô dụng với người dùng trình đọc màn hình vì chúng ta cần truyền đạt trạng thái của tiện ích. Chúng ta có thể thực hiện điều đó bằng cách chuyển đổi giá trị của thuộc tính
aria-expanded
:
Mã:
Tracklist
aria-controls
cho trình đọc màn hình hỗ trợ thuộc tính:
Mã:
Tracklist[LIST=1] …[/LIST]
Xem Bút [Tiện ích tiết lộ đơn giản với Alpine.js](https://codepen.io/smashingmag/pen/xxpdzNz) của Manuel Matuzovic.
Xem Bút Tiện ích tiết lộ đơn giản với Alpine.js của Manuel Matuzovic.
Thật tuyệt! Bạn có thể cải thiện nội dung tĩnh hiện có bằng JavaScript mà không cần phải viết một dòng JS nào. Tất nhiên, bạn có thể cần phải viết một số JavaScript, đặc biệt là nếu bạn đang làm việc trên các thành phần phức tạp hơn.
Danh sách phân trang tĩnh
Được rồi, bây giờ chúng ta đã biết những điều cơ bản về Alpine.js, tôi cho rằng đã đến lúc xây dựng một thành phần phức tạp hơn.Tôi muốn xây dựng một danh sách phân trang các đĩa than của mình hoạt động mà không cần JavaScript. Chúng tôi sẽ sử dụng trình tạo trang web tĩnh eleventy (hay viết tắt là “11ty”) cho mục đích đó và Alpine.js để cải thiện nó bằng cách làm cho danh sách có thể lọc được.Lưu ý: Bạn có thể xem kết quả cuối cùng trước khi chúng ta bắt đầu.

Thiết lập
Trước khi bắt đầu, chúng ta hãy thiết lập trang web của mình. Chúng ta cần:- một thư mục dự án cho trang web của chúng ta,
- 11ty để tạo các tệp HTML,
- một tệp đầu vào cho HTML của chúng ta,
- một tệp dữ liệu chứa danh sách các bản ghi.
cd
vào đó:
Mã:
cd Sites # hoặc bất cứ nơi nào bạn muốn lưu dự ánmkdir myrecordcollection # chọn bất kỳ tên nàocd myrecordcollection
package.json
và cài đặt eleventy:
Mã:
npm init -ynpm install @11ty/eleventy
index.njk
(.njk
có nghĩa là đây là tệp Nunjucks; thông tin chi tiết bên dưới) và thư mục _data
có records.json
:
Mã:
touch index.njkmkdir _datatouch _data/records.json

Thêm nội dung
11ty cho phép bạn viết nội dung trực tiếp vào tệp HTML (hoặc Markdown, Nunjucks và các ngôn ngữ mẫu khác). Bạn thậm chí có thể lưu trữ dữ liệu trong phần nội dung hoặc trong tệp JSON. Tôi không muốn quản lý hàng trăm mục nhập theo cách thủ công, vì vậy tôi sẽ lưu trữ chúng trong tệp JSON mà chúng ta vừa tạo. Hãy thêm một số dữ liệu vào tệp:
Mã:
[ { "artist": "Akne Kid Joe", "title": "Die große Palmöllüge", "year": 2020 }, { "artist": "Bring me the Horizon", "title": "Post Human: Survial Horror", "year": 2020 }, { "artist": "Idles", "title": "Joy as an Act of Resistance", "year": 2018 }, { "artist": "Beastie Boys", "title": "Được cấp phép cho Ill", "year": 1986 }, { "artist": "Beastie Boys", "title": "Paul's Boutique", "year": 1989 }, { "artist": "Beastie Boys", "title": "Check Your Head", "year": 1992 }, { "artist": "Beastie Boys", "title": "Ill Communication", "year": 1994 }]
index.njk
và bắt đầu eleventy:
Mã:
My Record Collection [HEADING=1]My Record Collection[/HEADING]
http://localhost:8080
:
Mã:
eleventy --serve

Hiển thị nội dung
Bây giờ chúng ta hãy lấy dữ liệu từ tệp JSON của mình và chuyển nó thành HTML. Chúng ta có thể truy cập nó bằng cách lặp qua đối tượngrecords
trong nunjucks:
Mã:
[LIST=1] {% for record in records %}
[*] [B]{{ record.title }}[/b]
Phát hành vào {{ record.year }} bởi {{ record.artist }}. {% endfor %} [/LIST]

Phân trang
Eleventy hỗ trợ phân trang ngay khi cài đặt. Tất cả những gì chúng ta phải làm là thêm một khối frontmatter vào trang của mình, cho 11ty biết tập dữ liệu nào sẽ được sử dụng để phân trang và cuối cùng, chúng ta phải điều chỉnh vòng lặpfor
của mình để sử dụng danh sách được phân trang thay vì tất cả các bản ghi:
Mã:
---pagination: data: records size: 5--- My Record Collection [HEADING=1]My Record Collection[/HEADING]
Hiển thị {{ records.length }} bản ghi
[LIST=1] {% for record in pagination.items %}
[*] [B]{{ record.title }}[/b]
Đã phát hành vào {{ record.year }} bởi {{ record.artist }}. {% endfor %} [/LIST]
output
lúc này), gói danh sách trong div
với role
"region", và tôi đã gắn nhãn cho nó bằng cách tạo tham chiếu đến #message
bằng aria-labelledby
. Tôi đã làm như vậy để biến nó thành landmark và cho phép người dùng trình đọc màn hình truy cập trực tiếp vào danh sách kết quả bằng phím tắt.Tiếp theo, chúng ta sẽ thêm một điều hướng có liên kết đến tất cả các trang được tạo bởi trình tạo trang tĩnh. Đối tượng
pagination
giữ một mảng
chứa tất cả các trang. Chúng ta sử dụng aria-current="page"
để làm nổi bật trang hiện tại:
Mã:
[LIST=1] {% for page_entry in pagination.pages %} {%- set page_url = pagination.hrefs[loop.index0] -%}
[*] [URL={{ page_url }}] Trang {{ loop.index }} [/URL] {% endfor %} [/LIST]
Mã:
body { font-family: sans-serif; line-height: 1.5;}ol { list-style: none; margin: 0; padding: 0;}.records > * + * { margin-top: 2rem;}h2 { margin-bottom: 0;}nav { margin-top: 1.5rem;}.pages { display: flex; flex-wrap: wrap; gap: 0.5rem;}.pages a { border: 1px solid #000000; padding: 0.5rem; border-radius: 5px; display: flex; text-decoration: none;}.pages a:where([aria-current]) { background-color: #000000; color: #ffffff;}.pages a:where(:focus, :hover) { background-color: #6c6c6c; color: #ffffff;}

Bạn có thể xem nó hoạt động trong bản demo trực tiếp và bạn có thể xem mã trên GitHub.
Điều này hoạt động khá tốt với 7 bản ghi. Nó thậm chí có thể hoạt động với 10, 20 hoặc 50, nhưng tôi có hơn 400 bản ghi. Chúng ta có thể duyệt danh sách dễ dàng hơn bằng cách thêm bộ lọc.
Danh sách phân trang và lọc động
Tôi thích JavaScript, nhưng tôi cũng tin rằng nội dung cốt lõi và chức năng của một trang web phải có thể truy cập được mà không cần JavaScript. Điều này không có nghĩa là bạn không thể sử dụng JavaScript, nó chỉ có nghĩa là bạn bắt đầu với nền tảng kết xuất máy chủ cơ bản của thành phần hoặc trang web của mình và bạn thêm chức năng từng lớp một. Đây được gọi là cải tiến tiến bộ.Nền tảng của chúng tôi trong ví dụ này là danh sách tĩnh được tạo bằng 11ty và bây giờ chúng tôi thêm một lớp chức năng bằng Alpine.
Đầu tiên, ngay trước thẻ đóng
body
, chúng tôi tham chiếu đến phiên bản mới nhất (tính đến thời điểm viết 3.9.1) của Alpine.js:
Mã:
Trong trường hợp bạn đang tự hỏi tại sao bạn không thấy hàm băm Subresource Integrity trong tài liệu chính thức, thì đó là vì tôi đã tạo và thêm hàm băm này theo cách thủ công.
Vì chúng ta đang chuyển sang thế giới JavaScript, chúng ta cần cung cấp các bản ghi của mình cho Alpine.js. Có lẽ không phải là giải pháp tốt nhất, nhưng giải pháp nhanh nhất là tạo tệp
.eleventy.js
trong thư mục gốc của bạn và thêm các dòng sau:
Mã:
module.exports = function(eleventyConfig) { eleventyConfig.addPassthroughCopy("_data");};
_data
vào thư mục đích của chúng ta, giúp các tập lệnh của chúng ta có thể truy cập vào thư mục đó.Đang lấy dữ liệu
Giống như trong ví dụ trước, chúng ta sẽ thêm lệnhx-data
vào thành phần của mình để truyền dữ liệu:
Mã:
x-init
cho phép chúng ta móc vào giai đoạn khởi tạo của bất kỳ phần tử nào và thực hiện các tác vụ:
Mã:
[…]
[object Object]
, vì chúng ta đang lấy và nhận một mảng
. Thay vào đó, chúng ta nên lặp lại danh sách bằng cách sử dụng lệnh x-for
trên thẻ template
và xuất dữ liệu bằng cách sử dụng x-text
:
Mã:
[*] [B][/b]
Phát hành vào bởi .
Dưới đây là toàn bộ danh sách trông như thế nào bây giờ:"Phần tử HTMLlà một cơ chế giữ HTML không được hiển thị ngay lập tức khi trang được tải nhưng có thể được khởi tạo sau đó trong thời gian chạy bằng JavaScript."
MDN:: Phần tử mẫu nội dung
Mã:
Hiển thị {{ records.length }} bản ghi
[LIST=1]
[*] [B][/b]
Phát hành vào bởi . {%- for record in pagination.items %}
[*] [B]{{ record.title }}[/b]
Đã phát hành vào {{ record.year }} bởi {{ record.artist }}. {%- endfor %} [/LIST] […]
Gợi ý: Bạn không thấy bất kỳ mã Nunjucks nào trong CodePen này, vì 11ty không chạy trên trình duyệt. Tôi vừa sao chép và dán mã HTML đã kết xuất của trang đầu tiên.
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 1](https://codepen.io/smashingmag/pen/abEWRMY) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 1 của Manuel Matuzovic.
Bạn có thể đạt được nhiều thành tựu bằng cách sử dụng Alpine chỉ thị, nhưng tại một số điểm chỉ dựa vào các thuộc tính có thể trở nên lộn xộn. Đó là lý do tại sao tôi quyết định di chuyển dữ liệu và một số logic vào một đối tượng thành phần Alpine riêng biệt.
Đây là cách thức hoạt động: Thay vì truyền dữ liệu trực tiếp, giờ đây chúng ta tham chiếu đến một thành phần bằng cách sử dụng
x-data
. Phần còn lại khá giống nhau: Xác định một biến để chứa dữ liệu của chúng ta, sau đó lấy tệp JSON của chúng ta trong giai đoạn khởi tạo. Tuy nhiên, chúng ta không thực hiện điều đó bên trong một thuộc tính, mà thay vào đó là bên trong thẻ hoặc tệp script
:
Mã:
[…][…] document.addEventListener('alpine:init', () => { Alpine.data('collection', () => ({ records: [], async getRecords() { this.records = await (await fetch('/_data/records.json')).json(); }, init() { this.getRecords(); } })) })
x-ignore
vào 11 mục danh sách của mình và thêm một lớp vào phần tử html
khi dữ liệu đã được tải và sau đó sử dụng lớp và thuộc tính để ẩn các mục danh sách đó trong CSS:
Mã:
.alpine [x-ignore] { display: none; }[…]{%- for record in pagination.items %}
[*] [B]{{ record.title }}[/b]
Đã phát hành vào {{ record.year }} bởi {{ record.artist }}. {%- endfor %}[…] document.addEventListener('alpine:init', () => { Alpine.data('collection', () => ({ records: [], async getRecords() { this.records = await (await fetch('/_data/records.json')).json(); document.documentElement.classList.add('alpine'); }, init() { this.getRecords(); } })) })
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 2](https://codepen.io/smashingmag/pen/eYyWQOe) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 2 của Manuel Matuzovic.
Phân trang
Trước khi thêm bộ lọc, hãy phân trang dữ liệu của chúng ta. 11ty đã giúp chúng ta xử lý tất cả logic, nhưng bây giờ chúng ta phải tự mình thực hiện. Để chia dữ liệu của chúng ta thành nhiều trang, chúng ta cần những thông tin sau:- số mục trên mỗi trang (
itemsPerPage
), - trang hiện tại (
currentPage
), - tổng số trang (
numOfPages
), - một tập hợp con động, được phân trang của toàn bộ dữ liệu (
page
).
Mã:
document.addEventListener('alpine:init', () => { Alpine.data('collection', () => ({ records: [], itemsPerPage: 5, currentPage: 0, numOfPages: // tổng số trang, trang: // các mục được phân trang async getRecords() { this.records = await (await fetch('/_data/records.json')).json(); document.documentElement.classList.add('alpine'); }, init() { this.getRecords(); } }))})
(5)
và trang hiện tại bắt đầu bằng 0
. Chúng ta có được số trang bằng cách chia tổng số mục cho số mục trên mỗi trang:
Mã:
numOfPages() { return Math.ceil(this.records.length / this.itemsPerPage) // 7 / 5 = 1.4 // Math.ceil(7 / 5) = 2},
slice()
trong JavaScript và lấy ra phần của tập dữ liệu mà tôi cần cho trang hiện tại:
Mã:
page() { return this.records.slice(this.currentPage * this.itemsPerPage, (this.currentPage + 1) * this.itemsPerPage) // this.currentPage * this.itemsPerPage, (this.currentPage + 1) * this.itemsPerPage // Trang 1: 0 * 5, (0 + 1) * 5 (=> slice(0, 5);) // Trang 2: 1 * 5, (1 + 1) * 5 (=> slice(5, 10);) // Trang 3: 2 * 5, (2 + 1) * 5 (=> slice(10, 15);)}
for
để lặp qua page
thay vì records
:
Mã:
[LIST=1]
[*] [B][/b]
Đã phát hành vào bởi . [/LIST]
template
và chỉ thị x-for
để hiển thị các liên kết trang của chúng ta:
Mã:
[LIST=1]
[*] {% cho page_entry trong pagination.pages %}
[*] […] {% endfor %}[/LIST]
nhấp
vào mỗi liên kết, ngăn chặn hành vi nhấp mặc định và thay đổi số trang hiện tại khi nhấp:
Mã:
[URL=/][/URL]
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 3](https://codepen.io/smashingmag/pen/GRymwjg) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 3 của Manuel Matuzovic.
Lọc
Tôi muốn có thể lọc danh sách theo nghệ sĩ và theo thập kỷ.Chúng tôi thêm hai phần tử select được bọc trong
fieldset
vào thành phần của mình và đặt lệnh x-model
trên mỗi phần tử. x-model
cho phép chúng tôi liên kết giá trị của phần tử đầu vào với dữ liệu Alpine:
Mã:
Lọc theo Artist All Decade Tất cả
Mã:
document.addEventListener('alpine:init', () => { Alpine.data('collection', () => ({ filters: { year: '', artist: '', }, records: [], itemsPerPage: 5, currentPage: 0, numOfPages() { return Math.ceil(this.records.length / this.itemsPerPage) }, page() { return this.records.slice(this.currentPage * this.itemsPerPage, (this.currentPage + 1) * this.itemsPerPage) }, async getRecords() { this.records = await (await fetch('/_data/records.json')).json(); document.documentElement.classList.add('alpine'); }, init() { this.getRecords(); } }))})
select
, filters.artist
và filters.year
sẽ tự động cập nhật. Bạn có thể thử ở đây với một số dữ liệu giả mà tôi đã thêm thủ công:Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 4](https://codepen.io/smashingmag/pen/GRymwEp) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 4 của Manuel Matuzovic.
Bây giờ chúng ta có
select
phần tử và chúng tôi đã liên kết dữ liệu với thành phần của mình. Bước tiếp theo là điền động từng select
với nghệ sĩ và thập kỷ tương ứng. Đối với điều đó, chúng ta lấy mảng records
và thao tác dữ liệu một chút:
Mã:
document.addEventListener('alpine:init', () => { Alpine.data('collection', () => ({ artists: [], decade: [], // […] async getRecords() { this.records = await (await fetch('/_data/records.json')).json(); this.artists = [...new Set(this.records.map(record => record.artist))].sort(); this.decades = [...new Set(this.records.map(record => record.year.toString().slice(0, -1)))].sort(); document.documentElement.classList.add('alpine'); }, // […] }))})
map()
), đảm bảo rằng mỗi mục là duy nhất (đó là những gì [...new Set()]
thực hiện ở đây) và sắp xếp mảng theo thứ tự bảng chữ cái (sort()
). Đối với mảng thập kỷ, tôi cũng sẽ cắt bỏ chữ số cuối cùng của năm vì tôi không muốn bộ lọc này quá chi tiết. Lọc theo thập kỷ là đủ tốt.Tiếp theo, chúng ta điền thông tin cho các phần tử
select
của nghệ sĩ và thập kỷ, một lần nữa sử dụng phần tử template
và chỉ thị x-for
:
Mã:
Artist All Decade All
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 5](https://codepen.io/smashingmag/pen/OJzmaZb) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 5 của Manuel Matuzovic.
Chúng tôi đã điền thành công các phần tử được chọn bằng dữ liệu từ tệp JSON của mình. Để lọc dữ liệu cuối cùng, chúng ta duyệt qua tất cả các bản ghi, chúng ta kiểm tra xem bộ lọc có được đặt hay không. Nếu đúng như vậy, chúng ta kiểm tra xem trường tương ứng của bản ghi có tương ứng với giá trị đã chọn của bộ lọc hay không. Nếu không, chúng ta lọc bản ghi này ra. Chúng ta còn lại một mảng đã lọc khớp với tiêu chí:
Mã:
get filteredRecords() { const filtered = this.records.filter((item) => { for (var key in this.filters) { if (this.filters[key] === '') { continue } if(!String(item[key]).includes(this.filters[key])) { return false } } return true }); trả về đã lọc}
numOfPages()
và page()
của mình để chỉ sử dụng các bản ghi đã lọc:
Mã:
numOfPages() { return Math.ceil(this.filteredRecords.length / this.itemsPerPage)},page() { return this.filteredRecords.slice(this.currentPage * this.itemsPerPage, (this.currentPage + 1) * this.itemsPerPage)},
Xem Bút Phân trang + Lọc bằng Alpine.js Bước 6 của Manuel Matuzovic.
Còn ba việc phải làm:
- sửa lỗi;
- ẩn biểu mẫu;
- cập nhật thông báo trạng thái.
Sửa lỗi: Xem Thuộc tính Thành phần
Khi bạn mở trang đầu tiên, hãy nhấp vào trang 6, sau đó chọn “1990” — bạn không thấy bất kỳ kết quả nào. Đó là vì bộ lọc của chúng ta nghĩ rằng chúng ta vẫn đang ở trang 6, nhưng 1) chúng ta thực sự đang ở trang 1 và 2) không có trang 6 nào có "1990" đang hoạt động. Chúng ta có thể khắc phục điều đó bằng cách đặt lạicurrentPage
khi người dùng thay đổi một trong các bộ lọc. Để theo dõi các thay đổi trong đối tượng filter
, chúng ta có thể sử dụng cái gọi là phương thức ma thuật:
Mã:
init() { this.getRecords(); this.$watch('filters', filter => this.currentPage = 0);}
filter
thay đổi, currentPage sẽ được đặt thành 0
.Ẩn biểu mẫu
Vì các bộ lọc chỉ hoạt động khi JavaScript được bật và hoạt động, chúng ta nên ẩn toàn bộ biểu mẫu khi không phải trường hợp đó. Chúng ta có thể sử dụng lớp.alpine
mà chúng ta đã tạo trước đó cho mục đích đó:
Mã:
[…]
Mã:
.filters { display: block;}html:not(.alpine) .filters { visibility: hidden;}
visibility: hidden
thay vì hidden
chỉ để tránh nội dung bị dịch chuyển trong khi Alpine vẫn đang tải.Thông báo thay đổi
Thông báo trạng thái ở đầu danh sách của chúng ta vẫn ghi là "Hiển thị 7 bản ghi", nhưng thông báo này không thay đổi khi người dùng thay đổi trang hoặc lọc danh sách. Có hai điều chúng ta phải làm để làm cho đoạn văn trở nên động: liên kết dữ liệu với nó và truyền đạt những thay đổi cho công nghệ hỗ trợ (ví dụ: trình đọc màn hình).Đầu tiên, chúng ta liên kết dữ liệu với phần tử
output
trong đoạn văn thay đổi dựa trên trang hiện tại và bộ lọc:
Mã:
Hiển thị {{ records.length }} records
Mã:
Alpine.data('collection', () => ({ message() { return `${this.filteredRecords.length} records`; },// […]
- Chúng ta có thể biến một phần tử thành cái gọi là vùng trực tiếp bằng cách sử dụng thuộc tính
aria-live
. Vùng trực tiếp là phần tử thông báo nội dung của nó cho trình đọc màn hình mỗi khi nó thay đổi.
Mã:Những thay đổi động sẽ được thông báo
output
(bạn còn nhớ không?) là vùng trực tiếp ngầm định theo mặc định.
Mã:Hiển thị {{ records.length }} records
“Phần tử HTMLlà phần tử chứa mà trang web hoặc ứng dụng có thể đưa kết quả tính toán hoặc kết quả của người dùng vào hành động.”
Nguồn:: Phần tử đầu ra, MDN Web Docs
- Chúng ta có thể làm cho vùng có thể lấy nét được và di chuyển tiêu điểm đến vùng đó khi nội dung của vùng đó thay đổi. Vì vùng được gắn nhãn, tên và vai trò của vùng đó sẽ được thông báo khi điều đó xảy ra.
Mã:x-ref
.
Mã:
- Khi người dùng lọc trang, chúng tôi cập nhật vùng trực tiếp, nhưng chúng tôi không di chuyển tiêu điểm.
- Khi họ thay đổi trang, chúng tôi di chuyển tiêu điểm đến danh sách.
Xem Bút [Phân trang + Bộ lọc với Alpine.js Bước 7](https://codepen.io/smashingmag/pen/zYpwMXX) của Manuel Matuzovic.
Xem Bút Phân trang + Bộ lọc với Alpine.js Bước 7 của Manuel Matuzovic.
Lưu ý: Khi bạn lọc theo nghệ sĩ và thông báo trạng thái hiển thị "1 bản ghi" và bạn lọc lại theo một nghệ sĩ khác, cũng chỉ có một bản ghi, nội dung của phần tử
đầu ra
không thay đổi và không có thông báo nào được báo cáo cho trình đọc màn hình. Điều này có thể được coi là lỗi hoặc là tính năng để giảm thông báo trùng lặp. Bạn sẽ phải thử nghiệm điều này với người dùng.Tiếp theo là gì?
Những gì tôi đã làm ở đây có vẻ trùng lặp, nhưng nếu bạn giống tôi và không đủ tin tưởng vào JavaScript, thì nó đáng để nỗ lực. Và nếu bạn xem CodePen cuối cùng hoặc mã hoàn chỉnh trên GitHub, thực ra không cần quá nhiều công sức. Các khuôn khổ tối giản như Alpine.js giúp dễ dàng cải thiện dần dần các thành phần tĩnh và khiến chúng phản ứng.Tôi khá hài lòng với kết quả, nhưng vẫn còn một vài điều nữa có thể cải thiện:
- Phân trang có thể thông minh hơn (số trang tối đa, liên kết trước và sau, v.v.).
- Cho phép người dùng chọn số mục trên mỗi trang.
- Sắp xếp sẽ là một tính năng hay.
- Làm việc với API lịch sử sẽ rất tuyệt.
- Có thể cải thiện việc chuyển đổi nội dung.
- Giải pháp cần được người dùng thử nghiệm và trình duyệt/trình đọc màn hình thử nghiệm.
x-
tùy chỉnh của nó. Điều đó làm tôi đau lòng như nó làm bạn đau lòng, nhưng miễn là nó không ảnh hưởng đến người dùng, tôi có thể chấp nhận được. P.S.S. Xin gửi lời cảm ơn đặc biệt đến Scott, Søren, Thain, David, Saptak và Christian vì phản hồi của họ.
Các nguồn tài nguyên khác
- “Cách xây dựng danh sách các thứ có thể lọc được”, Søren Birkemeyer
- “Cân nhắc kết quả tìm kiếm động và nội dung”, Scott O’Hara
Đọc thêm
- Chuyển đổi các mục nhập lớp trên cùng và thuộc tính hiển thị trong CSS
- Cách OWASP giúp bạn bảo mật các ứng dụng web toàn bộ ngăn xếp của mình
- Tạo biểu mẫu nhiều bước hiệu quả để có trải nghiệm người dùng tốt hơn
- Tầm quan trọng của sự suy giảm duyên dáng trong thiết kế giao diện có thể truy cập