- vừa được xem lúc

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

0 0 10

Người đăng: Semi Dev

Theo Viblo Asia

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.

port module Main exposing (..) import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Json.Decode as D -- PORTS - - - - - - - - - port sendMessage : String -> Cmd msg
port messageReceiver : (String -> msg) -> Sub msg main : Program () Model Msg
main = Browser.element { init = init , view = view , update = update , subscriptions = subscriptions } -- INIT - - - - - - - - - type alias Model = { draft : String , messages : List String } init : () -> ( Model, Cmd Msg )
init flags = ( { draft = "", messages = [] } , Cmd.none ) -- VIEW - - - - - - - - - view : Model -> Html Msg
view model = div [] [ h1 [] [ text "Echo Chat" ] , ul [] (List.map (\msg -> li [] [ text msg ]) model.messages) , input [ type_ "text" , placeholder "Draft" , onInput DraftChanged , on "keydown" (ifIsEnter Send) , value model.draft ] [] , button [ onClick Send ] [ text "Send" ] ] ifIsEnter : msg -> D.Decoder msg
ifIsEnter msg = let getKey = D.field "key" D.string decodeKey = (\key -> if key == "Enter" then D.succeed msg else D.fail "some other key") in getKey |> D.andThen decodeKey type Msg = DraftChanged String | Send | Recv String -- UPDATE - - - - - - - - - update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = case msg of DraftChanged draft -> ( { model | draft = draft } , Cmd.none ) Send -> ( { model | draft = "" } , sendMessage model.draft ) Recv message -> ( { model | messages = model.messages ++ [message] } , Cmd.none ) subscriptions : Model -> Sub Msg
subscriptions _ = messageReceiver Recv

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.

<!doctype HTML>
<html> <head> <meta charset="UTF-8" /> <title> Elm + WebSocket </title> <script type="text/javascript" src="elm.js"> </script>
</head> <body> <div id="elm-spa"> </div>
</body> <script type="text/javascript"> // -- Create your WebSocket var socket = new WebSocket('wss://echo.websocket.org'); // -- Init Elm SPA var 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 Elm function localizeDate (lang, year, month) { const dateTimeFormat = new Intl.DateTimeFormat (lang, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); return dateTimeFormat.format (new Date (year, month));
} // -- Define custom HTMLElement class customElements.define ('formatted-date', class extends HTMLElement { // -- things required by Custom Elements constructor ( ) { super ( ); } connectedCallback ( ) { this.setTextContent ( ); } attributeChangedCallback ( ) { this.setTextContent ( ); } static get observedAttributes ( ) { 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.

module Custom exposing (..) import Html exposing (Html, node)
import Html.Attributes (attribute) viewDate : String -> Int -> Int -> Html msg
viewDate lang year month = node "intl-date" [ attribute "lang" lang , attribute "year" (String.fromInt year) , attribute "month" (String.fromInt month) ] []

Bình luận

Bài viết tương tự

- vừa được xem lúc

Closure trong Javascript - Phần 2: Định nghĩa và cách dùng

Các bạn có thể đọc qua phần 1 ở đây. Để mọi người không quên, mình xin tóm tắt gọn lại khái niệm lexical environment:.

0 0 51

- vừa được xem lúc

Var vs let vs const? Các cách khai báo biến và hằng trong Javascript

Dạo này mình tập tành học Javascript, thấy có 2 cách khai báo biến khác nhau nên đã tìm tòi sự khác biệt. Nay xin đăng lên đây để mọi người đọc xong hy vọng phân biệt được giữa let và var, và sau đó là khai báo hằng bằng const.

0 0 31

- vừa được xem lúc

VueJS: Tính năng Mixins

Chào mọi người, hôm nay mình sẽ viết về Mixins và 1 số vấn đề trong sử dụng Mixins hay ho mà mình gặp trong dự án thực. Trích dẫn từ trang chủ của VueJS:.

0 0 27

- vừa được xem lúc

Asset Pipeline là cái chi chi?

Asset Pipeline. Asset pipeline là cái chi chi. . Giải thích:.

0 0 47

- vừa được xem lúc

Tạo data table web app lấy dữ liệu từ Google Sheets sử dụng Apps Script

Google Sheets là công cụ tuyệt vời để lưu trữ bảng tính trực tuyến, bạn có thể truy cập bảng tính bất kỳ lúc nào ở bất kỳ đâu và luôn sẵn sàng để chia sẻ với người khác. Bài này gồm 2 phần.

0 0 266

- vừa được xem lúc

Học Deep Learning trên Coursera miễn phí

Bạn muốn bắt đầu với Deep Learning nhưng không biết bắt đầu từ đâu? Bạn muốn có một công việc ở mức fresher về Deep Learning? Bạn muốn khoe bạn bè về kiến thức Deep Learning của mình. Bắt đầu từ đâu.

0 0 35