git.iko.soy/typeable/servant
on master help sign in
Add more JS documentation.
95d077fJulian K. Arni
doc/tutorial/Javascript.lhs+314−43
@@ -151,57 +151,36 @@ so we need a `Proxy` for our API type `API'` without its `Raw` endpoint.
151
151
152Very similarly to how one can derive haskell functions, we can derive the
152Very similarly to how one can derive haskell functions, we can derive the
153javascript with just a simple function call to `jsForAPI` from
153javascript with just a simple function call to `jsForAPI` from
154−`Servant.JQuery`.
154+`Servant.JS`.
155
155
156``` haskell
156``` haskell
157apiJS :: Text
157apiJS :: Text
158−apiJS = jsForAPI api vanillaJS
158+apiJS = jsForAPI api jquery
159```
159```
160
160
161This `Text` contains 2 Javascript functions, 'getPoint' and 'getBooks':
161This `Text` contains 2 Javascript functions, 'getPoint' and 'getBooks':
162
162
163``` javascript
163``` javascript
164−var getPoint = function(onSuccess, onError)
164+var getPoint = function (onSuccess, onError)
165−{
165+ {
166− var xhr = new XMLHttpRequest();
166+ $.ajax(
167− xhr.open('GET', '/point', true);
167+ { url: '/point'
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();
168+ , success: onSuccess
173− } else if (xhr.status >= 200 && xhr.status < 300) {
169+ , error: onError
174− var value = JSON.parse(xhr.responseText);
170+ , method: 'GET'
175− onSuccess(value);
171+ });
176− } else {
177− var value = JSON.parse(xhr.responseText);
178− onError(value);
179− }
180− }
172+};
181− }
173+
182− xhr.send(null);
174+
183−}
184−
185−var getBooks = function(q, onSuccess, onError)
175+var getBooks = function (q, onSuccess, onError)
186−{
176+ {
187− var xhr = new XMLHttpRequest();
177+ $.ajax(
188− xhr.open('GET', '/books' + '?q=' + encodeURIComponent(q), true);
178+ { url: '/books' + '?q=' + encodeURIComponent(q)
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();
179+ , success: onSuccess
194− } else if (xhr.status >= 200 && xhr.status < 300) {
180+ , error: onError
195− var value = JSON.parse(xhr.responseText);
181+ , method: 'GET'
196− onSuccess(value);
182+ });
197− } else {
198− var value = JSON.parse(xhr.responseText);
199− onError(value);
200− }
201− }
183+};
202− }
203− xhr.send(null);
204−}
205```
184```
206
185
207We created a directory `static` that contains two static files: `index.html`,
186We 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
226`http://localhost:8000/`. Start typing in the name of one of the authors in our
205`http://localhost:8000/`. Start typing in the name of one of the authors in our
227database or part of a book title, and check out how long it takes to
206database or part of a book title, and check out how long it takes to
228approximate pi using the method mentioned above.
207approximate 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+