9We've done purely functional web pages as hook files, and we've
10done command line apps, so now let's interact with an app over
11the web. The fundamental concepts are no different than from the
12command line -- you send "poke" messages to apps and subscribe to
13data streams. The only difference is that you do it with
16Let's create a small web app that counts the number of times you
17poke it. There'll be a button to poke the app, and line of text
18below it saying how many times the app has been poked. We can
19update this by subscribing our page to the relevant path on its app.
21Let's first write the app, `/app/click.hoon`:
27++ move {bone $diff $mark *}
34 :_ +>.$(clicks +(clicks))
35 %+ turn (prey /the-path hid)
36 |=({o/bone *} [o %diff %clicks +(clicks)])
39 [[[ost.hid %diff %clicks clicks] ~] +>.$]
43There's nothing really new here, except that we use a couple of
44new marks, `click` and `clicks`. When we get poked with a
45`click`, we increment the variable `clicks` in our state and tell
46all our subcribers the new value. When someone subscribes to us
47on the path `/the-path`, we immediately give them the current
50Let's take a look at the new marks. Here's `/mar/click.hoon`:
59 ?> =('click' (need (so:jo jon)))
65The mark `click` has hoon type `%click`, which means the only
66valid value is `%click`. We can convert from `noun` by just
69We also convert from json by parsing a json string with `so:jo`,
70asserting the parsing succeeded with `need`, and asserting the
71result was 'click' with `?>`, which asserts that its first child
74<blockquote class="blockquote">
75Note the argument to `++json`is `jon/^json`. Why `^json`?
76`++json` shadows the type definition, so if we want to refer to
77the type, we have to prepend a `^`. This extends to multiple
78levels: `^^^foo` means the fourth most innermost instance of `foo`.
80`++jo` in `zuse` is a useful library for parsing complex json
81into hoon structures. In this case, the `:` between `so` and
82`jo` means 'inside of', because `so` is an arm contained within
83the core `jo`. Our case is actually simple enough that the
84`?>` line could have been `?> =([%s 'click'] jon)`.
87We can test this mark from the command line (don't forget to start your app with `|start %click`)
90~fintud-macrep:dojo> &click %click
92~fintud-macrep:dojo> &click &json [%s 'click']
94~fintud-macrep:dojo> &click &json &mime [/text/json (taco '"click"')]
96~fintud-macrep:dojo> &click &json &mime [/text/json (taco '"clickety"')]
97/~fintud-macrep/home/0/mar/click:<[7 5].[8 11]>
98ford: casting %json to %click
102And `/mar/clicks.hoon`:
109 (joba %clicks (jone clicks))
114`clicks` is just an atom. We convert to json by creating an
115object with a single field "clicks" with our value.
117<blockquote class="blockquote">
118Be sure to checkout section 3bD, JSON and XML, in zuse.hoon
119`++joba` is just a function that takes a key-value pair and
120produces a JSON object with one element.
124~fintud-macrep:dojo> &json &clicks 6
125[%o p={[p='clicks' q=[%n p=~.6]]}]
126~fintud-macrep:dojo> &mime &json &clicks 6
127[[%text %json ~] p=12 q='{"clicks":6}']
130Now that we know the marks involved, take another look at the app
131above. Everything should be pretty straightforward. Let's poke
132the app from the command line.
135~fintud-macrep:dojo> |start %click
137~fintud-macrep:dojo> :click &click %click
140~fintud-macrep:dojo> :click &click %click
147- Modify `:sink` from the subcriptions chapter to listen to
148 `:click` and print out the subscription updates on the command
153That's all that's needed for the back end. The front end is just
154some "sail" html (Hoon markup for XML) and javascript. Here's `/web/click.hoon`:
159 ;script(type "text/javascript", src "//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js");
160 ;script(type "text/javascript", src "/~/at/lib/js/urb.js");
165 ;input#go(type "button", value "Poke!");
166 ;div#err(class "disabled");
169 ;script(type "text/javascript", src "/click/main.js");
174You should recognize the sail syntax from an earlier chapter.
175Aside from jquery, we also include `urb.js`, which is a framework
176for interacting with urbit.
178To view the frontend, point your browser at `ship-name.urbit.org/~~/click` if you're on the live network. If you're running a fake galaxy, navigate to whatever port it's running on, which is usually: `https://localhost:8443/~~/click`.
180We have a button labeled "Poke!" and a div with id `clicks` where
181we'll put the number of clicks. We also include a small
182javascript file where the client-side application logic can be
183found. It's in `/web/click/main.js`:
187 var clicks, $go, $clicks, $err
190 $clicks = $('#clicks')
193 window.urb.appl = "click"
198 "click", {mark: "click"}
201 return $err.text("There was an error. Sorry!")
202 if(res.data !== undefined &&
203 res.data.ok !== undefined &&
204 res.data.ok !== true)
205 $err.text(res.data.res)
207 console.log("poke succeeded");
211 window.urb.bind('/the-path',
213 clicks = dat.data.clicks
220We set up two event handlers. When we click the button, we run
221`window.urb.send`, which sends a poke to the app specified as the first argument, in this case our :click app. The arguments
222are data, parameters, and the callback. The data is the specific
223data we want to send. The parameters are all optional, but here's a list
224of the available ones:
226- `ship`: target urbit. Defaults to `window.urb.ship`, which
227 defaults to the urbit which served the page.
228- `appl`: target app. Defaults to `window.urb.appl`, which
230- `mark`: mark of the data. Defaults to `window.urb.send.mark`,
231 which defaults to "json".
232- `wire`: wire of the poke. Defaults to `/`.
234In our case, we specify only that the mark is "click".
236The callback funciton is called when we receive an
237acknowledgment. If there was an error, we put it in the `err`
238div that we defined above. Otherwise, we printf to the console a
239message saying the poke succeeded. As is common, we gray
240out the button when it is clicked. It is only reenabled when positive acknowledgement is received.
242Our second event handler is `window.urb.bind`, which is called immediately when the page is loaded, subscribing the page to the specified data stream on the app set with `urb.appl` (which is, in this case, :click). It also takes three arguments: path, parameters, and callback. The path is the path on the app to subscribe to. The parameters are all optional (indeed, we omit them entirely here), but are similar to those for `send`:
244- `ship`: target urbit. Defaults to `window.urb.ship`, which
245 defaults to the urbit which served the page.
246- `appl`: target app. Defaults to `window.urb.appl`, which
248- `mark`: mark of expected data. Data of other marks is
249 converted to this mark on the server before coming to the web
250 front end. Defaults to `window.urb.bind.mark`, which defaults
252- `wire`: wire of the subscription. Defaults to the given path.
254The callback function here just updates the data in the `clicks`
255div. A truly robust app would intelligently handle errors, of
258Note that the app doesn't have anything web-specific in itself.
259As far as it knows, it's just receiving pokes and subscriptions.
260The javascript is fairly pure as well, sending and receiving json
261everywhere. The marks are the translation layer, and they're the
262only things that need to know how the hoon types map to json.
266- Open the app in multiple tabs, click the button, and verify
267 that all the tabs stay in sink. Poke it manually from the
268 command line and verify the tabs are updated as well.
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list