on 8564483 help sign in
urbit.org/docs/arvo/web-apps.md
269 lines 8.6 kB history raw
1---
2navhome: /docs
3next: true
4sort: 7
5title: Web Apps
6---
8# Backend
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
15javascript.
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`:
24```
25/? 314
26!:
27|%
28++ move {bone $diff $mark *}
29--
30!:
31|_ {hid/bowl clicks/@}
32++ poke-click
33 |= click/$click
34 ~& [%poked +(clicks)]
35 :_ +>.$(clicks +(clicks))
36 %+ turn (prey /the-path hid)
37 |=({o/bone *} [o %diff %clicks +(clicks)])
38++ peer-the-path
39 |= pax/path
40 [[[ost.hid %diff %clicks clicks] ~] +>.$]
41--
42```
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
49number of clicks.
51Let's take a look at the new marks. Here's `/mar/click.hoon`:
53```
54|_ click/$click
55++ grab
56 |%
57 ++ noun |=(* %click)
58 ++ json
59 |= jon/^json
60 ?> =('click' (need (so:jo jon)))
61 %click
62 --
63--
64```
66The mark `click` has hoon type `%click`, which means the only
67valid value is `%click`. We can convert from `noun` by just
68producing `%click`.
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
73is true.
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)`.
86</blockquote>
88We can test this mark from the command line (don't forget to start your app with `|start %click`)
90```
91~fintud-macrep:dojo> &click %click
92%click
93~fintud-macrep:dojo> &click &json [%s 'click']
94%click
95~fintud-macrep:dojo> &click &json &mime [/text/json (taco '"click"')]
96%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
100ford: cast %click
101```
103And `/mar/clicks.hoon`:
105```
106|_ clicks/@
107++ grow
108 |%
109 ++ json
110 (joba %clicks (jone clicks))
111 --
112--
113```
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.
122</blockquote>
124```
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}']
129```
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.
135```
136~fintud-macrep:dojo> |start %click
137>=
138~fintud-macrep:dojo> :click &click %click
139[%poked 1]
140>=
141~fintud-macrep:dojo> :click &click %click
142[%poked 2]
143>=
144```
146**Exercise**:
148- Modify `:sink` from the subcriptions chapter to listen to
149 `:click` and print out the subscription updates on the command
150 line.
152#Frontend
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`:
157```
158;html
159 ;head
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");
162 ;title: Clickety!
163 ==
164 ;body
165 ;div#cont
166 ;input#go(type "button", value "Poke!");
167 ;div#err(class "disabled");
168 ;div#clicks;
169 ==
170 ;script(type "text/javascript", src "/click/main.js");
171 ==
172==
173```
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`:
186```
187$(function() {
188 var clicks, $go, $clicks, $err
190 $go = $('#go')
191 $clicks = $('#clicks')
192 $err = $('#err')
194 window.urb.appl = "click"
196 $go.on("click",
197 function() {
198 window.urb.send(
199 "click", {mark: "click"}
200 ,function(err,res) {
201 if(err)
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)
207 else
208 console.log("poke succeeded");
209 })
210 })
212 window.urb.bind('/the-path',
213 function(err,dat) {
214 clicks = dat.data.clicks
215 $clicks.text(clicks)
216 }
217 )
218})
219```
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
230 defaults to null.
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
248 defaults to null.
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
252 to "json".
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
257course.
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.
265**Exercise**:
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.