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> Note the argument to `++json`is `jon/^json`. Why `^json`?
75> `++json` shadows the type definition, so if we want to refer to
76> the type, we have to prepend a `^`. This extends to multiple
77> levels: `^^^foo` means the fourth most innermost instance of `foo`.
79> `++jo` in `zuse` is a useful library for parsing complex json
80> into hoon structures. In this case, the `:` between `so` and
81> `jo` means 'inside of', because `so` is an arm contained within
82> the core `jo`. Our case is actually simple enough that the
83> `?>` line could have been `?> =([%s 'click'] jon)`.
85We can test this mark from the command line (don't forget to start your app with `|start %click`)
88~fintud-macrep:dojo> &click %click
90~fintud-macrep:dojo> &click &json [%s 'click']
92~fintud-macrep:dojo> &click &json &mime [/text/json (taco '"click"')]
94~fintud-macrep:dojo> &click &json &mime [/text/json (taco '"clickety"')]
95/~fintud-macrep/home/0/mar/click:<[7 5].[8 11]>
96ford: casting %json to %click
100And `/mar/clicks.hoon`:
107 (joba %clicks (jone clicks))
112`clicks` is just an atom. We convert to json by creating an
113object with a single field "clicks" with our value.
115> Be sure to checkout section 3bD, JSON and XML, in zuse.hoon
116> `++joba` is just a function that takes a key-value pair and
117> produces a JSON object with one element.
120~fintud-macrep:dojo> &json &clicks 6
121[%o p={[p='clicks' q=[%n p=~.6]]}]
122~fintud-macrep:dojo> &mime &json &clicks 6
123[[%text %json ~] p=12 q='{"clicks":6}']
126Now that we know the marks involved, take another look at the app
127above. Everything should be pretty straightforward. Let's poke
128the app from the command line.
131~fintud-macrep:dojo> |start %click
133~fintud-macrep:dojo> :click &click %click
136~fintud-macrep:dojo> :click &click %click
143- Modify `:sink` from the subcriptions chapter to listen to
144 `:click` and print out the subscription updates on the command
149That's all that's needed for the back end. The front end is just
150some "sail" html (Hoon markup for XML) and javascript. Here's `/web/click.hoon`:
155 ;script(type "text/javascript", src "//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js");
156 ;script(type "text/javascript", src "/~/at/lib/js/urb.js");
161 ;input#go(type "button", value "Poke!");
162 ;div#err(class "disabled");
165 ;script(type "text/javascript", src "/click/main.js");
170You should recognize the sail syntax from an earlier chapter.
171Aside from jquery, we also include `urb.js`, which is a framework
172for interacting with urbit.
174To 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`.
176We have a button labeled "Poke!" and a div with id `clicks` where
177we'll put the number of clicks. We also include a small
178javascript file where the client-side application logic can be
179found. It's in `/web/click/main.js`:
183 var clicks, $go, $clicks, $err
186 $clicks = $('#clicks')
189 window.urb.appl = "click"
194 "click", {mark: "click"}
197 return $err.text("There was an error. Sorry!")
198 if(res.data !== undefined &&
199 res.data.ok !== undefined &&
200 res.data.ok !== true)
201 $err.text(res.data.res)
203 console.log("poke succeeded");
207 window.urb.bind('/the-path',
209 clicks = dat.data.clicks
216We set up two event handlers. When we click the button, we run
217`window.urb.send`, which sends a poke to the app specified as the first argument, in this case our :click app. The arguments
218are data, parameters, and the callback. The data is the specific
219data we want to send. The parameters are all optional, but here's a list
220of the available ones:
222- `ship`: target urbit. Defaults to `window.urb.ship`, which
223 defaults to the urbit which served the page.
224- `appl`: target app. Defaults to `window.urb.appl`, which
226- `mark`: mark of the data. Defaults to `window.urb.send.mark`,
227 which defaults to "json".
228- `wire`: wire of the poke. Defaults to `/`.
230In our case, we specify only that the mark is "click".
232The callback funciton is called when we receive an
233acknowledgment. If there was an error, we put it in the `err`
234div that we defined above. Otherwise, we printf to the console a
235message saying the poke succeeded. As is common, we gray
236out the button when it is clicked. It is only reenabled when positive acknowledgement is received.
238Our 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`:
240- `ship`: target urbit. Defaults to `window.urb.ship`, which
241 defaults to the urbit which served the page.
242- `appl`: target app. Defaults to `window.urb.appl`, which
244- `mark`: mark of expected data. Data of other marks is
245 converted to this mark on the server before coming to the web
246 front end. Defaults to `window.urb.bind.mark`, which defaults
248- `wire`: wire of the subscription. Defaults to the given path.
250The callback function here just updates the data in the `clicks`
251div. A truly robust app would intelligently handle errors, of
254Note that the app doesn't have anything web-specific in itself.
255As far as it knows, it's just receiving pokes and subscriptions.
256The javascript is fairly pure as well, sending and receiving json
257everywhere. The marks are the translation layer, and they're the
258only things that need to know how the hoon types map to json.
262- Open the app in multiple tabs, click the button, and verify
263 that all the tabs stay in sink. Poke it manually from the
264 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