[Declarative Programming + Elm] Bài 17 – JavaScript Interop

Như đã nói trước đó thì chúng ta sẽ không thể trực tiếp định nghĩa một hàm thay đổi nội dung của phần tử <head> bởi vì các trình đóng gói của Elm đều không cung cấp giao diện lập trình hỗ trợ. Tuy nhiên, khi người dùng nhấn vào một liên kết bất kỳ

Như đã nói trước đó thì chúng ta sẽ không thể trực tiếp định nghĩa một hàm thay đổi nội dung của phần tử <head> bởi vì các trình đóng gói của Elm đều không cung cấp giao diện lập trình hỗ trợ. Tuy nhiên, khi người dùng nhấn vào một liên kết bất kỳ trong trang web và nội dung đã được thay đổi để đáp ứng lại yêu cầu đó thì chúng ta cũng cần phải quan tâm tới một vài yếu tố mô tả quan trọng khác.

Ví dụ điển hình là tên của trang đơn được gắn ở thẻ <title>, hoặc xa hơn thì sẽ là các thẻ <meta> mô tả nội dung của trang đơn mà người dùng vừa chuyển tới để chương trình tự động duyệt web của các dịch vụ tìm kiếm Google, Bing, v.v… có thể dễ dàng xếp loại được nội dung của mỗi trang đơn có mặt trong tập dữ liệu.

Và giải pháp ở đây là chúng ta sẽ cần một phương thức để tương tác 2 chiều với code JavaScript bên ngoài. Tức là ở thời điểm code Elm cần thực hiện một tác vụ như trên thì chúng ta có thể gửi yêu cầu ủy thác cho code JavaScript hoặc gọi một hàm được định nghĩa bằng code JavaScript.

Elm Ports & WebSocket

Elm có hỗ trợ một phương thức để gửi/nhận yêu cầu với code JavaScript qua các cổng port được khai báo như code ví dụ dưới đây. Chúng ta sẽ tạo ra một cổng thông tin gửi/nhận với code JavaScript được tạo ra bởi từ khóa port ở dòng đầu tiên khai báo module Main và ở định nghĩa của các hàm sendMessagemessageReceiver ngay sau phần import.

portmoduleMainexposing(..)import Browserimport Html exposing(..)import Html.Attributes exposing(..)import Html.Events exposing(..)import Json.Decode as D-- PORTS - - - - - - - - -portsendMessage:String->CmdmsgportmessageReceiver:(String->msg)->Submsgmain:Program()ModelMsgmain=Browser.element{init=init,view=view,update=update,subscriptions=subscriptions}-- INIT - - - - - - - - -typealiasModel={draft:String,messages:ListString}init:()->(Model,CmdMsg)initflags=({draft="",messages=[]},Cmd.none)-- VIEW - - - - - - - - -view:Model->HtmlMsgviewmodel=div[][h1[][text"Echo Chat"],ul[](List.map(msg->li[][textmsg])model.messages),input[type_"text",placeholder"Draft",onInputDraftChanged,on"keydown"(ifIsEnterSend),valuemodel.draft][],button[onClickSend][text"Send"]]ifIsEnter:msg->D.DecodermsgifIsEntermsg=letgetKey=D.field"key"D.stringdecodeKey=(key->ifkey=="Enter"thenD.succeedmsgelseD.fail"some other key")ingetKey|>D.andThendecodeKeytypeMsg=DraftChangedString|Send|RecvString-- UPDATE - - - - - - - - -update:Msg->Model->(Model,CmdMsg)updatemsgmodel=casemsgofDraftChangeddraft->({model|draft=draft},Cmd.none)Send->({model|draft=""},sendMessagemodel.draft)Recvmessage->({model|messages=model.messages++[message]},Cmd.none)subscriptions:Model->SubMsgsubscriptions _ =messageReceiverRecv

Như vậy là code ở view chúng ta có khi người dùng nhấn phím Enter hoặc nhấn vào nút gửi <form> thì sẽ có một thông báo Send được gửi tới trình update. Lúc này update sẽ sử dụng hàm sendMessage để gửi dữ liệu và yêu cầu xử lý tới code JavaScript bên ngoài.

