git.iko.soy/typeable/servant
on master help sign in
Merge pull request #506 from haskell-servant/jkarni/servant-js-docs

Add more JS documentation.

commit9e7356c6d3a42864e939dc9f1803943ac828a002 authorJulian Arni <jkarni@gmail.com> dateThu 25 Aug 2016, 16:14 (10 years ago) parent23493d9b22e4e7
3 files changed+318−24
doc/tutorial/Javascript.lhs+312−22 doc/tutorial/test/JavascriptSpec.hs+5−2 servant-js/src/Servant/JS.hs+1
doc/tutorial/Javascript.lhs+312−22
@@ -151,21 +151,120 @@ so we need a `Proxy` for our API type `API'` without its `Raw` endpoint.
151151
152152Very similarly to how one can derive haskell functions, we can derive the
153153javascript with just a simple function call to `jsForAPI` from
154−`Servant.JQuery`.
154+`Servant.JS`.
155155
156156``` haskell
157−apiJS :: Text
158−apiJS = jsForAPI api vanillaJS
157+apiJS1 :: Text
158+apiJS1 = jsForAPI api jquery
159159```
160160
161161This `Text` contains 2 Javascript functions, 'getPoint' and 'getBooks':
162162
163163``` javascript
164+
165+var getPoint = function(onSuccess, onError)
166+{
167+ $.ajax(
168+ { url: '/point'
169+ , success: onSuccess
170+ , error: onError
171+ , type: 'GET'
172+ });
173+}
174+
175+var getBooks = function(q, onSuccess, onError)
176+{
177+ $.ajax(
178+ { url: '/books' + '?q=' + encodeURIComponent(q)
179+ , success: onSuccess
180+ , error: onError
181+ , type: 'GET'
182+ });
183+}
184+```
185+
186+We created a directory `static` that contains two static files: `index.html`,
187+which is the entrypoint to our little web application; and `ui.js`, which
188+contains some hand-written javascript. This javascript code assumes the two
189+generated functions `getPoint` and `getBooks` in scope. Therefore we need to
190+write the generated javascript into a file:
191+
192+``` haskell
193+writeJSFiles :: IO ()
194+writeJSFiles = do
195+ T.writeFile "static/api.js" apiJS1
196+ jq <- T.readFile =<< Language.Javascript.JQuery.file
197+ T.writeFile "static/jq.js" jq
198+```
199+
200+(We're also writing the jquery library into a file, as it's also used by
201+`ui.js`.) `static/api.js` will be included in `index.html` and the two
202+generated functions will therefore be available in `ui.js`.
203+
204+And we're good to go. You can start the `main` function of this file and go to
205+`http://localhost:8000/`. Start typing in the name of one of the authors in our
206+database or part of a book title, and check out how long it takes to
207+approximate pi using the method mentioned above.
208+
209+## Customizations
210+
211+Instead of calling `jquery`, you can call its variant `jqueryWith`.
212+Here are the type definitions
213+
214+```haskell ignore
215+jquery :: JavaScriptGenerator
216+jqueryWith :: CommonGeneratorOptions -> JavaScriptGenerator
217+```
218+
219+The `CommonGeneratorOptions` will let you define different behaviors to
220+change how functions are generated. Here is the definition of currently
221+available options:
222+
223+```haskell ignore
224+data CommonGeneratorOptions = CommonGeneratorOptions
225+ {
226+ -- | function generating function names
227+ functionNameBuilder :: FunctionName -> Text
228+ -- | name used when a user want to send the request body (to let you redefine it)
229+ , requestBody :: Text
230+ -- | name of the callback parameter when the request was successful
231+ , successCallback :: Text
232+ -- | name of the callback parameter when the request reported an error
233+ , errorCallback :: Text
234+ -- | namespace on which we define the js function (empty mean local var)
235+ , moduleName :: Text
236+ -- | a prefix that should be prepended to the URL in the generated JS
237+ , urlPrefix :: Text
238+ }
239+```
240+
241+This pattern is available with all supported backends, and default values are provided.
242+
243+## Vanilla support
244+
245+If you don't use JQuery for your application, you can reduce your
246+dependencies to simply use the `XMLHttpRequest` object from the standard API.
247+
248+Use the same code as before but simply replace the previous `apiJS` with
249+the following one:
250+
251+``` haskell
252+apiJS2 :: Text
253+apiJS2 = jsForAPI api vanillaJS
254+```
255+
256+The rest is *completely* unchanged.
257+
258+The output file is a bit different, but it has the same parameters,
259+
260+``` javascript
261+
262+
164263var getPoint = function(onSuccess, onError)
165264{
166265 var xhr = new XMLHttpRequest();
167266 xhr.open('GET', '/point', true);
168− xhr.setRequestHeader("Accept","application/json");
267+ xhr.setRequestHeader(\"Accept\",\"application/json\");
169268 xhr.onreadystatechange = function (e) {
170269 if (xhr.readyState == 4) {
171270 if (xhr.status == 204 || xhr.status == 205) {
@@ -186,7 +285,7 @@ var getBooks = function(q, onSuccess, onError)
186285{
187286 var xhr = new XMLHttpRequest();
188287 xhr.open('GET', '/books' + '?q=' + encodeURIComponent(q), true);
189− xhr.setRequestHeader("Accept","application/json");
288+ xhr.setRequestHeader(\"Accept\",\"application/json\");
190289 xhr.onreadystatechange = function (e) {
191290 if (xhr.readyState == 4) {
192291 if (xhr.status == 204 || xhr.status == 205) {
@@ -202,27 +301,218 @@ var getBooks = function(q, onSuccess, onError)
202301 }
203302 xhr.send(null);
204303}
304+
305+
205306```
206307
207−We created a directory `static` that contains two static files: `index.html`,
208−which is the entrypoint to our little web application; and `ui.js`, which
209−contains some hand-written javascript. This javascript code assumes the two
210−generated functions `getPoint` and `getBooks` in scope. Therefore we need to
211−write the generated javascript into a file:
308+And that's all, your web service can of course be accessible from those
309+two clients at the same time!
310+
311+## Axios support
312+
313+### Simple usage
314+
315+If you use Axios library for your application, we support that too!
316+
317+Use the same code as before but simply replace the previous `apiJS` with
318+the following one:
212319
213320``` haskell
214−writeJSFiles :: IO ()
215−writeJSFiles = do
216− T.writeFile "static/api.js" apiJS
217− jq <- T.readFile =<< Language.Javascript.JQuery.file
218− T.writeFile "static/jq.js" jq
321+apiJS3 :: Text
322+apiJS3 = jsForAPI api $ axios defAxiosOptions
219323```
220324
221−(We're also writing the jquery library into a file, as it's also used by
222−`ui.js`.) `static/api.js` will be included in `index.html` and the two
223−generated functions will therefore be available in `ui.js`.
325+The rest is *completely* unchanged.
326+
327+The output file is a bit different,
328+
329+``` javascript
330+
331+
332+var getPoint = function()
333+{
334+ return axios({ url: '/point'
335+ , method: 'get'
336+ });
337+}
338+
339+
340+
341+var getBooks = function(q)
342+{
343+ return axios({ url: '/books' + '?q=' + encodeURIComponent(q)
344+ , method: 'get'
345+ });
346+}
347+
348+```
349+
350+**Caution:** In order to support the promise style of the API, there are no onSuccess
351+nor onError callback functions.
352+
353+### Defining Axios configuration
354+
355+Axios lets you define a 'configuration' to determine the behavior of the
356+program when the AJAX request is sent.
357+
358+We mapped this into a configuration
359+
360+``` haskell
361+data AxiosOptions = AxiosOptions
362+ { -- | indicates whether or not cross-site Access-Control requests
363+ -- should be made using credentials
364+ withCredentials :: !Bool
365+ -- | the name of the cookie to use as a value for xsrf token
366+ , xsrfCookieName :: !(Maybe Text)
367+ -- | the name of the header to use as a value for xsrf token
368+ , xsrfHeaderName :: !(Maybe Text)
369+ }
370+```
371+
372+## Angular support
373+
374+### Simple usage
375+
376+You can apply the same procedure as with `vanillaJS` and `jquery`, and
377+generate top level functions.
378+
379+The difference is that `angular` Generator always takes an argument.
380+
381+``` haskell
382+apiJS4 :: Text
383+apiJS4 = jsForAPI api $ angular defAngularOptions
384+```
385+
386+The generated code will be a bit different than previous generators. An extra
387+argument `$http` will be added to let Angular magical Dependency Injector
388+operate.
389+
390+**Caution:** In order to support the promise style of the API, there are no onSuccess
391+nor onError callback functions.
392+
393+``` javascript
394+
395+
396+var getPoint = function($http)
397+{
398+ return $http(
399+ { url: '/point'
400+ , method: 'GET'
401+ });
402+}
403+
404+
405+
406+var getBooks = function($http, q)
407+{
408+ return $http(
409+ { url: '/books' + '?q=' + encodeURIComponent(q)
410+ , method: 'GET'
411+ });
412+}
413+
414+```
415+
416+You can then build your controllers easily
417+
418+``` javascript
419+
420+app.controller("MyController", function($http) {
421+ this.getPoint = getPoint($http)
422+ .success(/* Do something */)
423+ .error(/* Report error */);
424+
425+ this.getPoint = getBooks($http, q)
426+ .success(/* Do something */)
427+ .error(/* Report error */);
428+});
429+```
430+
431+### Service generator
432+
433+You can also generate automatically a service to wrap the whole API as
434+a single Angular service:
435+
436+``` javascript
437+app.service('MyService', function($http) {
438+ return ({
439+ postCounter: function()
440+ {
441+ return $http(
442+ { url: '/counter'
443+ , method: 'POST'
444+ });
445+ },
446+ getCounter: function()
447+ {
448+ return $http(
449+ { url: '/books' + '?q=' + encodeURIComponent(q), true);
450+ , method: 'GET'
451+ });
452+ }
453+ });
454+});
455+```
456+
457+To do so, you just have to use an alternate generator.
458+
459+``` haskell
460+apiJS5 :: Text
461+apiJS5 = jsForAPI api $ angularService defAngularOptions
462+```
463+
464+Again, it is possible to customize some portions with the options.
465+
466+``` haskell
467+data AngularOptions = AngularOptions
468+ { -- | When generating code with wrapInService, name of the service to generate, default is 'app'
469+ serviceName :: Text
470+ , -- | beginning of the service definition
471+ prologue :: Text -> Text -> Text
472+ , -- | end of the service definition
473+ epilogue :: Text
474+ }
475+```
476+
477+# Custom function name builder
478+
479+Servant comes with three name builders included:
480+
481+- camelCase (the default)
482+- concatCase
483+- snakeCase
484+
485+Keeping the JQuery as an example, let's see the impact:
486+
487+``` haskell
488+apiJS6 :: Text
489+apiJS6 = jsForAPI api $ jqueryWith defCommonGeneratorOptions { functionNameBuilder= snakeCase }
490+```
491+
492+This `Text` contains 2 Javascript functions:
493+
494+``` javascript
495+
496+
497+var get_point = function(onSuccess, onError)
498+{
499+ $.ajax(
500+ { url: '/point'
501+ , success: onSuccess
502+ , error: onError
503+ , type: 'GET'
504+ });
505+}
506+
507+var get_books = function(q, onSuccess, onError)
508+{
509+ $.ajax(
510+ { url: '/books' + '?q=' + encodeURIComponent(q)
511+ , success: onSuccess
512+ , error: onError
513+ , type: 'GET'
514+ });
515+}
516+
517+```
224518
225−And we're good to go. You can start the `main` function of this file and go to
226−`http://localhost:8000/`. Start typing in the name of one of the authors in our
227−database or part of a book title, and check out how long it takes to
228−approximate pi using the method mentioned above.
doc/tutorial/test/JavascriptSpec.hs+5−2
@@ -15,7 +15,10 @@ spec = do
1515 describe "apiJS" $ do
1616 it "is contained verbatim in Javascript.lhs" $ do
1717 code <- readFile "Javascript.lhs"
18− cs apiJS `shouldSatisfy` (`isInfixOf` code)
18+ cs apiJS1 `shouldSatisfy` (`isInfixOf` code)
19+ cs apiJS3 `shouldSatisfy` (`isInfixOf` code)
20+ cs apiJS4 `shouldSatisfy` (`isInfixOf` code)
21+ cs apiJS6 `shouldSatisfy` (`isInfixOf` code)
1922
2023 describe "writeJSFiles" $ do
2124 it "[not a test] write apiJS to static/api.js" $ do
@@ -24,7 +27,7 @@ spec = do
2427 describe "app" $ with (return app) $ do
2528 context "/api.js" $ do
2629 it "delivers apiJS" $ do
27− get "/api.js" `shouldRespondWith` (fromString (cs apiJS))
30+ get "/api.js" `shouldRespondWith` (fromString (cs apiJS1))
2831
2932 context "/" $ do
3033 it "delivers something" $ do
servant-js/src/Servant/JS.hs+1
@@ -112,6 +112,7 @@ module Servant.JS
112112 , javascript
113113 , NoTypes
114114 , GenerateList(..)
115+ , FunctionName(..)
115116 ) where
116117
117118import Prelude hiding (writeFile)