Skip to content

Remote responses

Petter Eriksson edited this page Dec 6, 2017 · 8 revisions

Other times when we’ve built UI’s that have to communicate with a remote server, each component - usually with the use of a “controller” - would request whatever data they needed. Handling requests this way makes it very easy to do any request and handle any type of response. When using om.next the UI component doesn’t know anything about the remote server. It can only do remote requests by transacting om.next mutations, which is expressed with a data structure. Each mutation in om.next declare whether it should go remote or not (and which remote it should go to), so the remote communication is abstracted away from the UI, making it non-obvious how to handle responses for a specific mutations for a specific UI component.

To get back the power of being able to wait for responses synchronously, we tag each action with an ID and we associate the response from the server with this ID. We've created a namespace for using these messages this which is eponai.client.parser.message.

This fine grained control of mutation responses is usually not needed, as the component's query is describing what it needs from the app-state/database. This level of control is needed when the UI needs to await data (block interactions) from the server until the UI can progress to the state.

.

Usages

We'll show you how we use the eponai.client.parser.message namespace from the UI, as well as how we define messages on the server.

Using messages in the UI

In this example we'll create a UI component that:

  1. Can trigger mutations
  2. Display that the mutation hasn't done a round trip yet.
  3. Display the success or error message.
(ns eponai.web.ui.hello-messages
  (:require
    [om.next :as om :refer [defui]]
    [om.dom :as dom]
    [eponai.client.parser.message :as msg]))

(defui HelloMessages
  static om/IQuery
  (query [this]
    ;; UI components that use messages need to have this key to be 
    ;; re-rendered whenever messages are received.
    [:query/messages])
  Object
  (render [this]
    (dom/div nil
                  ;; When clicking this button, we'll do an om transact! to greet the server.
                  ;; msg/om-transact! transact the query and store the ID for this mutation in the
                  ;; component, so we can look it up later.
      (dom/button {:onClick #(msg/om-transact! this `[(server/greet {:client-message "Hello server"})])}
                  "Send greeting to server!")
            ;; Here we're getting the latest response we've gotten from the server for a specific action
            ;; in this case 'server/greet
      (let [response (msg/last-message this 'server/greet)]
              ;; If we've never done an action, there's no response. (Responses can also be cleared if needed).
        (when (some? response)
          (dom/div nil
                          ;; We can check if the response has been sent but not yet received.
            (dom/span nil (if (msg/pending? response) 
                            "Awaiting response..."
                            "Server responded with: "))
                          ;; If we've received the response (it is finalized), we can check if it
                          ;; was a success or if an error occurred.
            (dom/span nil (if (msg/final? response)
                            (if (msg/success? response)
                              ;; We get the message from the response by calling the message function.
                              ;; We accept any responses that can be sent via cognitect's transit library.
                              ;; Which that we can return arbitrary data in our responses which is really nice.
                              (dom/span nil (str (msg/message response)))
                              (dom/span nil (str "Error: " (msg/message response))))))))))))

Defining messages from the server