Sau đó chúng ta có các subscriptions được tạo ra để theo dõi sự kiện phản hồi từ code JavaScript, và khi nhận thấy sự kiện thì sẽ có một tin nhắn Recv kèm theo kết quả xử lý từ JavaScript được gửi tới trình update. Lúc này có thể một bản ghi model mới sẽ được tạo ra và giao diện của trang web sẽ được thay đổi để đáp ứng với thao tác gửi <form>.

Ở phía của JavaScript, chúng ta cần sử dụng WebSocket để tạo một mini server trong môi trường trình duyệt web và định nghĩa một kênh gửi/nhận tương tác. Công cụ này được các trình duyệt web hỗ trợ bắt đầu từ Internet Explorer 10.

<!doctypeHTML><html><head><metacharset="UTF-8"/><title> Elm + WebSocket </title><scripttype="text/javascript"src="elm.js"></script></head><body><divid="elm-spa"></div></body><scripttype="text/javascript">// -- Create your WebSocketvar socket =newWebSocket('wss://echo.websocket.org');// -- Init Elm SPAvar app = Elm.Main.init({
   node: document.getElementById('elm-spa')});// -- Create channel "message" on socket

socket.addEventListener("message",function(event){
    app.ports.messageReceiver.send(event.data);});// -- Send request message from Elm SPA

app.ports.sendMessage.subscribe(function(message){
    socket.send(message);});</script></html>

Custom Elements

Một cách thức khác để thực hiện tương tác gửi/nhận 2 chiều với code JavaScript đó là chúng ta có thể tự định nghĩa một class mô tả phần tử HTML với giao diện lập trình cần sử dụng, và sau đó tạo ra HTML node bằng code Elm để sử dụng giao diện lập trình vừa định nghĩa.

Ví dụ HTML5 cho phép chúng ta sử dụng các thẻ với tên tự đặt và có thể là <formatted-date>. Tuy nhiên chúng ta cũng muốn rằng phần tử được tạo ra bởi thẻ này có một vài chức năng đặc biệt và quyết định tự định nghĩa một class kế thừa của HTMLElement. Và ở đây chúng ta có code ví dụ về một hàm định dạng thông tin localizeDate được viết trong JavaScript, sau đó được sử dụng trong giao diện lập trình của class tự định nghĩa kế thừa HTMLElement.

<script>// -- Extension for ElmfunctionlocalizeDate(lang, year, month){const dateTimeFormat =newIntl.DateTimeFormat(lang,{
      weekday:'long',
      year:'numeric',
      month:'long',
      day:'numeric'});return dateTimeFormat.format(newDate(year, month));}// -- Define custom HTMLElement class

customElements.define('formatted-date',classextends HTMLElement {// -- things required by Custom Elementsconstructor(){super();}connectedCallback(){this.setTextContent();}attributeChangedCallback(){this.setTextContent();}staticgetobservedAttributes(){return['lang','year','month'];}setTextContent(){const lang =this.getAttribute('lang');const year =this.getAttribute('year');const month =this.getAttribute('month');this.textContent =localizeDate(lang, year, month);}}// class);// -- Init Elm SPA ...</script>

Và trong code của Elm thì chúng ta có thể sử dụng hàm khởi tạo node để tạo phần tử HTML với tên tự định nghĩa ở trên và như vậy phần tử này sẽ có tính năng hoạt động đặc biệt như mong muốn.

moduleCustomexposing(..)import Html exposing(Html,node)import Html.Attributes(attribute)viewDate:String->Int->Int->HtmlmsgviewDatelangyearmonth=node"formatted-date"[attribute"lang"lang,attribute"year"(String.fromIntyear),attribute"month"(String.fromIntmonth)][]

Như vậy là chúng ta đã có đầy đủ các công cụ để bắt đầu xây dựng một SPA với Elm.

(chưa đăng tải) [Declarative Programming + Elm] Bài 18 – SPA Wikipedia Elm

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