ajax-form.component.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. /**
  2. * <ajax-form>
  3. * -----------------------------------------------------------------------------
  4. * A form that talks to the backend using AJAX.
  5. *
  6. * @type {Component}
  7. *
  8. * @event submitted [emitted after the server responds with a 2xx status code]
  9. * -----------------------------------------------------------------------------
  10. */
  11. parasails.registerComponent('ajaxForm', {
  12. // ╔═╗╦═╗╔═╗╔═╗╔═╗
  13. // ╠═╝╠╦╝║ ║╠═╝╚═╗
  14. // ╩ ╩╚═╚═╝╩ ╚═╝
  15. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  16. // Note:
  17. // Some of these props rely on the `.sync` modifier re-introduced in Vue 2.3.x.
  18. // For more info, see: https://vuejs.org/v2/guide/components.html#sync-Modifier
  19. //
  20. // Specifically, these special props are:
  21. // • syncing
  22. // • cloudError
  23. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  24. props: [
  25. 'syncing',// « 2-way bound (.sync)
  26. 'action',
  27. 'handleParsing',
  28. 'cloudError'// « 2-way bound (.sync)
  29. ],
  30. // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
  31. // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
  32. // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
  33. data: function (){
  34. return {
  35. };
  36. },
  37. // ╦ ╦╔╦╗╔╦╗╦
  38. // ╠═╣ ║ ║║║║
  39. // ╩ ╩ ╩ ╩ ╩╩═╝
  40. template: `
  41. <form class="ajax-form" @submit.prevent="submit()">
  42. <slot name="default"></slot>
  43. </form>
  44. `,
  45. // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
  46. // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
  47. // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
  48. beforeMount: function() {
  49. },
  50. mounted: function (){
  51. },
  52. beforeDestroy: function() {
  53. },
  54. // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
  55. // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
  56. // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
  57. methods: {
  58. submit: async function () {
  59. if (!this.action || !_.isString(this.action) || !_.isFunction(Cloud[_.camelCase(this.action)])) {
  60. throw new Error('Missing or invalid `action` in <ajax-form>. `action` should be the name of a method on the `Cloud` global. For example: `action="login"` would make this form communicate using `Cloud.login()`, which corresponds to the "login" action on the server.');
  61. }
  62. else if (!_.isFunction(Cloud[this.action])) {
  63. throw new Error('Unrecognized `action` in <ajax-form>. Did you mean to type `action="'+_.camelCase(this.action)+'"`? (<ajax-form> expects `action` to be provided in camlCase format. In other words, to reference the action at "api/controllers/foo/bar/do-something", use `action="doSomething"`.)');
  64. }
  65. if (!_.isFunction(this.handleParsing)) {
  66. throw new Error('Missing or invalid `handle-parsing` in <ajax-form>. For example: `:handle-parsing="handleParsingSomeForm"`. This function should return a dictionary (plain JavaScript object like `{}`) of parsed form data, ready to be sent in a request to the server.');
  67. }
  68. // Prevent double-posting.
  69. if (this.syncing) {
  70. return;
  71. }
  72. // Clear the userland "cloudError" prop.
  73. this.$emit('update:cloudError', '');
  74. // Run the provided "handle-parsing" logic.
  75. // > This should clear out any pre-existing error messages, perform any additional
  76. // > client-side form validation checks, and do any necessary data transformations
  77. // > to munge the form data into the format expected by the server.
  78. var argins = this.handleParsing();
  79. // If argins came back undefined, then avast.
  80. // (This means that parsing the form failed.)
  81. if (argins === undefined) {
  82. return;
  83. } else if (!_.isObject(argins) || _.isArray(argins) || _.isFunction(argins)) {
  84. throw new Error('Invalid data returned from custom form parsing logic. (Should return a dictionary of argins, like `{}`.)');
  85. }
  86. // Set syncing state to `true` on userland "syncing" prop.
  87. this.$emit('update:syncing', true);
  88. var didResponseIndicateFailure;
  89. var result = await Cloud[this.action].with(argins)
  90. .tolerate((err)=>{
  91. // When a cloud error occurs, tolerate it, but set the userland "cloudError" prop accordingly.
  92. this.$emit('update:cloudError', err.exit || 'error');
  93. didResponseIndicateFailure = true;
  94. });
  95. // Set syncing state to `false` on userland "syncing" prop.
  96. this.$emit('update:syncing', false);
  97. // If the server says we were successful, then emit the "submitted" event.
  98. if (!didResponseIndicateFailure) {
  99. this.$emit('submitted', result);
  100. }
  101. },
  102. }
  103. });