on 01ac191 help sign in
268 lines 8.6 kB history raw
1---
2next: true
3sort: 7
4title: Web Apps
5---
7# Backend
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
14javascript.
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`:
23```
24/? 314
25!:
26|%
27++ move {bone $diff $mark *}
28--
29!:
30|_ {hid/bowl clicks/@}
31++ poke-click
32 |= click/$click
33 ~& [%poked +(clicks)]
34 :_ +>.$(clicks +(clicks))
35 %+ turn (prey /the-path hid)
36 |=({o/bone *} [o %diff %clicks +(clicks)])
37++ peer-the-path
38 |= pax/path
39 [[[ost.hid %diff %clicks clicks] ~] +>.$]
40--
41```
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
48number of clicks.
50Let's take a look at the new marks. Here's `/mar/click.hoon`:
52```
53|_ click/$click
54++ grab
55 |%
56 ++ noun |=(* %click)
57 ++ json
58 |= jon/^json
59 ?> =('click' (need (so:jo jon)))
60 %click
61 --
62--
63```
65The mark `click` has hoon type `%click`, which means the only
66valid value is `%click`. We can convert from `noun` by just
67producing `%click`.
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
72is true.
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)`.
85</blockquote>
87We can test this mark from the command line (don't forget to start your app with `|start %click`)
89```
90~fintud-macrep:dojo> &click %click
91%click
92~fintud-macrep:dojo> &click &json [%s 'click']
93%click
94~fintud-macrep:dojo> &click &json &mime [/text/json (taco '"click"')]
95%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
99ford: cast %click
100```
102And `/mar/clicks.hoon`:
104```
105|_ clicks/@
106++ grow
107 |%
108 ++ json
109 (joba %clicks (jone clicks))
110 --
111--
112```
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.
121</blockquote>
123```
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}']
128```
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.
134```
135~fintud-macrep:dojo> |start %click
136>=
137~fintud-macrep:dojo> :click &click %click
138[%poked 1]
139>=
140~fintud-macrep:dojo> :click &click %click
141[%poked 2]
142>=
143```
145**Exercise**:
147- Modify `:sink` from the subcriptions chapter to listen to
148 `:click` and print out the subscription updates on the command
149 line.
151#Frontend
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`:
156```
157;html
158 ;head
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");
161 ;title: Clickety!
162 ==
163 ;body
164 ;div#cont
165 ;input#go(type "button", value "Poke!");
166 ;div#err(class "disabled");
167 ;div#clicks;
168 ==
169 ;script(type "text/javascript", src "/click/main.js");
170 ==
171==
172```
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`:
185```
186$(function() {
187 var clicks, $go, $clicks, $err
189 $go = $('#go')
190 $clicks = $('#clicks')
191 $err = $('#err')
193 window.urb.appl = "click"
195 $go.on("click",
196 function() {
197 window.urb.send(
198 "click", {mark: "click"}
199 ,function(err,res) {
200 if(err)
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)
206 else
207 console.log("poke succeeded");
208 })
209 })
211 window.urb.bind('/the-path',
212 function(err,dat) {
213 clicks = dat.data.clicks
214 $clicks.text(clicks)
215 }
216 )
217})
218```
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
229 defaults to null.
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
247 defaults to null.
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
251 to "json".
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
256course.
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.
264**Exercise**:
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.