on 6d45ede help sign in
urbit.org/docs/arvo/web-apps.md
265 lines 8.9 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 checkout the app `/app/examples/click.hoon`:
24```
25/? 314
26!:
27|%
28++ move {bone $diff $mark *}
29--
30!:
31|_ {hid/bowl clicks/@}
32++ poke-examples-click-click
33 |= click/$click
34 ~& [%poked +(clicks)]
35 :_ +>.$(clicks +(clicks))
36 %+ turn (prey /the-path hid)
37 |=({o/bone *} [o %diff %examples-click-clicks +(clicks)])
38++ peer-the-path
39 |= pax/path
40 [[[ost.hid %diff %examples-click-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/examples/click/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 `examples-click-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> Note the argument to `++json`is `jon/^json`. Why `^json`?
76> `++json` shadows the type definition, so if we want to refer to
77> the type, we have to prepend a `^`. This extends to multiple
78> levels: `^^^foo` means the fourth most innermost instance of `foo`.
80> `++jo` in `zuse` is a useful library for parsing complex json
81> into hoon structures. In this case, the `:` between `so` and
82> `jo` means 'inside of', because `so` is an arm contained within
83> the core `jo`. Our case is actually simple enough that the
84> `?>` line could have been `?> =([%s 'click'] jon)`.
86We can test this mark from the command line (don't forget to start your app with `|start %examples-click`)
88```
89~fintud-macrep:dojo> &examples-click-click %click
90%click
91~fintud-macrep:dojo> &examples-click-click &json [%s 'click']
92%click
93~fintud-macrep:dojo> &examples-click-click &json &mime [/text/json (taco '"click"')]
94%click
95~fintud-macrep:dojo> &examples-click-click &json &mime [/text/json (taco '"clickety"')]
96/~fintud-macrep/home/0/mar/click:<[7 5].[8 11]>
97ford: casting %json to %click
98ford: cast %click
99```
101And `/mar/examples/click/clicks.hoon`:
103```
104|_ clicks/@
105++ grow
106 |%
107 ++ json
108 (joba %clicks (jone clicks))
109 --
110--
111```
113`examples-clicks-clicks` is just an atom. We convert to json by creating an
114object with a single field "clicks" with our value.
116> Be sure to checkout section 3bD, JSON and XML, in zuse.hoon
117> `++joba` is just a function that takes a key-value pair and
118> produces a JSON object with one element.
120```
121~fintud-macrep:dojo> &json &examples-click-clicks 6
122[%o p={[p='clicks' q=[%n p=~.6]]}]
123```
125Now that we know the marks involved, take another look at the app
126above. Everything should be pretty straightforward. Let's poke
127the app from the command line.
129```
130~fintud-macrep:dojo> |start %examples-click
131>=
132~fintud-macrep:dojo> :examples-click &examples-click-click %click
133[%poked 1]
134>=
135~fintud-macrep:dojo> :examples-click &examples-click-click %click
136[%poked 2]
137>=
138```
140Please pardon the awkwardness of the name 'examples-click-click', but we used it to stay true to our examples naming convention.
142**Exercise**:
144- Modify `:sink` from the subcriptions chapter to listen to
145 `:click` and print out the subscription updates on the command
146 line.
148# Frontend
150That's all that's needed for the back end. The front end is just
151some "sail" html (Hoon markup for XML) and javascript. Here's `/web/click.hoon`:
153```
154;html
155 ;head
156 ;script(type "text/javascript", src "//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js");
157 ;script(type "text/javascript", src "/~/at/lib/js/urb.js");
158 ;title: Clickety!
159 ==
160 ;body
161 ;div#cont
162 ;input#go(type "button", value "Poke!");
163 ;div#err(class "disabled");
164 ;div#clicks;
165 ==
166 ;script(type "text/javascript", src "/click/main.js");
167 ==
168==
169```
171You should recognize the sail syntax from an earlier chapter.
172Aside from jquery, we also include `urb.js`, which is a framework
173for interacting with urbit.
175To view the frontend, point your browser at `ship-name.urbit.org/~~/pages/examples/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/~~/pages/examples/click`.
177We have a button labeled "Poke!" and a div with id `clicks` where
178we'll put the number of clicks. We also include a small
179javascript file where the client-side application logic can be
180found. It's in `/web/pages/examples/click.js`:
182```
184$(function() {
185 var clicks, $go, $clicks, $err
187 $go = $('#go')
188 $clicks = $('#clicks')
189 $err = $('#err')
191 $go.on("click",
192 function() {
193 window.urb.send(
194 "click", {mark: "examples-click-click"}
195 ,function(err,res) {
196 if(err)
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)
202 else
203 console.log("poke succeeded");
204 })
205 })
207 window.urb.appl = "examples-click"
208 window.urb.bind('/the-path',
209 function(err,dat) {
210 clicks = dat.data.clicks
211 $clicks.text(clicks)
212 }
213 )
214})
215```
217We set up two event handlers. When we click the button, we run
218`window.urb.send`, which sends a poke to the app specified as the first argument, in this case our :click app. The arguments
219are data, parameters, and the callback. The data is the specific
220data we want to send. The parameters are all optional, but here's a list
221of the available ones:
223- `ship`: target urbit. Defaults to `window.urb.ship`, which
224 defaults to the urbit which served the page.
225- `appl`: target app. Defaults to `window.urb.appl`, which
226 defaults to null.
227- `mark`: mark of the data. Defaults to `window.urb.send.mark`,
228 which defaults to "json".
229- `wire`: wire of the poke. Defaults to `/`.
231In our case, we specify only that the mark is "click".
233The callback funciton is called when we receive an
234acknowledgment. If there was an error, we put it in the `err`
235div that we defined above. Otherwise, we printf to the console a
236message saying the poke succeeded. As is common, we gray
237out the button when it is clicked. It is only reenabled when positive acknowledgement is received.
239Our 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`:
241- `ship`: target urbit. Defaults to `window.urb.ship`, which
242 defaults to the urbit which served the page.
243- `appl`: target app. Defaults to `window.urb.appl`, which
244 defaults to null.
245- `mark`: mark of expected data. Data of other marks is
246 converted to this mark on the server before coming to the web
247 front end. Defaults to `window.urb.bind.mark`, which defaults
248 to "json".
249- `wire`: wire of the subscription. Defaults to the given path.
251The callback function here just updates the data in the `clicks`
252div. A truly robust app would intelligently handle errors, of
253course.
255Note that the app doesn't have anything web-specific in itself.
256As far as it knows, it's just receiving pokes and subscriptions.
257The javascript is fairly pure as well, sending and receiving json
258everywhere. The marks are the translation layer, and they're the
259only things that need to know how the hoon types map to json.
261**Exercise**:
263- Open the app in multiple tabs, click the button, and verify
264 that all the tabs stay in sink. Poke it manually from the
265 command line and verify the tabs are updated as well.