git.iko.soy/typeable/servant
on master help sign in
Add more JS documentation.

Contributed by freezeboy in PR #11 - https://github.com/haskell-servant/haskell-servant.github.io/pull/11/files

commit95d077f5867fe70f253a54640ef03ab8a6c131dd authorJulian K. Arni <jkarni@gmail.com> dateTue 10 May 2016, 17:44 (10 years ago) parentf5efaf9
1 file changed+314−43
doc/tutorial/Javascript.lhs+314−43
doc/tutorial/Javascript.lhs+314−43
@@ -151,57 +151,36 @@ 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
157157apiJS :: Text
158−apiJS = jsForAPI api vanillaJS
158+apiJS = jsForAPI api jquery
159159```
160160
161161This `Text` contains 2 Javascript functions, 'getPoint' and 'getBooks':
162162
163163``` javascript
164−var getPoint = function(onSuccess, onError)
165−{
166− var xhr = new XMLHttpRequest();
167− xhr.open('GET', '/point', true);
168− xhr.setRequestHeader("Accept","application/json");
169− xhr.onreadystatechange = function (e) {
170− if (xhr.readyState == 4) {
171− if (xhr.status == 204 || xhr.status == 205) {
172− onSuccess();
173− } else if (xhr.status >= 200 && xhr.status < 300) {
174− var value = JSON.parse(xhr.responseText);
175− onSuccess(value);
176− } else {
177− var value = JSON.parse(xhr.responseText);
178− onError(value);
179− }
180− }
181− }
182− xhr.send(null);
183−}
184−
185−var getBooks = function(q, onSuccess, onError)
186−{
187− var xhr = new XMLHttpRequest();
188− xhr.open('GET', '/books' + '?q=' + encodeURIComponent(q), true);
189− xhr.setRequestHeader("Accept","application/json");
190− xhr.onreadystatechange = function (e) {
191− if (xhr.readyState == 4) {
192− if (xhr.status == 204 || xhr.status == 205) {
193− onSuccess();
194− } else if (xhr.status >= 200 && xhr.status < 300) {
195− var value = JSON.parse(xhr.responseText);
196− onSuccess(value);
197− } else {
198− var value = JSON.parse(xhr.responseText);
199− onError(value);
200− }
201− }
202− }
203− xhr.send(null);
204−}
164+var getPoint = function (onSuccess, onError)
165+ {
166+ $.ajax(
167+ { url: '/point'
168+ , success: onSuccess
169+ , error: onError
170+ , method: 'GET'
171+ });
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+ , method: 'GET'
182+ });
183+};
205184```
206185
207186We created a directory `static` that contains two static files: `index.html`,
@@ -226,3 +205,295 @@ And we're good to go. You can start the `main` function of this file and go to
226205`http://localhost:8000/`. Start typing in the name of one of the authors in our
227206database or part of a book title, and check out how long it takes to
228207approximate 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
224+data CommonGeneratorOptions = CommonGeneratorOptions
225+ {
226+ -- | function generating function names
227+ functionNameBuilder :: FunctionName -> String
228+ -- | name used when a user want to send the request body (to let you redefine it)
229+ , requestBody :: String
230+ -- | name of the callback parameter when the request was successful
231+ , successCallback :: String
232+ -- | name of the callback parameter when the request reported an error
233+ , errorCallback :: String
234+ -- | namespace on which we define the js function (empty mean local var)
235+ , moduleName :: String
236+ -- | a prefix that should be prepended to the URL in the generated JS
237+ , urlPrefix :: String
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+apiJS :: String
253+apiJS = 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+var getPoint = function (onSuccess, onError)
263+{
264+ var xhr = new XMLHttpRequest();
265+ xhr.open('GET', '/point', true);
266+
267+ xhr.onreadystatechange = function (e) {
268+ if (xhr.readyState == 4) {
269+ var value = JSON.parse(xhr.responseText);
270+ if (xhr.status == 200 || xhr.status == 201) {
271+ onSuccess(value);
272+ } else {
273+ onError(value);
274+ }
275+ }
276+ }
277+ xhr.send(null);
278+};
279+
280+var getBooks = function (q, onSuccess, onError)
281+{
282+ var xhr = new XMLHttpRequest();
283+ xhr.open('GET', '/books' + '?q=' + encodeURIComponent(q), true);
284+
285+ xhr.onreadystatechange = function (e) {
286+ if (xhr.readyState == 4) {
287+ var value = JSON.parse(xhr.responseText);
288+ if (xhr.status == 200 || xhr.status == 201) {
289+ onSuccess(value);
290+ } else {
291+ onError(value);
292+ }
293+ }
294+ }
295+ xhr.send(null);
296+};
297+```
298+
299+And that's all, your web service can of course be accessible from those
300+two clients at the same time!
301+
302+## Axios support
303+
304+### Simple usage
305+
306+If you use Axios library for your application, we support that too!
307+
308+Use the same code as before but simply replace the previous `apiJS` with
309+the following one:
310+
311+``` haskell
312+apiJS :: String
313+apiJS = jsForAPI api $ axios defAxiosOptions
314+```
315+
316+The rest is *completely* unchanged.
317+
318+The output file is a bit different,
319+
320+``` javascript
321+
322+var getPoint = function ()
323+{
324+ return axios({ url: '/point'
325+ , method: 'get'
326+ });
327+};
328+
329+var getBooks = function (q)
330+{
331+ return axios({ url: '/books' + '?q=' + encodeURIComponent(q)
332+ , method: 'get'
333+ });
334+};
335+```
336+
337+**Caution:** In order to support the promise style of the API, there are no onSuccess
338+nor onError callback functions.
339+
340+### Defining Axios configuration
341+
342+Axios lets you define a 'configuration' to determine the behavior of the
343+program when the AJAX request is sent.
344+
345+We mapped this into a configuration
346+
347+``` haskell
348+data AxiosOptions = AxiosOptions
349+ { -- | indicates whether or not cross-site Access-Control requests
350+ -- should be made using credentials
351+ withCredentials :: !Bool
352+ -- | the name of the cookie to use as a value for xsrf token
353+ , xsrfCookieName :: !(Maybe String)
354+ -- | the name of the header to use as a value for xsrf token
355+ , xsrfHeaderName :: !(Maybe String)
356+ }
357+```
358+
359+## Angular support
360+
361+### Simple usage
362+
363+You can apply the same procedure as with `vanillaJS` and `jquery`, and
364+generate top level functions.
365+
366+The difference is that `angular` Generator always takes an argument.
367+
368+``` haskell
369+apiJS :: String
370+apiJS = jsForAPI api $ angular defAngularOptions
371+```
372+
373+The generated code will be a bit different than previous generators. An extra
374+argument `$http` will be added to let Angular magical Dependency Injector
375+operate.
376+
377+**Caution:** In order to support the promise style of the API, there are no onSuccess
378+nor onError callback functions.
379+
380+``` javascript
381+
382+var getPoint = function($http)
383+{
384+ return $http(
385+ { url: '/counter'
386+ , method: 'GET'
387+ });
388+}
389+
390+var getBooks = function($http, q)
391+{
392+ return $http(
393+ { url: '/books' + '?q=' + encodeURIComponent(q), true);
394+ , method: 'GET'
395+ });
396+}
397+```
398+
399+You can then build your controllers easily
400+
401+``` javascript
402+
403+app.controller("MyController", function($http) {
404+ this.getPoint = getPoint($http)
405+ .success(/* Do something */)
406+ .error(/* Report error */);
407+
408+ this.getPoint = getBooks($http, q)
409+ .success(/* Do something */)
410+ .error(/* Report error */);
411+});
412+```
413+
414+### Service generator
415+
416+You can also generate automatically a service to wrap the whole API as
417+a single Angular service:
418+
419+``` javascript
420+app.service('MyService', function($http) {
421+ return ({
422+ postCounter: function()
423+ {
424+ return $http(
425+ { url: '/counter'
426+ , method: 'POST'
427+ });
428+ },
429+ getCounter: function()
430+ {
431+ return $http(
432+ { url: '/books' + '?q=' + encodeURIComponent(q), true);
433+ , method: 'GET'
434+ });
435+ }
436+ });
437+});
438+```
439+
440+To do so, you just have to use an alternate generator.
441+
442+``` haskell
443+apiJS :: String
444+apiJS = jsForAPI api $ angularService defAngularOptions
445+```
446+
447+Again, it is possible to customize some portions with the options.
448+
449+``` haskell
450+data AngularOptions = AngularOptions
451+ { -- | When generating code with wrapInService, name of the service to generate, default is 'app'
452+ serviceName :: String
453+ , -- | beginning of the service definition
454+ prologue :: String -> String -> String
455+ , -- | end of the service definition
456+ epilogue :: String
457+ }
458+```
459+
460+# Custom function name builder
461+
462+Servant comes with three name builders included:
463+
464+- camelCase (the default)
465+- concatCase
466+- snakeCase
467+
468+Keeping the JQuery as an example, let's see the impact:
469+
470+``` haskell
471+apiJS :: String
472+apiJS = jsForAPI api $ jqueryWith defCommonGeneratorOptions { functionNameBuilder: snakeCase }
473+```
474+
475+This `String` contains 2 Javascript functions:
476+
477+``` javascript
478+
479+var getPoint = function (onSuccess, onError)
480+{
481+ $.ajax(
482+ { url: '/point'
483+ , success: onSuccess
484+ , error: onError
485+ , method: 'GET'
486+ });
487+};
488+
489+var getBooks = function (q, onSuccess, onError)
490+{
491+ $.ajax(
492+ { url: '/books' + '?q=' + encodeURIComponent(q)
493+ , success: onSuccess
494+ , error: onError
495+ , method: 'GET'
496+ });
497+};
498+```
499+