10We've done purely functional web pages as hook files, and we've
11done command line apps, so now let's interact with an app over
12the web. The fundamental concepts are no different than from the
13command line -- you send "poke" messages to apps and subscribe to
14data streams. The only difference is that you do it with
17Let's create a small web app that counts the number of times you
18poke it. There'll be a button to poke the app, and line of text
19below it saying how many times the app has been poked. We can
20update this by subscribing our page to the relevant path on its app.
22Let's first write the app, `/app/click.hoon`:
28++ move {bone $diff $mark *}
35 :_ +>.$(clicks +(clicks))
36 %+ turn (prey /the-path hid)
37 |=({o/bone *} [o %diff %clicks +(clicks)])
40 [[[ost.hid %diff %clicks clicks] ~] +>.$]
44There's nothing really new here, except that we use a couple of
45new marks, `click` and `clicks`. When we get poked with a
46`click`, we increment the variable `clicks` in our state and tell
47all our subcribers the new value. When someone subscribes to us
48on the path `/the-path`, we immediately give them the current
51Let's take a look at the new marks. Here's `/mar/click.hoon`:
60 ?> =('click' (need (so:jo jon)))
66The mark `click` has hoon type `%click`, which means the only
67valid value is `%click`. We can convert from `noun` by just
70We also convert from json by parsing a json string with `so:jo`,
71asserting the parsing succeeded with `need`, and asserting the
72result was 'click' with `?>`, which asserts that its first child
75<blockquote class="blockquote">
76Note the argument to `++json`is `jon/^json`. Why `^json`?
77`++json` shadows the type definition, so if we want to refer to
78the type, we have to prepend a `^`. This extends to multiple
79levels: `^^^foo` means the fourth most innermost instance of `foo`.
81`++jo` in `zuse` is a useful library for parsing complex json
82into hoon structures. In this case, the `:` between `so` and
83`jo` means 'inside of', because `so` is an arm contained within
84the core `jo`. Our case is actually simple enough that the
85`?>` line could have been `?> =([%s 'click'] jon)`.
88We can test this mark from the command line (don't forget to start your app with `|start %click`)
91~fintud-macrep:dojo> &click %click
93~fintud-macrep:dojo> &click &json [%s 'click']
95~fintud-macrep:dojo> &click &json &mime [/text/json (taco '"click"')]
97~fintud-macrep:dojo> &click &json &mime [/text/json (taco '"clickety"')]
98/~fintud-macrep/home/0/mar/click:<[7 5].[8 11]>
99ford: casting %json to %click
103And `/mar/clicks.hoon`:
110 (joba %clicks (jone clicks))
115`clicks` is just an atom. We convert to json by creating an
116object with a single field "clicks" with our value.
118<blockquote class="blockquote">
119Be sure to checkout section 3bD, JSON and XML, in zuse.hoon
120`++joba` is just a function that takes a key-value pair and
121produces a JSON object with one element.
125~fintud-macrep:dojo> &json &clicks 6
126[%o p={[p='clicks' q=[%n p=~.6]]}]
127~fintud-macrep:dojo> &mime &json &clicks 6
128[[%text %json ~] p=12 q='{"clicks":6}']
131Now that we know the marks involved, take another look at the app
132above. Everything should be pretty straightforward. Let's poke
133the app from the command line.
136~fintud-macrep:dojo> |start %click
138~fintud-macrep:dojo> :click &click %click
141~fintud-macrep:dojo> :click &click %click
148- Modify `:sink` from the subcriptions chapter to listen to
149 `:click` and print out the subscription updates on the command
154That's all that's needed for the back end. The front end is just
155some "sail" html (Hoon markup for XML) and javascript. Here's `/web/click.hoon`:
160 ;script(type "text/javascript", src "//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js");
161 ;script(type "text/javascript", src "/~/at/lib/js/urb.js");
166 ;input#go(type "button", value "Poke!");
167 ;div#err(class "disabled");
170 ;script(type "text/javascript", src "/click/main.js");
175You should recognize the sail syntax from an earlier chapter.
176Aside from jquery, we also include `urb.js`, which is a framework
177for interacting with urbit.
179To 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`.
181We have a button labeled "Poke!" and a div with id `clicks` where
182we'll put the number of clicks. We also include a small
183javascript file where the client-side application logic can be
184found. It's in `/web/click/main.js`:
188 var clicks, $go, $clicks, $err
191 $clicks = $('#clicks')
194 window.urb.appl = "click"
199 "click", {mark: "click"}
202 return $err.text("There was an error. Sorry!")
203 if(res.data !== undefined &&
204 res.data.ok !== undefined &&
205 res.data.ok !== true)
206 $err.text(res.data.res)
208 console.log("poke succeeded");
212 window.urb.bind('/the-path',
214 clicks = dat.data.clicks
221We set up two event handlers. When we click the button, we run
222`window.urb.send`, which sends a poke to the app specified as the first argument, in this case our :click app. The arguments
223are data, parameters, and the callback. The data is the specific
224data we want to send. The parameters are all optional, but here's a list
225of the available ones:
227- `ship`: target urbit. Defaults to `window.urb.ship`, which
228 defaults to the urbit which served the page.
229- `appl`: target app. Defaults to `window.urb.appl`, which
231- `mark`: mark of the data. Defaults to `window.urb.send.mark`,
232 which defaults to "json".
233- `wire`: wire of the poke. Defaults to `/`.
235In our case, we specify only that the mark is "click".
237The callback funciton is called when we receive an
238acknowledgment. If there was an error, we put it in the `err`
239div that we defined above. Otherwise, we printf to the console a
240message saying the poke succeeded. As is common, we gray
241out the button when it is clicked. It is only reenabled when positive acknowledgement is received.
243Our 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`:
245- `ship`: target urbit. Defaults to `window.urb.ship`, which
246 defaults to the urbit which served the page.
247- `appl`: target app. Defaults to `window.urb.appl`, which
249- `mark`: mark of expected data. Data of other marks is
250 converted to this mark on the server before coming to the web
251 front end. Defaults to `window.urb.bind.mark`, which defaults
253- `wire`: wire of the subscription. Defaults to the given path.
255The callback function here just updates the data in the `clicks`
256div. A truly robust app would intelligently handle errors, of
259Note that the app doesn't have anything web-specific in itself.
260As far as it knows, it's just receiving pokes and subscriptions.
261The javascript is fairly pure as well, sending and receiving json
262everywhere. The marks are the translation layer, and they're the
263only things that need to know how the hoon types map to json.
267- Open the app in multiple tabs, click the button, and verify
268 that all the tabs stay in sink. Poke it manually from the
269 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